diff options
author | Benjamin Baxter <benbaxter@google.com> | 2017-03-22 15:58:49 -0700 |
---|---|---|
committer | Benjamin Baxter <benbaxter@google.com> | 2017-04-18 10:00:19 -0700 |
commit | f6a7013b7b9a9ca8d05713f5c16c95abf3af71cb (patch) | |
tree | f8ea3878d0c3c0296df9546dc17134951230b12b /wearable/wear/WearMessagingApp | |
parent | c8a79136f775bf9098779d62777dc69f1762d546 (diff) | |
download | android-f6a7013b7b9a9ca8d05713f5c16c95abf3af71cb.tar.gz |
Adding sign in with google activity and a base activity for activities that required a signed in user
Bug: 34841755
Change-Id: I8c4630d1fd49acb735cd39650addd8ed8ef600b9
Diffstat (limited to 'wearable/wear/WearMessagingApp')
5 files changed, 426 insertions, 2 deletions
diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/GoogleSignedInActivity.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/GoogleSignedInActivity.java new file mode 100644 index 00000000..494c9c7b --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/GoogleSignedInActivity.java @@ -0,0 +1,171 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.wearable.wear.messaging; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.wearable.activity.WearableActivity; +import android.util.Log; +import com.example.android.wearable.wear.messaging.model.Profile; +import com.example.android.wearable.wear.messaging.util.SharedPreferencesHelper; +import com.google.android.gms.auth.api.Auth; +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; +import com.google.android.gms.auth.api.signin.GoogleSignInOptions; +import com.google.android.gms.auth.api.signin.GoogleSignInResult; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.OptionalPendingResult; +import com.google.android.gms.common.api.ResultCallback; +import java.io.IOException; + +/** + * This activity should be extended for any activity that requires an authenticated user. This + * activity handles the signin flow with Google signin. + * + * <p>When the activity starts, it will silently try to verify that the user is valid. If a user is + * not signed in, it will redirect to a SignInActivity. + * + * <p>It also provides a hook for any sub class to get a reference to the user object {@link + * #getUser()} + */ +public abstract class GoogleSignedInActivity extends WearableActivity + implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { + + private static final String TAG = "GoogleSignedInActivity"; + + protected GoogleApiClient mGoogleApiClient; + protected GoogleSignInAccount mGoogleSignInAccount; + private String mUserIdToken; + private Profile mUser; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setAmbientEnabled(); + + // Try to get the user if they don't exist, return to sign in. + try { + mUser = SharedPreferencesHelper.readUserFromJsonPref(this); + } catch (IOException e) { + Log.e(TAG, "User is not stored locally"); + } + if (mUser == null) { + onGoogleSignInFailure(); + } + + setupGoogleApiClient(); + } + + /* gives a handle to the user object for the sub-activities */ + protected Profile getUser() { + return mUser; + } + + /** Configures the GoogleApiClient used for sign in. Requests scopes profile and email. */ + protected void setupGoogleApiClient() { + GoogleSignInOptions gso = + new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestProfile() + .requestEmail() + .requestIdToken(getString(R.string.default_web_client_id)) + .build(); + + mGoogleApiClient = + new GoogleApiClient.Builder(this) + .addConnectionCallbacks(this) + .addOnConnectionFailedListener(this) + .addApi(Auth.GOOGLE_SIGN_IN_API, gso) + .build(); + } + + @Override + protected void onStart() { + super.onStart(); + if (mGoogleApiClient != null && !mGoogleApiClient.isConnected()) { + mGoogleApiClient.connect(); + } + } + + @Override + protected void onStop() { + super.onStop(); + if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { + mGoogleApiClient.disconnect(); + } + } + + @Override + public void onConnected(@Nullable Bundle bundle) { + Log.d(TAG, "onConnected(): refreshing sign in"); + refreshSignIn(); + } + + @Override + public void onConnectionSuspended(int i) { + Log.d(TAG, "onConnectionSuspended(): connection to location client suspended: " + i); + } + + @Override + public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { + Log.d(TAG, "Connection Failed."); + } + + /** + * Handles sign in result and gets the UserIdToken. + * + * @param result sign in result + */ + protected void handleSignInResult(GoogleSignInResult result) { + if (result != null && result.isSuccess()) { + mGoogleSignInAccount = result.getSignInAccount(); + if (mGoogleSignInAccount != null) { + mUserIdToken = mGoogleSignInAccount.getIdToken(); + Log.d(TAG, "Google sign in success " + mUserIdToken); + } + } else if (result != null && !result.isSuccess()) { + Log.d(TAG, "Google sign in failure: " + result.getStatus()); + onGoogleSignInFailure(); + } else { + Log.d(TAG, "Google sign in result is null"); + onGoogleSignInFailure(); + } + } + + protected void onGoogleSignInFailure() { + // If sign in fails, ask them to sign in + Intent signinIntent = new Intent(this, SignInActivity.class); + startActivity(signinIntent); + } + + /** Silently signs in. */ + private void refreshSignIn() { + OptionalPendingResult<GoogleSignInResult> pendingResult = + Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient); + if (pendingResult.isDone()) { + handleSignInResult(pendingResult.get()); + } else { + pendingResult.setResultCallback( + new ResultCallback<GoogleSignInResult>() { + @Override + public void onResult(@NonNull GoogleSignInResult googleSignInResult) { + handleSignInResult(googleSignInResult); + } + }); + } + } +} diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/SignInActivity.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/SignInActivity.java new file mode 100644 index 00000000..af3d74be --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/SignInActivity.java @@ -0,0 +1,208 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.example.android.wearable.wear.messaging; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.wearable.activity.WearableActivity; +import android.util.Log; +import android.view.View; +import android.widget.Toast; +import com.example.android.wearable.wear.messaging.mock.MockDatabase; +import com.example.android.wearable.wear.messaging.model.Profile; +import com.google.android.gms.auth.api.Auth; +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; +import com.google.android.gms.auth.api.signin.GoogleSignInOptions; +import com.google.android.gms.auth.api.signin.GoogleSignInResult; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.SignInButton; +import com.google.android.gms.common.api.GoogleApiClient; + +/** + * Activity authenticates via Google and mocks backend model with shared preferences. + * + * <p>The login flow: + * + * <p>On sign in: Create mProfile from the user details from their google account Check if the user + * exists in our 'backend' + * + * <p>If the user does not exist: Add them to our 'backend' Once a user has been established, then + * reset the UI's state and launch the 'Main' Activity. In this case, we will go to the ChatActivity + * and view the list of chats for the user. + */ +public class SignInActivity extends WearableActivity + implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { + + private static final String TAG = "SignInActivity"; + + /* request code for signing in with a google account */ + private static final int RC_SIGN_IN = 9001; + + private GoogleApiClient mGoogleApiClient; + + private SignInButton mSignInButton; + + private Profile mProfile; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_signin); + setAmbientEnabled(); + MockDatabase.init(this); + + // Configure Google Sign In + GoogleSignInOptions.Builder builder = + new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestIdToken(getString(R.string.default_web_client_id)) + .requestEmail(); + GoogleSignInOptions gso = builder.build(); + mGoogleApiClient = + new GoogleApiClient.Builder(this) + .addConnectionCallbacks(this) + .addOnConnectionFailedListener(this) + .addApi(Auth.GOOGLE_SIGN_IN_API, gso) + .build(); + + mSignInButton = (SignInButton) findViewById(R.id.sign_in_button); + mSignInButton.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent signInIntent = + Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient); + startActivityForResult(signInIntent, RC_SIGN_IN); + } + }); + } + + @Override + protected void onStart() { + super.onStart(); + if (mGoogleApiClient != null && !mGoogleApiClient.isConnected()) { + mGoogleApiClient.connect(); + } + } + + @Override + protected void onStop() { + super.onStop(); + if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) { + mGoogleApiClient.disconnect(); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...); + if (requestCode == RC_SIGN_IN) { + GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data); + handleGoogleSigninResult(result); + } + } + + private void handleGoogleSigninResult(GoogleSignInResult result) { + if (result.isSuccess()) { + GoogleSignInAccount account = result.getSignInAccount(); + syncUserWithBackend(account); + } else { + // Google Sign-In failed + Log.e(TAG, "Google Sign-In failed."); + Toast.makeText(SignInActivity.this, R.string.google_signin_failed, Toast.LENGTH_SHORT) + .show(); + } + } + + /* + * Syncs with mock backend (shared preferences). You will want to put in your + * backend logic here. + */ + private void syncUserWithBackend(GoogleSignInAccount acct) { + Log.d(TAG, "syncUserWithBackend:" + acct.getId()); + + mProfile = new Profile(acct); + + MockDatabase.getUser( + mProfile.getId(), + new MockDatabase.RetrieveUserCallback() { + @Override + public void retrieved(Profile user) { + if (user == null) { + // User did not exists so create the user. + // Using mProfile since user is null + MockDatabase.createUser( + mProfile, + new MockDatabase.CreateUserCallback() { + @Override + public void onSuccess() { + finishSuccessfulSignin(); + } + + @Override + public void onError(Exception e) { + setAuthFailedState(); + } + }); + } else { + finishSuccessfulSignin(); + } + } + + @Override + public void error(Exception e) { + setAuthFailedState(); + } + }); + } + + private void setAuthFailedState() { + if (mSignInButton != null) { + mSignInButton.setEnabled(false); + } + Toast.makeText(SignInActivity.this, R.string.authentication_failed, Toast.LENGTH_SHORT) + .show(); + } + + private void finishSuccessfulSignin() { + if (mSignInButton != null) { + mSignInButton.setEnabled(false); + } + +// Intent chatActivityIntent = new Intent(this, ChatListActivity.class); +// startActivity(chatActivityIntent); + finish(); + } + + @Override + public void onConnected(@Nullable Bundle bundle) { + Log.d(TAG, "onConnected()"); + } + + @Override + public void onConnectionSuspended(int i) { + Log.d(TAG, "onConnectionSuspended(): connection to location client suspended: " + i); + } + + @Override + public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { + Log.d(TAG, "onConnectionFailed:" + connectionResult); + Toast.makeText(this, R.string.connection_failed, Toast.LENGTH_SHORT).show(); + } +} diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/mock/MockDatabase.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/mock/MockDatabase.java index 1b0d91a8..034baa1b 100644 --- a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/mock/MockDatabase.java +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/mock/MockDatabase.java @@ -43,14 +43,14 @@ public class MockDatabase { private static Context mContext; /** A callback for events when retrieving a user asynchronously */ - interface RetrieveUserCallback { + public interface RetrieveUserCallback { void retrieved(Profile user); void error(Exception e); } /** A callback for getting events during the creation of a user */ - interface CreateUserCallback { + public interface CreateUserCallback { void onSuccess(); void onError(Exception e); diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/activity_signin.xml b/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/activity_signin.xml new file mode 100644 index 00000000..716cd78f --- /dev/null +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/activity_signin.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright 2017 Google Inc. + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/blue_500" + android:gravity="center" + android:orientation="vertical" + tools:context=".SignInActivity"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="@style/LogoText" + android:text="@string/wear_messaging_app" /> + + <com.google.android.gms.common.SignInButton + android:id="@+id/sign_in_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:buttonSize="standard" + app:colorScheme="auto" + /> + +</LinearLayout>
\ No newline at end of file diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/values/strings.xml b/wearable/wear/WearMessagingApp/Wearable/src/main/res/values/strings.xml index e52f0300..7a024fed 100644 --- a/wearable/wear/WearMessagingApp/Wearable/src/main/res/values/strings.xml +++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/values/strings.xml @@ -38,4 +38,7 @@ <string name="open_keyboard_input">Open keyboard input</string> <string name="wear_messaging_app">Wear Messaging App</string> <string name="you_have_no_messages">You have no messages.\nStart a message with the actions below!</string> + <string name="google_signin_failed">Google Sign-In Failed.</string> + <string name="authentication_failed">Authentication Failed.</string> + <string name="connection_failed">Connection failed.</string> </resources>
\ No newline at end of file |