diff --git a/api/current.xml b/api/current.xml index 6a9728375ca72..6c0bec63b696f 100644 --- a/api/current.xml +++ b/api/current.xml @@ -12554,7 +12554,7 @@ > + + + + + + - + + + + + + - - - - - - - - + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + + - + + + + - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - + + - - - - - - - - - - - - - - - - - + + + - - - + + + + + + + + + - - - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - + + + + + + + - - - - - - - - - - - - - + + + - - + - + - - - - - - - - - + @@ -14222,21 +14935,6 @@ > - - - - - - - - - - - + @@ -14377,7 +15073,7 @@ - - - - - - + - + - - - - - - - - - - - + @@ -14447,6 +15111,44 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -14510,6 +15214,23 @@ + + + + + + + + @@ -14528,25 +15249,6 @@ - - - - - - - - - - - + + + + + + @@ -14759,21 +15467,6 @@ > - - - - - - - - - - - - - - + @@ -14886,6 +15564,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -60371,7 +61147,7 @@ type="float" transient="false" volatile="false" - value="0.0010f" + value="0.001f" static="true" final="true" deprecated="not deprecated" diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java index ed683d7bb9dc9..587279a3696c8 100644 --- a/core/java/android/accounts/AbstractAccountAuthenticator.java +++ b/core/java/android/accounts/AbstractAccountAuthenticator.java @@ -16,6 +16,7 @@ package android.accounts; +import android.os.Bundle; import android.os.RemoteException; /** @@ -24,54 +25,113 @@ import android.os.RemoteException; * AccountAuthenticators. */ public abstract class AbstractAccountAuthenticator { - private static final String TAG = "AccountAuthenticator"; - class Transport extends IAccountAuthenticator.Stub { - public void addAccount(IAccountAuthenticatorResponse response, String accountType) + public void addAccount(IAccountAuthenticatorResponse response, String accountType, + String authTokenType, Bundle options) throws RemoteException { - AbstractAccountAuthenticator.this.addAccount(new AccountAuthenticatorResponse(response), - accountType); + final Bundle result; + try { + result = AbstractAccountAuthenticator.this.addAccount( + new AccountAuthenticatorResponse(response), + accountType, authTokenType, options); + } catch (NetworkErrorException e) { + response.onError(Constants.ERROR_CODE_NETWORK_ERROR, e.getMessage()); + return; + } catch (UnsupportedOperationException e) { + response.onError(Constants.ERROR_CODE_UNSUPPORTED_OPERATION, + "addAccount not supported"); + return; + } + if (result != null) { + response.onResult(result); + } } - 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 confirmPassword(IAccountAuthenticatorResponse response, + Account account, String password) throws RemoteException { + boolean result; + try { + result = AbstractAccountAuthenticator.this.confirmPassword( + new AccountAuthenticatorResponse(response), + account, password); + } catch (UnsupportedOperationException e) { + response.onError(Constants.ERROR_CODE_UNSUPPORTED_OPERATION, + "confirmPassword not supported"); + return; + } catch (NetworkErrorException e) { + response.onError(Constants.ERROR_CODE_NETWORK_ERROR, e.getMessage()); + return; + } + Bundle bundle = new Bundle(); + bundle.putBoolean(Constants.BOOLEAN_RESULT_KEY, result); + response.onResult(bundle); + } + + public void confirmCredentials(IAccountAuthenticatorResponse response, + Account account) throws RemoteException { + final Bundle result; + try { + result = AbstractAccountAuthenticator.this.confirmCredentials( + new AccountAuthenticatorResponse(response), account); + } catch (UnsupportedOperationException e) { + response.onError(Constants.ERROR_CODE_UNSUPPORTED_OPERATION, + "confirmCredentials not supported"); + return; + } + if (result != null) { + response.onResult(result); + } } public void getAuthToken(IAccountAuthenticatorResponse response, - String name, String type, String authTokenType) + Account account, String authTokenType, Bundle loginOptions) throws RemoteException { - AbstractAccountAuthenticator.this.getAuthToken( - new AccountAuthenticatorResponse(response), - new Account(name, type), authTokenType); + try { + final Bundle result = AbstractAccountAuthenticator.this.getAuthToken( + new AccountAuthenticatorResponse(response), account, + authTokenType, loginOptions); + if (result != null) { + response.onResult(result); + } + } catch (UnsupportedOperationException e) { + response.onError(Constants.ERROR_CODE_UNSUPPORTED_OPERATION, + "getAuthToken not supported"); + } catch (NetworkErrorException e) { + response.onError(Constants.ERROR_CODE_NETWORK_ERROR, e.getMessage()); + } } - public void getPasswordStrength(IAccountAuthenticatorResponse response, - String accountType, String password) - throws RemoteException { - AbstractAccountAuthenticator.this.getPasswordStrength( - new AccountAuthenticatorResponse(response), accountType, password); + public void updateCredentials(IAccountAuthenticatorResponse response, Account account, + String authTokenType, Bundle loginOptions) throws RemoteException { + final Bundle result; + try { + result = AbstractAccountAuthenticator.this.updateCredentials( + new AccountAuthenticatorResponse(response), account, + authTokenType, loginOptions); + } catch (UnsupportedOperationException e) { + response.onError(Constants.ERROR_CODE_UNSUPPORTED_OPERATION, + "updateCredentials not supported"); + return; + } + if (result != null) { + response.onResult(result); + } } - 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( + public void editProperties(IAccountAuthenticatorResponse response, + String accountType) throws RemoteException { + final Bundle result; + try { + result = AbstractAccountAuthenticator.this.editProperties( new AccountAuthenticatorResponse(response), accountType); + } catch (UnsupportedOperationException e) { + response.onError(Constants.ERROR_CODE_UNSUPPORTED_OPERATION, + "editProperties not supported"); + return; + } + if (result != null) { + response.onResult(result); + } } } @@ -86,41 +146,29 @@ public abstract class AbstractAccountAuthenticator { } /** - * prompts the user for account information and adds the result to the IAccountManager + * Returns a Bundle that contains the Intent of the activity that can be used to edit the + * properties. In order to indicate success the activity should call response.setResult() + * with a non-null Bundle. + * @param response used to set the result for the request. If the Constants.INTENT_KEY + * is set in the bundle then this response field is to be used for sending future + * results if and when the Intent is started. + * @param accountType the AccountType whose properties are to be edited. + * @return a Bundle containing the result or the Intent to start to continue the request. + * If this is null then the request is considered to still be active and the result should + * sent later using response. */ - 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); + public abstract Bundle editProperties(AccountAuthenticatorResponse response, + String accountType); + public abstract Bundle addAccount(AccountAuthenticatorResponse response, String accountType, + String authTokenType, Bundle options) throws NetworkErrorException; + /* @deprecated */ + public abstract boolean confirmPassword(AccountAuthenticatorResponse response, + Account account, String password) throws NetworkErrorException; + public abstract Bundle confirmCredentials(AccountAuthenticatorResponse response, + Account account); + public abstract Bundle getAuthToken(AccountAuthenticatorResponse response, + Account account, String authTokenType, Bundle loginOptions) + throws NetworkErrorException; + public abstract Bundle updateCredentials(AccountAuthenticatorResponse response, + Account account, String authTokenType, Bundle loginOptions); } diff --git a/core/java/android/accounts/Account.java b/core/java/android/accounts/Account.java index efcd3666af21b..30c91b0625340 100644 --- a/core/java/android/accounts/Account.java +++ b/core/java/android/accounts/Account.java @@ -18,6 +18,7 @@ package android.accounts; import android.os.Parcelable; import android.os.Parcel; +import android.text.TextUtils; /** * Value type that represents an Account in the {@link AccountManager}. This object is @@ -43,6 +44,12 @@ public class Account implements Parcelable { } public Account(String name, String type) { + if (TextUtils.isEmpty(name)) { + throw new IllegalArgumentException("the name must not be empty: " + name); + } + if (TextUtils.isEmpty(type)) { + throw new IllegalArgumentException("the type must not be empty: " + type); + } mName = name; mType = type; } diff --git a/core/java/android/accounts/AccountAuthenticatorActivity.java b/core/java/android/accounts/AccountAuthenticatorActivity.java new file mode 100644 index 0000000000000..0319ab9bdc9e0 --- /dev/null +++ b/core/java/android/accounts/AccountAuthenticatorActivity.java @@ -0,0 +1,102 @@ +/* + * 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.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +/** + * Base class for implementing an Activity that is used to help implement an + * AbstractAccountAuthenticator. If the AbstractAccountAuthenticator needs to return an Intent + * that is to be used to launch an Activity that needs to return results to satisfy an + * AbstractAccountAuthenticator request, it should store the AccountAuthenticatorResponse + * inside of the Intent as follows: + *

+ * intent.putExtra(Constants.ACCOUNT_AUTHENTICATOR_RESPONSE_KEY, response); + *

+ * The activity that it launches should extend the AccountAuthenticatorActivity. If this + * activity has a result that satisfies the original request it sets it via: + *

+ * setAccountAuthenticatorResult(result) + *

+ * This result will be sent as the result of the request when the activity finishes. If this + * is never set or if it is set to null then the request will be canceled when the activity + * finishes. + */ +public class AccountAuthenticatorActivity extends Activity { + private AccountAuthenticatorResponse mAccountAuthenticatorResponse = null; + private Bundle mResultBundle = null; + + /** + * Set the result that is to be sent as the result of the request that caused this + * Activity to be launched. If result is null or this method is never called then + * the request will be canceled. + * @param result this is returned as the result of the AbstractAccountAuthenticator request + */ + public final void setAccountAuthenticatorResult(Bundle result) { + mResultBundle = result; + } + + /** + * Retreives the AccountAuthenticatorResponse from either the intent of the icicle, if the + * icicle is non-zero. + * @param icicle the save instance data of this Activity, may be null + */ + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + if (icicle == null) { + Intent intent = getIntent(); + mAccountAuthenticatorResponse = + intent.getParcelableExtra(Constants.ACCOUNT_AUTHENTICATOR_RESPONSE_KEY); + } else { + mAccountAuthenticatorResponse = + icicle.getParcelable(Constants.ACCOUNT_AUTHENTICATOR_RESPONSE_KEY); + } + + if (mAccountAuthenticatorResponse != null) { + mAccountAuthenticatorResponse.onRequestContinued(); + } + } + + /** + * Saves the AccountAuthenticatorResponse in the instance state. + * @param outState where to store any instance data + */ + protected void onSaveInstanceState(Bundle outState) { + outState.putParcelable(Constants.ACCOUNT_AUTHENTICATOR_RESPONSE_KEY, + mAccountAuthenticatorResponse); + super.onSaveInstanceState(outState); + } + + /** + * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present. + */ + public void finish() { + if (mAccountAuthenticatorResponse != null) { + // send the result bundle back if set, otherwise send an error. + if (mResultBundle != null) { + mAccountAuthenticatorResponse.onResult(mResultBundle); + } else { + mAccountAuthenticatorResponse.onError(Constants.ERROR_CODE_CANCELED, "canceled"); + } + mAccountAuthenticatorResponse = null; + } + super.finish(); + } +} diff --git a/core/java/android/accounts/AccountAuthenticatorCache.java b/core/java/android/accounts/AccountAuthenticatorCache.java index 72b6bdebcb75b..6a14ff80e5358 100644 --- a/core/java/android/accounts/AccountAuthenticatorCache.java +++ b/core/java/android/accounts/AccountAuthenticatorCache.java @@ -16,18 +16,27 @@ 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.content.res.XmlResourceParser; +import android.content.res.TypedArray; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.util.Log; import android.util.AttributeSet; import android.util.Xml; -import java.util.*; import java.io.IOException; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; import com.google.android.collect.Maps; import org.xmlpull.v1.XmlPullParserException; @@ -59,6 +68,15 @@ public class AccountAuthenticatorCache { }; } + protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + getAllAuthenticators(); + Map authenticators = mAuthenticators; + fout.println("AccountAuthenticatorCache: " + authenticators.size() + " authenticators"); + for (AuthenticatorInfo info : authenticators.values()) { + fout.println(" " + info); + } + } + private void monitorPackageChanges() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); @@ -73,13 +91,15 @@ public class AccountAuthenticatorCache { */ 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(); + } + + public String toString() { + return "AuthenticatorInfo: " + mType + ", " + mComponentName; } } diff --git a/core/java/android/accounts/AccountAuthenticatorResponse.java b/core/java/android/accounts/AccountAuthenticatorResponse.java index 07cee400b6437..7198046cfa254 100644 --- a/core/java/android/accounts/AccountAuthenticatorResponse.java +++ b/core/java/android/accounts/AccountAuthenticatorResponse.java @@ -16,38 +16,38 @@ package android.accounts; +import android.os.Bundle; +import android.os.Parcelable; +import android.os.Parcel; import android.os.RemoteException; /** * Object that wraps calls to an {@link IAccountAuthenticatorResponse} object. * TODO: this interface is still in flux */ -public class AccountAuthenticatorResponse { +public class AccountAuthenticatorResponse implements Parcelable { private IAccountAuthenticatorResponse mAccountAuthenticatorResponse; public AccountAuthenticatorResponse(IAccountAuthenticatorResponse response) { mAccountAuthenticatorResponse = response; } - public void onFinished(int result) { + public AccountAuthenticatorResponse(Parcel parcel) { + mAccountAuthenticatorResponse = + IAccountAuthenticatorResponse.Stub.asInterface(parcel.readStrongBinder()); + } + + public void onResult(Bundle result) { try { - mAccountAuthenticatorResponse.onIntResult(result); + mAccountAuthenticatorResponse.onResult(result); } catch (RemoteException e) { // this should never happen } } - public void onFinished(String result) { + public void onRequestContinued() { try { - mAccountAuthenticatorResponse.onStringResult(result); - } catch (RemoteException e) { - // this should never happen - } - } - - public void onFinished(boolean result) { - try { - mAccountAuthenticatorResponse.onBooleanResult(result); + mAccountAuthenticatorResponse.onRequestContinued(); } catch (RemoteException e) { // this should never happen } @@ -61,7 +61,22 @@ public class AccountAuthenticatorResponse { } } - public IAccountAuthenticatorResponse getIAccountAuthenticatorResponse() { - return mAccountAuthenticatorResponse; + public int describeContents() { + return 0; } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongBinder(mAccountAuthenticatorResponse.asBinder()); + } + + public static final Creator CREATOR = + new Creator() { + public AccountAuthenticatorResponse createFromParcel(Parcel source) { + return new AccountAuthenticatorResponse(source); + } + + public AccountAuthenticatorResponse[] newArray(int size) { + return new AccountAuthenticatorResponse[size]; + } + }; } diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index d0d475025c225..c60f15da11a5c 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -16,27 +16,31 @@ 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 android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.concurrent.locks.Condition; +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.TimeUnit; /** * 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) + * AccountManager accountManager = AccountManager.get(context); * *

- * TODO: this interface is still in flux + * TODO(fredq) this interface is still in flux */ public class AccountManager { private static final String TAG = "AccountManager"; @@ -49,7 +53,12 @@ public class AccountManager { mService = service; } - public String getPassword(Account account) { + public static AccountManager get(Context context) { + return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); + } + + public String blockingGetPassword(Account account) { + ensureNotOnMainThread(); try { return mService.getPassword(account); } catch (RemoteException e) { @@ -58,7 +67,17 @@ public class AccountManager { } } - public String getUserData(Account account, String key) { + public Future1 getPassword(final Future1Callback callback, + final Account account, final Handler handler) { + return startAsFuture(callback, handler, new Callable() { + public String call() throws Exception { + return blockingGetPassword(account); + } + }); + } + + public String blockingGetUserData(Account account, String key) { + ensureNotOnMainThread(); try { return mService.getUserData(account, key); } catch (RemoteException e) { @@ -67,7 +86,36 @@ public class AccountManager { } } + public Future1 getUserData(Future1Callback callback, + final Account account, final String key, Handler handler) { + return startAsFuture(callback, handler, new Callable() { + public String call() throws Exception { + return blockingGetUserData(account, key); + } + }); + } + + public String[] blockingGetAuthenticatorTypes() { + ensureNotOnMainThread(); + try { + return mService.getAuthenticatorTypes(); + } catch (RemoteException e) { + // if this happens the entire runtime will restart + throw new RuntimeException(e); + } + } + + public Future1 getAuthenticatorTypes(Future1Callback callback, + Handler handler) { + return startAsFuture(callback, handler, new Callable() { + public String[] call() throws Exception { + return blockingGetAuthenticatorTypes(); + } + }); + } + public Account[] blockingGetAccounts() { + ensureNotOnMainThread(); try { return mService.getAccounts(); } catch (RemoteException e) { @@ -76,34 +124,8 @@ public class AccountManager { } } - 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) { + ensureNotOnMainThread(); try { return mService.getAccountsByType(accountType); } catch (RemoteException e) { @@ -112,7 +134,25 @@ public class AccountManager { } } - public boolean addAccount(Account account, String password, Bundle extras) { + public Future1 getAccounts(Future1Callback callback, Handler handler) { + return startAsFuture(callback, handler, new Callable() { + public Account[] call() throws Exception { + return blockingGetAccounts(); + } + }); + } + + public Future1 getAccountsByType(Future1Callback callback, + final String type, Handler handler) { + return startAsFuture(callback, handler, new Callable() { + public Account[] call() throws Exception { + return blockingGetAccountsByType(type); + } + }); + } + + public boolean blockingAddAccountExplicitly(Account account, String password, Bundle extras) { + ensureNotOnMainThread(); try { return mService.addAccount(account, password, extras); } catch (RemoteException e) { @@ -121,7 +161,18 @@ public class AccountManager { } } - public void removeAccount(Account account) { + public Future1 addAccountExplicitly(final Future1Callback callback, + final Account account, final String password, final Bundle extras, + final Handler handler) { + return startAsFuture(callback, handler, new Callable() { + public Boolean call() throws Exception { + return blockingAddAccountExplicitly(account, password, extras); + } + }); + } + + public void blockingRemoveAccount(Account account) { + ensureNotOnMainThread(); try { mService.removeAccount(account); } catch (RemoteException e) { @@ -129,7 +180,18 @@ public class AccountManager { } } - public void invalidateAuthToken(String accountType, String authToken) { + public Future1 removeAccount(Future1Callback callback, final Account account, + final Handler handler) { + return startAsFuture(callback, handler, new Callable() { + public Void call() throws Exception { + blockingRemoveAccount(account); + return null; + } + }); + } + + public void blockingInvalidateAuthToken(String accountType, String authToken) { + ensureNotOnMainThread(); try { mService.invalidateAuthToken(accountType, authToken); } catch (RemoteException e) { @@ -137,7 +199,18 @@ public class AccountManager { } } - public String peekAuthToken(Account account, String authTokenType) { + public Future1 invalidateAuthToken(Future1Callback callback, + final String accountType, final String authToken, final Handler handler) { + return startAsFuture(callback, handler, new Callable() { + public Void call() throws Exception { + blockingInvalidateAuthToken(accountType, authToken); + return null; + } + }); + } + + public String blockingPeekAuthToken(Account account, String authTokenType) { + ensureNotOnMainThread(); try { return mService.peekAuthToken(account, authTokenType); } catch (RemoteException e) { @@ -146,7 +219,17 @@ public class AccountManager { } } - public void setPassword(Account account, String password) { + public Future1 peekAuthToken(Future1Callback callback, + final Account account, final String authTokenType, final Handler handler) { + return startAsFuture(callback, handler, new Callable() { + public String call() throws Exception { + return blockingPeekAuthToken(account, authTokenType); + } + }); + } + + public void blockingSetPassword(Account account, String password) { + ensureNotOnMainThread(); try { mService.setPassword(account, password); } catch (RemoteException e) { @@ -154,7 +237,18 @@ public class AccountManager { } } - public void clearPassword(Account account) { + public Future1 setPassword(Future1Callback callback, + final Account account, final String password, final Handler handler) { + return startAsFuture(callback, handler, new Callable() { + public Void call() throws Exception { + blockingSetPassword(account, password); + return null; + } + }); + } + + public void blockingClearPassword(Account account) { + ensureNotOnMainThread(); try { mService.clearPassword(account); } catch (RemoteException e) { @@ -162,7 +256,18 @@ public class AccountManager { } } - public void setUserData(Account account, String key, String value) { + public Future1 clearPassword(final Future1Callback callback, final Account account, + final Handler handler) { + return startAsFuture(callback, handler, new Callable() { + public Void call() throws Exception { + blockingClearPassword(account); + return null; + } + }); + } + + public void blockingSetUserData(Account account, String key, String value) { + ensureNotOnMainThread(); try { mService.setUserData(account, key, value); } catch (RemoteException e) { @@ -170,17 +275,18 @@ public class AccountManager { } } - 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 Future1 setUserData(Future1Callback callback, + final Account account, final String key, final String value, final Handler handler) { + return startAsFuture(callback, handler, new Callable() { + public Void call() throws Exception { + blockingSetUserData(account, key, value); + return null; + } + }); } - public void setAuthToken(Account account, String authTokenType, String authToken) { + public void blockingSetAuthToken(Account account, String authTokenType, String authToken) { + ensureNotOnMainThread(); try { mService.setAuthToken(account, authTokenType, authToken); } catch (RemoteException e) { @@ -188,119 +294,435 @@ public class AccountManager { } } - 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 Future1 setAuthToken(Future1Callback callback, + final Account account, final String authTokenType, final String authToken, + final Handler handler) { + return startAsFuture(callback, handler, new Callable() { + public Void call() throws Exception { + blockingSetAuthToken(account, authTokenType, authToken); + return null; + } + }); + } + + public String blockingGetAuthToken(Account account, String authTokenType, + boolean notifyAuthFailure) + throws OperationCanceledException, IOException, AuthenticatorException { + ensureNotOnMainThread(); + Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */, + null /* handler */).getResult(); + return bundle.getString(Constants.AUTHTOKEN_KEY); + } + + /** + * Request the auth token for this account/authTokenType. If this succeeds then the + * auth token will then be passed to the activity. If this results in an authentication + * failure then a login intent will be returned that can be invoked to prompt the user to + * update their credentials. This login activity will return the auth token to the calling + * activity. If activity is null then the login intent will not be invoked. + * + * @param account the account whose auth token should be retrieved + * @param authTokenType the auth token type that should be retrieved + * @param loginOptions + * @param activity the activity to launch the login intent, if necessary, and to which + */ + public Future2 getAuthToken( + final Account account, final String authTokenType, final Bundle loginOptions, + final Activity activity, Future2Callback callback, Handler handler) { + if (activity == null) throw new IllegalArgumentException("activity is null"); + if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + return new AmsTask(activity, handler, callback) { + public void doWork() throws RemoteException { + mService.getAuthToken(mResponse, account, authTokenType, + false /* notifyOnAuthFailure */, true /* expectActivityLaunch */, + loginOptions); + } + }; + } + + public Future2 getAuthToken( + final Account account, final String authTokenType, final boolean notifyAuthFailure, + Future2Callback callback, Handler handler) { + if (account == null) throw new IllegalArgumentException("account is null"); + if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + return new AmsTask(null, handler, callback) { + public void doWork() throws RemoteException { + mService.getAuthToken(mResponse, account, authTokenType, + notifyAuthFailure, false /* expectActivityLaunch */, null /* options */); + } + }; + } + + public Future2 addAccount(final String accountType, + final String authTokenType, final Bundle addAccountOptions, + final Activity activity, Future2Callback callback, Handler handler) { + return new AmsTask(activity, handler, callback) { + public void doWork() throws RemoteException { + mService.addAcount(mResponse, accountType, authTokenType, + activity != null, addAccountOptions); + } + }; + } + + /** @deprecated use {@link #confirmCredentials} instead */ + public Future1 confirmPassword(final Account account, final String password, + Future1Callback callback, Handler handler) { + return new AMSTaskBoolean(handler, callback) { + public void doWork() throws RemoteException { + mService.confirmPassword(response, account, password); + } + }; + } + + public Future2 confirmCredentials(final Account account, final Activity activity, + final Future2Callback callback, + final Handler handler) { + return new AmsTask(activity, handler, callback) { + public void doWork() throws RemoteException { + mService.confirmCredentials(mResponse, account, activity != null); + } + }; + } + + public Future2 updateCredentials(final Account account, final String authTokenType, + final Bundle loginOptions, final Activity activity, + final Future2Callback callback, + final Handler handler) { + return new AmsTask(activity, handler, callback) { + public void doWork() throws RemoteException { + mService.updateCredentials(mResponse, account, authTokenType, activity != null, + loginOptions); + } + }; + } + + public Future2 editProperties(final String accountType, final Activity activity, + final Future2Callback callback, + final Handler handler) { + return new AmsTask(activity, handler, callback) { + public void doWork() throws RemoteException { + mService.editProperties(mResponse, accountType, activity != null); + } + }; + } + + private void ensureNotOnMainThread() { + final Looper looper = Looper.myLooper(); + if (looper != null && looper == mContext.getMainLooper()) { + // We really want to throw an exception here, but GTalkService exercises this + // path quite a bit and needs some serious rewrite in order to work properly. + //noinspection ThrowableInstanceNeverThrow +// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs", +// new Exception()); + // TODO(fredq) remove the log and throw this exception when the callers are fixed +// throw new IllegalStateException( +// "calling this from your main thread can lead to deadlock"); } } - 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; + private void postToHandler(Handler handler, final Future2Callback callback, + final Future2 future) { + if (handler == null) { + handler = new Handler(mContext.getMainLooper()); } - public void run() { - try { - IAccountManagerResponse response = new IAccountManagerResponse.Stub() { - public void onStringResult(String value) throws RemoteException { - } + final Handler innerHandler = handler; + innerHandler.post(new Runnable() { + public void run() { + callback.run(future); + } + }); + } - public void onIntResult(int value) throws RemoteException { - } + private void postToHandler(Handler handler, final Future1Callback callback, + final Future1 future) { + if (handler == null) { + handler = new Handler(mContext.getMainLooper()); + } + final Handler innerHandler = handler; + innerHandler.post(new Runnable() { + public void run() { + callback.run(future); + } + }); + } - public void onBooleanResult(boolean value) throws RemoteException { - mLock.lock(); - try { - if (!mFailure && !mSuccess) { - mSuccess = true; - mResult = value; - mCondition.signalAll(); - } - } finally { - mLock.unlock(); - } - } + private Future1 startAsFuture(Future1Callback callback, Handler handler, + Callable callable) { + final FutureTaskWithCallback task = + new FutureTaskWithCallback(callback, callable, handler); + new Thread(task).start(); + return task; + } - public void onError(int errorCode, String errorMessage) { - mLock.lock(); - try { - if (!mFailure && !mSuccess) { - mFailure = true; - mCondition.signalAll(); - } - } finally { - mLock.unlock(); - } - } - }; + private class FutureTaskWithCallback extends FutureTask implements Future1 { + final Future1Callback mCallback; + final Handler mHandler; - mService.authenticateAccount(response, mAccount, mPassword); - } catch (RemoteException e) { - // if this happens the entire runtime will restart + public FutureTaskWithCallback(Future1Callback callback, Callable callable, + Handler handler) { + super(callable); + mCallback = callback; + mHandler = handler; + } + + protected void done() { + if (mCallback != null) { + postToHandler(mHandler, mCallback, this); } } - } - 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 V internalGetResult(Long timeout, TimeUnit unit) throws OperationCanceledException { + try { + if (timeout == null) { + return get(); + } else { + return get(timeout, unit); + } + } catch (InterruptedException e) { + // we will cancel the task below + } catch (CancellationException e) { + // we will cancel the task below + } catch (TimeoutException e) { + // we will cancel the task below + } catch (ExecutionException e) { + // this should never happen + throw new IllegalStateException(e.getCause()); + } finally { + cancel(true /* interruptIfRunning */); + } + throw new OperationCanceledException(); + } + + public V getResult() throws OperationCanceledException { + return internalGetResult(null, null); + } + + public V getResult(long timeout, TimeUnit unit) throws OperationCanceledException { + return internalGetResult(null, null); } } - 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 abstract class AmsTask extends FutureTask implements Future2 { + final IAccountManagerResponse mResponse; + final Handler mHandler; + final Future2Callback mCallback; + final Activity mActivity; + public AmsTask(Activity activity, Handler handler, Future2Callback callback) { + super(new Callable() { + public Bundle call() throws Exception { + throw new IllegalStateException("this should never be called"); + } + }); + + mHandler = handler; + mCallback = callback; + mActivity = activity; + mResponse = new Response(); + + new Thread(new Runnable() { + public void run() { + try { + doWork(); + } catch (RemoteException e) { + // never happens + } + } + }).start(); } + + public abstract void doWork() throws RemoteException; + + private Bundle internalGetResult(Long timeout, TimeUnit unit) + throws OperationCanceledException, IOException, AuthenticatorException { + try { + if (timeout == null) { + return get(); + } else { + return get(timeout, unit); + } + } catch (CancellationException e) { + throw new OperationCanceledException(); + } catch (TimeoutException e) { + // fall through and cancel + } catch (InterruptedException e) { + // fall through and cancel + } catch (ExecutionException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IOException) { + throw (IOException) cause; + } else if (cause instanceof UnsupportedOperationException) { + throw new AuthenticatorException(cause); + } else if (cause instanceof AuthenticatorException) { + throw (AuthenticatorException) cause; + } else if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof Error) { + throw (Error) cause; + } else { + throw new IllegalStateException(cause); + } + } finally { + cancel(true /* interrupt if running */); + } + throw new OperationCanceledException(); + } + + public Bundle getResult() + throws OperationCanceledException, IOException, AuthenticatorException { + return internalGetResult(null, null); + } + + public Bundle getResult(long timeout, TimeUnit unit) + throws OperationCanceledException, IOException, AuthenticatorException { + return internalGetResult(timeout, unit); + } + + protected void done() { + if (mCallback != null) { + postToHandler(mHandler, mCallback, this); + } + } + + /** Handles the responses from the AccountManager */ + private class Response extends IAccountManagerResponse.Stub { + public void onResult(Bundle bundle) { + Intent intent = bundle.getParcelable("intent"); + if (intent != null && mActivity != null) { + // since the user provided an Activity we will silently start intents + // that we see + mActivity.startActivity(intent); + // leave the Future running to wait for the real response to this request + } else { + set(bundle); + } + } + + public void onError(int code, String message) { + if (code == Constants.ERROR_CODE_CANCELED) { + // the authenticator indicated that this request was canceled, do so now + cancel(true /* mayInterruptIfRunning */); + return; + } + setException(convertErrorToException(code, message)); + } + } + } - 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 abstract class AMSTaskBoolean extends FutureTask implements Future1 { + final IAccountManagerResponse response; + final Handler mHandler; + final Future1Callback mCallback; + public AMSTaskBoolean(Handler handler, Future1Callback callback) { + super(new Callable() { + public Boolean call() throws Exception { + throw new IllegalStateException("this should never be called"); + } + }); + + mHandler = handler; + mCallback = callback; + response = new Response(); + + new Thread(new Runnable() { + public void run() { + try { + doWork(); + } catch (RemoteException e) { + // never happens + } + } + }).start(); } + + public abstract void doWork() throws RemoteException; + + + protected void done() { + if (mCallback != null) { + postToHandler(mHandler, mCallback, this); + } + } + + private Boolean internalGetResult(Long timeout, TimeUnit unit) { + try { + if (timeout == null) { + return get(); + } else { + return get(timeout, unit); + } + } catch (InterruptedException e) { + // fall through and cancel + } catch (TimeoutException e) { + // fall through and cancel + } catch (CancellationException e) { + return false; + } catch (ExecutionException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IOException) { + return false; + } else if (cause instanceof UnsupportedOperationException) { + return false; + } else if (cause instanceof AuthenticatorException) { + return false; + } else if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof Error) { + throw (Error) cause; + } else { + throw new IllegalStateException(cause); + } + } finally { + cancel(true /* interrupt if running */); + } + return false; + } + + public Boolean getResult() throws OperationCanceledException { + return internalGetResult(null, null); + } + + public Boolean getResult(long timeout, TimeUnit unit) throws OperationCanceledException { + return internalGetResult(timeout, unit); + } + + private class Response extends IAccountManagerResponse.Stub { + public void onResult(Bundle bundle) { + try { + if (bundle.containsKey(Constants.BOOLEAN_RESULT_KEY)) { + set(bundle.getBoolean(Constants.BOOLEAN_RESULT_KEY)); + return; + } + } catch (ClassCastException e) { + // we will set the exception below + } + onError(Constants.ERROR_CODE_INVALID_RESPONSE, "no result in response"); + } + + public void onError(int code, String message) { + if (code == Constants.ERROR_CODE_CANCELED) { + cancel(true /* mayInterruptIfRunning */); + return; + } + setException(convertErrorToException(code, message)); + } + } + } - 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 + private Exception convertErrorToException(int code, String message) { + if (code == Constants.ERROR_CODE_NETWORK_ERROR) { + return new IOException(message); } - } - 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 + if (code == Constants.ERROR_CODE_UNSUPPORTED_OPERATION) { + return new UnsupportedOperationException(); } + + if (code == Constants.ERROR_CODE_INVALID_RESPONSE) { + return new AuthenticatorException("invalid response"); + } + + return new AuthenticatorException("unknown error code"); } } diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index 78d4535f6bbac..29074ccbfeb43 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -16,19 +16,37 @@ package android.accounts; -import android.os.*; -import android.content.*; -import android.database.sqlite.*; +import android.content.BroadcastReceiver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.database.Cursor; import android.database.DatabaseUtils; -import android.util.Log; -import android.text.TextUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemClock; import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; +import java.util.LinkedHashMap; -import com.google.android.collect.Maps; import com.android.internal.telephony.TelephonyIntents; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; /** * A system service that provides account, password, and authtoken management for all @@ -43,7 +61,7 @@ public class AccountManagerService extends IAccountManager.Stub { 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 static final int DATABASE_VERSION = 2; private final Context mContext; @@ -86,7 +104,10 @@ public class AccountManagerService extends IAccountManager.Stub { 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); + new Intent(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION); + + private final LinkedHashMap mSessions = new LinkedHashMap(); + private static final int NOTIFICATION_ID = 234; public class AuthTokenKey { public final Account mAccount; @@ -143,7 +164,7 @@ public class AccountManagerService extends IAccountManager.Stub { mSimWatcher = new SimWatcher(mContext); } - public String getPassword(Account account) throws RemoteException { + public String getPassword(Account account) { SQLiteDatabase db = mOpenHelper.getReadableDatabase(); Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD}, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", @@ -158,7 +179,7 @@ public class AccountManagerService extends IAccountManager.Stub { } } - public String getUserData(Account account, String key) throws RemoteException { + public String getUserData(Account account, String key) { SQLiteDatabase db = mOpenHelper.getReadableDatabase(); db.beginTransaction(); try { @@ -183,11 +204,23 @@ public class AccountManagerService extends IAccountManager.Stub { } } - public Account[] getAccounts() throws RemoteException { + public String[] getAuthenticatorTypes() { + Collection authenticatorCollection = + mAuthenticatorCache.getAllAuthenticators(); + String[] types = new String[authenticatorCollection.size()]; + int i = 0; + for (AccountAuthenticatorCache.AuthenticatorInfo authenticator : authenticatorCollection) { + types[i] = authenticator.mType; + i++; + } + return types; + } + + public Account[] getAccounts() { return getAccountsByType(null); } - public Account[] getAccountsByType(String accountType) throws RemoteException { + public Account[] getAccountsByType(String accountType) { SQLiteDatabase db = mOpenHelper.getReadableDatabase(); final String selection = accountType == null ? null : (ACCOUNTS_TYPE + "=?"); @@ -207,8 +240,7 @@ public class AccountManagerService extends IAccountManager.Stub { } } - public boolean addAccount(Account account, String password, Bundle extras) - throws RemoteException { + public boolean addAccount(Account account, String password, Bundle extras) { // fails if the account already exists SQLiteDatabase db = mOpenHelper.getWritableDatabase(); db.beginTransaction(); @@ -252,15 +284,26 @@ public class AccountManagerService extends IAccountManager.Stub { 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 removeAccount(Account account) { + synchronized (mAuthTokenCache) { + ArrayList keysToRemove = Lists.newArrayList(); + for (AuthTokenKey key : mAuthTokenCache.keySet()) { + if (key.mAccount.equals(account)) { + keysToRemove.add(key); + } + } + for (AuthTokenKey key : keysToRemove) { + mAuthTokenCache.remove(key); + } + + 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 { + public void invalidateAuthToken(String accountType, String authToken) { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); db.beginTransaction(); try { @@ -272,29 +315,31 @@ public class AccountManagerService extends IAccountManager.Stub { } 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); + synchronized (mAuthTokenCache) { + 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(); } - } finally { - cursor.close(); } } @@ -344,20 +389,21 @@ public class AccountManagerService extends IAccountManager.Stub { } } - public String peekAuthToken(Account account, String authTokenType) throws RemoteException { - AuthTokenKey key = new AuthTokenKey(account, authTokenType); - if (mAuthTokenCache.containsKey(key)) { - return mAuthTokenCache.get(key); + public String peekAuthToken(Account account, String authTokenType) { + synchronized (mAuthTokenCache) { + AuthTokenKey key = new AuthTokenKey(account, authTokenType); + if (mAuthTokenCache.containsKey(key)) { + return mAuthTokenCache.get(key); + } + return readAuthTokenFromDatabase(account, authTokenType); } - return readAuthTokenFromDatabase(account, authTokenType); } - public void setAuthToken(Account account, String authTokenType, String authToken) - throws RemoteException { + public void setAuthToken(Account account, String authTokenType, String authToken) { cacheAuthToken(account, authTokenType, authToken); } - public void setPassword(Account account, String password) throws RemoteException { + public void setPassword(Account account, String password) { ContentValues values = new ContentValues(); values.put(ACCOUNTS_PASSWORD, password); mOpenHelper.getWritableDatabase().update(TABLE_ACCOUNTS, values, @@ -366,11 +412,11 @@ public class AccountManagerService extends IAccountManager.Stub { mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT); } - public void clearPassword(Account account) throws RemoteException { + public void clearPassword(Account account) { setPassword(account, null); } - public void setUserData(Account account, String key, String value) throws RemoteException { + public void setUserData(Account account, String key, String value) { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); db.beginTransaction(); try { @@ -398,66 +444,154 @@ public class AccountManagerService extends IAccountManager.Stub { } } - 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); - + public void getAuthToken(IAccountManagerResponse response, final Account account, + final String authTokenType, final boolean notifyOnAuthFailure, + final boolean expectActivityLaunch, final Bundle loginOptions) { String authToken = getCachedAuthToken(account, authTokenType); if (authToken != null) { - session.onStringResult(authToken); + try { + Bundle result = new Bundle(); + result.putString(Constants.AUTHTOKEN_KEY, authToken); + result.putString(Constants.ACCOUNT_NAME_KEY, account.mName); + result.putString(Constants.ACCOUNT_TYPE_KEY, account.mType); + response.onResult(result); + } catch (RemoteException e) { + // if the caller is dead then there is no one to care about remote exceptions + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "failure while notifying response", e); + } + } return; } - session.bind(); + new Session(response, account.mType, expectActivityLaunch) { + protected String toDebugString(long now) { + if (loginOptions != null) loginOptions.keySet(); + return super.toDebugString(now) + ", getAuthToken" + + ", " + account + + ", authTokenType " + authTokenType + + ", loginOptions " + loginOptions + + ", notifyOnAuthFailure " + notifyOnAuthFailure; + } + + public void run() throws RemoteException { + mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions); + } + + public void onResult(Bundle result) { + if (result != null) { + String authToken = result.getString(Constants.AUTHTOKEN_KEY); + if (authToken != null) { + String name = result.getString(Constants.ACCOUNT_NAME_KEY); + String type = result.getString(Constants.ACCOUNT_TYPE_KEY); + if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) { + onError(Constants.ERROR_CODE_INVALID_RESPONSE, + "the type and name should not be empty"); + return; + } + cacheAuthToken(new Account(name, type), authTokenType, authToken); + } + + Intent intent = result.getParcelable(Constants.INTENT_KEY); + if (intent != null && notifyOnAuthFailure) { + doNotification(result.getString(Constants.AUTH_FAILED_MESSAGE_KEY), intent); + } + } + super.onResult(result); + } + }.bind(); } - public void addAccountInteractively(IAccountManagerResponse response, String accountType) - throws RemoteException { - new AddAccountInteractivelySession(response, accountType).bind(); + + public void addAcount(final IAccountManagerResponse response, + final String accountType, final String authTokenType, + final boolean expectActivityLaunch, final Bundle options) { + new Session(response, accountType, expectActivityLaunch) { + public void run() throws RemoteException { + mAuthenticator.addAccount(this, mAccountType, authTokenType, options); + } + + protected String toDebugString(long now) { + return super.toDebugString(now) + ", addAccount" + + ", accountType " + accountType; + } + }.bind(); } - public void authenticateAccount(IAccountManagerResponse response, Account account, - String password) - throws RemoteException { - new AuthenticateAccountSession(response, account, password).bind(); + public void confirmCredentials(IAccountManagerResponse response, + final Account account, final boolean expectActivityLaunch) { + new Session(response, account.mType, expectActivityLaunch) { + public void run() throws RemoteException { + mAuthenticator.confirmCredentials(this, account); + } + protected String toDebugString(long now) { + return super.toDebugString(now) + ", confirmCredentials" + + ", " + account; + } + }.bind(); } - public void updatePassword(IAccountManagerResponse response, Account account) - throws RemoteException { - new UpdatePasswordSession(response, account).bind(); + public void confirmPassword(IAccountManagerResponse response, final Account account, + final String password) { + new Session(response, account.mType, false /* expectActivityLaunch */) { + public void run() throws RemoteException { + mAuthenticator.confirmPassword(this, account, password); + } + protected String toDebugString(long now) { + return super.toDebugString(now) + ", confirmPassword" + + ", " + account; + } + }.bind(); } - public void editProperties(IAccountManagerResponse response, String accountType) - throws RemoteException { - new EditPropertiesSession(response, accountType).bind(); + public void updateCredentials(IAccountManagerResponse response, final Account account, + final String authTokenType, final boolean expectActivityLaunch, + final Bundle loginOptions) { + new Session(response, account.mType, expectActivityLaunch) { + public void run() throws RemoteException { + mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions); + } + protected String toDebugString(long now) { + if (loginOptions != null) loginOptions.keySet(); + return super.toDebugString(now) + ", updateCredentials" + + ", " + account + + ", authTokenType " + authTokenType + + ", loginOptions " + loginOptions; + } + }.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(); + public void editProperties(IAccountManagerResponse response, final String accountType, + final boolean expectActivityLaunch) { + new Session(response, accountType, expectActivityLaunch) { + public void run() throws RemoteException { + mAuthenticator.editProperties(this, mAccountType); + } + protected String toDebugString(long now) { + return super.toDebugString(now) + ", editProperties" + + ", accountType " + 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; + synchronized (mAuthTokenCache) { + 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); + synchronized (mAuthTokenCache) { + final AuthTokenKey key = new AuthTokenKey(account, authTokenType); + if (!mAuthTokenCache.containsKey(key)) return null; + return mAuthTokenCache.get(key); + } } private long getAccountId(SQLiteDatabase db, Account account) { @@ -502,34 +636,91 @@ public class AccountManagerService extends IAccountManager.Stub { } } - private class Session extends IAccountAuthenticatorResponse.Stub - implements AuthenticatorBindHelper.Callback { + private abstract class Session extends IAccountAuthenticatorResponse.Stub + implements AuthenticatorBindHelper.Callback, IBinder.DeathRecipient { IAccountManagerResponse mResponse; final String mAccountType; + final boolean mExpectActivityLaunch; + final long mCreationTime; + + private int mNumResults = 0; + private int mNumRequestContinued = 0; + private int mNumErrors = 0; + IAccountAuthenticator mAuthenticator = null; - public Session(IAccountManagerResponse response, String accountType) { + public Session(IAccountManagerResponse response, String accountType, + boolean expectActivityLaunch) { super(); + if (response == null) throw new IllegalArgumentException("response is null"); mResponse = response; mAccountType = accountType; + mExpectActivityLaunch = expectActivityLaunch; + mCreationTime = SystemClock.elapsedRealtime(); + synchronized (mSessions) { + mSessions.put(toString(), this); + } + try { + response.asBinder().linkToDeath(this, 0 /* flags */); + } catch (RemoteException e) { + mResponse = null; + binderDied(); + } } - IAccountManagerResponse close() { + IAccountManagerResponse getResponseAndClose() { if (mResponse == null) { // this session has already been closed return null; } - cancelTimeout(); - unbind(); IAccountManagerResponse response = mResponse; - mResponse = null; + close(); // this clears mResponse so we need to save the response before this call return response; } + private void close() { + synchronized (mSessions) { + if (mSessions.remove(toString()) == null) { + // the session was already closed, so bail out now + return; + } + } + if (mResponse != null) { + // stop listening for response deaths + mResponse.asBinder().unlinkToDeath(this, 0 /* flags */); + + // clear this so that we don't accidentally send any further results + mResponse = null; + } + cancelTimeout(); + unbind(); + } + + public void binderDied() { + mResponse = null; + close(); + } + + protected String toDebugString() { + return toDebugString(SystemClock.elapsedRealtime()); + } + + protected String toDebugString(long now) { + return "Session: expectLaunch " + mExpectActivityLaunch + + ", connected " + (mAuthenticator != null) + + ", stats (" + mNumResults + "/" + mNumRequestContinued + + "/" + mNumErrors + ")" + + ", lifetime " + ((now - mCreationTime) / 1000.0); + } + void bind() { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "initiating bind to authenticator type " + mAccountType); + } if (!mBindHelper.bind(mAccountType, this)) { - onError(6, "bind failure"); + Log.d(TAG, "bind attempt failed for " + toDebugString()); + onError(Constants.ERROR_CODE_REMOTE_EXCEPTION, "bind failure"); } } @@ -551,206 +742,87 @@ public class AccountManagerService extends IAccountManager.Stub { public void onConnected(IBinder service) { mAuthenticator = IAccountAuthenticator.Stub.asInterface(service); - // do the next step + try { + run(); + } catch (RemoteException e) { + onError(Constants.ERROR_CODE_REMOTE_EXCEPTION, + "remote exception"); + } } + public abstract void run() throws RemoteException; + public void onDisconnected() { - IAccountManagerResponse response = close(); + mAuthenticator = null; + IAccountManagerResponse response = getResponseAndClose(); if (response != null) { - onError(3, "disconnected"); + onError(Constants.ERROR_CODE_REMOTE_EXCEPTION, + "disconnected"); } } public void onTimedOut() { - IAccountManagerResponse response = close(); + IAccountManagerResponse response = getResponseAndClose(); if (response != null) { - onError(4, "timeout"); + onError(Constants.ERROR_CODE_REMOTE_EXCEPTION, + "timeout"); } } - public void onIntResult(int result) throws RemoteException { - IAccountManagerResponse response = close(); - if (response != null) { - response.onIntResult(result); + public void onResult(Bundle result) { + mNumResults++; + if (result != null && !TextUtils.isEmpty(result.getString(Constants.AUTHTOKEN_KEY))) { + cancelNotification(); } - } - - public void onBooleanResult(boolean result) throws RemoteException { - IAccountManagerResponse response = close(); - if (response != null) { - response.onBooleanResult(result); + IAccountManagerResponse response; + if (mExpectActivityLaunch && result != null + && result.containsKey(Constants.INTENT_KEY)) { + response = mResponse; + } else { + response = getResponseAndClose(); } - } - - 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); + if (result == null) { + response.onError(Constants.ERROR_CODE_INVALID_RESPONSE, + "null bundle returned"); + } else { + response.onResult(result); + } } catch (RemoteException e) { - // error while trying to notify user of an error + // if the caller is dead then there is no one to care about remote exceptions + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "failure while notifying response", e); + } } } } - } - 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 onRequestContinued() { + mNumRequestContinued++; } public void onError(int errorCode, String errorMessage) { - if (mNotifyOnAuthFailure && errorCode == 0 /* TODO: put the real value here */) { - // TODO: authentication failed, pop up the notification + mNumErrors++; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Session.onError: " + errorCode + ", " + errorMessage); } - 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"); + IAccountManagerResponse response = getResponseAndClose(); + if (response != null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Session.onError: responding"); + } + try { + response.onError(errorCode, errorMessage); + } catch (RemoteException e) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Session.onError: caught RemoteException while responding", e); + } + } + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Session.onError: already closed"); + } } } } @@ -807,18 +879,32 @@ public class AccountManagerService extends IAccountManager.Stub { db.execSQL("CREATE TABLE " + TABLE_META + " ( " + META_KEY + " TEXT PRIMARY KEY NOT NULL, " + META_VALUE + " TEXT)"); + + db.execSQL("" + + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS + + " BEGIN" + + " DELETE FROM " + TABLE_AUTHTOKENS + + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" + + " DELETE FROM " + TABLE_EXTRAS + + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" + + " END"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - Log.e(TAG, "GLS upgrade from version " + oldVersion + " to version " + - newVersion + " not supported"); + Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion); - 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); + if (oldVersion == 1) { + db.execSQL("" + + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS + + " BEGIN" + + " DELETE FROM " + TABLE_AUTHTOKENS + + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + " =OLD." + ACCOUNTS_ID + " ;" + + " DELETE FROM " + TABLE_EXTRAS + + " WHERE " + EXTRAS_ACCOUNTS_ID + " =OLD." + ACCOUNTS_ID + " ;" + + " END"); + oldVersion++; + } } @Override @@ -898,4 +984,36 @@ public class AccountManagerService extends IAccountManager.Stub { public IBinder onBind(Intent intent) { return asBinder(); } + + protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { + synchronized (mSessions) { + final long now = SystemClock.elapsedRealtime(); + fout.println("AccountManagerService: " + mSessions.size() + " sessions"); + for (Session session : mSessions.values()) { + fout.println(" " + session.toDebugString(now)); + } + } + + fout.println(); + + mAuthenticatorCache.dump(fd, fout, args); + } + + private void doNotification(CharSequence message, Intent intent) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "doNotification: " + message + " intent:" + intent); + } + + // TODO(fredq) add this back in when we fix permissions +// Notification n = new Notification(android.R.drawable.stat_sys_warning, null, 0 /* when */); +// n.setLatestEventInfo(mContext, mContext.getText(R.string.notification_title), message, +// PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)); +// ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) +// .notify(NOTIFICATION_ID, n); + } + + private void cancelNotification() { +// ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) +// .cancel(NOTIFICATION_ID); + } } diff --git a/core/java/android/accounts/AccountMonitor.java b/core/java/android/accounts/AccountMonitor.java index 4ad06d493b944..38032c59ba661 100644 --- a/core/java/android/accounts/AccountMonitor.java +++ b/core/java/android/accounts/AccountMonitor.java @@ -60,7 +60,7 @@ public class AccountMonitor extends BroadcastReceiver { // Register a broadcast receiver to monitor account changes IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(AccountsServiceConstants.LOGIN_ACCOUNTS_CHANGED_ACTION); + intentFilter.addAction(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION); intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); // To recover from disk-full. mContext.registerReceiver(this, intentFilter); @@ -77,15 +77,23 @@ public class AccountMonitor extends BroadcastReceiver { notifyListener(); } - private synchronized void notifyListener() { - 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; + private Future1Callback mGetAccountsCallback = new Future1Callback() { + public void run(Future1 future) { + try { + Account[] accounts = future.getResult(); + String[] accountNames = new String[accounts.length]; + for (int i = 0; i < accounts.length; i++) { + accountNames[i] = accounts[i].mName; + } + mListener.onAccountsUpdated(accountNames); + } catch (OperationCanceledException e) { + // the request was canceled + } } - mListener.onAccountsUpdated(accountNames); + }; + + private synchronized void notifyListener() { + AccountManager.get(mContext).getAccounts(mGetAccountsCallback, null /* handler */); } /** diff --git a/core/java/android/accounts/AccountsServiceConstants.java b/core/java/android/accounts/AccountsServiceConstants.java deleted file mode 100644 index b882e7b3ceb73..0000000000000 --- a/core/java/android/accounts/AccountsServiceConstants.java +++ /dev/null @@ -1,78 +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; - -import android.content.Intent; - -/** - * Miscellaneous constants used by the AccountsService and its - * clients. - */ -// TODO: These constants *could* come directly from the -// IAccountsService interface, but that's not possible since the -// aidl compiler doesn't let you define constants (yet.) -public class AccountsServiceConstants { - /** This class is never instantiated. */ - private AccountsServiceConstants() { - } - - /** - * Action sent as a broadcast Intent by the AccountsService - * when accounts are added to and/or removed from the device's - * database, or when the primary account is changed. - */ - public static final String LOGIN_ACCOUNTS_CHANGED_ACTION = - "android.accounts.LOGIN_ACCOUNTS_CHANGED"; - - /** - * Action sent as a broadcast Intent by the AccountsService - * when it starts up and no accounts are available (so some should be added). - */ - public static final String LOGIN_ACCOUNTS_MISSING_ACTION = - "android.accounts.LOGIN_ACCOUNTS_MISSING"; - - /** - * Action on the intent used to bind to the IAccountsService interface. This - * is used for services that have multiple interfaces (allowing - * them to differentiate the interface intended, and return the proper - * Binder.) - */ - private static final String ACCOUNTS_SERVICE_ACTION = "android.accounts.IAccountsService"; - - /* - * The intent uses a component in addition to the action to ensure the actual - * accounts service is bound to (a malicious third-party app could - * theoretically have a service with the same action). - */ - /** The intent used to bind to the accounts service. */ - public static final Intent SERVICE_INTENT = - new Intent() - .setClassName("com.google.android.googleapps", - "com.google.android.googleapps.GoogleLoginService") - .setAction(ACCOUNTS_SERVICE_ACTION); - - /** - * Checks whether the intent is to bind to the accounts service. - * - * @param bindIntent The Intent used to bind to the service. - * @return Whether the intent is to bind to the accounts service. - */ - public static final boolean isForAccountsService(Intent bindIntent) { - String otherAction = bindIntent.getAction(); - return otherAction != null && otherAction.equals(ACCOUNTS_SERVICE_ACTION); - } -} diff --git a/core/java/android/accounts/AuthenticatorBindHelper.java b/core/java/android/accounts/AuthenticatorBindHelper.java index ec418108d962f..6c28485c8ad99 100644 --- a/core/java/android/accounts/AuthenticatorBindHelper.java +++ b/core/java/android/accounts/AuthenticatorBindHelper.java @@ -16,19 +16,20 @@ package android.accounts; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; 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 android.util.Log; -import java.util.Map; import java.util.ArrayList; +import java.util.Map; -import com.google.android.collect.Maps; import com.google.android.collect.Lists; +import com.google.android.collect.Maps; /** * A helper object that simplifies binding to Account Authenticators. It uses the @@ -39,13 +40,14 @@ import com.google.android.collect.Lists; * 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 mServiceConnections = Maps.newHashMap(); - final private Map> mServiceUsers = Maps.newHashMap(); - final private AccountAuthenticatorCache mAuthenticatorCache; + private static final String TAG = "Accounts"; + private final Handler mHandler; + private final Context mContext; + private final int mMessageWhatConnected; + private final int mMessageWhatDisconnected; + private final Map mServiceConnections = Maps.newHashMap(); + private final Map> mServiceUsers = Maps.newHashMap(); + private final AccountAuthenticatorCache mAuthenticatorCache; public AuthenticatorBindHelper(Context context, AccountAuthenticatorCache authenticatorCache, Handler handler, @@ -66,15 +68,39 @@ public class AuthenticatorBindHelper { // if the authenticator is connecting or connected then return true synchronized (mServiceConnections) { if (mServiceConnections.containsKey(authenticatorType)) { + MyServiceConnection connection = mServiceConnections.get(authenticatorType); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "service connection already exists for " + authenticatorType); + } mServiceUsers.get(authenticatorType).add(callback); + if (connection.mService != null) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "the service is connected, scheduling a connected message for " + + authenticatorType); + } + connection.scheduleCallbackConnectedMessage(callback); + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "the service is *not* connected, waiting for for " + + authenticatorType); + } + } return true; } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "there is no service connection for " + authenticatorType); + } + // 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) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "there is no authenticator for " + authenticatorType + + ", bailing out"); + } return false; } @@ -83,7 +109,13 @@ public class AuthenticatorBindHelper { Intent intent = new Intent(); intent.setAction("android.accounts.AccountAuthenticator"); intent.setComponent(authenticatorInfo.mComponentName); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "performing bindService to " + authenticatorInfo.mComponentName); + } if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "bindService to " + authenticatorInfo.mComponentName + " failed"); + } return false; } @@ -94,24 +126,43 @@ public class AuthenticatorBindHelper { } public void unbind(Callback callbackToUnbind) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "unbinding callback " + callbackToUnbind); + } synchronized (mServiceConnections) { for (Map.Entry> entry : mServiceUsers.entrySet()) { final String authenticatorType = entry.getKey(); final ArrayList serviceUsers = entry.getValue(); for (Callback callback : serviceUsers) { if (callback == callbackToUnbind) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "found callback in service" + authenticatorType); + } serviceUsers.remove(callbackToUnbind); if (serviceUsers.isEmpty()) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "there are no more callbacks for service " + + authenticatorType + ", unbinding service"); + } unbindFromService(authenticatorType); + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "leaving service " + authenticatorType + + " around since there are still callbacks using it"); + } } return; } } } + Log.e(TAG, "did not find callback " + callbackToUnbind + " in any of the services"); } } private void unbindFromService(String authenticatorType) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "unbindService from " + authenticatorType); + } mContext.unbindService(mServiceConnections.get(authenticatorType)); mServiceUsers.remove(authenticatorType); mServiceConnections.remove(authenticatorType); @@ -127,28 +178,49 @@ public class AuthenticatorBindHelper { } private class MyServiceConnection implements ServiceConnection { - final private String mAuthenticatorType; + private final String mAuthenticatorType; + private IBinder mService = null; public MyServiceConnection(String authenticatorType) { mAuthenticatorType = authenticatorType; } public void onServiceConnected(ComponentName name, IBinder service) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "onServiceConnected for account type " + mAuthenticatorType); + } // post a message for each service user to tell them that the service is connected synchronized (mServiceConnections) { + mService = service; for (Callback callback : mServiceUsers.get(mAuthenticatorType)) { - final ConnectedMessagePayload payload = - new ConnectedMessagePayload(service, callback); - mHandler.obtainMessage(mMessageWhatConnected, payload).sendToTarget(); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "the service became connected, scheduling a connected " + + "message for " + mAuthenticatorType); + } + scheduleCallbackConnectedMessage(callback); } } } + private void scheduleCallbackConnectedMessage(Callback callback) { + final ConnectedMessagePayload payload = + new ConnectedMessagePayload(mService, callback); + mHandler.obtainMessage(mMessageWhatConnected, payload).sendToTarget(); + } + public void onServiceDisconnected(ComponentName name) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "onServiceDisconnected for account type " + mAuthenticatorType); + } // 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)) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "the service became disconnected, scheduling a " + + "disconnected message for " + + mAuthenticatorType); + } mHandler.obtainMessage(mMessageWhatDisconnected, callback).sendToTarget(); } unbindFromService(mAuthenticatorType); @@ -159,10 +231,16 @@ public class AuthenticatorBindHelper { boolean handleMessage(Message message) { if (message.what == mMessageWhatConnected) { ConnectedMessagePayload payload = (ConnectedMessagePayload)message.obj; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "notifying callback " + payload.mCallback + " that it is connected"); + } payload.mCallback.onConnected(payload.mService); return true; } else if (message.what == mMessageWhatDisconnected) { Callback callback = (Callback)message.obj; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "notifying callback " + callback + " that it is disconnected"); + } callback.onDisconnected(); return true; } else { diff --git a/core/java/android/accounts/AccountManagerResponse.java b/core/java/android/accounts/AuthenticatorException.java similarity index 60% rename from core/java/android/accounts/AccountManagerResponse.java rename to core/java/android/accounts/AuthenticatorException.java index b15fb131a0c06..40234945cfbe3 100644 --- a/core/java/android/accounts/AccountManagerResponse.java +++ b/core/java/android/accounts/AuthenticatorException.java @@ -13,20 +13,20 @@ * 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 class AuthenticatorException extends Exception { + public AuthenticatorException() { + super(); } - - public IAccountManagerResponse getIAccountManagerResponse() { - return mResponse; + public AuthenticatorException(String message) { + super(message); + } + public AuthenticatorException(String message, Throwable cause) { + super(message, cause); + } + public AuthenticatorException(Throwable cause) { + super(cause); } } diff --git a/core/java/android/accounts/Constants.java b/core/java/android/accounts/Constants.java new file mode 100644 index 0000000000000..d3b6aa0f0b5cc --- /dev/null +++ b/core/java/android/accounts/Constants.java @@ -0,0 +1,48 @@ +/* + * 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; + +public class Constants { + // this should never be instantiated + private Constants() {} + + public static final int ERROR_CODE_REMOTE_EXCEPTION = 1; + public static final int ERROR_CODE_NETWORK_ERROR = 3; + public static final int ERROR_CODE_CANCELED = 4; + public static final int ERROR_CODE_INVALID_RESPONSE = 5; + public static final int ERROR_CODE_UNSUPPORTED_OPERATION = 6; + + public static final String ACCOUNTS_KEY = "accounts"; + public static final String AUTHENTICATOR_TYPES_KEY = "authenticator_types"; + public static final String PASSWORD_KEY = "password"; + public static final String USERDATA_KEY = "userdata"; + public static final String AUTHTOKEN_KEY = "authtoken"; + public static final String ACCOUNT_NAME_KEY = "authAccount"; + public static final String ACCOUNT_TYPE_KEY = "accountType"; + public static final String ERROR_CODE_KEY = "errorCode"; + public static final String ERROR_MESSAGE_KEY = "errorMessage"; + public static final String INTENT_KEY = "intent"; + public static final String BOOLEAN_RESULT_KEY = "booleanResult"; + public static final String ACCOUNT_AUTHENTICATOR_RESPONSE_KEY = "accountAuthenticatorResponse"; + public static final String AUTH_FAILED_MESSAGE_KEY = "authFailedMessage"; + /** + * Action sent as a broadcast Intent by the AccountsService + * when accounts are added to and/or removed from the device's + * database, or when the primary account is changed. + */ + public static final String LOGIN_ACCOUNTS_CHANGED_ACTION = + "android.accounts.LOGIN_ACCOUNTS_CHANGED"; +} diff --git a/core/java/android/accounts/Future1.java b/core/java/android/accounts/Future1.java new file mode 100644 index 0000000000000..386cb6ec2ad06 --- /dev/null +++ b/core/java/android/accounts/Future1.java @@ -0,0 +1,46 @@ +/* + * 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 java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * An extension of {@link Future} that provides wrappers for {@link #get()} that handle the various + * exceptions that {@link #get()} may return and rethrows them as exceptions specific to + * {@link AccountManager}. + */ +public interface Future1 extends Future { + /** + * Wrapper for {@link Future#get()}. If the get() throws {@link InterruptedException} then the + * {@link Future1} is canceled and {@link OperationCanceledException} is thrown. + * @return the {@link android.os.Bundle} that is returned by get() + * @throws OperationCanceledException if get() throws the unchecked CancellationException + * or if the Future was interrupted. + */ + V getResult() throws OperationCanceledException; + + /** + * Wrapper for {@link Future#get()}. If the get() throws {@link InterruptedException} then the + * {@link Future1} is canceled and {@link OperationCanceledException} is thrown. + * @param timeout the maximum time to wait + * @param unit the time unit of the timeout argument + * @return the {@link android.os.Bundle} that is returned by {@link Future#get()} + * @throws OperationCanceledException if get() throws the unchecked + * {@link java.util.concurrent.CancellationException} or if the {@link Future1} was interrupted. + */ + V getResult(long timeout, TimeUnit unit) throws OperationCanceledException; +} \ No newline at end of file diff --git a/core/java/android/accounts/Future1Callback.java b/core/java/android/accounts/Future1Callback.java new file mode 100644 index 0000000000000..886671ba6b36b --- /dev/null +++ b/core/java/android/accounts/Future1Callback.java @@ -0,0 +1,20 @@ +/* + * 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; + +public interface Future1Callback { + void run(Future1 future); +} diff --git a/core/java/android/accounts/Future2.java b/core/java/android/accounts/Future2.java new file mode 100644 index 0000000000000..b2ea84f493060 --- /dev/null +++ b/core/java/android/accounts/Future2.java @@ -0,0 +1,57 @@ +/* + * 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.Bundle; + +import java.io.IOException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +/** + * An extension of {@link Future} that provides wrappers for {@link #get()} that handle the various + * exceptions that {@link #get()} may return and rethrows them as exceptions specific to + * {@link AccountManager}. + */ +public interface Future2 extends Future { + /** + * Wrapper for {@link Future#get()}. If the get() throws {@link InterruptedException} then the + * {@link Future2} is canceled and {@link OperationCanceledException} is thrown. + * @return the {@link android.os.Bundle} that is returned by {@link Future#get()} + * @throws OperationCanceledException if get() throws the unchecked + * {@link java.util.concurrent.CancellationException} or if the {@link Future2} was interrupted. + * @throws IOException if the request was unable to complete due to a network error + * @throws AuthenticatorException if there was an error communicating with the + * {@link AbstractAccountAuthenticator}. + */ + Bundle getResult() + throws OperationCanceledException, IOException, AuthenticatorException; + + /** + * Wrapper for {@link Future#get()}. If the get() throws {@link InterruptedException} then the + * {@link Future2} is canceled and {@link OperationCanceledException} is thrown. + * @param timeout the maximum time to wait + * @param unit the time unit of the timeout argument + * @return the {@link android.os.Bundle} that is returned by {@link Future#get()} + * @throws OperationCanceledException if get() throws the unchecked + * {@link java.util.concurrent.CancellationException} or if the {@link Future2} was interrupted. + * @throws IOException if the request was unable to complete due to a network error + * @throws AuthenticatorException if there was an error communicating with the + * {@link AbstractAccountAuthenticator}. + */ + Bundle getResult(long timeout, TimeUnit unit) + throws OperationCanceledException, IOException, AuthenticatorException; +} diff --git a/core/java/android/accounts/Future2Callback.java b/core/java/android/accounts/Future2Callback.java new file mode 100644 index 0000000000000..7ef0c94741d09 --- /dev/null +++ b/core/java/android/accounts/Future2Callback.java @@ -0,0 +1,20 @@ +/* + * 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; + +public interface Future2Callback { + void run(Future2 future); +} \ No newline at end of file diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl index b2c222343bc57..70c075208cb73 100644 --- a/core/java/android/accounts/IAccountAuthenticator.aidl +++ b/core/java/android/accounts/IAccountAuthenticator.aidl @@ -17,6 +17,8 @@ package android.accounts; import android.accounts.IAccountAuthenticatorResponse; +import android.accounts.Account; +import android.os.Bundle; /** * Service that allows the interaction with an authentication server. @@ -25,36 +27,32 @@ oneway interface IAccountAuthenticator { /** * prompts the user for account information and adds the result to the IAccountManager */ - void addAccount(in IAccountAuthenticatorResponse response, String accountType); + void addAccount(in IAccountAuthenticatorResponse response, String accountType, + String authTokenType, in Bundle options); + + /** + * Checks that the account/password combination is valid. + * @deprecated + */ + void confirmPassword(in IAccountAuthenticatorResponse response, + in Account account, String password); /** * prompts the user for the credentials of the account */ - void authenticateAccount(in IAccountAuthenticatorResponse response, String name, - String type, String password); + void confirmCredentials(in IAccountAuthenticatorResponse response, in Account account); /** * 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); + void getAuthToken(in IAccountAuthenticatorResponse response, in Account account, + String authTokenType, in Bundle options); /** * prompts the user for a new password and writes it to the IAccountManager */ - void updatePassword(in IAccountAuthenticatorResponse response, String name, String type); + void updateCredentials(in IAccountAuthenticatorResponse response, in Account account, + String authTokenType, in Bundle options); /** * launches an activity that lets the user edit and set the properties for an authenticator diff --git a/core/java/android/accounts/IAccountAuthenticatorResponse.aidl b/core/java/android/accounts/IAccountAuthenticatorResponse.aidl index 83504d3304f2f..a9ac2f1bb0802 100644 --- a/core/java/android/accounts/IAccountAuthenticatorResponse.aidl +++ b/core/java/android/accounts/IAccountAuthenticatorResponse.aidl @@ -15,14 +15,13 @@ */ package android.accounts; -import android.os.Parcelable; +import android.os.Bundle; /** * 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 onResult(in Bundle value); + void onRequestContinued(); void onError(int errorCode, String errorMessage); } diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index 8a0d1c45906af..365a92a05cbb5 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -26,6 +26,7 @@ import android.os.Bundle; interface IAccountManager { String getPassword(in Account account); String getUserData(in Account account, String key); + String[] getAuthenticatorTypes(); Account[] getAccounts(); Account[] getAccountsByType(String accountType); boolean addAccount(in Account account, String password, in Bundle extras); @@ -37,19 +38,21 @@ interface IAccountManager { 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, boolean expectActivityLaunch, + in Bundle options); + void addAcount(in IAccountManagerResponse response, String accountType, + String authTokenType, boolean expectActivityLaunch, in Bundle options); + void updateCredentials(in IAccountManagerResponse response, in Account account, + String authTokenType, boolean expectActivityLaunch, in Bundle options); + void editProperties(in IAccountManagerResponse response, String accountType, + boolean expectActivityLaunch); + void confirmCredentials(in IAccountManagerResponse response, in Account account, + boolean expectActivityLaunch); - 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, + /* + * @Deprecated + */ + void confirmPassword(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 index 7bbaa8b20f54f..52f21bc6b322d 100644 --- a/core/java/android/accounts/IAccountManagerResponse.aidl +++ b/core/java/android/accounts/IAccountManagerResponse.aidl @@ -15,13 +15,12 @@ */ package android.accounts; +import android.os.Bundle; /** * 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); + void onResult(in Bundle value); + void onError(int errorCode, String errorMessage); } diff --git a/core/java/android/accounts/NetworkErrorException.java b/core/java/android/accounts/NetworkErrorException.java new file mode 100644 index 0000000000000..f855cc802915b --- /dev/null +++ b/core/java/android/accounts/NetworkErrorException.java @@ -0,0 +1,31 @@ +/* + * 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; + +public class NetworkErrorException extends Exception { + public NetworkErrorException() { + super(); + } + public NetworkErrorException(String message) { + super(message); + } + public NetworkErrorException(String message, Throwable cause) { + super(message, cause); + } + public NetworkErrorException(Throwable cause) { + super(cause); + } +} \ No newline at end of file diff --git a/core/java/android/accounts/OperationCanceledException.java b/core/java/android/accounts/OperationCanceledException.java new file mode 100644 index 0000000000000..2f2c1646caf36 --- /dev/null +++ b/core/java/android/accounts/OperationCanceledException.java @@ -0,0 +1,31 @@ +/* + * 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; + +public class OperationCanceledException extends Exception { + public OperationCanceledException() { + super(); + } + public OperationCanceledException(String message) { + super(message); + } + public OperationCanceledException(String message, Throwable cause) { + super(message, cause); + } + public OperationCanceledException(Throwable cause) { + super(cause); + } +} diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 7eddda6ba40e4..1bb8d3e667c17 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -129,7 +129,7 @@ Voice/SMS service is blocked. All voice/SMS services are blocked. - + Voice @@ -206,6 +206,13 @@ Too many requests are being processed. Try again later. + + + Sign-in error + Sync @@ -276,7 +283,7 @@ Android System - + Services that cost you money @@ -1292,7 +1299,7 @@ Confirm - + Do you want the browser to remember this password? @@ -1320,8 +1327,8 @@ delete - - Search @@ -1383,7 +1390,7 @@ tomorrow in %d days - + 1 sec ago @@ -1511,7 +1518,7 @@ "%m/%d/%Y" - "%1$s, %2$s, %3$s \u2013 %4$s, %5$s, %6$s" @@ -1519,7 +1526,7 @@ Example: "Mon, Dec 31, 2007 - Tue, Jan 1, 2008" --> "%1$s, %2$s \u2013 %4$s, %5$s" - "%2$s, %3$s \u2013 %5$s, %6$s" @@ -2216,7 +2223,7 @@ Mount Don\'t mount - + There is a problem using your SD card for USB storage. USB connected @@ -2237,7 +2244,7 @@ Turn Off Cancel - + We've encountered a problem turning off USB storage. Check to make sure you have unmounted the USB host, then try again. @@ -2258,10 +2265,10 @@ Select Input Method - + \u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ \u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ - + candidates @@ -2308,23 +2315,23 @@ Go - + Search - + Send - + Next - + Done - + Execute - - @@ -2337,7 +2344,7 @@ - +