/*
* Copyright (c) 2016 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.google.samples.apps.iosched.util;
import android.accounts.Account;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.UserRecoverableNotifiedException;
import com.google.android.gms.common.AccountPicker;
import com.google.android.gms.common.Scopes;
import com.google.samples.apps.iosched.provider.ScheduleContract;
import java.io.IOException;
import java.util.UUID;
import static com.google.samples.apps.iosched.util.LogUtils.LOGD;
import static com.google.samples.apps.iosched.util.LogUtils.LOGE;
import static com.google.samples.apps.iosched.util.LogUtils.LOGI;
import static com.google.samples.apps.iosched.util.LogUtils.LOGV;
import static com.google.samples.apps.iosched.util.LogUtils.LOGW;
import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag;
/**
* Account and login utilities. This class manages a local shared preferences object
* that stores which account is currently active, and can store associated information
* such as Google+ profile info (name, image URL, cover URL) and also the auth token
* associated with the account.
*/
public class AccountUtils {
private static final String TAG = makeLogTag(AccountUtils.class);
public static final String DEFAULT_OAUTH_PROVIDER = "google";
public static final String PREF_ACTIVE_ACCOUNT = "chosen_account";
// These names are are prefixes; the account is appended to them.
public static final String PREFIX_PREF_AUTH_TOKEN = "auth_token_";
private static final String PREFIX_PREF_PLUS_PROFILE_ID = "plus_profile_id_";
private static final String PREFIX_PREF_PLUS_NAME = "plus_name_";
private static final String PREFIX_PREF_PLUS_IMAGE_URL = "plus_image_url_";
private static final String PREFIX_PREF_PLUS_COVER_URL = "plus_cover_url_";
private static final String PREFIX_PREF_GCM_KEY = "gcm_key_";
public static final String AUTH_SCOPES[] = {
Scopes.PLUS_LOGIN,
Scopes.DRIVE_APPFOLDER,
"https://www.googleapis.com/auth/userinfo.email"};
static final String AUTH_TOKEN_TYPE;
static {
StringBuilder sb = new StringBuilder();
sb.append("oauth2:");
for (String scope : AUTH_SCOPES) {
sb.append(scope);
sb.append(" ");
}
AUTH_TOKEN_TYPE = sb.toString();
}
private static SharedPreferences getSharedPreferences(final Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
/**
* Specify whether the app has an active account set.
*
* @param context Context used to lookup {@link SharedPreferences} the value is stored with.
*/
public static boolean hasActiveAccount(final Context context) {
return !TextUtils.isEmpty(getActiveAccountName(context));
}
/**
* Return the accountName the app is using as the active Google Account.
*
* @param context Context used to lookup {@link SharedPreferences} the value is stored with.
*/
public static String getActiveAccountName(final Context context) {
SharedPreferences sp = getSharedPreferences(context);
return sp.getString(PREF_ACTIVE_ACCOUNT, null);
}
/**
* Return the {@code Account} the app is using as the active Google Account.
*
* @param context Context used to lookup {@link SharedPreferences} the value is stored with.
*/
public static Account getActiveAccount(final Context context) {
String account = getActiveAccountName(context);
if (account != null) {
return new Account(account, GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE);
} else {
return null;
}
}
public static void setActiveAccount(final Context context, final String accountName) {
LOGD(TAG, "Set active account to: " + accountName);
SharedPreferences sp = getSharedPreferences(context);
sp.edit().putString(PREF_ACTIVE_ACCOUNT, accountName).apply();
}
public static void clearActiveAccount(final Context context) {
LOGD(TAG, "Clearing active account");
SharedPreferences sp = getSharedPreferences(context);
sp.edit().remove(PREF_ACTIVE_ACCOUNT).apply();
}
protected static String makeAccountSpecificPrefKey(Context ctx, String prefix) {
return hasActiveAccount(ctx) ? makeAccountSpecificPrefKey(getActiveAccountName(ctx),
prefix) : null;
}
protected static String makeAccountSpecificPrefKey(String accountName, String prefix) {
return prefix + accountName;
}
public static String getAuthToken(final Context context) {
SharedPreferences sp = getSharedPreferences(context);
return hasActiveAccount(context) ?
sp.getString(makeAccountSpecificPrefKey(context, PREFIX_PREF_AUTH_TOKEN), null) : null;
}
public static void setAuthToken(final Context context, final String accountName, final String authToken) {
LOGI(TAG, "Auth token of length "
+ (TextUtils.isEmpty(authToken) ? 0 : authToken.length()) + " for "
+ accountName);
SharedPreferences sp = getSharedPreferences(context);
sp.edit().putString(makeAccountSpecificPrefKey(accountName, PREFIX_PREF_AUTH_TOKEN),
authToken).apply();
LOGV(TAG, "Auth Token: " + authToken);
}
public static void setAuthToken(final Context context, final String authToken) {
if (hasActiveAccount(context)) {
setAuthToken(context, getActiveAccountName(context), authToken);
} else {
LOGE(TAG, "Can't set auth token because there is no chosen account!");
}
}
static void invalidateAuthToken(final Context context) {
setAuthToken(context, null);
}
public static void setPlusProfileId(final Context context, final String accountName, final String profileId) {
SharedPreferences sp = getSharedPreferences(context);
sp.edit().putString(makeAccountSpecificPrefKey(accountName, PREFIX_PREF_PLUS_PROFILE_ID),
profileId).apply();
}
public static String getPlusProfileId(final Context context) {
SharedPreferences sp = getSharedPreferences(context);
return hasActiveAccount(context) ? sp.getString(makeAccountSpecificPrefKey(context,
PREFIX_PREF_PLUS_PROFILE_ID), null) : null;
}
public static boolean hasPlusInfo(final Context context, final String accountName) {
SharedPreferences sp = getSharedPreferences(context);
return !TextUtils.isEmpty(sp.getString(makeAccountSpecificPrefKey(accountName,
PREFIX_PREF_PLUS_PROFILE_ID), null));
}
public static boolean hasToken(final Context context, final String accountName) {
SharedPreferences sp = getSharedPreferences(context);
return !TextUtils.isEmpty(sp.getString(makeAccountSpecificPrefKey(accountName,
PREFIX_PREF_AUTH_TOKEN), null));
}
public static void setPlusName(final Context context, final String accountName, final String name) {
SharedPreferences sp = getSharedPreferences(context);
sp.edit().putString(makeAccountSpecificPrefKey(accountName, PREFIX_PREF_PLUS_NAME),
name).apply();
}
public static String getPlusName(final Context context) {
SharedPreferences sp = getSharedPreferences(context);
return hasActiveAccount(context) ? sp.getString(makeAccountSpecificPrefKey(context,
PREFIX_PREF_PLUS_NAME), null) : null;
}
public static void setPlusImageUrl(final Context context, final String accountName, final String imageUrl) {
SharedPreferences sp = getSharedPreferences(context);
sp.edit().putString(makeAccountSpecificPrefKey(accountName, PREFIX_PREF_PLUS_IMAGE_URL),
imageUrl).apply();
}
public static String getPlusImageUrl(final Context context) {
SharedPreferences sp = getSharedPreferences(context);
return hasActiveAccount(context) ? sp.getString(makeAccountSpecificPrefKey(context,
PREFIX_PREF_PLUS_IMAGE_URL), null) : null;
}
public static String getPlusImageUrl(final Context context, final String accountName) {
SharedPreferences sp = getSharedPreferences(context);
return hasActiveAccount(context) ? sp.getString(makeAccountSpecificPrefKey(accountName,
PREFIX_PREF_PLUS_IMAGE_URL), null) : null;
}
public static void refreshAuthToken(Context mContext) {
invalidateAuthToken(mContext);
tryAuthenticateWithErrorNotification(mContext, ScheduleContract.CONTENT_AUTHORITY);
}
public static void setPlusCoverUrl(final Context context, final String accountName, String coverPhotoUrl) {
SharedPreferences sp = getSharedPreferences(context);
sp.edit().putString(makeAccountSpecificPrefKey(accountName, PREFIX_PREF_PLUS_COVER_URL),
coverPhotoUrl).apply();
}
public static String getPlusCoverUrl(final Context context) {
SharedPreferences sp = getSharedPreferences(context);
return hasActiveAccount(context) ? sp.getString(makeAccountSpecificPrefKey(context,
PREFIX_PREF_PLUS_COVER_URL), null) : null;
}
static void tryAuthenticateWithErrorNotification(Context context, String syncAuthority) {
try {
String accountName = getActiveAccountName(context);
if (accountName != null) {
LOGI(TAG, "Requesting new auth token (with notification)");
final String token = GoogleAuthUtil.getTokenWithNotification(context, accountName, AUTH_TOKEN_TYPE,
null, syncAuthority, null);
setAuthToken(context, token);
} else {
LOGE(TAG, "Can't try authentication because no account is chosen.");
}
} catch (UserRecoverableNotifiedException e) {
// Notification has already been pushed.
LOGW(TAG, "User recoverable exception. Check notification.", e);
} catch (GoogleAuthException e) {
// This is likely unrecoverable.
LOGE(TAG, "Unrecoverable authentication exception: " + e.getMessage(), e);
} catch (IOException e) {
LOGE(TAG, "transient error encountered: " + e.getMessage());
}
}
public static void setGcmKey(final Context context, final String accountName, final String gcmKey) {
SharedPreferences sp = getSharedPreferences(context);
sp.edit().putString(makeAccountSpecificPrefKey(accountName, PREFIX_PREF_GCM_KEY),
gcmKey).apply();
LOGD(TAG, "GCM key of account " + accountName + " set to: " + sanitizeGcmKey(gcmKey));
}
public static String getGcmKey(final Context context, final String accountName) {
SharedPreferences sp = getSharedPreferences(context);
String gcmKey = sp.getString(makeAccountSpecificPrefKey(accountName,
PREFIX_PREF_GCM_KEY), null);
// if there is no current GCM key, generate a new random one
if (TextUtils.isEmpty(gcmKey)) {
gcmKey = UUID.randomUUID().toString();
LOGD(TAG, "No GCM key on account " + accountName + ". Generating random one: "
+ sanitizeGcmKey(gcmKey));
setGcmKey(context, accountName, gcmKey);
}
return gcmKey;
}
public static String sanitizeGcmKey(String key) {
if (key == null) {
return "(null)";
} else if (key.length() > 8) {
return key.substring(0, 4) + "........" + key.substring(key.length() - 4);
} else {
return "........";
}
}
/**
* Enforce an active Google Account by checking to see if an active account is already set. If
* it is not set then use the {@link AccountPicker} to have the user select an account.
*
* @param activity The context to be used for starting an activity.
* @param activityResultCode The result to be used to start the {@link AccountPicker}.
* @return Returns whether the user already has an active account registered.
*/
public static boolean enforceActiveGoogleAccount(Activity activity, int activityResultCode) {
if (hasActiveAccount(activity)) {
return true;
} else {
Intent intent = AccountPicker.newChooseAccountIntent(null, null,
new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE},
true, null, null, null, null);
activity.startActivityForResult(intent, activityResultCode);
return false;
}
}
}