blob: c760e1657d6ea9de0c84511f8a8d18a0c7dcf6d3 [file] [log] [blame]
// 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.android.apps.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);
}
}