blob: 29074ccbfeb43574fe9304b53912af16fece89f1 [file] [log] [blame]
Fred Quintana60307342009-03-24 22:48:12 -07001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.accounts;
18
Fred Quintanaa698f422009-04-08 19:14:54 -070019import android.content.BroadcastReceiver;
20import android.content.ContentValues;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
Fred Quintana60307342009-03-24 22:48:12 -070024import android.database.Cursor;
25import android.database.DatabaseUtils;
Fred Quintanaa698f422009-04-08 19:14:54 -070026import android.database.sqlite.SQLiteDatabase;
27import android.database.sqlite.SQLiteOpenHelper;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.HandlerThread;
31import android.os.IBinder;
32import android.os.Looper;
33import android.os.Message;
34import android.os.RemoteException;
35import android.os.SystemClock;
Fred Quintana60307342009-03-24 22:48:12 -070036import android.telephony.TelephonyManager;
Fred Quintanaa698f422009-04-08 19:14:54 -070037import android.text.TextUtils;
38import android.util.Log;
Fred Quintana60307342009-03-24 22:48:12 -070039
Fred Quintanaa698f422009-04-08 19:14:54 -070040import java.io.FileDescriptor;
41import java.io.PrintWriter;
42import java.util.ArrayList;
43import java.util.Collection;
Fred Quintana60307342009-03-24 22:48:12 -070044import java.util.HashMap;
Fred Quintanaa698f422009-04-08 19:14:54 -070045import java.util.LinkedHashMap;
Fred Quintana60307342009-03-24 22:48:12 -070046
Fred Quintana60307342009-03-24 22:48:12 -070047import com.android.internal.telephony.TelephonyIntents;
Fred Quintanaa698f422009-04-08 19:14:54 -070048import com.google.android.collect.Lists;
49import com.google.android.collect.Maps;
Fred Quintana60307342009-03-24 22:48:12 -070050
51/**
52 * A system service that provides account, password, and authtoken management for all
53 * accounts on the device. Some of these calls are implemented with the help of the corresponding
54 * {@link IAccountAuthenticator} services. This service is not accessed by users directly,
55 * instead one uses an instance of {@link AccountManager}, which can be accessed as follows:
56 * AccountManager accountManager =
57 * (AccountManager)context.getSystemService(Context.ACCOUNT_SERVICE)
58 */
59public class AccountManagerService extends IAccountManager.Stub {
60 private static final String TAG = "AccountManagerService";
61
62 private static final int TIMEOUT_DELAY_MS = 1000 * 60;
63 private static final String DATABASE_NAME = "accounts.db";
Fred Quintanaa698f422009-04-08 19:14:54 -070064 private static final int DATABASE_VERSION = 2;
Fred Quintana60307342009-03-24 22:48:12 -070065
66 private final Context mContext;
67
68 private HandlerThread mMessageThread;
69 private final MessageHandler mMessageHandler;
70
71 // Messages that can be sent on mHandler
72 private static final int MESSAGE_TIMED_OUT = 3;
73 private static final int MESSAGE_CONNECTED = 7;
74 private static final int MESSAGE_DISCONNECTED = 8;
75
76 private final AccountAuthenticatorCache mAuthenticatorCache;
77 private final AuthenticatorBindHelper mBindHelper;
78 public final HashMap<AuthTokenKey, String> mAuthTokenCache = Maps.newHashMap();
79 private final DatabaseHelper mOpenHelper;
80 private final SimWatcher mSimWatcher;
81
82 private static final String TABLE_ACCOUNTS = "accounts";
83 private static final String ACCOUNTS_ID = "_id";
84 private static final String ACCOUNTS_NAME = "name";
85 private static final String ACCOUNTS_TYPE = "type";
86 private static final String ACCOUNTS_PASSWORD = "password";
87
88 private static final String TABLE_AUTHTOKENS = "authtokens";
89 private static final String AUTHTOKENS_ID = "_id";
90 private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id";
91 private static final String AUTHTOKENS_TYPE = "type";
92 private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
93
94 private static final String TABLE_EXTRAS = "extras";
95 private static final String EXTRAS_ID = "_id";
96 private static final String EXTRAS_ACCOUNTS_ID = "accounts_id";
97 private static final String EXTRAS_KEY = "key";
98 private static final String EXTRAS_VALUE = "value";
99
100 private static final String TABLE_META = "meta";
101 private static final String META_KEY = "key";
102 private static final String META_VALUE = "value";
103
104 private static final String[] ACCOUNT_NAME_TYPE_PROJECTION =
105 new String[]{ACCOUNTS_ID, ACCOUNTS_NAME, ACCOUNTS_TYPE};
106 private static final Intent ACCOUNTS_CHANGED_INTENT =
Fred Quintanaa698f422009-04-08 19:14:54 -0700107 new Intent(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION);
108
109 private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
110 private static final int NOTIFICATION_ID = 234;
Fred Quintana60307342009-03-24 22:48:12 -0700111
112 public class AuthTokenKey {
113 public final Account mAccount;
114 public final String mAuthTokenType;
115 private final int mHashCode;
116
117 public AuthTokenKey(Account account, String authTokenType) {
118 mAccount = account;
119 mAuthTokenType = authTokenType;
120 mHashCode = computeHashCode();
121 }
122
123 public boolean equals(Object o) {
124 if (o == this) {
125 return true;
126 }
127 if (!(o instanceof AuthTokenKey)) {
128 return false;
129 }
130 AuthTokenKey other = (AuthTokenKey)o;
131 if (!mAccount.equals(other.mAccount)) {
132 return false;
133 }
134 return (mAuthTokenType == null)
135 ? other.mAuthTokenType == null
136 : mAuthTokenType.equals(other.mAuthTokenType);
137 }
138
139 private int computeHashCode() {
140 int result = 17;
141 result = 31 * result + mAccount.hashCode();
142 result = 31 * result + ((mAuthTokenType == null) ? 0 : mAuthTokenType.hashCode());
143 return result;
144 }
145
146 public int hashCode() {
147 return mHashCode;
148 }
149 }
150
151 public AccountManagerService(Context context) {
152 mContext = context;
153
154 mOpenHelper = new DatabaseHelper(mContext);
155
156 mMessageThread = new HandlerThread("AccountManagerService");
157 mMessageThread.start();
158 mMessageHandler = new MessageHandler(mMessageThread.getLooper());
159
160 mAuthenticatorCache = new AccountAuthenticatorCache(mContext);
161 mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler,
162 MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
163
164 mSimWatcher = new SimWatcher(mContext);
165 }
166
Fred Quintanaa698f422009-04-08 19:14:54 -0700167 public String getPassword(Account account) {
Fred Quintana60307342009-03-24 22:48:12 -0700168 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
169 Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD},
170 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
171 new String[]{account.mName, account.mType}, null, null, null);
172 try {
173 if (cursor.moveToNext()) {
174 return cursor.getString(0);
175 }
176 return null;
177 } finally {
178 cursor.close();
179 }
180 }
181
Fred Quintanaa698f422009-04-08 19:14:54 -0700182 public String getUserData(Account account, String key) {
Fred Quintana60307342009-03-24 22:48:12 -0700183 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
184 db.beginTransaction();
185 try {
186 long accountId = getAccountId(db, account);
187 if (accountId < 0) {
188 return null;
189 }
190 Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_VALUE},
191 EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
192 new String[]{key}, null, null, null);
193 try {
194 if (cursor.moveToNext()) {
195 return cursor.getString(0);
196 }
197 return null;
198 } finally {
199 cursor.close();
200 }
201 } finally {
202 db.setTransactionSuccessful();
203 db.endTransaction();
204 }
205 }
206
Fred Quintanaa698f422009-04-08 19:14:54 -0700207 public String[] getAuthenticatorTypes() {
208 Collection<AccountAuthenticatorCache.AuthenticatorInfo> authenticatorCollection =
209 mAuthenticatorCache.getAllAuthenticators();
210 String[] types = new String[authenticatorCollection.size()];
211 int i = 0;
212 for (AccountAuthenticatorCache.AuthenticatorInfo authenticator : authenticatorCollection) {
213 types[i] = authenticator.mType;
214 i++;
215 }
216 return types;
217 }
218
219 public Account[] getAccounts() {
Fred Quintana60307342009-03-24 22:48:12 -0700220 return getAccountsByType(null);
221 }
222
Fred Quintanaa698f422009-04-08 19:14:54 -0700223 public Account[] getAccountsByType(String accountType) {
Fred Quintana60307342009-03-24 22:48:12 -0700224 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
225
226 final String selection = accountType == null ? null : (ACCOUNTS_TYPE + "=?");
227 final String[] selectionArgs = accountType == null ? null : new String[]{accountType};
228 Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_NAME_TYPE_PROJECTION,
229 selection, selectionArgs, null, null, null);
230 try {
231 int i = 0;
232 Account[] accounts = new Account[cursor.getCount()];
233 while (cursor.moveToNext()) {
234 accounts[i] = new Account(cursor.getString(1), cursor.getString(2));
235 i++;
236 }
237 return accounts;
238 } finally {
239 cursor.close();
240 }
241 }
242
Fred Quintanaa698f422009-04-08 19:14:54 -0700243 public boolean addAccount(Account account, String password, Bundle extras) {
Fred Quintana60307342009-03-24 22:48:12 -0700244 // fails if the account already exists
245 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
246 db.beginTransaction();
247 try {
248 long numMatches = DatabaseUtils.longForQuery(db,
249 "select count(*) from " + TABLE_ACCOUNTS
250 + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
251 new String[]{account.mName, account.mType});
252 if (numMatches > 0) {
253 return false;
254 }
255 ContentValues values = new ContentValues();
256 values.put(ACCOUNTS_NAME, account.mName);
257 values.put(ACCOUNTS_TYPE, account.mType);
258 values.put(ACCOUNTS_PASSWORD, password);
259 long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
260 if (accountId < 0) {
261 return false;
262 }
263 if (extras != null) {
264 for (String key : extras.keySet()) {
265 final String value = extras.getString(key);
266 if (insertExtra(db, accountId, key, value) < 0) {
267 return false;
268 }
269 }
270 }
271 db.setTransactionSuccessful();
272 mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
273 return true;
274 } finally {
275 db.endTransaction();
276 }
277 }
278
279 private long insertExtra(SQLiteDatabase db, long accountId, String key, String value) {
280 ContentValues values = new ContentValues();
281 values.put(EXTRAS_KEY, key);
282 values.put(EXTRAS_ACCOUNTS_ID, accountId);
283 values.put(EXTRAS_VALUE, value);
284 return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
285 }
286
Fred Quintanaa698f422009-04-08 19:14:54 -0700287 public void removeAccount(Account account) {
288 synchronized (mAuthTokenCache) {
289 ArrayList<AuthTokenKey> keysToRemove = Lists.newArrayList();
290 for (AuthTokenKey key : mAuthTokenCache.keySet()) {
291 if (key.mAccount.equals(account)) {
292 keysToRemove.add(key);
293 }
294 }
295 for (AuthTokenKey key : keysToRemove) {
296 mAuthTokenCache.remove(key);
297 }
298
299 final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
300 db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
301 new String[]{account.mName, account.mType});
302 mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
303 }
Fred Quintana60307342009-03-24 22:48:12 -0700304 }
305
Fred Quintanaa698f422009-04-08 19:14:54 -0700306 public void invalidateAuthToken(String accountType, String authToken) {
Fred Quintana60307342009-03-24 22:48:12 -0700307 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
308 db.beginTransaction();
309 try {
310 invalidateAuthToken(db, accountType, authToken);
311 db.setTransactionSuccessful();
312 } finally {
313 db.endTransaction();
314 }
315 }
316
317 private void invalidateAuthToken(SQLiteDatabase db, String accountType, String authToken) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700318 synchronized (mAuthTokenCache) {
319 Cursor cursor = db.rawQuery(
320 "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
321 + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
322 + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
323 + " FROM " + TABLE_ACCOUNTS
324 + " JOIN " + TABLE_AUTHTOKENS
325 + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
326 + " = " + AUTHTOKENS_ACCOUNTS_ID
327 + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND "
328 + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
329 new String[]{authToken, accountType});
330 try {
331 while (cursor.moveToNext()) {
332 long authTokenId = cursor.getLong(0);
333 String accountName = cursor.getString(1);
334 String authTokenType = cursor.getString(2);
335 AuthTokenKey key = new AuthTokenKey(new Account(accountName, accountType),
336 authTokenType);
337 mAuthTokenCache.remove(key);
338 db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
339 }
340 } finally {
341 cursor.close();
Fred Quintana60307342009-03-24 22:48:12 -0700342 }
Fred Quintana60307342009-03-24 22:48:12 -0700343 }
344 }
345
346 private boolean saveAuthTokenToDatabase(Account account, String type, String authToken) {
347 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
348 db.beginTransaction();
349 try {
350 if (saveAuthTokenToDatabase(db, account, type, authToken)) {
351 mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
352 db.setTransactionSuccessful();
353 return true;
354 }
355 return false;
356 } finally {
357 db.endTransaction();
358 }
359 }
360
361 private boolean saveAuthTokenToDatabase(SQLiteDatabase db, Account account,
362 String type, String authToken) {
363 long accountId = getAccountId(db, account);
364 if (accountId < 0) {
365 return false;
366 }
367 db.delete(TABLE_AUTHTOKENS,
368 AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
369 new String[]{type});
370 ContentValues values = new ContentValues();
371 values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
372 values.put(AUTHTOKENS_TYPE, type);
373 values.put(AUTHTOKENS_AUTHTOKEN, authToken);
374 return db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0;
375 }
376
377 public String readAuthTokenFromDatabase(Account account, String authTokenType) {
378 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
379 db.beginTransaction();
380 try {
381 long accountId = getAccountId(db, account);
382 if (accountId < 0) {
383 return null;
384 }
385 return getAuthToken(db, accountId, authTokenType);
386 } finally {
387 db.setTransactionSuccessful();
388 db.endTransaction();
389 }
390 }
391
Fred Quintanaa698f422009-04-08 19:14:54 -0700392 public String peekAuthToken(Account account, String authTokenType) {
393 synchronized (mAuthTokenCache) {
394 AuthTokenKey key = new AuthTokenKey(account, authTokenType);
395 if (mAuthTokenCache.containsKey(key)) {
396 return mAuthTokenCache.get(key);
397 }
398 return readAuthTokenFromDatabase(account, authTokenType);
Fred Quintana60307342009-03-24 22:48:12 -0700399 }
Fred Quintana60307342009-03-24 22:48:12 -0700400 }
401
Fred Quintanaa698f422009-04-08 19:14:54 -0700402 public void setAuthToken(Account account, String authTokenType, String authToken) {
Fred Quintana60307342009-03-24 22:48:12 -0700403 cacheAuthToken(account, authTokenType, authToken);
404 }
405
Fred Quintanaa698f422009-04-08 19:14:54 -0700406 public void setPassword(Account account, String password) {
Fred Quintana60307342009-03-24 22:48:12 -0700407 ContentValues values = new ContentValues();
408 values.put(ACCOUNTS_PASSWORD, password);
409 mOpenHelper.getWritableDatabase().update(TABLE_ACCOUNTS, values,
410 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
411 new String[]{account.mName, account.mType});
412 mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
413 }
414
Fred Quintanaa698f422009-04-08 19:14:54 -0700415 public void clearPassword(Account account) {
Fred Quintana60307342009-03-24 22:48:12 -0700416 setPassword(account, null);
417 }
418
Fred Quintanaa698f422009-04-08 19:14:54 -0700419 public void setUserData(Account account, String key, String value) {
Fred Quintana60307342009-03-24 22:48:12 -0700420 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
421 db.beginTransaction();
422 try {
423 long accountId = getAccountId(db, account);
424 if (accountId < 0) {
425 return;
426 }
427 long extrasId = getExtrasId(db, accountId, key);
428 if (extrasId < 0 ) {
429 extrasId = insertExtra(db, accountId, key, value);
430 if (extrasId < 0) {
431 return;
432 }
433 } else {
434 ContentValues values = new ContentValues();
435 values.put(EXTRAS_VALUE, value);
436 if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
437 return;
438 }
439
440 }
441 db.setTransactionSuccessful();
442 } finally {
443 db.endTransaction();
444 }
445 }
446
Fred Quintanaa698f422009-04-08 19:14:54 -0700447 public void getAuthToken(IAccountManagerResponse response, final Account account,
448 final String authTokenType, final boolean notifyOnAuthFailure,
449 final boolean expectActivityLaunch, final Bundle loginOptions) {
Fred Quintana60307342009-03-24 22:48:12 -0700450 String authToken = getCachedAuthToken(account, authTokenType);
451 if (authToken != null) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700452 try {
453 Bundle result = new Bundle();
454 result.putString(Constants.AUTHTOKEN_KEY, authToken);
455 result.putString(Constants.ACCOUNT_NAME_KEY, account.mName);
456 result.putString(Constants.ACCOUNT_TYPE_KEY, account.mType);
457 response.onResult(result);
458 } catch (RemoteException e) {
459 // if the caller is dead then there is no one to care about remote exceptions
460 if (Log.isLoggable(TAG, Log.VERBOSE)) {
461 Log.v(TAG, "failure while notifying response", e);
462 }
463 }
Fred Quintana60307342009-03-24 22:48:12 -0700464 return;
465 }
466
Fred Quintanaa698f422009-04-08 19:14:54 -0700467 new Session(response, account.mType, expectActivityLaunch) {
468 protected String toDebugString(long now) {
469 if (loginOptions != null) loginOptions.keySet();
470 return super.toDebugString(now) + ", getAuthToken"
471 + ", " + account
472 + ", authTokenType " + authTokenType
473 + ", loginOptions " + loginOptions
474 + ", notifyOnAuthFailure " + notifyOnAuthFailure;
475 }
476
477 public void run() throws RemoteException {
478 mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
479 }
480
481 public void onResult(Bundle result) {
482 if (result != null) {
483 String authToken = result.getString(Constants.AUTHTOKEN_KEY);
484 if (authToken != null) {
485 String name = result.getString(Constants.ACCOUNT_NAME_KEY);
486 String type = result.getString(Constants.ACCOUNT_TYPE_KEY);
487 if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) {
488 onError(Constants.ERROR_CODE_INVALID_RESPONSE,
489 "the type and name should not be empty");
490 return;
491 }
492 cacheAuthToken(new Account(name, type), authTokenType, authToken);
493 }
494
495 Intent intent = result.getParcelable(Constants.INTENT_KEY);
496 if (intent != null && notifyOnAuthFailure) {
497 doNotification(result.getString(Constants.AUTH_FAILED_MESSAGE_KEY), intent);
498 }
499 }
500 super.onResult(result);
501 }
502 }.bind();
Fred Quintana60307342009-03-24 22:48:12 -0700503 }
504
Fred Quintanaa698f422009-04-08 19:14:54 -0700505
506 public void addAcount(final IAccountManagerResponse response,
507 final String accountType, final String authTokenType,
508 final boolean expectActivityLaunch, final Bundle options) {
509 new Session(response, accountType, expectActivityLaunch) {
510 public void run() throws RemoteException {
511 mAuthenticator.addAccount(this, mAccountType, authTokenType, options);
512 }
513
514 protected String toDebugString(long now) {
515 return super.toDebugString(now) + ", addAccount"
516 + ", accountType " + accountType;
517 }
518 }.bind();
Fred Quintana60307342009-03-24 22:48:12 -0700519 }
520
Fred Quintanaa698f422009-04-08 19:14:54 -0700521 public void confirmCredentials(IAccountManagerResponse response,
522 final Account account, final boolean expectActivityLaunch) {
523 new Session(response, account.mType, expectActivityLaunch) {
524 public void run() throws RemoteException {
525 mAuthenticator.confirmCredentials(this, account);
526 }
527 protected String toDebugString(long now) {
528 return super.toDebugString(now) + ", confirmCredentials"
529 + ", " + account;
530 }
531 }.bind();
Fred Quintana60307342009-03-24 22:48:12 -0700532 }
533
Fred Quintanaa698f422009-04-08 19:14:54 -0700534 public void confirmPassword(IAccountManagerResponse response, final Account account,
535 final String password) {
536 new Session(response, account.mType, false /* expectActivityLaunch */) {
537 public void run() throws RemoteException {
538 mAuthenticator.confirmPassword(this, account, password);
539 }
540 protected String toDebugString(long now) {
541 return super.toDebugString(now) + ", confirmPassword"
542 + ", " + account;
543 }
544 }.bind();
Fred Quintana60307342009-03-24 22:48:12 -0700545 }
546
Fred Quintanaa698f422009-04-08 19:14:54 -0700547 public void updateCredentials(IAccountManagerResponse response, final Account account,
548 final String authTokenType, final boolean expectActivityLaunch,
549 final Bundle loginOptions) {
550 new Session(response, account.mType, expectActivityLaunch) {
551 public void run() throws RemoteException {
552 mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
553 }
554 protected String toDebugString(long now) {
555 if (loginOptions != null) loginOptions.keySet();
556 return super.toDebugString(now) + ", updateCredentials"
557 + ", " + account
558 + ", authTokenType " + authTokenType
559 + ", loginOptions " + loginOptions;
560 }
561 }.bind();
Fred Quintana60307342009-03-24 22:48:12 -0700562 }
563
Fred Quintanaa698f422009-04-08 19:14:54 -0700564 public void editProperties(IAccountManagerResponse response, final String accountType,
565 final boolean expectActivityLaunch) {
566 new Session(response, accountType, expectActivityLaunch) {
567 public void run() throws RemoteException {
568 mAuthenticator.editProperties(this, mAccountType);
569 }
570 protected String toDebugString(long now) {
571 return super.toDebugString(now) + ", editProperties"
572 + ", accountType " + accountType;
573 }
574 }.bind();
Fred Quintana60307342009-03-24 22:48:12 -0700575 }
576
577 private boolean cacheAuthToken(Account account, String authTokenType, String authToken) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700578 synchronized (mAuthTokenCache) {
579 if (saveAuthTokenToDatabase(account, authTokenType, authToken)) {
580 final AuthTokenKey key = new AuthTokenKey(account, authTokenType);
581 mAuthTokenCache.put(key, authToken);
582 return true;
583 } else {
584 return false;
585 }
Fred Quintana60307342009-03-24 22:48:12 -0700586 }
587 }
588
589 private String getCachedAuthToken(Account account, String authTokenType) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700590 synchronized (mAuthTokenCache) {
591 final AuthTokenKey key = new AuthTokenKey(account, authTokenType);
592 if (!mAuthTokenCache.containsKey(key)) return null;
593 return mAuthTokenCache.get(key);
594 }
Fred Quintana60307342009-03-24 22:48:12 -0700595 }
596
597 private long getAccountId(SQLiteDatabase db, Account account) {
598 Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID},
599 "name=? AND type=?", new String[]{account.mName, account.mType}, null, null, null);
600 try {
601 if (cursor.moveToNext()) {
602 return cursor.getLong(0);
603 }
604 return -1;
605 } finally {
606 cursor.close();
607 }
608 }
609
610 private long getExtrasId(SQLiteDatabase db, long accountId, String key) {
611 Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID},
612 EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
613 new String[]{key}, null, null, null);
614 try {
615 if (cursor.moveToNext()) {
616 return cursor.getLong(0);
617 }
618 return -1;
619 } finally {
620 cursor.close();
621 }
622 }
623
624 private String getAuthToken(SQLiteDatabase db, long accountId, String authTokenType) {
625 Cursor cursor = db.query(TABLE_AUTHTOKENS, new String[]{AUTHTOKENS_AUTHTOKEN},
626 AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
627 new String[]{authTokenType},
628 null, null, null);
629 try {
630 if (cursor.moveToNext()) {
631 return cursor.getString(0);
632 }
633 return null;
634 } finally {
635 cursor.close();
636 }
637 }
638
Fred Quintanaa698f422009-04-08 19:14:54 -0700639 private abstract class Session extends IAccountAuthenticatorResponse.Stub
640 implements AuthenticatorBindHelper.Callback, IBinder.DeathRecipient {
Fred Quintana60307342009-03-24 22:48:12 -0700641 IAccountManagerResponse mResponse;
642 final String mAccountType;
Fred Quintanaa698f422009-04-08 19:14:54 -0700643 final boolean mExpectActivityLaunch;
644 final long mCreationTime;
645
646 private int mNumResults = 0;
647 private int mNumRequestContinued = 0;
648 private int mNumErrors = 0;
649
Fred Quintana60307342009-03-24 22:48:12 -0700650
651 IAccountAuthenticator mAuthenticator = null;
652
Fred Quintanaa698f422009-04-08 19:14:54 -0700653 public Session(IAccountManagerResponse response, String accountType,
654 boolean expectActivityLaunch) {
Fred Quintana60307342009-03-24 22:48:12 -0700655 super();
Fred Quintanaa698f422009-04-08 19:14:54 -0700656 if (response == null) throw new IllegalArgumentException("response is null");
Fred Quintana60307342009-03-24 22:48:12 -0700657 mResponse = response;
658 mAccountType = accountType;
Fred Quintanaa698f422009-04-08 19:14:54 -0700659 mExpectActivityLaunch = expectActivityLaunch;
660 mCreationTime = SystemClock.elapsedRealtime();
661 synchronized (mSessions) {
662 mSessions.put(toString(), this);
663 }
664 try {
665 response.asBinder().linkToDeath(this, 0 /* flags */);
666 } catch (RemoteException e) {
667 mResponse = null;
668 binderDied();
669 }
Fred Quintana60307342009-03-24 22:48:12 -0700670 }
671
Fred Quintanaa698f422009-04-08 19:14:54 -0700672 IAccountManagerResponse getResponseAndClose() {
Fred Quintana60307342009-03-24 22:48:12 -0700673 if (mResponse == null) {
674 // this session has already been closed
675 return null;
676 }
Fred Quintana60307342009-03-24 22:48:12 -0700677 IAccountManagerResponse response = mResponse;
Fred Quintanaa698f422009-04-08 19:14:54 -0700678 close(); // this clears mResponse so we need to save the response before this call
Fred Quintana60307342009-03-24 22:48:12 -0700679 return response;
680 }
681
Fred Quintanaa698f422009-04-08 19:14:54 -0700682 private void close() {
683 synchronized (mSessions) {
684 if (mSessions.remove(toString()) == null) {
685 // the session was already closed, so bail out now
686 return;
687 }
688 }
689 if (mResponse != null) {
690 // stop listening for response deaths
691 mResponse.asBinder().unlinkToDeath(this, 0 /* flags */);
692
693 // clear this so that we don't accidentally send any further results
694 mResponse = null;
695 }
696 cancelTimeout();
697 unbind();
698 }
699
700 public void binderDied() {
701 mResponse = null;
702 close();
703 }
704
705 protected String toDebugString() {
706 return toDebugString(SystemClock.elapsedRealtime());
707 }
708
709 protected String toDebugString(long now) {
710 return "Session: expectLaunch " + mExpectActivityLaunch
711 + ", connected " + (mAuthenticator != null)
712 + ", stats (" + mNumResults + "/" + mNumRequestContinued
713 + "/" + mNumErrors + ")"
714 + ", lifetime " + ((now - mCreationTime) / 1000.0);
715 }
716
Fred Quintana60307342009-03-24 22:48:12 -0700717 void bind() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700718 if (Log.isLoggable(TAG, Log.VERBOSE)) {
719 Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
720 }
Fred Quintana60307342009-03-24 22:48:12 -0700721 if (!mBindHelper.bind(mAccountType, this)) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700722 Log.d(TAG, "bind attempt failed for " + toDebugString());
723 onError(Constants.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
Fred Quintana60307342009-03-24 22:48:12 -0700724 }
725 }
726
727 private void unbind() {
728 if (mAuthenticator != null) {
729 mAuthenticator = null;
730 mBindHelper.unbind(this);
731 }
732 }
733
734 public void scheduleTimeout() {
735 mMessageHandler.sendMessageDelayed(
736 mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
737 }
738
739 public void cancelTimeout() {
740 mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
741 }
742
743 public void onConnected(IBinder service) {
744 mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
Fred Quintanaa698f422009-04-08 19:14:54 -0700745 try {
746 run();
747 } catch (RemoteException e) {
748 onError(Constants.ERROR_CODE_REMOTE_EXCEPTION,
749 "remote exception");
750 }
Fred Quintana60307342009-03-24 22:48:12 -0700751 }
752
Fred Quintanaa698f422009-04-08 19:14:54 -0700753 public abstract void run() throws RemoteException;
754
Fred Quintana60307342009-03-24 22:48:12 -0700755 public void onDisconnected() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700756 mAuthenticator = null;
757 IAccountManagerResponse response = getResponseAndClose();
Fred Quintana60307342009-03-24 22:48:12 -0700758 if (response != null) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700759 onError(Constants.ERROR_CODE_REMOTE_EXCEPTION,
760 "disconnected");
Fred Quintana60307342009-03-24 22:48:12 -0700761 }
762 }
763
764 public void onTimedOut() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700765 IAccountManagerResponse response = getResponseAndClose();
Fred Quintana60307342009-03-24 22:48:12 -0700766 if (response != null) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700767 onError(Constants.ERROR_CODE_REMOTE_EXCEPTION,
768 "timeout");
Fred Quintana60307342009-03-24 22:48:12 -0700769 }
770 }
771
Fred Quintanaa698f422009-04-08 19:14:54 -0700772 public void onResult(Bundle result) {
773 mNumResults++;
774 if (result != null && !TextUtils.isEmpty(result.getString(Constants.AUTHTOKEN_KEY))) {
775 cancelNotification();
Fred Quintana60307342009-03-24 22:48:12 -0700776 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700777 IAccountManagerResponse response;
778 if (mExpectActivityLaunch && result != null
779 && result.containsKey(Constants.INTENT_KEY)) {
780 response = mResponse;
781 } else {
782 response = getResponseAndClose();
Fred Quintana60307342009-03-24 22:48:12 -0700783 }
Fred Quintana60307342009-03-24 22:48:12 -0700784 if (response != null) {
785 try {
Fred Quintanaa698f422009-04-08 19:14:54 -0700786 if (result == null) {
787 response.onError(Constants.ERROR_CODE_INVALID_RESPONSE,
788 "null bundle returned");
789 } else {
790 response.onResult(result);
791 }
Fred Quintana60307342009-03-24 22:48:12 -0700792 } catch (RemoteException e) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700793 // if the caller is dead then there is no one to care about remote exceptions
794 if (Log.isLoggable(TAG, Log.VERBOSE)) {
795 Log.v(TAG, "failure while notifying response", e);
796 }
Fred Quintana60307342009-03-24 22:48:12 -0700797 }
798 }
799 }
Fred Quintana60307342009-03-24 22:48:12 -0700800
Fred Quintanaa698f422009-04-08 19:14:54 -0700801 public void onRequestContinued() {
802 mNumRequestContinued++;
Fred Quintana60307342009-03-24 22:48:12 -0700803 }
804
805 public void onError(int errorCode, String errorMessage) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700806 mNumErrors++;
807 if (Log.isLoggable(TAG, Log.VERBOSE)) {
808 Log.v(TAG, "Session.onError: " + errorCode + ", " + errorMessage);
Fred Quintana60307342009-03-24 22:48:12 -0700809 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700810 IAccountManagerResponse response = getResponseAndClose();
811 if (response != null) {
812 if (Log.isLoggable(TAG, Log.VERBOSE)) {
813 Log.v(TAG, "Session.onError: responding");
814 }
815 try {
816 response.onError(errorCode, errorMessage);
817 } catch (RemoteException e) {
818 if (Log.isLoggable(TAG, Log.VERBOSE)) {
819 Log.v(TAG, "Session.onError: caught RemoteException while responding", e);
820 }
821 }
822 } else {
823 if (Log.isLoggable(TAG, Log.VERBOSE)) {
824 Log.v(TAG, "Session.onError: already closed");
825 }
Fred Quintana60307342009-03-24 22:48:12 -0700826 }
827 }
828 }
829
830 private class MessageHandler extends Handler {
831 MessageHandler(Looper looper) {
832 super(looper);
833 }
834
835 public void handleMessage(Message msg) {
836 if (mBindHelper.handleMessage(msg)) {
837 return;
838 }
839 switch (msg.what) {
840 case MESSAGE_TIMED_OUT:
841 Session session = (Session)msg.obj;
842 session.onTimedOut();
843 break;
844
845 default:
846 throw new IllegalStateException("unhandled message: " + msg.what);
847 }
848 }
849 }
850
851 private class DatabaseHelper extends SQLiteOpenHelper {
852 public DatabaseHelper(Context context) {
853 super(context, DATABASE_NAME, null, DATABASE_VERSION);
854 }
855
856 @Override
857 public void onCreate(SQLiteDatabase db) {
858 db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
859 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
860 + ACCOUNTS_NAME + " TEXT NOT NULL, "
861 + ACCOUNTS_TYPE + " TEXT NOT NULL, "
862 + ACCOUNTS_PASSWORD + " TEXT, "
863 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
864
865 db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( "
866 + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
867 + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
868 + AUTHTOKENS_TYPE + " TEXT NOT NULL, "
869 + AUTHTOKENS_AUTHTOKEN + " TEXT, "
870 + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
871
872 db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
873 + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
874 + EXTRAS_ACCOUNTS_ID + " INTEGER, "
875 + EXTRAS_KEY + " TEXT NOT NULL, "
876 + EXTRAS_VALUE + " TEXT, "
877 + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
878
879 db.execSQL("CREATE TABLE " + TABLE_META + " ( "
880 + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
881 + META_VALUE + " TEXT)");
Fred Quintanaa698f422009-04-08 19:14:54 -0700882
883 db.execSQL(""
884 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
885 + " BEGIN"
886 + " DELETE FROM " + TABLE_AUTHTOKENS
887 + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
888 + " DELETE FROM " + TABLE_EXTRAS
889 + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
890 + " END");
Fred Quintana60307342009-03-24 22:48:12 -0700891 }
892
893 @Override
894 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700895 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
Fred Quintana60307342009-03-24 22:48:12 -0700896
Fred Quintanaa698f422009-04-08 19:14:54 -0700897 if (oldVersion == 1) {
898 db.execSQL(""
899 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
900 + " BEGIN"
901 + " DELETE FROM " + TABLE_AUTHTOKENS
902 + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + " =OLD." + ACCOUNTS_ID + " ;"
903 + " DELETE FROM " + TABLE_EXTRAS
904 + " WHERE " + EXTRAS_ACCOUNTS_ID + " =OLD." + ACCOUNTS_ID + " ;"
905 + " END");
906 oldVersion++;
907 }
Fred Quintana60307342009-03-24 22:48:12 -0700908 }
909
910 @Override
911 public void onOpen(SQLiteDatabase db) {
912 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
913 }
914 }
915
916 private void setMetaValue(String key, String value) {
917 ContentValues values = new ContentValues();
918 values.put(META_KEY, key);
919 values.put(META_VALUE, value);
920 mOpenHelper.getWritableDatabase().replace(TABLE_META, META_KEY, values);
921 }
922
923 private String getMetaValue(String key) {
924 Cursor c = mOpenHelper.getReadableDatabase().query(TABLE_META,
925 new String[]{META_VALUE}, META_KEY + "=?", new String[]{key}, null, null, null);
926 try {
927 if (c.moveToNext()) {
928 return c.getString(0);
929 }
930 return null;
931 } finally {
932 c.close();
933 }
934 }
935
936 private class SimWatcher extends BroadcastReceiver {
937 public SimWatcher(Context context) {
938 // Re-scan the SIM card when the SIM state changes, and also if
939 // the disk recovers from a full state (we may have failed to handle
940 // things properly while the disk was full).
941 final IntentFilter filter = new IntentFilter();
942 filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
943 filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
944 context.registerReceiver(this, filter);
945 }
946
947 /**
948 * Compare the IMSI to the one stored in the login service's
949 * database. If they differ, erase all passwords and
950 * authtokens (and store the new IMSI).
951 */
952 @Override
953 public void onReceive(Context context, Intent intent) {
954 // Check IMSI on every update; nothing happens if the IMSI is missing or unchanged.
955 String imsi = ((TelephonyManager) context.getSystemService(
956 Context.TELEPHONY_SERVICE)).getSubscriberId();
957 if (TextUtils.isEmpty(imsi)) return;
958
959 String storedImsi = getMetaValue("imsi");
960
961 if (Log.isLoggable(TAG, Log.VERBOSE)) {
962 Log.v(TAG, "current IMSI=" + imsi + "; stored IMSI=" + storedImsi);
963 }
964
965 if (!imsi.equals(storedImsi) && !"initial".equals(storedImsi)) {
966 if (Log.isLoggable(TAG, Log.VERBOSE)) {
967 Log.v(TAG, "wiping all passwords and authtokens");
968 }
969 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
970 db.beginTransaction();
971 try {
972 db.execSQL("DELETE from " + TABLE_AUTHTOKENS);
973 db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_PASSWORD + " = ''");
974 mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
975 db.setTransactionSuccessful();
976 } finally {
977 db.endTransaction();
978 }
979 }
980 setMetaValue("imsi", imsi);
981 }
982 }
983
984 public IBinder onBind(Intent intent) {
985 return asBinder();
986 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700987
988 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
989 synchronized (mSessions) {
990 final long now = SystemClock.elapsedRealtime();
991 fout.println("AccountManagerService: " + mSessions.size() + " sessions");
992 for (Session session : mSessions.values()) {
993 fout.println(" " + session.toDebugString(now));
994 }
995 }
996
997 fout.println();
998
999 mAuthenticatorCache.dump(fd, fout, args);
1000 }
1001
1002 private void doNotification(CharSequence message, Intent intent) {
1003 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1004 Log.v(TAG, "doNotification: " + message + " intent:" + intent);
1005 }
1006
1007 // TODO(fredq) add this back in when we fix permissions
1008// Notification n = new Notification(android.R.drawable.stat_sys_warning, null, 0 /* when */);
1009// n.setLatestEventInfo(mContext, mContext.getText(R.string.notification_title), message,
1010// PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
1011// ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
1012// .notify(NOTIFICATION_ID, n);
1013 }
1014
1015 private void cancelNotification() {
1016// ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
1017// .cancel(NOTIFICATION_ID);
1018 }
Fred Quintana60307342009-03-24 22:48:12 -07001019}