| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package io.v.syncslides; |
| |
| import android.accounts.Account; |
| import android.accounts.AccountManager; |
| import android.accounts.AccountManagerCallback; |
| import android.accounts.AccountManagerFuture; |
| import android.accounts.AuthenticatorException; |
| import android.accounts.OperationCanceledException; |
| import android.app.ProgressDialog; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.database.Cursor; |
| import android.os.AsyncTask; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.preference.PreferenceManager; |
| import android.provider.ContactsContract; |
| import android.support.v7.app.AppCompatActivity; |
| import android.os.Bundle; |
| import android.util.Log; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.widget.Toast; |
| |
| import com.google.common.base.Charsets; |
| import com.google.common.io.CharStreams; |
| |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| |
| /** |
| * Signs in the user into one of his Gmail accounts. |
| */ |
| public class SignInActivity extends AppCompatActivity { |
| private static final String TAG = "SignInActivity"; |
| |
| private static final String PREF_USER_ACCOUNT_NAME = "user_account"; |
| private static final String PREF_USER_NAME_FROM_CONTACTS = "user_name_from_contacts"; |
| private static final String PREF_USER_NAME_FROM_PROFILE = "user_name_from_profile"; |
| private static final String PREF_USER_PROFILE_JSON = "user_profile"; |
| |
| private static final int REQUEST_CODE_PICK_ACCOUNT = 1000; |
| private static final int REQUEST_CODE_FETCH_USER_PROFILE_APPROVAL = 1001; |
| |
| private static final String OAUTH_PROFILE = "email"; |
| private static final String OAUTH_SCOPE = "oauth2:" + OAUTH_PROFILE; |
| private static final String OAUTH_USERINFO_URL = |
| "https://www.googleapis.com/oauth2/v2/userinfo"; |
| |
| private SharedPreferences mPrefs; |
| private String mAccountName; |
| private ProgressDialog mProgressDialog; |
| |
| /** |
| * Returns the best-effort email of the signed-in user. |
| */ |
| public static String getUserEmail(Context ctx) { |
| SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); |
| return prefs.getString(PREF_USER_ACCOUNT_NAME, ""); |
| } |
| |
| /** |
| * Returns the best-effort full name of the signed-in user. |
| */ |
| public static String getUserName(Context ctx) { |
| SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); |
| // First try to read the user name we obtained from profile, as it's most accurate. |
| if (prefs.contains(PREF_USER_NAME_FROM_PROFILE)) { |
| return prefs.getString(PREF_USER_NAME_FROM_PROFILE, "Anonymous User"); |
| } |
| return prefs.getString(PREF_USER_NAME_FROM_CONTACTS, "Anonymous User"); |
| } |
| |
| /** |
| * Returns the Google profile information of the signed-in user, or {@code null} if the |
| * profile information couldn't be retrieved. |
| */ |
| public static JSONObject getUserProfile(Context ctx) { |
| SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); |
| String userProfileJsonStr = prefs.getString(PREF_USER_PROFILE_JSON, ""); |
| if (!userProfileJsonStr.isEmpty()) { |
| try { |
| return new JSONObject(userProfileJsonStr); |
| } catch (JSONException e) { |
| Log.e(TAG, "Couldn't parse user profile data: " + userProfileJsonStr); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| setContentView(R.layout.activity_sign_in); |
| mPrefs = PreferenceManager.getDefaultSharedPreferences(this); |
| mAccountName = mPrefs.getString(SignInActivity.PREF_USER_ACCOUNT_NAME, ""); |
| mProgressDialog = new ProgressDialog(this); |
| if (mAccountName.isEmpty()) { |
| mProgressDialog.setMessage("Signing in..."); |
| mProgressDialog.show(); |
| pickAccount(); |
| } else { |
| finishActivity(); |
| } |
| } |
| |
| @Override |
| public void onActivityResult(int requestCode, int resultCode, Intent data) { |
| switch (requestCode) { |
| case REQUEST_CODE_PICK_ACCOUNT: { |
| if (resultCode != RESULT_OK) { |
| Toast.makeText(this, "Must pick account", Toast.LENGTH_LONG).show(); |
| pickAccount(); |
| break; |
| } |
| pickAccountDone(data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)); |
| break; |
| } |
| case REQUEST_CODE_FETCH_USER_PROFILE_APPROVAL: |
| if (resultCode != RESULT_OK) { |
| Log.e(TAG, "User didn't approve oauth2 request"); |
| break; |
| } |
| fetchUserProfile(); |
| break; |
| } |
| } |
| |
| private void pickAccount() { |
| Intent chooseIntent = AccountManager.newChooseAccountIntent( |
| null, null, new String[]{"com.google"}, false, null, null, null, null); |
| startActivityForResult(chooseIntent, REQUEST_CODE_PICK_ACCOUNT); |
| } |
| |
| private void pickAccountDone(String accountName) { |
| mAccountName = accountName; |
| SharedPreferences.Editor editor = mPrefs.edit(); |
| editor.putString(PREF_USER_ACCOUNT_NAME, accountName); |
| editor.commit(); |
| |
| fetchUserNameFromContacts(); |
| |
| // NOTE(spetrovic): For demo purposes, fetching user profile is too risky as it requires |
| // internet access. So we disable it for now. |
| //fetchUserProfile(); |
| finishActivity(); |
| } |
| |
| private void fetchUserNameFromContacts() { |
| // Get the user's full name from Contacts. |
| Cursor c = getContentResolver().query(ContactsContract.Profile.CONTENT_URI, |
| null, null, null, null); |
| String[] columnNames = c.getColumnNames(); |
| String userName = "Anonymous User"; |
| while (c.moveToNext()) { |
| for (int j = 0; j < columnNames.length; j++) { |
| String columnName = columnNames[j]; |
| if (!columnName.equals(ContactsContract.Contacts.DISPLAY_NAME)) { |
| continue; |
| } |
| userName = c.getString(c.getColumnIndex(columnName)); |
| } |
| } |
| c.close(); |
| SharedPreferences.Editor editor = mPrefs.edit(); |
| editor.putString(PREF_USER_NAME_FROM_CONTACTS, userName); |
| editor.commit(); |
| } |
| |
| private void fetchUserProfile() { |
| AccountManager manager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE); |
| Account[] accounts = manager.getAccountsByType("com.google"); |
| Account account = null; |
| for (int i = 0; i < accounts.length; i++) { |
| if (accounts[i].name.equals(mAccountName)) { |
| account = accounts[i]; |
| break; |
| } |
| } |
| if (account == null) { |
| Log.e(TAG, "Couldn't find Google account with name: " + mAccountName); |
| pickAccount(); |
| return; |
| } |
| manager.getAuthToken(account, |
| OAUTH_SCOPE, |
| new Bundle(), |
| false, |
| new OnTokenAcquired(), |
| new Handler(new Handler.Callback() { |
| @Override |
| public boolean handleMessage(Message msg) { |
| Log.e(TAG, "Error getting auth token: " + msg.toString()); |
| fetchUserProfileDone(null); |
| return true; |
| } |
| })); |
| } |
| |
| private void fetchUserProfileDone(JSONObject userProfile) { |
| if (userProfile != null) { |
| SharedPreferences.Editor editor = mPrefs.edit(); |
| editor.putString(PREF_USER_PROFILE_JSON, userProfile.toString()); |
| try { |
| if (userProfile.has("name") && !userProfile.getString("name").isEmpty()) { |
| editor.putString(PREF_USER_NAME_FROM_PROFILE, userProfile.getString("name")); |
| } |
| } catch (JSONException e) { |
| Log.e(TAG, "Couldn't read user name from user profile: " + e.getMessage()); |
| } |
| editor.commit(); |
| } |
| finishActivity(); |
| } |
| |
| |
| private void finishActivity() { |
| mProgressDialog.dismiss(); |
| startActivity(new Intent(this, DeckChooserActivity.class)); |
| finish(); |
| } |
| |
| private class OnTokenAcquired implements AccountManagerCallback<Bundle> { |
| @Override |
| public void run(AccountManagerFuture<Bundle> result) { |
| try { |
| Bundle bundle = result.getResult(); |
| Intent launch = (Intent) bundle.get(AccountManager.KEY_INTENT); |
| if (launch != null) { // Needs user approval. |
| // NOTE(spetrovic): The returned intent has the wrong flag value |
| // FLAG_ACTIVITY_NEW_TASK set, which results in the launched intent replying |
| // immediately with RESULT_CANCELED. Hence, we clear the flag here. |
| launch.setFlags(0); |
| startActivityForResult(launch, REQUEST_CODE_FETCH_USER_PROFILE_APPROVAL); |
| return; |
| } |
| String token = bundle.getString(AccountManager.KEY_AUTHTOKEN); |
| new ProfileInfoFetcher().execute(token); |
| } catch (AuthenticatorException e) { |
| Log.e(TAG, "Couldn't authorize: " + e.getMessage()); |
| fetchUserProfileDone(null); |
| } catch (OperationCanceledException e) { |
| Log.e(TAG, "Authorization cancelled: " + e.getMessage()); |
| fetchUserProfileDone(null); |
| } catch (IOException e) { |
| Log.e(TAG, "Unexpected error: " + e.getMessage()); |
| fetchUserProfileDone(null); |
| } |
| } |
| } |
| |
| private class ProfileInfoFetcher extends AsyncTask<String, Void, JSONObject> { |
| @Override |
| protected JSONObject doInBackground(String... params) { |
| try { |
| URL url = new URL(OAUTH_USERINFO_URL + "?access_token=" + params[0]); |
| return new JSONObject(CharStreams.toString( |
| new InputStreamReader(url.openConnection().getInputStream(), |
| Charsets.US_ASCII))); |
| } catch (MalformedURLException e) { |
| Log.e(TAG, "Error fetching user's profile info" + e.getMessage()); |
| } catch (JSONException e) { |
| Log.e(TAG, "Error fetching user's profile info" + e.getMessage()); |
| } catch (IOException e) { |
| Log.e(TAG, "Error fetching user's profile info" + e.getMessage()); |
| } |
| return null; |
| } |
| |
| @Override |
| protected void onPostExecute(JSONObject userProfile) { |
| fetchUserProfileDone(userProfile); |
| } |
| } |
| |
| @Override |
| public boolean onCreateOptionsMenu(Menu menu) { |
| // Inflate the menu; this adds items to the action bar if it is present. |
| getMenuInflater().inflate(R.menu.menu_sign_in, menu); |
| return true; |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| // Handle action bar item clicks here. The action bar will |
| // automatically handle clicks on the Home/Up button, so long |
| // as you specify a parent activity in AndroidManifest.xml. |
| int id = item.getItemId(); |
| |
| //noinspection SimplifiableIfStatement |
| if (id == R.id.action_settings) { |
| return true; |
| } |
| |
| return super.onOptionsItemSelected(item); |
| } |
| } |