summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accounts/AbstractAccountAuthenticator.java126
-rw-r--r--core/java/android/accounts/Account.aidl19
-rw-r--r--core/java/android/accounts/Account.java77
-rw-r--r--core/java/android/accounts/AccountAuthenticatorCache.java197
-rw-r--r--core/java/android/accounts/AccountAuthenticatorResponse.java67
-rw-r--r--core/java/android/accounts/AccountManager.java306
-rw-r--r--core/java/android/accounts/AccountManagerResponse.java32
-rw-r--r--core/java/android/accounts/AccountManagerService.java901
-rw-r--r--core/java/android/accounts/AccountMonitor.java120
-rw-r--r--core/java/android/accounts/AuthenticatorBindHelper.java172
-rw-r--r--core/java/android/accounts/IAccountAuthenticator.aidl63
-rw-r--r--core/java/android/accounts/IAccountAuthenticatorResponse.aidl28
-rw-r--r--core/java/android/accounts/IAccountManager.aidl55
-rw-r--r--core/java/android/accounts/IAccountManagerResponse.aidl27
-rw-r--r--core/java/android/accounts/IAccountsService.aidl54
-rwxr-xr-xcore/java/android/accounts/package.html5
-rw-r--r--core/java/android/app/ApplicationContext.java16
-rw-r--r--core/java/android/content/Context.java10
-rw-r--r--core/java/android/content/SyncManager.java84
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() {