blob: ef0875c670552a9432ec4726ffd2c13f6cefff19 [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 Quintana26fc5eb2009-04-09 15:05:50 -070039import android.app.PendingIntent;
40import android.app.NotificationManager;
41import android.app.Notification;
Fred Quintana60307342009-03-24 22:48:12 -070042
Fred Quintanaa698f422009-04-08 19:14:54 -070043import java.io.FileDescriptor;
44import java.io.PrintWriter;
45import java.util.ArrayList;
46import java.util.Collection;
Fred Quintana60307342009-03-24 22:48:12 -070047import java.util.HashMap;
Fred Quintanaa698f422009-04-08 19:14:54 -070048import java.util.LinkedHashMap;
Fred Quintana60307342009-03-24 22:48:12 -070049
Fred Quintana60307342009-03-24 22:48:12 -070050import com.android.internal.telephony.TelephonyIntents;
Fred Quintana26fc5eb2009-04-09 15:05:50 -070051import com.android.internal.R;
Fred Quintanaa698f422009-04-08 19:14:54 -070052import com.google.android.collect.Lists;
53import com.google.android.collect.Maps;
Fred Quintana60307342009-03-24 22:48:12 -070054
55/**
56 * A system service that provides account, password, and authtoken management for all
57 * accounts on the device. Some of these calls are implemented with the help of the corresponding
58 * {@link IAccountAuthenticator} services. This service is not accessed by users directly,
59 * instead one uses an instance of {@link AccountManager}, which can be accessed as follows:
60 * AccountManager accountManager =
61 * (AccountManager)context.getSystemService(Context.ACCOUNT_SERVICE)
Fred Quintana33269202009-04-20 16:05:10 -070062 * @hide
Fred Quintana60307342009-03-24 22:48:12 -070063 */
64public class AccountManagerService extends IAccountManager.Stub {
65 private static final String TAG = "AccountManagerService";
66
67 private static final int TIMEOUT_DELAY_MS = 1000 * 60;
68 private static final String DATABASE_NAME = "accounts.db";
Fred Quintanaa698f422009-04-08 19:14:54 -070069 private static final int DATABASE_VERSION = 2;
Fred Quintana60307342009-03-24 22:48:12 -070070
71 private final Context mContext;
72
73 private HandlerThread mMessageThread;
74 private final MessageHandler mMessageHandler;
75
76 // Messages that can be sent on mHandler
77 private static final int MESSAGE_TIMED_OUT = 3;
78 private static final int MESSAGE_CONNECTED = 7;
79 private static final int MESSAGE_DISCONNECTED = 8;
80
81 private final AccountAuthenticatorCache mAuthenticatorCache;
82 private final AuthenticatorBindHelper mBindHelper;
Fred Quintana60307342009-03-24 22:48:12 -070083 private final DatabaseHelper mOpenHelper;
84 private final SimWatcher mSimWatcher;
85
86 private static final String TABLE_ACCOUNTS = "accounts";
87 private static final String ACCOUNTS_ID = "_id";
88 private static final String ACCOUNTS_NAME = "name";
89 private static final String ACCOUNTS_TYPE = "type";
90 private static final String ACCOUNTS_PASSWORD = "password";
91
92 private static final String TABLE_AUTHTOKENS = "authtokens";
93 private static final String AUTHTOKENS_ID = "_id";
94 private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id";
95 private static final String AUTHTOKENS_TYPE = "type";
96 private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
97
98 private static final String TABLE_EXTRAS = "extras";
99 private static final String EXTRAS_ID = "_id";
100 private static final String EXTRAS_ACCOUNTS_ID = "accounts_id";
101 private static final String EXTRAS_KEY = "key";
102 private static final String EXTRAS_VALUE = "value";
103
104 private static final String TABLE_META = "meta";
105 private static final String META_KEY = "key";
106 private static final String META_VALUE = "value";
107
108 private static final String[] ACCOUNT_NAME_TYPE_PROJECTION =
109 new String[]{ACCOUNTS_ID, ACCOUNTS_NAME, ACCOUNTS_TYPE};
110 private static final Intent ACCOUNTS_CHANGED_INTENT =
Fred Quintanaa698f422009-04-08 19:14:54 -0700111 new Intent(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION);
112
113 private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
114 private static final int NOTIFICATION_ID = 234;
Fred Quintana60307342009-03-24 22:48:12 -0700115
116 public class AuthTokenKey {
117 public final Account mAccount;
118 public final String mAuthTokenType;
119 private final int mHashCode;
120
121 public AuthTokenKey(Account account, String authTokenType) {
122 mAccount = account;
123 mAuthTokenType = authTokenType;
124 mHashCode = computeHashCode();
125 }
126
127 public boolean equals(Object o) {
128 if (o == this) {
129 return true;
130 }
131 if (!(o instanceof AuthTokenKey)) {
132 return false;
133 }
134 AuthTokenKey other = (AuthTokenKey)o;
135 if (!mAccount.equals(other.mAccount)) {
136 return false;
137 }
138 return (mAuthTokenType == null)
139 ? other.mAuthTokenType == null
140 : mAuthTokenType.equals(other.mAuthTokenType);
141 }
142
143 private int computeHashCode() {
144 int result = 17;
145 result = 31 * result + mAccount.hashCode();
146 result = 31 * result + ((mAuthTokenType == null) ? 0 : mAuthTokenType.hashCode());
147 return result;
148 }
149
150 public int hashCode() {
151 return mHashCode;
152 }
153 }
154
155 public AccountManagerService(Context context) {
156 mContext = context;
157
158 mOpenHelper = new DatabaseHelper(mContext);
159
160 mMessageThread = new HandlerThread("AccountManagerService");
161 mMessageThread.start();
162 mMessageHandler = new MessageHandler(mMessageThread.getLooper());
163
164 mAuthenticatorCache = new AccountAuthenticatorCache(mContext);
165 mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler,
166 MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
167
168 mSimWatcher = new SimWatcher(mContext);
169 }
170
Fred Quintanaa698f422009-04-08 19:14:54 -0700171 public String getPassword(Account account) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700172 long identityToken = clearCallingIdentity();
Fred Quintana60307342009-03-24 22:48:12 -0700173 try {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700174 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
175 Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD},
176 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
177 new String[]{account.mName, account.mType}, null, null, null);
Fred Quintana60307342009-03-24 22:48:12 -0700178 try {
179 if (cursor.moveToNext()) {
180 return cursor.getString(0);
181 }
182 return null;
183 } finally {
184 cursor.close();
185 }
186 } finally {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700187 restoreCallingIdentity(identityToken);
188 }
189 }
190
191 public String getUserData(Account account, String key) {
192 long identityToken = clearCallingIdentity();
193 try {
194 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
195 db.beginTransaction();
196 try {
197 long accountId = getAccountId(db, account);
198 if (accountId < 0) {
199 return null;
200 }
201 Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_VALUE},
202 EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
203 new String[]{key}, null, null, null);
204 try {
205 if (cursor.moveToNext()) {
206 return cursor.getString(0);
207 }
208 return null;
209 } finally {
210 cursor.close();
211 }
212 } finally {
213 db.setTransactionSuccessful();
214 db.endTransaction();
215 }
216 } finally {
217 restoreCallingIdentity(identityToken);
Fred Quintana60307342009-03-24 22:48:12 -0700218 }
219 }
220
Fred Quintanaa698f422009-04-08 19:14:54 -0700221 public String[] getAuthenticatorTypes() {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700222 long identityToken = clearCallingIdentity();
223 try {
224 Collection<AccountAuthenticatorCache.AuthenticatorInfo> authenticatorCollection =
225 mAuthenticatorCache.getAllAuthenticators();
226 String[] types = new String[authenticatorCollection.size()];
227 int i = 0;
228 for (AccountAuthenticatorCache.AuthenticatorInfo authenticator : authenticatorCollection) {
229 types[i] = authenticator.mType;
230 i++;
231 }
232 return types;
233 } finally {
234 restoreCallingIdentity(identityToken);
Fred Quintanaa698f422009-04-08 19:14:54 -0700235 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700236 }
237
238 public Account[] getAccounts() {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700239 long identityToken = clearCallingIdentity();
240 try {
241 return getAccountsByType(null);
242 } finally {
243 restoreCallingIdentity(identityToken);
244 }
Fred Quintana60307342009-03-24 22:48:12 -0700245 }
246
Fred Quintanaa698f422009-04-08 19:14:54 -0700247 public Account[] getAccountsByType(String accountType) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700248 long identityToken = clearCallingIdentity();
Fred Quintana60307342009-03-24 22:48:12 -0700249 try {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700250 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
251
252 final String selection = accountType == null ? null : (ACCOUNTS_TYPE + "=?");
253 final String[] selectionArgs = accountType == null ? null : new String[]{accountType};
254 Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_NAME_TYPE_PROJECTION,
255 selection, selectionArgs, null, null, null);
256 try {
257 int i = 0;
258 Account[] accounts = new Account[cursor.getCount()];
259 while (cursor.moveToNext()) {
260 accounts[i] = new Account(cursor.getString(1), cursor.getString(2));
261 i++;
262 }
263 return accounts;
264 } finally {
265 cursor.close();
Fred Quintana60307342009-03-24 22:48:12 -0700266 }
Fred Quintana60307342009-03-24 22:48:12 -0700267 } finally {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700268 restoreCallingIdentity(identityToken);
Fred Quintana60307342009-03-24 22:48:12 -0700269 }
270 }
271
Fred Quintanaa698f422009-04-08 19:14:54 -0700272 public boolean addAccount(Account account, String password, Bundle extras) {
Fred Quintana60307342009-03-24 22:48:12 -0700273 // fails if the account already exists
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700274 long identityToken = clearCallingIdentity();
Fred Quintana60307342009-03-24 22:48:12 -0700275 try {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700276 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
277 db.beginTransaction();
278 try {
279 long numMatches = DatabaseUtils.longForQuery(db,
280 "select count(*) from " + TABLE_ACCOUNTS
281 + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
282 new String[]{account.mName, account.mType});
283 if (numMatches > 0) {
284 return false;
285 }
286 ContentValues values = new ContentValues();
287 values.put(ACCOUNTS_NAME, account.mName);
288 values.put(ACCOUNTS_TYPE, account.mType);
289 values.put(ACCOUNTS_PASSWORD, password);
290 long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
291 if (accountId < 0) {
292 return false;
293 }
294 if (extras != null) {
295 for (String key : extras.keySet()) {
296 final String value = extras.getString(key);
297 if (insertExtra(db, accountId, key, value) < 0) {
298 return false;
299 }
Fred Quintana60307342009-03-24 22:48:12 -0700300 }
301 }
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700302 db.setTransactionSuccessful();
Fred Quintana33269202009-04-20 16:05:10 -0700303 sendAccountsChangedBroadcast();
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700304 return true;
305 } finally {
306 db.endTransaction();
Fred Quintana60307342009-03-24 22:48:12 -0700307 }
Fred Quintana60307342009-03-24 22:48:12 -0700308 } finally {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700309 restoreCallingIdentity(identityToken);
Fred Quintana60307342009-03-24 22:48:12 -0700310 }
311 }
312
313 private long insertExtra(SQLiteDatabase db, long accountId, String key, String value) {
314 ContentValues values = new ContentValues();
315 values.put(EXTRAS_KEY, key);
316 values.put(EXTRAS_ACCOUNTS_ID, accountId);
317 values.put(EXTRAS_VALUE, value);
318 return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
319 }
320
Fred Quintanaa698f422009-04-08 19:14:54 -0700321 public void removeAccount(Account account) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700322 long identityToken = clearCallingIdentity();
323 try {
Fred Quintana33269202009-04-20 16:05:10 -0700324 final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
325 db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
326 new String[]{account.mName, account.mType});
327 sendAccountsChangedBroadcast();
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700328 } finally {
329 restoreCallingIdentity(identityToken);
Fred Quintanaa698f422009-04-08 19:14:54 -0700330 }
Fred Quintana60307342009-03-24 22:48:12 -0700331 }
332
Fred Quintanaa698f422009-04-08 19:14:54 -0700333 public void invalidateAuthToken(String accountType, String authToken) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700334 long identityToken = clearCallingIdentity();
Fred Quintana60307342009-03-24 22:48:12 -0700335 try {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700336 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
337 db.beginTransaction();
338 try {
339 invalidateAuthToken(db, accountType, authToken);
340 db.setTransactionSuccessful();
341 } finally {
342 db.endTransaction();
343 }
Fred Quintana60307342009-03-24 22:48:12 -0700344 } finally {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700345 restoreCallingIdentity(identityToken);
Fred Quintana60307342009-03-24 22:48:12 -0700346 }
347 }
348
349 private void invalidateAuthToken(SQLiteDatabase db, String accountType, String authToken) {
Fred Quintana33269202009-04-20 16:05:10 -0700350 Cursor cursor = db.rawQuery(
351 "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
352 + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
353 + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
354 + " FROM " + TABLE_ACCOUNTS
355 + " JOIN " + TABLE_AUTHTOKENS
356 + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
357 + " = " + AUTHTOKENS_ACCOUNTS_ID
358 + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND "
359 + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
360 new String[]{authToken, accountType});
361 try {
362 while (cursor.moveToNext()) {
363 long authTokenId = cursor.getLong(0);
364 String accountName = cursor.getString(1);
365 String authTokenType = cursor.getString(2);
366 db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
Fred Quintana60307342009-03-24 22:48:12 -0700367 }
Fred Quintana33269202009-04-20 16:05:10 -0700368 } finally {
369 cursor.close();
Fred Quintana60307342009-03-24 22:48:12 -0700370 }
371 }
372
373 private boolean saveAuthTokenToDatabase(Account account, String type, String authToken) {
374 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
375 db.beginTransaction();
376 try {
Fred Quintana33269202009-04-20 16:05:10 -0700377 long accountId = getAccountId(db, account);
378 if (accountId < 0) {
379 return false;
380 }
381 db.delete(TABLE_AUTHTOKENS,
382 AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
383 new String[]{type});
384 ContentValues values = new ContentValues();
385 values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
386 values.put(AUTHTOKENS_TYPE, type);
387 values.put(AUTHTOKENS_AUTHTOKEN, authToken);
388 if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) {
Fred Quintana60307342009-03-24 22:48:12 -0700389 db.setTransactionSuccessful();
390 return true;
391 }
392 return false;
393 } finally {
394 db.endTransaction();
395 }
396 }
397
Fred Quintana60307342009-03-24 22:48:12 -0700398 public String readAuthTokenFromDatabase(Account account, String authTokenType) {
399 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
400 db.beginTransaction();
401 try {
402 long accountId = getAccountId(db, account);
403 if (accountId < 0) {
404 return null;
405 }
406 return getAuthToken(db, accountId, authTokenType);
407 } finally {
408 db.setTransactionSuccessful();
409 db.endTransaction();
410 }
411 }
412
Fred Quintanaa698f422009-04-08 19:14:54 -0700413 public String peekAuthToken(Account account, String authTokenType) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700414 long identityToken = clearCallingIdentity();
415 try {
Fred Quintana33269202009-04-20 16:05:10 -0700416 return readAuthTokenFromDatabase(account, authTokenType);
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700417 } finally {
418 restoreCallingIdentity(identityToken);
Fred Quintana60307342009-03-24 22:48:12 -0700419 }
Fred Quintana60307342009-03-24 22:48:12 -0700420 }
421
Fred Quintanaa698f422009-04-08 19:14:54 -0700422 public void setAuthToken(Account account, String authTokenType, String authToken) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700423 long identityToken = clearCallingIdentity();
424 try {
425 cacheAuthToken(account, authTokenType, authToken);
426 } finally {
427 restoreCallingIdentity(identityToken);
428 }
Fred Quintana60307342009-03-24 22:48:12 -0700429 }
430
Fred Quintanaa698f422009-04-08 19:14:54 -0700431 public void setPassword(Account account, String password) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700432 long identityToken = clearCallingIdentity();
433 try {
434 ContentValues values = new ContentValues();
435 values.put(ACCOUNTS_PASSWORD, password);
436 mOpenHelper.getWritableDatabase().update(TABLE_ACCOUNTS, values,
437 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
438 new String[]{account.mName, account.mType});
Fred Quintana33269202009-04-20 16:05:10 -0700439 sendAccountsChangedBroadcast();
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700440 } finally {
441 restoreCallingIdentity(identityToken);
442 }
Fred Quintana60307342009-03-24 22:48:12 -0700443 }
444
Fred Quintana33269202009-04-20 16:05:10 -0700445 private void sendAccountsChangedBroadcast() {
446 mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
447 }
448
Fred Quintanaa698f422009-04-08 19:14:54 -0700449 public void clearPassword(Account account) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700450 long identityToken = clearCallingIdentity();
451 try {
452 setPassword(account, null);
453 } finally {
454 restoreCallingIdentity(identityToken);
455 }
Fred Quintana60307342009-03-24 22:48:12 -0700456 }
457
Fred Quintanaa698f422009-04-08 19:14:54 -0700458 public void setUserData(Account account, String key, String value) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700459 long identityToken = clearCallingIdentity();
Fred Quintana60307342009-03-24 22:48:12 -0700460 try {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700461 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
462 db.beginTransaction();
463 try {
464 long accountId = getAccountId(db, account);
465 if (accountId < 0) {
Fred Quintana60307342009-03-24 22:48:12 -0700466 return;
467 }
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700468 long extrasId = getExtrasId(db, accountId, key);
469 if (extrasId < 0 ) {
470 extrasId = insertExtra(db, accountId, key, value);
471 if (extrasId < 0) {
472 return;
473 }
474 } else {
475 ContentValues values = new ContentValues();
476 values.put(EXTRAS_VALUE, value);
477 if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
478 return;
479 }
Fred Quintana60307342009-03-24 22:48:12 -0700480
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700481 }
482 db.setTransactionSuccessful();
483 } finally {
484 db.endTransaction();
Fred Quintana60307342009-03-24 22:48:12 -0700485 }
Fred Quintana60307342009-03-24 22:48:12 -0700486 } finally {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700487 restoreCallingIdentity(identityToken);
Fred Quintana60307342009-03-24 22:48:12 -0700488 }
489 }
490
Fred Quintanaa698f422009-04-08 19:14:54 -0700491 public void getAuthToken(IAccountManagerResponse response, final Account account,
492 final String authTokenType, final boolean notifyOnAuthFailure,
493 final boolean expectActivityLaunch, final Bundle loginOptions) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700494 long identityToken = clearCallingIdentity();
495 try {
Fred Quintana33269202009-04-20 16:05:10 -0700496 String authToken = readAuthTokenFromDatabase(account, authTokenType);
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700497 if (authToken != null) {
498 try {
499 Bundle result = new Bundle();
500 result.putString(Constants.AUTHTOKEN_KEY, authToken);
501 result.putString(Constants.ACCOUNT_NAME_KEY, account.mName);
502 result.putString(Constants.ACCOUNT_TYPE_KEY, account.mType);
503 response.onResult(result);
504 } catch (RemoteException e) {
505 // if the caller is dead then there is no one to care about remote exceptions
506 if (Log.isLoggable(TAG, Log.VERBOSE)) {
507 Log.v(TAG, "failure while notifying response", e);
508 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700509 }
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700510 return;
Fred Quintanaa698f422009-04-08 19:14:54 -0700511 }
512
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700513 new Session(response, account.mType, expectActivityLaunch) {
514 protected String toDebugString(long now) {
515 if (loginOptions != null) loginOptions.keySet();
516 return super.toDebugString(now) + ", getAuthToken"
517 + ", " + account
518 + ", authTokenType " + authTokenType
519 + ", loginOptions " + loginOptions
520 + ", notifyOnAuthFailure " + notifyOnAuthFailure;
521 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700522
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700523 public void run() throws RemoteException {
524 mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
525 }
526
527 public void onResult(Bundle result) {
528 if (result != null) {
529 String authToken = result.getString(Constants.AUTHTOKEN_KEY);
530 if (authToken != null) {
531 String name = result.getString(Constants.ACCOUNT_NAME_KEY);
532 String type = result.getString(Constants.ACCOUNT_TYPE_KEY);
533 if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) {
534 onError(Constants.ERROR_CODE_INVALID_RESPONSE,
535 "the type and name should not be empty");
536 return;
537 }
538 cacheAuthToken(new Account(name, type), authTokenType, authToken);
Fred Quintanaa698f422009-04-08 19:14:54 -0700539 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700540
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700541 Intent intent = result.getParcelable(Constants.INTENT_KEY);
542 if (intent != null && notifyOnAuthFailure) {
543 doNotification(result.getString(Constants.AUTH_FAILED_MESSAGE_KEY),
544 intent);
545 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700546 }
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700547 super.onResult(result);
Fred Quintanaa698f422009-04-08 19:14:54 -0700548 }
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700549 }.bind();
550 } finally {
551 restoreCallingIdentity(identityToken);
552 }
Fred Quintana60307342009-03-24 22:48:12 -0700553 }
554
Fred Quintanaa698f422009-04-08 19:14:54 -0700555
Fred Quintana33269202009-04-20 16:05:10 -0700556 public void addAcount(final IAccountManagerResponse response, final String accountType,
557 final String authTokenType, final String[] requiredFeatures,
Fred Quintanaa698f422009-04-08 19:14:54 -0700558 final boolean expectActivityLaunch, final Bundle options) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700559 long identityToken = clearCallingIdentity();
560 try {
561 new Session(response, accountType, expectActivityLaunch) {
562 public void run() throws RemoteException {
Fred Quintana33269202009-04-20 16:05:10 -0700563 mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
564 options);
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700565 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700566
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700567 protected String toDebugString(long now) {
568 return super.toDebugString(now) + ", addAccount"
Fred Quintana33269202009-04-20 16:05:10 -0700569 + ", accountType " + accountType
570 + ", requiredFeatures "
571 + (requiredFeatures != null
572 ? TextUtils.join(",", requiredFeatures)
573 : null);
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700574 }
575 }.bind();
576 } finally {
577 restoreCallingIdentity(identityToken);
578 }
Fred Quintana60307342009-03-24 22:48:12 -0700579 }
580
Fred Quintanaa698f422009-04-08 19:14:54 -0700581 public void confirmCredentials(IAccountManagerResponse response,
582 final Account account, final boolean expectActivityLaunch) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700583 long identityToken = clearCallingIdentity();
584 try {
585 new Session(response, account.mType, expectActivityLaunch) {
586 public void run() throws RemoteException {
587 mAuthenticator.confirmCredentials(this, account);
588 }
589 protected String toDebugString(long now) {
590 return super.toDebugString(now) + ", confirmCredentials"
591 + ", " + account;
592 }
593 }.bind();
594 } finally {
595 restoreCallingIdentity(identityToken);
596 }
Fred Quintana60307342009-03-24 22:48:12 -0700597 }
598
Fred Quintanaa698f422009-04-08 19:14:54 -0700599 public void confirmPassword(IAccountManagerResponse response, final Account account,
600 final String password) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700601 long identityToken = clearCallingIdentity();
602 try {
603 new Session(response, account.mType, false /* expectActivityLaunch */) {
604 public void run() throws RemoteException {
605 mAuthenticator.confirmPassword(this, account, password);
606 }
607 protected String toDebugString(long now) {
608 return super.toDebugString(now) + ", confirmPassword"
609 + ", " + account;
610 }
611 }.bind();
612 } finally {
613 restoreCallingIdentity(identityToken);
614 }
Fred Quintana60307342009-03-24 22:48:12 -0700615 }
616
Fred Quintanaa698f422009-04-08 19:14:54 -0700617 public void updateCredentials(IAccountManagerResponse response, final Account account,
618 final String authTokenType, final boolean expectActivityLaunch,
619 final Bundle loginOptions) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700620 long identityToken = clearCallingIdentity();
621 try {
622 new Session(response, account.mType, expectActivityLaunch) {
623 public void run() throws RemoteException {
624 mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
625 }
626 protected String toDebugString(long now) {
627 if (loginOptions != null) loginOptions.keySet();
628 return super.toDebugString(now) + ", updateCredentials"
629 + ", " + account
630 + ", authTokenType " + authTokenType
631 + ", loginOptions " + loginOptions;
632 }
633 }.bind();
634 } finally {
635 restoreCallingIdentity(identityToken);
636 }
Fred Quintana60307342009-03-24 22:48:12 -0700637 }
638
Fred Quintanaa698f422009-04-08 19:14:54 -0700639 public void editProperties(IAccountManagerResponse response, final String accountType,
640 final boolean expectActivityLaunch) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -0700641 long identityToken = clearCallingIdentity();
642 try {
643 new Session(response, accountType, expectActivityLaunch) {
644 public void run() throws RemoteException {
645 mAuthenticator.editProperties(this, mAccountType);
646 }
647 protected String toDebugString(long now) {
648 return super.toDebugString(now) + ", editProperties"
649 + ", accountType " + accountType;
650 }
651 }.bind();
652 } finally {
653 restoreCallingIdentity(identityToken);
654 }
Fred Quintana60307342009-03-24 22:48:12 -0700655 }
656
Fred Quintana33269202009-04-20 16:05:10 -0700657 private class GetAccountsByTypeAndFeatureSession extends Session {
658 private final String[] mFeatures;
659 private volatile Account[] mAccountsOfType = null;
660 private volatile ArrayList<Account> mAccountsWithFeatures = null;
661 private volatile int mCurrentAccount = 0;
662
663 public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response,
664 String type, String[] features) {
665 super(response, type, false /* expectActivityLaunch */);
666 mFeatures = features;
667 }
668
669 public void run() throws RemoteException {
670 mAccountsOfType = getAccountsByType(mAccountType);
671 // check whether each account matches the requested features
672 mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
673 mCurrentAccount = 0;
674
675 checkAccount();
676 }
677
678 public void checkAccount() {
679 if (mCurrentAccount >= mAccountsOfType.length) {
680 sendResult();
681 return;
Fred Quintanaa698f422009-04-08 19:14:54 -0700682 }
Fred Quintana33269202009-04-20 16:05:10 -0700683
684 try {
685 mAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures);
686 } catch (RemoteException e) {
687 onError(Constants.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
688 }
689 }
690
691 public void onResult(Bundle result) {
692 mNumResults++;
693 if (result == null) {
694 onError(Constants.ERROR_CODE_INVALID_RESPONSE, "null bundle");
695 return;
696 }
697 if (result.getBoolean(Constants.BOOLEAN_RESULT_KEY, false)) {
698 mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]);
699 }
700 mCurrentAccount++;
701 checkAccount();
702 }
703
704 public void sendResult() {
705 IAccountManagerResponse response = getResponseAndClose();
706 if (response != null) {
707 try {
708 Account[] accounts = new Account[mAccountsWithFeatures.size()];
709 for (int i = 0; i < accounts.length; i++) {
710 accounts[i] = mAccountsWithFeatures.get(i);
711 }
712 Bundle result = new Bundle();
713 result.putParcelableArray(Constants.ACCOUNTS_KEY, accounts);
714 response.onResult(result);
715 } catch (RemoteException e) {
716 // if the caller is dead then there is no one to care about remote exceptions
717 if (Log.isLoggable(TAG, Log.VERBOSE)) {
718 Log.v(TAG, "failure while notifying response", e);
719 }
720 }
721 }
722 }
723
724
725 protected String toDebugString(long now) {
726 return super.toDebugString(now) + ", getAccountsByTypeAndFeatures"
727 + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
728 }
729 }
730 public void getAccountsByTypeAndFeatures(IAccountManagerResponse response,
731 String type, String[] features) {
732 if (type == null) {
733 if (response != null) {
734 try {
735 response.onError(Constants.ERROR_CODE_BAD_ARGUMENTS, "type is null");
736 } catch (RemoteException e) {
737 // ignore this
738 }
739 }
740 return;
741 }
742 long identityToken = clearCallingIdentity();
743 try {
744 new GetAccountsByTypeAndFeatureSession(response, type, features).bind();
745 } finally {
746 restoreCallingIdentity(identityToken);
Fred Quintana60307342009-03-24 22:48:12 -0700747 }
748 }
749
Fred Quintana33269202009-04-20 16:05:10 -0700750 private boolean cacheAuthToken(Account account, String authTokenType, String authToken) {
751 return saveAuthTokenToDatabase(account, authTokenType, authToken);
Fred Quintana60307342009-03-24 22:48:12 -0700752 }
753
754 private long getAccountId(SQLiteDatabase db, Account account) {
755 Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID},
756 "name=? AND type=?", new String[]{account.mName, account.mType}, null, null, null);
757 try {
758 if (cursor.moveToNext()) {
759 return cursor.getLong(0);
760 }
761 return -1;
762 } finally {
763 cursor.close();
764 }
765 }
766
767 private long getExtrasId(SQLiteDatabase db, long accountId, String key) {
768 Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID},
769 EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
770 new String[]{key}, null, null, null);
771 try {
772 if (cursor.moveToNext()) {
773 return cursor.getLong(0);
774 }
775 return -1;
776 } finally {
777 cursor.close();
778 }
779 }
780
781 private String getAuthToken(SQLiteDatabase db, long accountId, String authTokenType) {
782 Cursor cursor = db.query(TABLE_AUTHTOKENS, new String[]{AUTHTOKENS_AUTHTOKEN},
783 AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
784 new String[]{authTokenType},
785 null, null, null);
786 try {
787 if (cursor.moveToNext()) {
788 return cursor.getString(0);
789 }
790 return null;
791 } finally {
792 cursor.close();
793 }
794 }
795
Fred Quintanaa698f422009-04-08 19:14:54 -0700796 private abstract class Session extends IAccountAuthenticatorResponse.Stub
797 implements AuthenticatorBindHelper.Callback, IBinder.DeathRecipient {
Fred Quintana60307342009-03-24 22:48:12 -0700798 IAccountManagerResponse mResponse;
799 final String mAccountType;
Fred Quintanaa698f422009-04-08 19:14:54 -0700800 final boolean mExpectActivityLaunch;
801 final long mCreationTime;
802
Fred Quintana33269202009-04-20 16:05:10 -0700803 public int mNumResults = 0;
Fred Quintanaa698f422009-04-08 19:14:54 -0700804 private int mNumRequestContinued = 0;
805 private int mNumErrors = 0;
806
Fred Quintana60307342009-03-24 22:48:12 -0700807
808 IAccountAuthenticator mAuthenticator = null;
809
Fred Quintanaa698f422009-04-08 19:14:54 -0700810 public Session(IAccountManagerResponse response, String accountType,
811 boolean expectActivityLaunch) {
Fred Quintana60307342009-03-24 22:48:12 -0700812 super();
Fred Quintanaa698f422009-04-08 19:14:54 -0700813 if (response == null) throw new IllegalArgumentException("response is null");
Fred Quintana33269202009-04-20 16:05:10 -0700814 if (accountType == null) throw new IllegalArgumentException("accountType is null");
Fred Quintana60307342009-03-24 22:48:12 -0700815 mResponse = response;
816 mAccountType = accountType;
Fred Quintanaa698f422009-04-08 19:14:54 -0700817 mExpectActivityLaunch = expectActivityLaunch;
818 mCreationTime = SystemClock.elapsedRealtime();
819 synchronized (mSessions) {
820 mSessions.put(toString(), this);
821 }
822 try {
823 response.asBinder().linkToDeath(this, 0 /* flags */);
824 } catch (RemoteException e) {
825 mResponse = null;
826 binderDied();
827 }
Fred Quintana60307342009-03-24 22:48:12 -0700828 }
829
Fred Quintanaa698f422009-04-08 19:14:54 -0700830 IAccountManagerResponse getResponseAndClose() {
Fred Quintana60307342009-03-24 22:48:12 -0700831 if (mResponse == null) {
832 // this session has already been closed
833 return null;
834 }
Fred Quintana60307342009-03-24 22:48:12 -0700835 IAccountManagerResponse response = mResponse;
Fred Quintanaa698f422009-04-08 19:14:54 -0700836 close(); // this clears mResponse so we need to save the response before this call
Fred Quintana60307342009-03-24 22:48:12 -0700837 return response;
838 }
839
Fred Quintanaa698f422009-04-08 19:14:54 -0700840 private void close() {
841 synchronized (mSessions) {
842 if (mSessions.remove(toString()) == null) {
843 // the session was already closed, so bail out now
844 return;
845 }
846 }
847 if (mResponse != null) {
848 // stop listening for response deaths
849 mResponse.asBinder().unlinkToDeath(this, 0 /* flags */);
850
851 // clear this so that we don't accidentally send any further results
852 mResponse = null;
853 }
854 cancelTimeout();
855 unbind();
856 }
857
858 public void binderDied() {
859 mResponse = null;
860 close();
861 }
862
863 protected String toDebugString() {
864 return toDebugString(SystemClock.elapsedRealtime());
865 }
866
867 protected String toDebugString(long now) {
868 return "Session: expectLaunch " + mExpectActivityLaunch
869 + ", connected " + (mAuthenticator != null)
870 + ", stats (" + mNumResults + "/" + mNumRequestContinued
871 + "/" + mNumErrors + ")"
872 + ", lifetime " + ((now - mCreationTime) / 1000.0);
873 }
874
Fred Quintana60307342009-03-24 22:48:12 -0700875 void bind() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700876 if (Log.isLoggable(TAG, Log.VERBOSE)) {
877 Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
878 }
Fred Quintana60307342009-03-24 22:48:12 -0700879 if (!mBindHelper.bind(mAccountType, this)) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700880 Log.d(TAG, "bind attempt failed for " + toDebugString());
881 onError(Constants.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
Fred Quintana60307342009-03-24 22:48:12 -0700882 }
883 }
884
885 private void unbind() {
886 if (mAuthenticator != null) {
887 mAuthenticator = null;
888 mBindHelper.unbind(this);
889 }
890 }
891
892 public void scheduleTimeout() {
893 mMessageHandler.sendMessageDelayed(
894 mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
895 }
896
897 public void cancelTimeout() {
898 mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
899 }
900
901 public void onConnected(IBinder service) {
902 mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
Fred Quintanaa698f422009-04-08 19:14:54 -0700903 try {
904 run();
905 } catch (RemoteException e) {
906 onError(Constants.ERROR_CODE_REMOTE_EXCEPTION,
907 "remote exception");
908 }
Fred Quintana60307342009-03-24 22:48:12 -0700909 }
910
Fred Quintanaa698f422009-04-08 19:14:54 -0700911 public abstract void run() throws RemoteException;
912
Fred Quintana60307342009-03-24 22:48:12 -0700913 public void onDisconnected() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700914 mAuthenticator = null;
915 IAccountManagerResponse response = getResponseAndClose();
Fred Quintana60307342009-03-24 22:48:12 -0700916 if (response != null) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700917 onError(Constants.ERROR_CODE_REMOTE_EXCEPTION,
918 "disconnected");
Fred Quintana60307342009-03-24 22:48:12 -0700919 }
920 }
921
922 public void onTimedOut() {
Fred Quintanaa698f422009-04-08 19:14:54 -0700923 IAccountManagerResponse response = getResponseAndClose();
Fred Quintana60307342009-03-24 22:48:12 -0700924 if (response != null) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700925 onError(Constants.ERROR_CODE_REMOTE_EXCEPTION,
926 "timeout");
Fred Quintana60307342009-03-24 22:48:12 -0700927 }
928 }
929
Fred Quintanaa698f422009-04-08 19:14:54 -0700930 public void onResult(Bundle result) {
931 mNumResults++;
932 if (result != null && !TextUtils.isEmpty(result.getString(Constants.AUTHTOKEN_KEY))) {
933 cancelNotification();
Fred Quintana60307342009-03-24 22:48:12 -0700934 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700935 IAccountManagerResponse response;
936 if (mExpectActivityLaunch && result != null
937 && result.containsKey(Constants.INTENT_KEY)) {
938 response = mResponse;
939 } else {
940 response = getResponseAndClose();
Fred Quintana60307342009-03-24 22:48:12 -0700941 }
Fred Quintana60307342009-03-24 22:48:12 -0700942 if (response != null) {
943 try {
Fred Quintanaa698f422009-04-08 19:14:54 -0700944 if (result == null) {
945 response.onError(Constants.ERROR_CODE_INVALID_RESPONSE,
946 "null bundle returned");
947 } else {
948 response.onResult(result);
949 }
Fred Quintana60307342009-03-24 22:48:12 -0700950 } catch (RemoteException e) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700951 // if the caller is dead then there is no one to care about remote exceptions
952 if (Log.isLoggable(TAG, Log.VERBOSE)) {
953 Log.v(TAG, "failure while notifying response", e);
954 }
Fred Quintana60307342009-03-24 22:48:12 -0700955 }
956 }
957 }
Fred Quintana60307342009-03-24 22:48:12 -0700958
Fred Quintanaa698f422009-04-08 19:14:54 -0700959 public void onRequestContinued() {
960 mNumRequestContinued++;
Fred Quintana60307342009-03-24 22:48:12 -0700961 }
962
963 public void onError(int errorCode, String errorMessage) {
Fred Quintanaa698f422009-04-08 19:14:54 -0700964 mNumErrors++;
965 if (Log.isLoggable(TAG, Log.VERBOSE)) {
966 Log.v(TAG, "Session.onError: " + errorCode + ", " + errorMessage);
Fred Quintana60307342009-03-24 22:48:12 -0700967 }
Fred Quintanaa698f422009-04-08 19:14:54 -0700968 IAccountManagerResponse response = getResponseAndClose();
969 if (response != null) {
970 if (Log.isLoggable(TAG, Log.VERBOSE)) {
971 Log.v(TAG, "Session.onError: responding");
972 }
973 try {
974 response.onError(errorCode, errorMessage);
975 } catch (RemoteException e) {
976 if (Log.isLoggable(TAG, Log.VERBOSE)) {
977 Log.v(TAG, "Session.onError: caught RemoteException while responding", e);
978 }
979 }
980 } else {
981 if (Log.isLoggable(TAG, Log.VERBOSE)) {
982 Log.v(TAG, "Session.onError: already closed");
983 }
Fred Quintana60307342009-03-24 22:48:12 -0700984 }
985 }
986 }
987
988 private class MessageHandler extends Handler {
989 MessageHandler(Looper looper) {
990 super(looper);
991 }
992
993 public void handleMessage(Message msg) {
994 if (mBindHelper.handleMessage(msg)) {
995 return;
996 }
997 switch (msg.what) {
998 case MESSAGE_TIMED_OUT:
999 Session session = (Session)msg.obj;
1000 session.onTimedOut();
1001 break;
1002
1003 default:
1004 throw new IllegalStateException("unhandled message: " + msg.what);
1005 }
1006 }
1007 }
1008
1009 private class DatabaseHelper extends SQLiteOpenHelper {
1010 public DatabaseHelper(Context context) {
1011 super(context, DATABASE_NAME, null, DATABASE_VERSION);
1012 }
1013
1014 @Override
1015 public void onCreate(SQLiteDatabase db) {
1016 db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
1017 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
1018 + ACCOUNTS_NAME + " TEXT NOT NULL, "
1019 + ACCOUNTS_TYPE + " TEXT NOT NULL, "
1020 + ACCOUNTS_PASSWORD + " TEXT, "
1021 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
1022
1023 db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( "
1024 + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
1025 + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
1026 + AUTHTOKENS_TYPE + " TEXT NOT NULL, "
1027 + AUTHTOKENS_AUTHTOKEN + " TEXT, "
1028 + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
1029
1030 db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
1031 + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
1032 + EXTRAS_ACCOUNTS_ID + " INTEGER, "
1033 + EXTRAS_KEY + " TEXT NOT NULL, "
1034 + EXTRAS_VALUE + " TEXT, "
1035 + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
1036
1037 db.execSQL("CREATE TABLE " + TABLE_META + " ( "
1038 + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
1039 + META_VALUE + " TEXT)");
Fred Quintanaa698f422009-04-08 19:14:54 -07001040
1041 db.execSQL(""
1042 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
1043 + " BEGIN"
1044 + " DELETE FROM " + TABLE_AUTHTOKENS
1045 + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1046 + " DELETE FROM " + TABLE_EXTRAS
1047 + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1048 + " END");
Fred Quintana60307342009-03-24 22:48:12 -07001049 }
1050
1051 @Override
1052 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Fred Quintanaa698f422009-04-08 19:14:54 -07001053 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
Fred Quintana60307342009-03-24 22:48:12 -07001054
Fred Quintanaa698f422009-04-08 19:14:54 -07001055 if (oldVersion == 1) {
1056 db.execSQL(""
1057 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
1058 + " BEGIN"
1059 + " DELETE FROM " + TABLE_AUTHTOKENS
1060 + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + " =OLD." + ACCOUNTS_ID + " ;"
1061 + " DELETE FROM " + TABLE_EXTRAS
1062 + " WHERE " + EXTRAS_ACCOUNTS_ID + " =OLD." + ACCOUNTS_ID + " ;"
1063 + " END");
1064 oldVersion++;
1065 }
Fred Quintana60307342009-03-24 22:48:12 -07001066 }
1067
1068 @Override
1069 public void onOpen(SQLiteDatabase db) {
1070 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
1071 }
1072 }
1073
1074 private void setMetaValue(String key, String value) {
1075 ContentValues values = new ContentValues();
1076 values.put(META_KEY, key);
1077 values.put(META_VALUE, value);
1078 mOpenHelper.getWritableDatabase().replace(TABLE_META, META_KEY, values);
1079 }
1080
1081 private String getMetaValue(String key) {
1082 Cursor c = mOpenHelper.getReadableDatabase().query(TABLE_META,
1083 new String[]{META_VALUE}, META_KEY + "=?", new String[]{key}, null, null, null);
1084 try {
1085 if (c.moveToNext()) {
1086 return c.getString(0);
1087 }
1088 return null;
1089 } finally {
1090 c.close();
1091 }
1092 }
1093
1094 private class SimWatcher extends BroadcastReceiver {
1095 public SimWatcher(Context context) {
1096 // Re-scan the SIM card when the SIM state changes, and also if
1097 // the disk recovers from a full state (we may have failed to handle
1098 // things properly while the disk was full).
1099 final IntentFilter filter = new IntentFilter();
1100 filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
1101 filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
1102 context.registerReceiver(this, filter);
1103 }
1104
1105 /**
1106 * Compare the IMSI to the one stored in the login service's
1107 * database. If they differ, erase all passwords and
1108 * authtokens (and store the new IMSI).
1109 */
1110 @Override
1111 public void onReceive(Context context, Intent intent) {
1112 // Check IMSI on every update; nothing happens if the IMSI is missing or unchanged.
1113 String imsi = ((TelephonyManager) context.getSystemService(
1114 Context.TELEPHONY_SERVICE)).getSubscriberId();
1115 if (TextUtils.isEmpty(imsi)) return;
1116
1117 String storedImsi = getMetaValue("imsi");
1118
1119 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1120 Log.v(TAG, "current IMSI=" + imsi + "; stored IMSI=" + storedImsi);
1121 }
1122
1123 if (!imsi.equals(storedImsi) && !"initial".equals(storedImsi)) {
1124 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1125 Log.v(TAG, "wiping all passwords and authtokens");
1126 }
1127 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1128 db.beginTransaction();
1129 try {
1130 db.execSQL("DELETE from " + TABLE_AUTHTOKENS);
1131 db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_PASSWORD + " = ''");
Fred Quintana33269202009-04-20 16:05:10 -07001132 sendAccountsChangedBroadcast();
Fred Quintana60307342009-03-24 22:48:12 -07001133 db.setTransactionSuccessful();
1134 } finally {
1135 db.endTransaction();
1136 }
1137 }
1138 setMetaValue("imsi", imsi);
1139 }
1140 }
1141
1142 public IBinder onBind(Intent intent) {
1143 return asBinder();
1144 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001145
1146 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
1147 synchronized (mSessions) {
1148 final long now = SystemClock.elapsedRealtime();
1149 fout.println("AccountManagerService: " + mSessions.size() + " sessions");
1150 for (Session session : mSessions.values()) {
1151 fout.println(" " + session.toDebugString(now));
1152 }
1153 }
1154
1155 fout.println();
1156
1157 mAuthenticatorCache.dump(fd, fout, args);
1158 }
1159
1160 private void doNotification(CharSequence message, Intent intent) {
Fred Quintana26fc5eb2009-04-09 15:05:50 -07001161 long identityToken = clearCallingIdentity();
1162 try {
1163 if (Log.isLoggable(TAG, Log.VERBOSE)) {
1164 Log.v(TAG, "doNotification: " + message + " intent:" + intent);
1165 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001166
Fred Quintana26fc5eb2009-04-09 15:05:50 -07001167 Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
1168 0 /* when */);
1169 n.setLatestEventInfo(mContext, mContext.getText(R.string.notification_title), message,
1170 PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
1171 ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
1172 .notify(NOTIFICATION_ID, n);
1173 } finally {
1174 restoreCallingIdentity(identityToken);
1175 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001176 }
1177
1178 private void cancelNotification() {
Fred Quintana26fc5eb2009-04-09 15:05:50 -07001179 long identityToken = clearCallingIdentity();
1180 try {
1181 ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
1182 .cancel(NOTIFICATION_ID);
1183 } finally {
1184 restoreCallingIdentity(identityToken);
1185 }
Fred Quintanaa698f422009-04-08 19:14:54 -07001186 }
Fred Quintana60307342009-03-24 22:48:12 -07001187}