OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.blimp.auth; | |
6 | |
7 import android.accounts.Account; | |
8 import android.accounts.AccountManager; | |
9 import android.content.Context; | |
10 import android.content.Intent; | |
11 import android.content.SharedPreferences; | |
12 import android.os.AsyncTask; | |
13 | |
14 import com.google.android.gms.auth.GoogleAuthException; | |
15 import com.google.android.gms.auth.GoogleAuthUtil; | |
16 import com.google.android.gms.auth.GooglePlayServicesAvailabilityException; | |
17 import com.google.android.gms.auth.UserRecoverableNotifiedException; | |
18 import com.google.android.gms.common.ConnectionResult; | |
19 | |
20 import org.chromium.base.ContextUtils; | |
21 import org.chromium.base.Log; | |
22 import org.chromium.base.ThreadUtils; | |
23 import org.chromium.blimp.R; | |
24 | |
25 import java.io.IOException; | |
26 import java.util.concurrent.atomic.AtomicBoolean; | |
27 | |
28 /** | |
29 * An implementation of TokenSource that handles querying {@link GoogleAuthUtil}
for a valid | |
30 * authentication token for a particular user account. The user account will be
saved as an | |
31 * application preference once set. See {@link TokenSource} for information on
how callers know | |
32 * whether or not an account is set and how they set one. | |
33 */ | |
34 public class TokenSourceImpl implements TokenSource { | |
35 private static final String ACCOUNT_NAME_PREF = "BlimpAccount"; | |
36 // Prefix with oauth2: to make sure we get back an oauth2 token. | |
37 private static final String ACCOUNT_OAUTH2_SCOPE = | |
38 "oauth2:https://www.googleapis.com/auth/userinfo.email"; | |
39 private static final String ACCOUNT_TYPE = "com.google"; | |
40 private static final String TAG = "BlimpTokenSource"; | |
41 | |
42 private final Context mAppContext; | |
43 | |
44 private TokenSource.Callback mCallback; | |
45 private AsyncTask<String, Void, String> mTokenQueryTask; | |
46 | |
47 /** | |
48 * Creates a {@link TokenSourceImpl} that will load and save account informa
tion from the | |
49 * application {@link Context} given by {@code context}. | |
50 * @param context | |
51 */ | |
52 public TokenSourceImpl(Context context) { | |
53 mAppContext = context.getApplicationContext(); | |
54 } | |
55 | |
56 // TokenSource implementation. | |
57 @Override | |
58 public void destroy() { | |
59 ThreadUtils.assertOnUiThread(); | |
60 | |
61 if (isRetrievingToken()) { | |
62 mTokenQueryTask.cancel(true); | |
63 } | |
64 } | |
65 | |
66 @Override | |
67 public void setCallback(TokenSource.Callback callback) { | |
68 ThreadUtils.assertOnUiThread(); | |
69 | |
70 mCallback = callback; | |
71 } | |
72 | |
73 @Override | |
74 public void getToken() { | |
75 ThreadUtils.assertOnUiThread(); | |
76 | |
77 if (isRetrievingToken()) { | |
78 mTokenQueryTask.cancel(true); | |
79 mTokenQueryTask = null; | |
80 } | |
81 | |
82 if (mCallback == null) return; | |
83 | |
84 // Find the current account tracked by settings. | |
85 SharedPreferences preferences = ContextUtils.getAppSharedPreferences(); | |
86 String accountName = preferences.getString(ACCOUNT_NAME_PREF, null); | |
87 | |
88 if (accountName == null || !doesAccountExist(accountName)) { | |
89 // Remove any old preference value in case the account is invalid. | |
90 preferences.edit().remove(ACCOUNT_NAME_PREF).apply(); | |
91 | |
92 // Trigger account selection. | |
93 mCallback.onNeedsAccountToBeSelected(getAccountChooserIntent()); | |
94 return; | |
95 } | |
96 | |
97 mTokenQueryTask = new TokenQueryTask(); | |
98 mTokenQueryTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, accoun
tName); | |
99 } | |
100 | |
101 @Override | |
102 public boolean isRetrievingToken() { | |
103 ThreadUtils.assertOnUiThread(); | |
104 | |
105 return mTokenQueryTask != null && mTokenQueryTask.getStatus() == AsyncTa
sk.Status.RUNNING; | |
106 } | |
107 | |
108 @Override | |
109 public int tokenIsInvalid(String token) { | |
110 ThreadUtils.assertOnUiThread(); | |
111 | |
112 // TODO(dtrainor): Handle failure cases for tokenIsInvalid. | |
113 try { | |
114 GoogleAuthUtil.clearToken(mAppContext, token); | |
115 } catch (GooglePlayServicesAvailabilityException ex) { | |
116 return ex.getConnectionStatusCode(); | |
117 } catch (GoogleAuthException ex) { | |
118 Log.e(TAG, "Authentication exception during token query."); | |
119 return ConnectionResult.SIGN_IN_FAILED; | |
120 } catch (IOException ex) { | |
121 Log.e(TAG, "IOException during token query."); | |
122 return ConnectionResult.SIGN_IN_FAILED; | |
123 } | |
124 return ConnectionResult.SUCCESS; | |
125 } | |
126 | |
127 @Override | |
128 public void onAccountSelected(Intent data) { | |
129 ThreadUtils.assertOnUiThread(); | |
130 | |
131 String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME
); | |
132 SharedPreferences preferences = ContextUtils.getAppSharedPreferences(); | |
133 preferences.edit().putString(ACCOUNT_NAME_PREF, accountName).apply(); | |
134 } | |
135 | |
136 @SuppressWarnings("deprecation") | |
137 private Intent getAccountChooserIntent() { | |
138 // TODO(dtrainor): Change this to com.google.android.gms.common.AccountP
icker. | |
139 return AccountManager.newChooseAccountIntent(null, null, new String[] {A
CCOUNT_TYPE}, false, | |
140 mAppContext.getString(R.string.signin_chooser_description), null
, null, null); | |
141 } | |
142 | |
143 private boolean doesAccountExist(String accountName) { | |
144 Account[] accounts = AccountManager.get(mAppContext).getAccountsByType(A
CCOUNT_TYPE); | |
145 for (Account account : accounts) { | |
146 if (account.name.equals(accountName)) return true; | |
147 } | |
148 | |
149 return false; | |
150 } | |
151 | |
152 private class TokenQueryTask extends AsyncTask<String, Void, String> { | |
153 private final AtomicBoolean mIsRecoverable = new AtomicBoolean(); | |
154 | |
155 @Override | |
156 protected String doInBackground(String... params) { | |
157 try { | |
158 return GoogleAuthUtil.getTokenWithNotification( | |
159 mAppContext, params[0], ACCOUNT_OAUTH2_SCOPE, null); | |
160 } catch (UserRecoverableNotifiedException ex) { | |
161 // TODO(dtrainor): Does it make sense for this to be true if we
can't recover until | |
162 // the user performs some action? | |
163 mIsRecoverable.set(true); | |
164 } catch (IOException ex) { | |
165 mIsRecoverable.set(true); | |
166 } catch (GoogleAuthException ex) { | |
167 mIsRecoverable.set(false); | |
168 } | |
169 return null; | |
170 } | |
171 | |
172 @Override | |
173 protected void onPostExecute(String token) { | |
174 if (mCallback == null || isCancelled()) return; | |
175 | |
176 if (token == null) { | |
177 mCallback.onTokenUnavailable(mIsRecoverable.get()); | |
178 } else { | |
179 mCallback.onTokenReceived(token); | |
180 } | |
181 if (mTokenQueryTask == TokenQueryTask.this) mTokenQueryTask = null; | |
182 } | |
183 } | |
184 } | |
OLD | NEW |