package com.pchmn.rxsocialauth.smartlock; import android.content.Intent; import android.content.IntentSender; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import com.google.android.gms.auth.api.Auth; import com.google.android.gms.auth.api.credentials.Credential; import com.google.android.gms.auth.api.credentials.CredentialRequest; import com.google.android.gms.auth.api.credentials.CredentialRequestResult; import com.google.android.gms.auth.api.credentials.IdentityProviders; import com.google.android.gms.common.api.CommonStatusCodes; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Status; import com.pchmn.rxsocialauth.R; import com.pchmn.rxsocialauth.auth.RxFacebookAuth; import com.pchmn.rxsocialauth.auth.RxGoogleAuth; import com.pchmn.rxsocialauth.common.RxAccount; import com.pchmn.rxsocialauth.common.RxStatus; import rx.Observable; import rx.functions.Action1; import rx.functions.Func1; import rx.subjects.PublishSubject; import static android.app.Activity.RESULT_OK; public class RxSmartLockPasswordsFragment extends Fragment implements GoogleApiClient.ConnectionCallbacks { public static final String TAG = RxSmartLockPasswordsFragment.class.toString(); // activity private FragmentActivity mActivity; // RC private static final int RC_READ = 0; private static final int RC_SAVE = 1; private static final int RC_SAVE_INTERN = 2; // options private CredentialRequest mCredentialRequest; private boolean mDisableAutoSignIn; // credential action private CredentialAction mCredentialAction; // credential private Credential mCredential; private SmartLockOptions mSmartLockOptions; // google api client private GoogleApiClient mCredentialsApiClient; // subjects private PublishSubject<Object> mAccountSubject; private PublishSubject<RxStatus> mStatusSubject; private PublishSubject<CredentialRequestResult> mRequestSubject; // current account private RxAccount mCurrentAccount; private PublishSubject<RxAccount> mAccountSubjectIntern; public static RxSmartLockPasswordsFragment newInstance(RxSmartLockPasswords.Builder builder) { RxSmartLockPasswordsFragment rxSmartLockPasswordsFragment = new RxSmartLockPasswordsFragment(); rxSmartLockPasswordsFragment.setCredentialRequest(builder.credentialRequestBuilder.build()); rxSmartLockPasswordsFragment.setDisableAutoSignIn(builder.disableAutoSignIn); return rxSmartLockPasswordsFragment; } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mActivity = getActivity(); // smart lock password mCredentialsApiClient = new GoogleApiClient.Builder(mActivity) .addConnectionCallbacks(this) .addApi(Auth.CREDENTIALS_API) .build(); } /** * Request credential */ public void requestCredential(PublishSubject<CredentialRequestResult> requestSubject) { mCredentialAction = CredentialAction.REQUEST; mRequestSubject = requestSubject; mCredentialsApiClient.connect(); } private void requestCredential() { // disable auto sign in if (mDisableAutoSignIn) Auth.CredentialsApi.disableAutoSignIn(mCredentialsApiClient); Auth.CredentialsApi.request(mCredentialsApiClient, mCredentialRequest).setResultCallback( new ResultCallback<CredentialRequestResult>() { @Override public void onResult(@NonNull CredentialRequestResult credentialRequestResult) { mCredentialsApiClient.disconnect(); mRequestSubject.onNext(credentialRequestResult); mRequestSubject.onCompleted(); } }); } /** * Request credential and auto sign in user */ public void requestCredentialAndAutoSignIn(PublishSubject<Object> accountSubject) { mCredentialAction = CredentialAction.REQUEST_AND_AUTO_SIGN_IN; mAccountSubject = accountSubject; mCredentialsApiClient.connect(); } private void requestCredentialAndAutoSignIn() { // disable auto sign in if(mDisableAutoSignIn) Auth.CredentialsApi.disableAutoSignIn(mCredentialsApiClient); Auth.CredentialsApi.request(mCredentialsApiClient, mCredentialRequest).setResultCallback( new ResultCallback<CredentialRequestResult>() { @Override public void onResult(@NonNull CredentialRequestResult credentialRequestResult) { if(credentialRequestResult.getStatus().isSuccess()) onCredentialRetrieved(credentialRequestResult.getCredential()); else resolveResult(credentialRequestResult.getStatus()); } }); } private void onCredentialRetrieved(Credential credential) { String accountType = credential.getAccountType(); // google account if (accountType != null && accountType.equals(IdentityProviders.GOOGLE)) { // build RxGoogleAuth object RxGoogleAuth.Builder builder = new RxGoogleAuth.Builder(mActivity); final GoogleOptions options = SmartLockHelper.getInstance(mActivity) .getGoogleSmartLockOptions(); if(options != null) { if(options.requestEmail) builder.requestEmail(); if(options.requestProfile) builder.requestProfile(); if(options.clientId != null) builder.requestIdToken(options.clientId); } else { builder.requestEmail().requestProfile(); } // silent sign in builder.build().silentSignIn(credential) .flatMap(new Func1<RxAccount, Observable<RxAccount>>() { @Override public Observable<RxAccount> call(RxAccount account) { mCurrentAccount = account; Credential newCredential = new Credential.Builder(account.getEmail()) .setAccountType(IdentityProviders.GOOGLE) .setName(account.getDisplayName()) .setProfilePictureUri(account.getPhotoUri()) .build(); // save credentials return saveCredentialIntern(newCredential, options); } }) .subscribe(new Action1<RxAccount>() { @Override public void call(RxAccount account) { mCredentialsApiClient.disconnect(); mAccountSubject.onNext(account); mAccountSubject.onCompleted(); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { mCredentialsApiClient.disconnect(); mAccountSubject.onError(throwable); } }); } // facebook account else if(accountType != null && accountType.equals(IdentityProviders.FACEBOOK)) { // build rxFacebookAuth object RxFacebookAuth.Builder builder = new RxFacebookAuth.Builder(mActivity); final FacebookOptions options = SmartLockHelper.getInstance(mActivity) .getFacebookSmartLockOptions(); if(options != null) { if(options.requestEmail) builder.requestEmail(); if(options.requestProfile) builder.requestProfile(); builder.requestPhotoSize(options.photoWidth, options.photoHeight); } else { builder.requestEmail().requestProfile(); } // sign in builder.build().signIn() .flatMap(new Func1<RxAccount, Observable<RxAccount>>() { @Override public Observable<RxAccount> call(RxAccount account) { mCurrentAccount = account; Credential newCredential = new Credential.Builder(account.getEmail()) .setAccountType(IdentityProviders.FACEBOOK) .setName(account.getDisplayName()) .setProfilePictureUri(account.getPhotoUri()) .build(); // save credentials return saveCredentialIntern(newCredential, options); } }) .subscribe(new Action1<RxAccount>() { @Override public void call(RxAccount account) { mCredentialsApiClient.disconnect(); mAccountSubject.onNext(account); mAccountSubject.onCompleted(); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { mCredentialsApiClient.disconnect(); mAccountSubject.onError(throwable); } }); } // login password account or other provider else { mCredentialsApiClient.disconnect(); mAccountSubject.onNext(credential); mAccountSubject.onCompleted(); } } private void resolveResult(Status status) { if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) { try { //status.startResolutionForResult(mActivity, RC_READ); startIntentSenderForResult(status.getResolution().getIntentSender(), RC_READ, null, 0, 0, 0, null); } catch (IntentSender.SendIntentException e) { e.printStackTrace(); mCredentialsApiClient.disconnect(); mAccountSubject.onError(new Throwable(e.toString())); } } else { // The user must create an account or sign in manually. mCredentialsApiClient.disconnect(); mAccountSubject.onError(new Throwable(getString(R.string.status_canceled_request_credential))); } } /** * Save credential in smart lock for passwords and save options as well * * @param credential the credential * @param options the options * @return an Observable */ private PublishSubject<RxAccount> saveCredentialIntern(Credential credential, final SmartLockOptions options) { mAccountSubjectIntern = PublishSubject.create(); Auth.CredentialsApi.save(mCredentialsApiClient, credential).setResultCallback( new ResultCallback<Status>() { @Override public void onResult(@NonNull Status status) { if (status.isSuccess()) { // save options SmartLockHelper.getInstance(mActivity) .saveSmartLockOptions(options); // credential saved mCredentialsApiClient.disconnect(); mAccountSubjectIntern.onNext(mCurrentAccount); mAccountSubjectIntern.onCompleted(); // reset current account mCurrentAccount = null; } else if(status.hasResolution()) { // Try to resolve the save request. This will prompt the user if // the credential is new. try { status.startResolutionForResult(mActivity, RC_SAVE_INTERN); } catch (IntentSender.SendIntentException e) { // Could not resolve the request mCredentialsApiClient.disconnect(); mAccountSubjectIntern.onError(new Throwable(e.toString())); // reset current account mCurrentAccount = null; } } else { // request has no resolution mCredentialsApiClient.disconnect(); mAccountSubjectIntern.onCompleted(); // reset current account mCurrentAccount = null; } } }); return mAccountSubjectIntern; } /** * Save credential in smart lock for passwords an notify a subject * * @param statusObject the subject to notify * @param credential the credential * @param smartLockOptions the options (may be null) */ public void saveCredential(PublishSubject<RxStatus> statusObject, Credential credential, @Nullable SmartLockOptions smartLockOptions) { mCredentialAction = CredentialAction.SAVE; mStatusSubject = statusObject; mCredential = credential; mSmartLockOptions = smartLockOptions; mCredentialsApiClient.connect(); } private void saveCredential() { Auth.CredentialsApi.save(mCredentialsApiClient, mCredential).setResultCallback( new ResultCallback<Status>() { @Override public void onResult(@NonNull Status status) { if (status.isSuccess()) { // save options SmartLockHelper.getInstance(mActivity).saveSmartLockOptions(mSmartLockOptions); // credential saved mCredentialsApiClient.disconnect(); mStatusSubject.onNext(new RxStatus(status)); mStatusSubject.onCompleted(); } else if(status.hasResolution()) { // Try to resolve the save request. This will prompt the user if // the credential is new. try { status.startResolutionForResult(mActivity, RC_SAVE); } catch (IntentSender.SendIntentException e) { // Could not resolve the request mCredentialsApiClient.disconnect(); mStatusSubject.onError(new Throwable(e.toString())); } } else { // request has no resolution mCredentialsApiClient.disconnect(); mStatusSubject.onCompleted(); } } }); } /** * Delete credential */ public void deleteCredential(PublishSubject<RxStatus> statusObject, Credential credential) { mCredentialAction = CredentialAction.DELETE; mStatusSubject = statusObject; mCredential = credential; mCredentialsApiClient.connect(); } private void deleteCredential() { Auth.CredentialsApi.delete(mCredentialsApiClient, mCredential).setResultCallback(new ResultCallback<Status>() { @Override public void onResult(@NonNull Status status) { mCredentialsApiClient.disconnect(); mStatusSubject.onNext(new RxStatus(status)); mStatusSubject.onCompleted(); } }); } /** * Disable auto sign in */ public void disableAutoSignIn(PublishSubject<RxStatus> statusObject) { mCredentialAction = CredentialAction.DISABLE_AUTO_SIGN_IN; mStatusSubject = statusObject; mCredentialsApiClient.connect(); } private void disableAutoSignIn() { Auth.CredentialsApi.disableAutoSignIn(mCredentialsApiClient).setResultCallback(new ResultCallback<Status>() { @Override public void onResult(@NonNull Status status) { mCredentialsApiClient.disconnect(); mStatusSubject.onNext(new RxStatus(status)); mStatusSubject.onCompleted(); } }); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == RC_READ) { if (resultCode == RESULT_OK) { Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY); onCredentialRetrieved(credential); } else { // The user must create an account or sign in manually. mCredentialsApiClient.disconnect(); mAccountSubject.onError(new Throwable(getString(R.string.status_canceled_request_credential))); } } else if (requestCode == RC_SAVE) { mCredentialsApiClient.disconnect(); if (resultCode == RESULT_OK) { // credentials saved mStatusSubject.onNext(new RxStatus( CommonStatusCodes.SUCCESS, getString(R.string.status_success_credential_saved_message) )); mStatusSubject.onCompleted(); } else { // cancel by user mStatusSubject.onNext(new RxStatus( CommonStatusCodes.CANCELED, getString(R.string.status_canceled_credential_saved_message) )); mStatusSubject.onCompleted(); } } else if (requestCode == RC_SAVE_INTERN) { mCredentialsApiClient.disconnect(); if (resultCode == RESULT_OK) { // credentials saved mAccountSubjectIntern.onNext(mCurrentAccount); mAccountSubjectIntern.onCompleted(); // reset current account mCurrentAccount = null; } else { // cancel by user mAccountSubjectIntern.onNext(mCurrentAccount); mAccountSubjectIntern.onCompleted(); // reset current account mCurrentAccount = null; } } } @Override public void onConnected(@Nullable Bundle bundle) { switch (mCredentialAction) { case REQUEST: requestCredential(); break; case REQUEST_AND_AUTO_SIGN_IN: requestCredentialAndAutoSignIn(); break; case DELETE: deleteCredential(); break; case DISABLE_AUTO_SIGN_IN: disableAutoSignIn(); break; case SAVE: saveCredential(); break; } } @Override public void onConnectionSuspended(int i) { } public void setCredentialRequest(CredentialRequest credentialRequest) { mCredentialRequest = credentialRequest; } public void setDisableAutoSignIn(boolean mDisableAutoSignIn) { this.mDisableAutoSignIn = mDisableAutoSignIn; } enum CredentialAction { REQUEST, REQUEST_AND_AUTO_SIGN_IN, SAVE, DELETE, DISABLE_AUTO_SIGN_IN } }