diff options
Diffstat (limited to 'core/java')
19 files changed, 2173 insertions, 186 deletions
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java new file mode 100644 index 0000000..ed683d7 --- /dev/null +++ b/core/java/android/accounts/AbstractAccountAuthenticator.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +import android.os.RemoteException; + +/** + * Base class for creating AccountAuthenticators. This implements the IAccountAuthenticator + * binder interface and also provides helper libraries to simplify the creation of + * AccountAuthenticators. + */ +public abstract class AbstractAccountAuthenticator { + private static final String TAG = "AccountAuthenticator"; + + class Transport extends IAccountAuthenticator.Stub { + public void addAccount(IAccountAuthenticatorResponse response, String accountType) + throws RemoteException { + AbstractAccountAuthenticator.this.addAccount(new AccountAuthenticatorResponse(response), + accountType); + } + + public void authenticateAccount(IAccountAuthenticatorResponse + response, String name, String type, String password) + throws RemoteException { + AbstractAccountAuthenticator.this.authenticateAccount( + new AccountAuthenticatorResponse(response), new Account(name, type), password); + } + + public void getAuthToken(IAccountAuthenticatorResponse response, + String name, String type, String authTokenType) + throws RemoteException { + AbstractAccountAuthenticator.this.getAuthToken( + new AccountAuthenticatorResponse(response), + new Account(name, type), authTokenType); + } + + public void getPasswordStrength(IAccountAuthenticatorResponse response, + String accountType, String password) + throws RemoteException { + AbstractAccountAuthenticator.this.getPasswordStrength( + new AccountAuthenticatorResponse(response), accountType, password); + } + + public void checkUsernameExistence(IAccountAuthenticatorResponse response, + String accountType, String username) + throws RemoteException { + AbstractAccountAuthenticator.this.checkUsernameExistence( + new AccountAuthenticatorResponse(response), accountType, username); + } + + public void updatePassword(IAccountAuthenticatorResponse response, String name, String type) + throws RemoteException { + AbstractAccountAuthenticator.this.updatePassword( + new AccountAuthenticatorResponse(response), new Account(name, type)); + } + + public void editProperties(IAccountAuthenticatorResponse response, String accountType) + throws RemoteException { + AbstractAccountAuthenticator.this.editProperties( + new AccountAuthenticatorResponse(response), accountType); + } + } + + Transport mTransport = new Transport(); + + /** + * @return the IAccountAuthenticator binder transport object + */ + public final IAccountAuthenticator getIAccountAuthenticator() + { + return mTransport; + } + + /** + * prompts the user for account information and adds the result to the IAccountManager + */ + public abstract void addAccount(AccountAuthenticatorResponse response, String accountType); + + /** + * prompts the user for the credentials of the account + */ + public abstract void authenticateAccount(AccountAuthenticatorResponse response, + Account account, String password); + + /** + * gets the password by either prompting the user or querying the IAccountManager + */ + public abstract void getAuthToken(AccountAuthenticatorResponse response, + Account account, String authTokenType); + + /** + * does local analysis or uses a service in the cloud + */ + public abstract void getPasswordStrength(AccountAuthenticatorResponse response, + String accountType, String password); + + /** + * checks with the login service in the cloud + */ + public abstract void checkUsernameExistence(AccountAuthenticatorResponse response, + String accountType, String username); + + /** + * prompts the user for a new password and writes it to the IAccountManager + */ + public abstract void updatePassword(AccountAuthenticatorResponse response, Account account); + + /** + * launches an activity that lets the user edit and set the properties for an authenticator + */ + public abstract void editProperties(AccountAuthenticatorResponse response, String accountType); +} diff --git a/core/java/android/accounts/Account.aidl b/core/java/android/accounts/Account.aidl new file mode 100644 index 0000000..8752d99 --- /dev/null +++ b/core/java/android/accounts/Account.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +parcelable Account; diff --git a/core/java/android/accounts/Account.java b/core/java/android/accounts/Account.java new file mode 100644 index 0000000..efcd366 --- /dev/null +++ b/core/java/android/accounts/Account.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +import android.os.Parcelable; +import android.os.Parcel; + +/** + * Value type that represents an Account in the {@link AccountManager}. This object is + * {@link Parcelable} and also overrides {@link #equals} and {@link #hashCode}, making it + * suitable for use as the key of a {@link java.util.Map} + */ +public class Account implements Parcelable { + public final String mName; + public final String mType; + + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof Account)) return false; + final Account other = (Account)o; + return mName.equals(other.mName) && mType.equals(other.mType); + } + + public int hashCode() { + int result = 17; + result = 31 * result + mName.hashCode(); + result = 31 * result + mType.hashCode(); + return result; + } + + public Account(String name, String type) { + mName = name; + mType = type; + } + + public Account(Parcel in) { + mName = in.readString(); + mType = in.readString(); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mName); + dest.writeString(mType); + } + + public static final Creator<Account> CREATOR = new Creator<Account>() { + public Account createFromParcel(Parcel source) { + return new Account(source); + } + + public Account[] newArray(int size) { + return new Account[size]; + } + }; + + public String toString() { + return "Account {name=" + mName + ", type=" + mType + "}"; + } +} diff --git a/core/java/android/accounts/AccountAuthenticatorCache.java b/core/java/android/accounts/AccountAuthenticatorCache.java new file mode 100644 index 0000000..72b6bde --- /dev/null +++ b/core/java/android/accounts/AccountAuthenticatorCache.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +import android.content.*; +import android.content.res.XmlResourceParser; +import android.content.res.TypedArray; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.util.Log; +import android.util.AttributeSet; +import android.util.Xml; + +import java.util.*; +import java.io.IOException; + +import com.google.android.collect.Maps; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParser; + +/** + * A cache of services that export the {@link IAccountAuthenticator} interface. This cache + * is built by interrogating the {@link PackageManager} and is updated as packages are added, + * removed and changed. The authenticators are referred to by their account type and + * are made available via the {@link #getAuthenticatorInfo(String type)} method. + */ +public class AccountAuthenticatorCache { + private static final String TAG = "Account"; + + private static final String SERVICE_INTERFACE = "android.accounts.AccountAuthenticator"; + private static final String SERVICE_META_DATA = "android.accounts.AccountAuthenticator"; + + private volatile Map<String, AuthenticatorInfo> mAuthenticators; + + private final Context mContext; + private BroadcastReceiver mReceiver; + + public AccountAuthenticatorCache(Context context) { + mContext = context; + mReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + buildAuthenticatorList(); + } + }; + } + + private void monitorPackageChanges() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + mContext.registerReceiver(mReceiver, intentFilter); + } + + /** + * Value type that describes an AccountAuthenticator. The information within can be used + * to bind to its {@link IAccountAuthenticator} interface. + */ + public class AuthenticatorInfo { + public final String mType; + public final String mComponentShortName; + public final ComponentName mComponentName; + + private AuthenticatorInfo(String type, ComponentName componentName) { + mType = type; + mComponentName = componentName; + mComponentShortName = componentName.flattenToShortString(); + } + } + + /** + * Accessor for the registered authenticators. + * @param type the account type of the authenticator + * @return the AuthenticatorInfo that matches the account type or null if none is present + */ + public AuthenticatorInfo getAuthenticatorInfo(String type) { + if (mAuthenticators == null) { + monitorPackageChanges(); + buildAuthenticatorList(); + } + return mAuthenticators.get(type); + } + + /** + * @return a collection of {@link AuthenticatorInfo} objects for all + * registered authenticators. + */ + public Collection<AuthenticatorInfo> getAllAuthenticators() { + if (mAuthenticators == null) { + monitorPackageChanges(); + buildAuthenticatorList(); + } + return Collections.unmodifiableCollection(mAuthenticators.values()); + } + + /** + * Stops the monitoring of package additions, removals and changes. + */ + public void close() { + if (mReceiver != null) { + mContext.unregisterReceiver(mReceiver); + mReceiver = null; + } + } + + protected void finalize() throws Throwable { + if (mReceiver != null) { + Log.e(TAG, "AccountAuthenticatorCache finalized without being closed"); + } + close(); + super.finalize(); + } + + private void buildAuthenticatorList() { + Map<String, AuthenticatorInfo> authenticators = Maps.newHashMap(); + PackageManager pm = mContext.getPackageManager(); + + List<ResolveInfo> services = + pm.queryIntentServices(new Intent(SERVICE_INTERFACE), PackageManager.GET_META_DATA); + + for (ResolveInfo resolveInfo : services) { + try { + AuthenticatorInfo info = parseAuthenticatorInfo(resolveInfo); + if (info != null) { + authenticators.put(info.mType, info); + } else { + Log.w(TAG, "Unable to load input method " + resolveInfo.toString()); + } + } catch (XmlPullParserException e) { + Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e); + } catch (IOException e) { + Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e); + } + } + + mAuthenticators = authenticators; + } + + public AuthenticatorInfo parseAuthenticatorInfo(ResolveInfo service) + throws XmlPullParserException, IOException { + ServiceInfo si = service.serviceInfo; + ComponentName componentName = new ComponentName(si.packageName, si.name); + + PackageManager pm = mContext.getPackageManager(); + String authenticatorType = null; + + XmlResourceParser parser = null; + try { + parser = si.loadXmlMetaData(pm, SERVICE_META_DATA); + if (parser == null) { + throw new XmlPullParserException("No " + SERVICE_META_DATA + " meta-data"); + } + + AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + } + + String nodeName = parser.getName(); + if (!"account-authenticator".equals(nodeName)) { + throw new XmlPullParserException( + "Meta-data does not start with account-authenticator tag"); + } + + TypedArray sa = mContext.getResources().obtainAttributes(attrs, + com.android.internal.R.styleable.AccountAuthenticator); + authenticatorType = sa.getString( + com.android.internal.R.styleable.AccountAuthenticator_accountType); + sa.recycle(); + } finally { + if (parser != null) parser.close(); + } + + if (authenticatorType == null) { + return null; + } + + return new AuthenticatorInfo(authenticatorType, componentName); + } +} diff --git a/core/java/android/accounts/AccountAuthenticatorResponse.java b/core/java/android/accounts/AccountAuthenticatorResponse.java new file mode 100644 index 0000000..07cee40 --- /dev/null +++ b/core/java/android/accounts/AccountAuthenticatorResponse.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +import android.os.RemoteException; + +/** + * Object that wraps calls to an {@link IAccountAuthenticatorResponse} object. + * TODO: this interface is still in flux + */ +public class AccountAuthenticatorResponse { + private IAccountAuthenticatorResponse mAccountAuthenticatorResponse; + + public AccountAuthenticatorResponse(IAccountAuthenticatorResponse response) { + mAccountAuthenticatorResponse = response; + } + + public void onFinished(int result) { + try { + mAccountAuthenticatorResponse.onIntResult(result); + } catch (RemoteException e) { + // this should never happen + } + } + + public void onFinished(String result) { + try { + mAccountAuthenticatorResponse.onStringResult(result); + } catch (RemoteException e) { + // this should never happen + } + } + + public void onFinished(boolean result) { + try { + mAccountAuthenticatorResponse.onBooleanResult(result); + } catch (RemoteException e) { + // this should never happen + } + } + + public void onError(int errorCode, String errorMessage) { + try { + mAccountAuthenticatorResponse.onError(errorCode, errorMessage); + } catch (RemoteException e) { + // this should never happen + } + } + + public IAccountAuthenticatorResponse getIAccountAuthenticatorResponse() { + return mAccountAuthenticatorResponse; + } +} diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java new file mode 100644 index 0000000..d0d4750 --- /dev/null +++ b/core/java/android/accounts/AccountManager.java @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +import android.os.RemoteException; +import android.os.Bundle; +import android.app.PendingIntent; +import android.app.Activity; +import android.content.Intent; +import android.content.Context; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.Condition; + +/** + * A class that helps with interactions with the {@link IAccountManager} interface. It provides + * methods to allow for account, password, and authtoken management for all accounts on the + * device. Some of these calls are implemented with the help of the corresponding + * {@link IAccountAuthenticator} services. One accesses the {@link AccountManager} by calling: + * AccountManager accountManager = + * (AccountManager)context.getSystemService(Context.ACCOUNT_SERVICE) + * + * <p> + * TODO: this interface is still in flux + */ +public class AccountManager { + private static final String TAG = "AccountManager"; + + private final Context mContext; + private final IAccountManager mService; + + public AccountManager(Context context, IAccountManager service) { + mContext = context; + mService = service; + } + + public String getPassword(Account account) { + try { + return mService.getPassword(account); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + throw new RuntimeException(e); + } + } + + public String getUserData(Account account, String key) { + try { + return mService.getUserData(account, key); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + throw new RuntimeException(e); + } + } + + public Account[] blockingGetAccounts() { + try { + return mService.getAccounts(); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + throw new RuntimeException(e); + } + } + + public void getAccounts(final PendingIntent intent, final int code) { + getAccountsByType(null /* all account types */, intent, code); + } + + public void getAccountsByType(final String accountType, + final PendingIntent intent, final int code) { + Thread t = new Thread() { + public void run() { + try { + Account[] accounts; + if (accountType != null) { + accounts = blockingGetAccountsByType(accountType); + } else { + accounts = blockingGetAccounts(); + } + Intent payload = new Intent(); + payload.putExtra("accounts", accounts); + intent.send(mContext, code, payload); + } catch (PendingIntent.CanceledException e) { + // the pending intent is no longer accepting results, we don't + // need to do anything to handle this + } + } + }; + t.start(); + } + + public Account[] blockingGetAccountsByType(String accountType) { + try { + return mService.getAccountsByType(accountType); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + throw new RuntimeException(e); + } + } + + public boolean addAccount(Account account, String password, Bundle extras) { + try { + return mService.addAccount(account, password, extras); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + throw new RuntimeException(e); + } + } + + public void removeAccount(Account account) { + try { + mService.removeAccount(account); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + } + } + + public void invalidateAuthToken(String accountType, String authToken) { + try { + mService.invalidateAuthToken(accountType, authToken); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + } + } + + public String peekAuthToken(Account account, String authTokenType) { + try { + return mService.peekAuthToken(account, authTokenType); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + throw new RuntimeException(e); + } + } + + public void setPassword(Account account, String password) { + try { + mService.setPassword(account, password); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + } + } + + public void clearPassword(Account account) { + try { + mService.clearPassword(account); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + } + } + + public void setUserData(Account account, String key, String value) { + try { + mService.setUserData(account, key, value); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + } + } + + public void getAuthToken(AccountManagerResponse response, + Account account, String authTokenType, boolean notifyAuthFailure) { + try { + mService.getAuthToken(response.getIAccountManagerResponse(), account, authTokenType, + notifyAuthFailure); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + } + } + + public void setAuthToken(Account account, String authTokenType, String authToken) { + try { + mService.setAuthToken(account, authTokenType, authToken); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + } + } + + public void addAccountInteractively(AccountManagerResponse response, String accountType) { + try { + mService.addAccountInteractively(response.getIAccountManagerResponse(), accountType); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + } + } + + public class AuthenticateAccountThread extends Thread { + public Lock mLock = new ReentrantLock(); + public Condition mCondition = mLock.newCondition(); + volatile boolean mSuccess = false; + volatile boolean mFailure = false; + volatile boolean mResult = false; + private final Account mAccount; + private final String mPassword; + public AuthenticateAccountThread(Account account, String password) { + mAccount = account; + mPassword = password; + } + public void run() { + try { + IAccountManagerResponse response = new IAccountManagerResponse.Stub() { + public void onStringResult(String value) throws RemoteException { + } + + public void onIntResult(int value) throws RemoteException { + } + + public void onBooleanResult(boolean value) throws RemoteException { + mLock.lock(); + try { + if (!mFailure && !mSuccess) { + mSuccess = true; + mResult = value; + mCondition.signalAll(); + } + } finally { + mLock.unlock(); + } + } + + public void onError(int errorCode, String errorMessage) { + mLock.lock(); + try { + if (!mFailure && !mSuccess) { + mFailure = true; + mCondition.signalAll(); + } + } finally { + mLock.unlock(); + } + } + }; + + mService.authenticateAccount(response, mAccount, mPassword); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + } + } + } + + public boolean authenticateAccount(Account account, String password) { + AuthenticateAccountThread thread = new AuthenticateAccountThread(account, password); + thread.mLock.lock(); + thread.start(); + try { + while (!thread.mSuccess && !thread.mFailure) { + try { + thread.mCondition.await(); + } catch (InterruptedException e) { + // keep waiting + } + } + return thread.mResult; + } finally { + thread.mLock.unlock(); + } + } + + public void updatePassword(AccountManagerResponse response, Account account) { + try { + mService.updatePassword(response.getIAccountManagerResponse(), account); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + } + } + + public void editProperties(AccountManagerResponse response, String accountType) { + try { + mService.editProperties(response.getIAccountManagerResponse(), accountType); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + } + } + + public void getPasswordStrength(AccountManagerResponse response, + String accountType, String password) { + try { + mService.getPasswordStrength(response.getIAccountManagerResponse(), + accountType, password); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + } + } + + public void checkUsernameExistence(AccountManagerResponse response, + String accountType, String username) { + try { + mService.checkUsernameExistence(response.getIAccountManagerResponse(), + accountType, username); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + } + } +} diff --git a/core/java/android/accounts/AccountManagerResponse.java b/core/java/android/accounts/AccountManagerResponse.java new file mode 100644 index 0000000..b15fb13 --- /dev/null +++ b/core/java/android/accounts/AccountManagerResponse.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.accounts; + +/** + * Object that wraps calls to an {@link IAccountManagerResponse} object. + * TODO: this interface is still in flux + */ +public class AccountManagerResponse { + private IAccountManagerResponse mResponse; + + public AccountManagerResponse(IAccountManagerResponse accountManagerResponse) { + mResponse = accountManagerResponse; + } + + public IAccountManagerResponse getIAccountManagerResponse() { + return mResponse; + } +} diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java new file mode 100644 index 0000000..78d4535 --- /dev/null +++ b/core/java/android/accounts/AccountManagerService.java @@ -0,0 +1,901 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +import android.os.*; +import android.content.*; +import android.database.sqlite.*; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.util.Log; +import android.text.TextUtils; +import android.telephony.TelephonyManager; + +import java.util.HashMap; + +import com.google.android.collect.Maps; +import com.android.internal.telephony.TelephonyIntents; + +/** + * A system service that provides account, password, and authtoken management for all + * accounts on the device. Some of these calls are implemented with the help of the corresponding + * {@link IAccountAuthenticator} services. This service is not accessed by users directly, + * instead one uses an instance of {@link AccountManager}, which can be accessed as follows: + * AccountManager accountManager = + * (AccountManager)context.getSystemService(Context.ACCOUNT_SERVICE) + */ +public class AccountManagerService extends IAccountManager.Stub { + private static final String TAG = "AccountManagerService"; + + private static final int TIMEOUT_DELAY_MS = 1000 * 60; + private static final String DATABASE_NAME = "accounts.db"; + private static final int DATABASE_VERSION = 1; + + private final Context mContext; + + private HandlerThread mMessageThread; + private final MessageHandler mMessageHandler; + + // Messages that can be sent on mHandler + private static final int MESSAGE_TIMED_OUT = 3; + private static final int MESSAGE_CONNECTED = 7; + private static final int MESSAGE_DISCONNECTED = 8; + + private final AccountAuthenticatorCache mAuthenticatorCache; + private final AuthenticatorBindHelper mBindHelper; + public final HashMap<AuthTokenKey, String> mAuthTokenCache = Maps.newHashMap(); + private final DatabaseHelper mOpenHelper; + private final SimWatcher mSimWatcher; + + private static final String TABLE_ACCOUNTS = "accounts"; + private static final String ACCOUNTS_ID = "_id"; + private static final String ACCOUNTS_NAME = "name"; + private static final String ACCOUNTS_TYPE = "type"; + private static final String ACCOUNTS_PASSWORD = "password"; + + private static final String TABLE_AUTHTOKENS = "authtokens"; + private static final String AUTHTOKENS_ID = "_id"; + private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id"; + private static final String AUTHTOKENS_TYPE = "type"; + private static final String AUTHTOKENS_AUTHTOKEN = "authtoken"; + + private static final String TABLE_EXTRAS = "extras"; + private static final String EXTRAS_ID = "_id"; + private static final String EXTRAS_ACCOUNTS_ID = "accounts_id"; + private static final String EXTRAS_KEY = "key"; + private static final String EXTRAS_VALUE = "value"; + + private static final String TABLE_META = "meta"; + private static final String META_KEY = "key"; + private static final String META_VALUE = "value"; + + private static final String[] ACCOUNT_NAME_TYPE_PROJECTION = + new String[]{ACCOUNTS_ID, ACCOUNTS_NAME, ACCOUNTS_TYPE}; + private static final Intent ACCOUNTS_CHANGED_INTENT = + new Intent(AccountsServiceConstants.LOGIN_ACCOUNTS_CHANGED_ACTION); + + public class AuthTokenKey { + public final Account mAccount; + public final String mAuthTokenType; + private final int mHashCode; + + public AuthTokenKey(Account account, String authTokenType) { + mAccount = account; + mAuthTokenType = authTokenType; + mHashCode = computeHashCode(); + } + + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof AuthTokenKey)) { + return false; + } + AuthTokenKey other = (AuthTokenKey)o; + if (!mAccount.equals(other.mAccount)) { + return false; + } + return (mAuthTokenType == null) + ? other.mAuthTokenType == null + : mAuthTokenType.equals(other.mAuthTokenType); + } + + private int computeHashCode() { + int result = 17; + result = 31 * result + mAccount.hashCode(); + result = 31 * result + ((mAuthTokenType == null) ? 0 : mAuthTokenType.hashCode()); + return result; + } + + public int hashCode() { + return mHashCode; + } + } + + public AccountManagerService(Context context) { + mContext = context; + + mOpenHelper = new DatabaseHelper(mContext); + + mMessageThread = new HandlerThread("AccountManagerService"); + mMessageThread.start(); + mMessageHandler = new MessageHandler(mMessageThread.getLooper()); + + mAuthenticatorCache = new AccountAuthenticatorCache(mContext); + mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler, + MESSAGE_CONNECTED, MESSAGE_DISCONNECTED); + + mSimWatcher = new SimWatcher(mContext); + } + + public String getPassword(Account account) throws RemoteException { + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD}, + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", + new String[]{account.mName, account.mType}, null, null, null); + try { + if (cursor.moveToNext()) { + return cursor.getString(0); + } + return null; + } finally { + cursor.close(); + } + } + + public String getUserData(Account account, String key) throws RemoteException { + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + db.beginTransaction(); + try { + long accountId = getAccountId(db, account); + if (accountId < 0) { + return null; + } + Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_VALUE}, + EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?", + new String[]{key}, null, null, null); + try { + if (cursor.moveToNext()) { + return cursor.getString(0); + } + return null; + } finally { + cursor.close(); + } + } finally { + db.setTransactionSuccessful(); + db.endTransaction(); + } + } + + public Account[] getAccounts() throws RemoteException { + return getAccountsByType(null); + } + + public Account[] getAccountsByType(String accountType) throws RemoteException { + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + + final String selection = accountType == null ? null : (ACCOUNTS_TYPE + "=?"); + final String[] selectionArgs = accountType == null ? null : new String[]{accountType}; + Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_NAME_TYPE_PROJECTION, + selection, selectionArgs, null, null, null); + try { + int i = 0; + Account[] accounts = new Account[cursor.getCount()]; + while (cursor.moveToNext()) { + accounts[i] = new Account(cursor.getString(1), cursor.getString(2)); + i++; + } + return accounts; + } finally { + cursor.close(); + } + } + + public boolean addAccount(Account account, String password, Bundle extras) + throws RemoteException { + // fails if the account already exists + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + try { + long numMatches = DatabaseUtils.longForQuery(db, + "select count(*) from " + TABLE_ACCOUNTS + + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", + new String[]{account.mName, account.mType}); + if (numMatches > 0) { + return false; + } + ContentValues values = new ContentValues(); + values.put(ACCOUNTS_NAME, account.mName); + values.put(ACCOUNTS_TYPE, account.mType); + values.put(ACCOUNTS_PASSWORD, password); + long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values); + if (accountId < 0) { + return false; + } + if (extras != null) { + for (String key : extras.keySet()) { + final String value = extras.getString(key); + if (insertExtra(db, accountId, key, value) < 0) { + return false; + } + } + } + db.setTransactionSuccessful(); + mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT); + return true; + } finally { + db.endTransaction(); + } + } + + private long insertExtra(SQLiteDatabase db, long accountId, String key, String value) { + ContentValues values = new ContentValues(); + values.put(EXTRAS_KEY, key); + values.put(EXTRAS_ACCOUNTS_ID, accountId); + values.put(EXTRAS_VALUE, value); + return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values); + } + + public void removeAccount(Account account) throws RemoteException { + // clear out matching authtokens from the cache + final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", + new String[]{account.mName, account.mType}); + mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT); + } + + public void invalidateAuthToken(String accountType, String authToken) throws RemoteException { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + try { + invalidateAuthToken(db, accountType, authToken); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + private void invalidateAuthToken(SQLiteDatabase db, String accountType, String authToken) { + Cursor cursor = db.rawQuery( + "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID + + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME + + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE + + " FROM " + TABLE_ACCOUNTS + + " JOIN " + TABLE_AUTHTOKENS + + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID + + " = " + AUTHTOKENS_ACCOUNTS_ID + + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND " + + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?", + new String[]{authToken, accountType}); + try { + while (cursor.moveToNext()) { + long authTokenId = cursor.getLong(0); + String accountName = cursor.getString(1); + String authTokenType = cursor.getString(2); + AuthTokenKey key = new AuthTokenKey(new Account(accountName, accountType), + authTokenType); + mAuthTokenCache.remove(key); + db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null); + } + } finally { + cursor.close(); + } + } + + private boolean saveAuthTokenToDatabase(Account account, String type, String authToken) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + try { + if (saveAuthTokenToDatabase(db, account, type, authToken)) { + mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT); + db.setTransactionSuccessful(); + return true; + } + return false; + } finally { + db.endTransaction(); + } + } + + private boolean saveAuthTokenToDatabase(SQLiteDatabase db, Account account, + String type, String authToken) { + long accountId = getAccountId(db, account); + if (accountId < 0) { + return false; + } + db.delete(TABLE_AUTHTOKENS, + AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?", + new String[]{type}); + ContentValues values = new ContentValues(); + values.put(AUTHTOKENS_ACCOUNTS_ID, accountId); + values.put(AUTHTOKENS_TYPE, type); + values.put(AUTHTOKENS_AUTHTOKEN, authToken); + return db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0; + } + + public String readAuthTokenFromDatabase(Account account, String authTokenType) { + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + db.beginTransaction(); + try { + long accountId = getAccountId(db, account); + if (accountId < 0) { + return null; + } + return getAuthToken(db, accountId, authTokenType); + } finally { + db.setTransactionSuccessful(); + db.endTransaction(); + } + } + + public String peekAuthToken(Account account, String authTokenType) throws RemoteException { + AuthTokenKey key = new AuthTokenKey(account, authTokenType); + if (mAuthTokenCache.containsKey(key)) { + return mAuthTokenCache.get(key); + } + return readAuthTokenFromDatabase(account, authTokenType); + } + + public void setAuthToken(Account account, String authTokenType, String authToken) + throws RemoteException { + cacheAuthToken(account, authTokenType, authToken); + } + + public void setPassword(Account account, String password) throws RemoteException { + ContentValues values = new ContentValues(); + values.put(ACCOUNTS_PASSWORD, password); + mOpenHelper.getWritableDatabase().update(TABLE_ACCOUNTS, values, + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", + new String[]{account.mName, account.mType}); + mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT); + } + + public void clearPassword(Account account) throws RemoteException { + setPassword(account, null); + } + + public void setUserData(Account account, String key, String value) throws RemoteException { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + try { + long accountId = getAccountId(db, account); + if (accountId < 0) { + return; + } + long extrasId = getExtrasId(db, accountId, key); + if (extrasId < 0 ) { + extrasId = insertExtra(db, accountId, key, value); + if (extrasId < 0) { + return; + } + } else { + ContentValues values = new ContentValues(); + values.put(EXTRAS_VALUE, value); + if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) { + return; + } + + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + public void getAuthToken(IAccountManagerResponse response, Account account, + String authTokenType, boolean notifyOnAuthFailure) throws RemoteException { + // create a new Session + Session session = new GetAuthTokenSession(response, account, authTokenType, + notifyOnAuthFailure); + + String authToken = getCachedAuthToken(account, authTokenType); + if (authToken != null) { + session.onStringResult(authToken); + return; + } + + session.bind(); + } + + public void addAccountInteractively(IAccountManagerResponse response, String accountType) + throws RemoteException { + new AddAccountInteractivelySession(response, accountType).bind(); + } + + public void authenticateAccount(IAccountManagerResponse response, Account account, + String password) + throws RemoteException { + new AuthenticateAccountSession(response, account, password).bind(); + } + + public void updatePassword(IAccountManagerResponse response, Account account) + throws RemoteException { + new UpdatePasswordSession(response, account).bind(); + } + + public void editProperties(IAccountManagerResponse response, String accountType) + throws RemoteException { + new EditPropertiesSession(response, accountType).bind(); + } + + public void getPasswordStrength(IAccountManagerResponse response, + String accountType, String password) throws RemoteException { + new GetPasswordStrengthSession(response, accountType, password).bind(); + } + + public void checkUsernameExistence(IAccountManagerResponse response, + String accountType, String username) throws RemoteException { + new CheckUsernameExistenceSession(response, username, accountType).bind(); + } + + private boolean cacheAuthToken(Account account, String authTokenType, String authToken) { + if (saveAuthTokenToDatabase(account, authTokenType, authToken)) { + final AuthTokenKey key = new AuthTokenKey(account, authTokenType); + mAuthTokenCache.put(key, authToken); + return true; + } else { + return false; + } + } + + private String getCachedAuthToken(Account account, String authTokenType) { + final AuthTokenKey key = new AuthTokenKey(account, authTokenType); + if (!mAuthTokenCache.containsKey(key)) return null; + return mAuthTokenCache.get(key); + } + + private long getAccountId(SQLiteDatabase db, Account account) { + Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID}, + "name=? AND type=?", new String[]{account.mName, account.mType}, null, null, null); + try { + if (cursor.moveToNext()) { + return cursor.getLong(0); + } + return -1; + } finally { + cursor.close(); + } + } + + private long getExtrasId(SQLiteDatabase db, long accountId, String key) { + Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID}, + EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?", + new String[]{key}, null, null, null); + try { + if (cursor.moveToNext()) { + return cursor.getLong(0); + } + return -1; + } finally { + cursor.close(); + } + } + + private String getAuthToken(SQLiteDatabase db, long accountId, String authTokenType) { + Cursor cursor = db.query(TABLE_AUTHTOKENS, new String[]{AUTHTOKENS_AUTHTOKEN}, + AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?", + new String[]{authTokenType}, + null, null, null); + try { + if (cursor.moveToNext()) { + return cursor.getString(0); + } + return null; + } finally { + cursor.close(); + } + } + + private class Session extends IAccountAuthenticatorResponse.Stub + implements AuthenticatorBindHelper.Callback { + IAccountManagerResponse mResponse; + final String mAccountType; + + IAccountAuthenticator mAuthenticator = null; + + public Session(IAccountManagerResponse response, String accountType) { + super(); + mResponse = response; + mAccountType = accountType; + } + + IAccountManagerResponse close() { + if (mResponse == null) { + // this session has already been closed + return null; + } + cancelTimeout(); + unbind(); + IAccountManagerResponse response = mResponse; + mResponse = null; + return response; + } + + void bind() { + if (!mBindHelper.bind(mAccountType, this)) { + onError(6, "bind failure"); + } + } + + private void unbind() { + if (mAuthenticator != null) { + mAuthenticator = null; + mBindHelper.unbind(this); + } + } + + public void scheduleTimeout() { + mMessageHandler.sendMessageDelayed( + mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS); + } + + public void cancelTimeout() { + mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this); + } + + public void onConnected(IBinder service) { + mAuthenticator = IAccountAuthenticator.Stub.asInterface(service); + // do the next step + } + + public void onDisconnected() { + IAccountManagerResponse response = close(); + if (response != null) { + onError(3, "disconnected"); + } + } + + public void onTimedOut() { + IAccountManagerResponse response = close(); + if (response != null) { + onError(4, "timeout"); + } + } + + public void onIntResult(int result) throws RemoteException { + IAccountManagerResponse response = close(); + if (response != null) { + response.onIntResult(result); + } + } + + public void onBooleanResult(boolean result) throws RemoteException { + IAccountManagerResponse response = close(); + if (response != null) { + response.onBooleanResult(result); + } + } + + public void onStringResult(String result) throws RemoteException { + IAccountManagerResponse response = close(); + if (response != null) { + response.onStringResult(result); + } + } + + public void onError(int errorCode, String errorMessage) { + IAccountManagerResponse response = close(); + if (response != null) { + try { + response.onError(errorCode, errorMessage); + } catch (RemoteException e) { + // error while trying to notify user of an error + } + } + } + } + + private class GetAuthTokenSession extends Session { + final Account mAccount; + final String mAuthTokenType; + final boolean mNotifyOnAuthFailure; + + public GetAuthTokenSession(IAccountManagerResponse response, + Account account, String authTokenType, boolean interactive) { + super(response, account.mType); + mAccount = account; + mAuthTokenType = authTokenType; + mNotifyOnAuthFailure = interactive; + } + + public void onConnected(IBinder service) { + super.onConnected(service); + + try { + mAuthenticator.getAuthToken(this, mAccount.mName, mAccount.mType, mAuthTokenType); + } catch (RemoteException e) { + onError(4, "remote exception"); + } + } + + public void onStringResult(String result) throws RemoteException { + IAccountManagerResponse response = close(); + if (response != null) { + cacheAuthToken(mAccount, mAccountType, result); + response.onStringResult(result); + } + } + + public void onError(int errorCode, String errorMessage) { + if (mNotifyOnAuthFailure && errorCode == 0 /* TODO: put the real value here */) { + // TODO: authentication failed, pop up the notification + } + super.onError(errorCode, errorMessage); + } + } + + private class CheckUsernameExistenceSession extends Session { + final String mUsername; + + public CheckUsernameExistenceSession(IAccountManagerResponse response, + String username, String accountType) { + super(response, accountType); + mUsername = username; + } + + public void onConnected(IBinder service) { + super.onConnected(service); + + try { + mAuthenticator.checkUsernameExistence(this, mAccountType, mUsername); + } catch (RemoteException e) { + onError(4, "remote exception"); + } + } + } + + private class AddAccountInteractivelySession extends Session { + + public AddAccountInteractivelySession(IAccountManagerResponse response, + String accountType) { + super(response, accountType); + } + + public void onConnected(IBinder service) { + super.onConnected(service); + + try { + mAuthenticator.addAccount(this, mAccountType); + } catch (RemoteException e) { + onError(4, "remote exception"); + } + } + } + + private class AuthenticateAccountSession extends Session { + final String mUsername; + final String mPassword; + + public AuthenticateAccountSession(IAccountManagerResponse response, Account account, + String password) { + super(response, account.mType); + mUsername = account.mName; + mPassword = password; + } + + public void onConnected(IBinder service) { + super.onConnected(service); + + try { + mAuthenticator.authenticateAccount(this, mUsername, mAccountType, mPassword); + } catch (RemoteException e) { + onError(4, "remote exception"); + } + } + } + + private class UpdatePasswordSession extends Session { + final String mUsername; + + public UpdatePasswordSession(IAccountManagerResponse response, Account account) { + super(response, account.mType); + mUsername = account.mName; + } + + public void onConnected(IBinder service) { + super.onConnected(service); + + try { + mAuthenticator.updatePassword(this, mUsername, mAccountType); + } catch (RemoteException e) { + onError(4, "remote exception"); + } + } + } + + private class EditPropertiesSession extends Session { + public EditPropertiesSession(IAccountManagerResponse response, String accountType) { + super(response, accountType); + } + + public void onConnected(IBinder service) { + super.onConnected(service); + + try { + mAuthenticator.editProperties(this, mAccountType); + } catch (RemoteException e) { + onError(4, "remote exception"); + } + } + } + + private class GetPasswordStrengthSession extends Session { + final String mPassword; + + public GetPasswordStrengthSession(IAccountManagerResponse response, + String accountType, String password) { + super(response, accountType); + mPassword = password; + } + + public void onConnected(IBinder service) { + super.onConnected(service); + + try { + mAuthenticator.getPasswordStrength(this, mAccountType, mPassword); + } catch (RemoteException e) { + onError(4, "remote exception"); + } + } + } + + private class MessageHandler extends Handler { + MessageHandler(Looper looper) { + super(looper); + } + + public void handleMessage(Message msg) { + if (mBindHelper.handleMessage(msg)) { + return; + } + switch (msg.what) { + case MESSAGE_TIMED_OUT: + Session session = (Session)msg.obj; + session.onTimedOut(); + break; + + default: + throw new IllegalStateException("unhandled message: " + msg.what); + } + } + } + + private class DatabaseHelper extends SQLiteOpenHelper { + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( " + + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + ACCOUNTS_NAME + " TEXT NOT NULL, " + + ACCOUNTS_TYPE + " TEXT NOT NULL, " + + ACCOUNTS_PASSWORD + " TEXT, " + + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); + + db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( " + + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, " + + AUTHTOKENS_TYPE + " TEXT NOT NULL, " + + AUTHTOKENS_AUTHTOKEN + " TEXT, " + + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))"); + + db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( " + + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + EXTRAS_ACCOUNTS_ID + " INTEGER, " + + EXTRAS_KEY + " TEXT NOT NULL, " + + EXTRAS_VALUE + " TEXT, " + + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))"); + + db.execSQL("CREATE TABLE " + TABLE_META + " ( " + + META_KEY + " TEXT PRIMARY KEY NOT NULL, " + + META_VALUE + " TEXT)"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.e(TAG, "GLS upgrade from version " + oldVersion + " to version " + + newVersion + " not supported"); + + db.execSQL("DROP TABLE " + TABLE_ACCOUNTS); + db.execSQL("DROP TABLE " + TABLE_AUTHTOKENS); + db.execSQL("DROP TABLE " + TABLE_EXTRAS); + db.execSQL("DROP TABLE " + TABLE_META); + onCreate(db); + } + + @Override + public void onOpen(SQLiteDatabase db) { + if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME); + } + } + + private void setMetaValue(String key, String value) { + ContentValues values = new ContentValues(); + values.put(META_KEY, key); + values.put(META_VALUE, value); + mOpenHelper.getWritableDatabase().replace(TABLE_META, META_KEY, values); + } + + private String getMetaValue(String key) { + Cursor c = mOpenHelper.getReadableDatabase().query(TABLE_META, + new String[]{META_VALUE}, META_KEY + "=?", new String[]{key}, null, null, null); + try { + if (c.moveToNext()) { + return c.getString(0); + } + return null; + } finally { + c.close(); + } + } + + private class SimWatcher extends BroadcastReceiver { + public SimWatcher(Context context) { + // Re-scan the SIM card when the SIM state changes, and also if + // the disk recovers from a full state (we may have failed to handle + // things properly while the disk was full). + final IntentFilter filter = new IntentFilter(); + filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); + context.registerReceiver(this, filter); + } + + /** + * Compare the IMSI to the one stored in the login service's + * database. If they differ, erase all passwords and + * authtokens (and store the new IMSI). + */ + @Override + public void onReceive(Context context, Intent intent) { + // Check IMSI on every update; nothing happens if the IMSI is missing or unchanged. + String imsi = ((TelephonyManager) context.getSystemService( + Context.TELEPHONY_SERVICE)).getSubscriberId(); + if (TextUtils.isEmpty(imsi)) return; + + String storedImsi = getMetaValue("imsi"); + + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "current IMSI=" + imsi + "; stored IMSI=" + storedImsi); + } + + if (!imsi.equals(storedImsi) && !"initial".equals(storedImsi)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "wiping all passwords and authtokens"); + } + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + try { + db.execSQL("DELETE from " + TABLE_AUTHTOKENS); + db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_PASSWORD + " = ''"); + mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + setMetaValue("imsi", imsi); + } + } + + public IBinder onBind(Intent intent) { + return asBinder(); + } +} diff --git a/core/java/android/accounts/AccountMonitor.java b/core/java/android/accounts/AccountMonitor.java index f21385e..4ad06d4 100644 --- a/core/java/android/accounts/AccountMonitor.java +++ b/core/java/android/accounts/AccountMonitor.java @@ -17,16 +17,10 @@ package android.accounts; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.database.SQLException; -import android.os.IBinder; -import android.os.Process; -import android.os.RemoteException; -import android.util.Log; +import android.os.*; /** * A helper class that calls back on the provided @@ -38,55 +32,15 @@ import android.util.Log; * fetch the current list of accounts (that is, when the * AccountMonitor is first created, and when the intent is received). */ -public class AccountMonitor extends BroadcastReceiver implements ServiceConnection { +public class AccountMonitor extends BroadcastReceiver { + private static final String TAG = "AccountMonitor"; + private final Context mContext; private final AccountMonitorListener mListener; private boolean mClosed = false; - private int pending = 0; - - // This thread runs in the background and runs the code to update accounts - // in the listener. - private class AccountUpdater extends Thread { - private IBinder mService; - - public AccountUpdater(IBinder service) { - mService = service; - } - @Override - public void run() { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - IAccountsService accountsService = IAccountsService.Stub.asInterface(mService); - String[] accounts = null; - do { - try { - accounts = accountsService.getAccounts(); - } catch (RemoteException e) { - // if the service was killed then the system will restart it and when it does we - // will get another onServiceConnected, at which point we will do a notify. - Log.w("AccountMonitor", "Remote exception when getting accounts", e); - return; - } - - synchronized (AccountMonitor.this) { - --pending; - if (pending == 0) { - break; - } - } - } while (true); - - mContext.unbindService(AccountMonitor.this); - - try { - mListener.onAccountsUpdated(accounts); - } catch (SQLException e) { - // Better luck next time. If the problem was disk-full, - // the STORAGE_OK intent will re-trigger the update. - Log.e("AccountMonitor", "Can't update accounts", e); - } - } - } + private volatile Looper mServiceLooper; + private volatile NotifierHandler mServiceHandler; /** * Initializes the AccountMonitor and initiates a bind to the @@ -110,8 +64,12 @@ public class AccountMonitor extends BroadcastReceiver implements ServiceConnecti intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); // To recover from disk-full. mContext.registerReceiver(this, intentFilter); - // Send the listener the initial state now. - notifyListener(); + HandlerThread thread = new HandlerThread("AccountMonitorHandlerThread"); + thread.start(); + mServiceLooper = thread.getLooper(); + mServiceHandler = new NotifierHandler(mServiceLooper); + + mServiceHandler.sendEmptyMessage(0); } @Override @@ -119,45 +77,15 @@ public class AccountMonitor extends BroadcastReceiver implements ServiceConnecti notifyListener(); } - public void onServiceConnected(ComponentName className, IBinder service) { - // Create a background thread to update the accounts. - new AccountUpdater(service).start(); - } - - public void onServiceDisconnected(ComponentName className) { - } - private synchronized void notifyListener() { - if (pending == 0) { - // initiate the bind - if (!mContext.bindService(AccountsServiceConstants.SERVICE_INTENT, - this, Context.BIND_AUTO_CREATE)) { - // This is normal if GLS isn't part of this build. - Log.w("AccountMonitor", - "Couldn't connect to " + - AccountsServiceConstants.SERVICE_INTENT + - " (Missing service?)"); - } - } else { - // already bound. bindService will not trigger another - // call to onServiceConnected, so instead we make sure - // that the existing background thread will call - // getAccounts() after this function returns, by - // incrementing pending. - // - // Yes, this else clause contains only a comment. + AccountManager accountManager = + (AccountManager)mContext.getSystemService(Context.ACCOUNT_SERVICE); + Account[] accounts = accountManager.blockingGetAccounts(); + String[] accountNames = new String[accounts.length]; + for (int i = 0; i < accounts.length; i++) { + accountNames[i] = accounts[i].mName; } - ++pending; - } - - /** - * calls close() - * @throws Throwable - */ - @Override - protected void finalize() throws Throwable { - close(); - super.finalize(); + mListener.onAccountsUpdated(accountNames); } /** @@ -171,4 +99,14 @@ public class AccountMonitor extends BroadcastReceiver implements ServiceConnecti mClosed = true; } } + + private final class NotifierHandler extends Handler { + public NotifierHandler(Looper looper) { + super(looper); + } + + public void handleMessage(Message msg) { + notifyListener(); + } + } } diff --git a/core/java/android/accounts/AuthenticatorBindHelper.java b/core/java/android/accounts/AuthenticatorBindHelper.java new file mode 100644 index 0000000..ec41810 --- /dev/null +++ b/core/java/android/accounts/AuthenticatorBindHelper.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.content.ComponentName; +import android.content.Context; +import android.content.ServiceConnection; +import android.content.Intent; + +import java.util.Map; +import java.util.ArrayList; + +import com.google.android.collect.Maps; +import com.google.android.collect.Lists; + +/** + * A helper object that simplifies binding to Account Authenticators. It uses the + * {@link AccountAuthenticatorCache} to find the component name of the authenticators, + * allowing the user to bind by account name. It also allows multiple, simultaneous binds + * to the same authenticator, with each bind call guaranteed to return either + * {@link Callback#onConnected} or {@link Callback#onDisconnected} if the bind() call + * itself succeeds, even if the authenticator is already bound internally. + */ +public class AuthenticatorBindHelper { + final private Handler mHandler; + final private Context mContext; + final private int mMessageWhatConnected; + final private int mMessageWhatDisconnected; + final private Map<String, MyServiceConnection> mServiceConnections = Maps.newHashMap(); + final private Map<String, ArrayList<Callback>> mServiceUsers = Maps.newHashMap(); + final private AccountAuthenticatorCache mAuthenticatorCache; + + public AuthenticatorBindHelper(Context context, + AccountAuthenticatorCache authenticatorCache, Handler handler, + int messageWhatConnected, int messageWhatDisconnected) { + mContext = context; + mHandler = handler; + mAuthenticatorCache = authenticatorCache; + mMessageWhatConnected = messageWhatConnected; + mMessageWhatDisconnected = messageWhatDisconnected; + } + + public interface Callback { + void onConnected(IBinder service); + void onDisconnected(); + } + + public boolean bind(String authenticatorType, Callback callback) { + // if the authenticator is connecting or connected then return true + synchronized (mServiceConnections) { + if (mServiceConnections.containsKey(authenticatorType)) { + mServiceUsers.get(authenticatorType).add(callback); + return true; + } + + // otherwise find the component name for the authenticator and initiate a bind + // if no authenticator or the bind fails then return false, otherwise return true + AccountAuthenticatorCache.AuthenticatorInfo authenticatorInfo = + mAuthenticatorCache.getAuthenticatorInfo(authenticatorType); + if (authenticatorInfo == null) { + return false; + } + + MyServiceConnection connection = new MyServiceConnection(authenticatorType); + + Intent intent = new Intent(); + intent.setAction("android.accounts.AccountAuthenticator"); + intent.setComponent(authenticatorInfo.mComponentName); + if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) { + return false; + } + + mServiceConnections.put(authenticatorType, connection); + mServiceUsers.put(authenticatorType, Lists.newArrayList(callback)); + return true; + } + } + + public void unbind(Callback callbackToUnbind) { + synchronized (mServiceConnections) { + for (Map.Entry<String, ArrayList<Callback>> entry : mServiceUsers.entrySet()) { + final String authenticatorType = entry.getKey(); + final ArrayList<Callback> serviceUsers = entry.getValue(); + for (Callback callback : serviceUsers) { + if (callback == callbackToUnbind) { + serviceUsers.remove(callbackToUnbind); + if (serviceUsers.isEmpty()) { + unbindFromService(authenticatorType); + } + return; + } + } + } + } + } + + private void unbindFromService(String authenticatorType) { + mContext.unbindService(mServiceConnections.get(authenticatorType)); + mServiceUsers.remove(authenticatorType); + mServiceConnections.remove(authenticatorType); + } + + private class ConnectedMessagePayload { + public final IBinder mService; + public final Callback mCallback; + public ConnectedMessagePayload(IBinder service, Callback callback) { + mService = service; + mCallback = callback; + } + } + + private class MyServiceConnection implements ServiceConnection { + final private String mAuthenticatorType; + + public MyServiceConnection(String authenticatorType) { + mAuthenticatorType = authenticatorType; + } + + public void onServiceConnected(ComponentName name, IBinder service) { + // post a message for each service user to tell them that the service is connected + synchronized (mServiceConnections) { + for (Callback callback : mServiceUsers.get(mAuthenticatorType)) { + final ConnectedMessagePayload payload = + new ConnectedMessagePayload(service, callback); + mHandler.obtainMessage(mMessageWhatConnected, payload).sendToTarget(); + } + } + } + + public void onServiceDisconnected(ComponentName name) { + // post a message for each service user to tell them that the service is disconnected, + // and unbind from the service. + synchronized (mServiceConnections) { + for (Callback callback : mServiceUsers.get(mAuthenticatorType)) { + mHandler.obtainMessage(mMessageWhatDisconnected, callback).sendToTarget(); + } + unbindFromService(mAuthenticatorType); + } + } + } + + boolean handleMessage(Message message) { + if (message.what == mMessageWhatConnected) { + ConnectedMessagePayload payload = (ConnectedMessagePayload)message.obj; + payload.mCallback.onConnected(payload.mService); + return true; + } else if (message.what == mMessageWhatDisconnected) { + Callback callback = (Callback)message.obj; + callback.onDisconnected(); + return true; + } else { + return false; + } + } +} diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl new file mode 100644 index 0000000..b2c2223 --- /dev/null +++ b/core/java/android/accounts/IAccountAuthenticator.aidl @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +import android.accounts.IAccountAuthenticatorResponse; + +/** + * Service that allows the interaction with an authentication server. + */ +oneway interface IAccountAuthenticator { + /** + * prompts the user for account information and adds the result to the IAccountManager + */ + void addAccount(in IAccountAuthenticatorResponse response, String accountType); + + /** + * prompts the user for the credentials of the account + */ + void authenticateAccount(in IAccountAuthenticatorResponse response, String name, + String type, String password); + + /** + * gets the password by either prompting the user or querying the IAccountManager + */ + void getAuthToken(in IAccountAuthenticatorResponse response, + String name, String type, String authTokenType); + + /** + * does local analysis or uses a service in the cloud + */ + void getPasswordStrength(in IAccountAuthenticatorResponse response, + String accountType, String password); + + /** + * checks with the login service in the cloud + */ + void checkUsernameExistence(in IAccountAuthenticatorResponse response, + String accountType, String username); + + /** + * prompts the user for a new password and writes it to the IAccountManager + */ + void updatePassword(in IAccountAuthenticatorResponse response, String name, String type); + + /** + * launches an activity that lets the user edit and set the properties for an authenticator + */ + void editProperties(in IAccountAuthenticatorResponse response, String accountType); +} diff --git a/core/java/android/accounts/IAccountAuthenticatorResponse.aidl b/core/java/android/accounts/IAccountAuthenticatorResponse.aidl new file mode 100644 index 0000000..83504d3 --- /dev/null +++ b/core/java/android/accounts/IAccountAuthenticatorResponse.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; +import android.os.Parcelable; + +/** + * The interface used to return responses from an {@link IAccountAuthenticator} + */ +oneway interface IAccountAuthenticatorResponse { + void onIntResult(int result); + void onBooleanResult(boolean result); + void onStringResult(String result); + void onError(int errorCode, String errorMessage); +} diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl new file mode 100644 index 0000000..8a0d1c4 --- /dev/null +++ b/core/java/android/accounts/IAccountManager.aidl @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +import android.accounts.IAccountManagerResponse; +import android.accounts.Account; +import android.os.Bundle; + +/** + * Central application service that provides account management. + */ +interface IAccountManager { + String getPassword(in Account account); + String getUserData(in Account account, String key); + Account[] getAccounts(); + Account[] getAccountsByType(String accountType); + boolean addAccount(in Account account, String password, in Bundle extras); + void removeAccount(in Account account); + void invalidateAuthToken(String accountType, String authToken); + String peekAuthToken(in Account account, String authTokenType); + void setAuthToken(in Account account, String authTokenType, String authToken); + void setPassword(in Account account, String password); + void clearPassword(in Account account); + void setUserData(in Account account, String key, String value); + + // interactive + + void getAuthToken(in IAccountManagerResponse response, in Account account, String authTokenType, + boolean notifyOnAuthFailure); + void addAccountInteractively(in IAccountManagerResponse response, String accountType); + void authenticateAccount(in IAccountManagerResponse response, in Account account, + String password); + void updatePassword(in IAccountManagerResponse response, in Account account); + void editProperties(in IAccountManagerResponse response, String accountType); + + // not interactive + void getPasswordStrength(in IAccountManagerResponse response, String accountType, + String password); + void checkUsernameExistence(in IAccountManagerResponse response, String accountType, + String username); +} diff --git a/core/java/android/accounts/IAccountManagerResponse.aidl b/core/java/android/accounts/IAccountManagerResponse.aidl new file mode 100644 index 0000000..7bbaa8b --- /dev/null +++ b/core/java/android/accounts/IAccountManagerResponse.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.accounts; + +/** + * The interface used to return responses for asynchronous calls to the {@link IAccountManager} + */ +oneway interface IAccountManagerResponse { + void onStringResult(String value); + void onIntResult(int value); + void onBooleanResult(boolean value); + void onError(int errorCode, String errorMessage); +} diff --git a/core/java/android/accounts/IAccountsService.aidl b/core/java/android/accounts/IAccountsService.aidl deleted file mode 100644 index dda513c..0000000 --- a/core/java/android/accounts/IAccountsService.aidl +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.accounts; - -/** - * Central application service that allows querying the list of accounts. - */ -interface IAccountsService { - /** - * Gets the list of Accounts the user has previously logged - * in to. Accounts are of the form "username@domain". - * <p> - * This method will return an empty array if the device doesn't - * know about any accounts (yet). - * - * @return The accounts. The array will be zero-length if the - * AccountsService doesn't know about any accounts yet. - */ - String[] getAccounts(); - - /** - * This is an interim solution for bypassing a forgotten gesture on the - * unlock screen (it is hidden, please make sure it stays this way!). This - * will be *removed* when the unlock screen design supports additional - * authenticators. - * <p> - * The user will be presented with username and password fields that are - * called as parameters to this method. If true is returned, the user is - * able to define a new gesture and get back into the system. If false, the - * user can try again. - * - * @param username The username entered. - * @param password The password entered. - * @return Whether to allow the user to bypass the lock screen and define a - * new gesture. - * @hide (The package is already hidden, but just in case someone - * unhides that, this should not be revealed.) - */ - boolean shouldUnlock(String username, String password); -} diff --git a/core/java/android/accounts/package.html b/core/java/android/accounts/package.html deleted file mode 100755 index c9f96a6..0000000 --- a/core/java/android/accounts/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<body> - -{@hide} - -</body> diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java index 55fce49..94f3373 100644 --- a/core/java/android/app/ApplicationContext.java +++ b/core/java/android/app/ApplicationContext.java @@ -88,6 +88,8 @@ import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.WindowManagerImpl; import android.view.inputmethod.InputMethodManager; +import android.accounts.AccountManager; +import android.accounts.IAccountManager; import com.android.internal.policy.PolicyManager; @@ -150,6 +152,7 @@ class ApplicationContext extends Context { private final static boolean DEBUG_ICONS = false; private static final Object sSync = new Object(); + private static AccountManager sAccountManager; private static AlarmManager sAlarmManager; private static PowerManager sPowerManager; private static ConnectivityManager sConnectivityManager; @@ -881,6 +884,8 @@ class ApplicationContext extends Context { return getActivityManager(); } else if (ALARM_SERVICE.equals(name)) { return getAlarmManager(); + } else if (ACCOUNT_SERVICE.equals(name)) { + return getAccountManager(); } else if (POWER_SERVICE.equals(name)) { return getPowerManager(); } else if (CONNECTIVITY_SERVICE.equals(name)) { @@ -921,6 +926,17 @@ class ApplicationContext extends Context { return null; } + private AccountManager getAccountManager() { + synchronized (sSync) { + if (sAccountManager == null) { + IBinder b = ServiceManager.getService(ACCOUNT_SERVICE); + IAccountManager service = IAccountManager.Stub.asInterface(b); + sAccountManager = new AccountManager(this, service); + } + } + return sAccountManager; + } + private ActivityManager getActivityManager() { synchronized (mSync) { if (mActivityManager == null) { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 9a0dc9f..abcc1ea 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1097,6 +1097,16 @@ public abstract class Context { public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater"; /** * Use with {@link #getSystemService} to retrieve a + * {@link android.accounts.AccountManager} for receiving intents at a + * time of your choosing. + * TODO STOPSHIP perform a final review of the the account apis before shipping + * + * @see #getSystemService + * @see android.accounts.AccountManager + */ + public static final String ACCOUNT_SERVICE = "account"; + /** + * Use with {@link #getSystemService} to retrieve a * {@link android.app.ActivityManager} for interacting with the global * system state. * diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index 96470c3..01b07eb 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -181,6 +181,47 @@ class SyncManager { } }; + private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + if (!mFactoryTest) { + AccountMonitorListener listener = new AccountMonitorListener() { + public void onAccountsUpdated(String[] accounts) { + final boolean hadAccountsAlready = mAccounts != null; + // copy the accounts into a new array and change mAccounts to point to it + String[] newAccounts = new String[accounts.length]; + System.arraycopy(accounts, 0, newAccounts, 0, accounts.length); + mAccounts = newAccounts; + + // if a sync is in progress yet it is no longer in the accounts list, + // cancel it + ActiveSyncContext activeSyncContext = mActiveSyncContext; + if (activeSyncContext != null) { + if (!ArrayUtils.contains(newAccounts, + activeSyncContext.mSyncOperation.account)) { + Log.d(TAG, "canceling sync since the account has been removed"); + sendSyncFinishedOrCanceledMessage(activeSyncContext, + null /* no result since this is a cancel */); + } + } + + // we must do this since we don't bother scheduling alarms when + // the accounts are not set yet + sendCheckAlarmsMessage(); + + mSyncStorageEngine.doDatabaseCleanup(accounts); + + if (hadAccountsAlready && mAccounts.length > 0) { + // request a sync so that if the password was changed we will + // retry any sync that failed when it was wrong + startSync(null /* all providers */, null /* no extras */); + } + } + }; + mAccountMonitor = new AccountMonitor(context, listener); + } + } + }; + private BroadcastReceiver mConnectivityIntentReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { @@ -241,7 +282,11 @@ class SyncManager { private static final String SYNCMANAGER_PREFS_FILENAME = "/data/system/syncmanager.prefs"; + private final boolean mFactoryTest; + public SyncManager(Context context, boolean factoryTest) { + mFactoryTest = factoryTest; + // Initialize the SyncStorageEngine first, before registering observers // and creating threads and so on; it may fail if the disk is full. SyncStorageEngine.init(context); @@ -265,6 +310,9 @@ class SyncManager { IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); context.registerReceiver(mConnectivityIntentReceiver, intentFilter); + intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); + context.registerReceiver(mBootCompletedReceiver, intentFilter); + intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW); intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); context.registerReceiver(mStorageIntentReceiver, intentFilter); @@ -288,42 +336,6 @@ class SyncManager { mHandleAlarmWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, HANDLE_SYNC_ALARM_WAKE_LOCK); mHandleAlarmWakeLock.setReferenceCounted(false); - - if (!factoryTest) { - AccountMonitorListener listener = new AccountMonitorListener() { - public void onAccountsUpdated(String[] accounts) { - final boolean hadAccountsAlready = mAccounts != null; - // copy the accounts into a new array and change mAccounts to point to it - String[] newAccounts = new String[accounts.length]; - System.arraycopy(accounts, 0, newAccounts, 0, accounts.length); - mAccounts = newAccounts; - - // if a sync is in progress yet it is no longer in the accounts list, cancel it - ActiveSyncContext activeSyncContext = mActiveSyncContext; - if (activeSyncContext != null) { - if (!ArrayUtils.contains(newAccounts, - activeSyncContext.mSyncOperation.account)) { - Log.d(TAG, "canceling sync since the account has been removed"); - sendSyncFinishedOrCanceledMessage(activeSyncContext, - null /* no result since this is a cancel */); - } - } - - // we must do this since we don't bother scheduling alarms when - // the accounts are not set yet - sendCheckAlarmsMessage(); - - mSyncStorageEngine.doDatabaseCleanup(accounts); - - if (hadAccountsAlready && mAccounts.length > 0) { - // request a sync so that if the password was changed we will retry any sync - // that failed when it was wrong - startSync(null /* all providers */, null /* no extras */); - } - } - }; - mAccountMonitor = new AccountMonitor(context, listener); - } } private synchronized void initializeSyncPoll() { |