Merge branch 'readonly-p4-master'

This commit is contained in:
Fred Quintana
2009-04-08 19:16:09 -07:00
committed by The Android Open Source Project
24 changed files with 2999 additions and 1224 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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:
* <p>
* intent.putExtra(Constants.ACCOUNT_AUTHENTICATOR_RESPONSE_KEY, response);
* <p>
* The activity that it launches should extend the AccountAuthenticatorActivity. If this
* activity has a result that satisfies the original request it sets it via:
* <p>
* setAccountAuthenticatorResult(result)
* <p>
* 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();
}
}

View File

@@ -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<String, AuthenticatorInfo> 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;
}
}

View File

@@ -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<AccountAuthenticatorResponse> CREATOR =
new Creator<AccountAuthenticatorResponse>() {
public AccountAuthenticatorResponse createFromParcel(Parcel source) {
return new AccountAuthenticatorResponse(source);
}
public AccountAuthenticatorResponse[] newArray(int size) {
return new AccountAuthenticatorResponse[size];
}
};
}

View File

@@ -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);
*
* <p>
* 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<String> getPassword(final Future1Callback<String> callback,
final Account account, final Handler handler) {
return startAsFuture(callback, handler, new Callable<String>() {
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<String> getUserData(Future1Callback<String> callback,
final Account account, final String key, Handler handler) {
return startAsFuture(callback, handler, new Callable<String>() {
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<String[]> getAuthenticatorTypes(Future1Callback<String[]> callback,
Handler handler) {
return startAsFuture(callback, handler, new Callable<String[]>() {
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<Account[]> getAccounts(Future1Callback<Account[]> callback, Handler handler) {
return startAsFuture(callback, handler, new Callable<Account[]>() {
public Account[] call() throws Exception {
return blockingGetAccounts();
}
});
}
public Future1<Account[]> getAccountsByType(Future1Callback<Account[]> callback,
final String type, Handler handler) {
return startAsFuture(callback, handler, new Callable<Account[]>() {
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<Boolean> addAccountExplicitly(final Future1Callback<Boolean> callback,
final Account account, final String password, final Bundle extras,
final Handler handler) {
return startAsFuture(callback, handler, new Callable<Boolean>() {
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<Void> removeAccount(Future1Callback<Void> callback, final Account account,
final Handler handler) {
return startAsFuture(callback, handler, new Callable<Void>() {
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<Void> invalidateAuthToken(Future1Callback<Void> callback,
final String accountType, final String authToken, final Handler handler) {
return startAsFuture(callback, handler, new Callable<Void>() {
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<String> peekAuthToken(Future1Callback<String> callback,
final Account account, final String authTokenType, final Handler handler) {
return startAsFuture(callback, handler, new Callable<String>() {
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<Void> setPassword(Future1Callback<Void> callback,
final Account account, final String password, final Handler handler) {
return startAsFuture(callback, handler, new Callable<Void>() {
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<Void> clearPassword(final Future1Callback<Void> callback, final Account account,
final Handler handler) {
return startAsFuture(callback, handler, new Callable<Void>() {
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<Void> setUserData(Future1Callback<Void> callback,
final Account account, final String key, final String value, final Handler handler) {
return startAsFuture(callback, handler, new Callable<Void>() {
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<Void> setAuthToken(Future1Callback<Void> callback,
final Account account, final String authTokenType, final String authToken,
final Handler handler) {
return startAsFuture(callback, handler, new Callable<Void>() {
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<Boolean> confirmPassword(final Account account, final String password,
Future1Callback<Boolean> 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 <V> void postToHandler(Handler handler, final Future1Callback<V> callback,
final Future1<V> 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 <V> Future1<V> startAsFuture(Future1Callback<V> callback, Handler handler,
Callable<V> callable) {
final FutureTaskWithCallback<V> task =
new FutureTaskWithCallback<V>(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<V> extends FutureTask<V> implements Future1<V> {
final Future1Callback<V> mCallback;
final Handler mHandler;
mService.authenticateAccount(response, mAccount, mPassword);
} catch (RemoteException e) {
// if this happens the entire runtime will restart
public FutureTaskWithCallback(Future1Callback<V> callback, Callable<V> 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<Bundle> 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<Bundle>() {
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<Boolean> implements Future1<Boolean> {
final IAccountManagerResponse response;
final Handler mHandler;
final Future1Callback<Boolean> mCallback;
public AMSTaskBoolean(Handler handler, Future1Callback<Boolean> callback) {
super(new Callable<Boolean>() {
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");
}
}

View File

@@ -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<String, Session> mSessions = new LinkedHashMap<String, Session>();
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<AccountAuthenticatorCache.AuthenticatorInfo> 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<AuthTokenKey> 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);
}
}

View File

@@ -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<Account[]> mGetAccountsCallback = new Future1Callback<Account[]>() {
public void run(Future1<Account[]> 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 */);
}
/**

View File

@@ -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);
}
}

View File

@@ -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<String, MyServiceConnection> mServiceConnections = Maps.newHashMap();
final private Map<String, ArrayList<Callback>> 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<String, MyServiceConnection> mServiceConnections = Maps.newHashMap();
private final Map<String, ArrayList<Callback>> 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<String, ArrayList<Callback>> entry : mServiceUsers.entrySet()) {
final String authenticatorType = entry.getKey();
final ArrayList<Callback> 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 {

View File

@@ -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);
}
}

View File

@@ -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";
}

View File

@@ -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<V> extends Future<V> {
/**
* 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;
}

View File

@@ -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<V> {
void run(Future1<V> future);
}

View File

@@ -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<Bundle> {
/**
* 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;
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -129,7 +129,7 @@
<string name="RestrictedOnNormal">Voice/SMS service is blocked.</string>
<!-- Displayed to tell the user that all voice service is blocked by access control. -->
<string name="RestrictedOnAll">All voice/SMS services are blocked.</string>
<!-- Mappings between TS 27.007 +CFCC/+CLCK "service classes" and human-readable strings--> <skip />
<!-- Example: Service was enabled for: Voice, Data -->
<string name="serviceClassVoice">Voice</string>
@@ -206,6 +206,13 @@
<!-- Displayed when a request failed because there are too many requests right now. -->
<string name="httpErrorTooManyRequests">Too many requests are being processed. Try again later.</string>
<!-- Account notifications --> <skip />
<!-- A notification is shown when the AccountManager is unable to
supply an auth token without prompting the user to re-enter the
password. This is the text that will scroll through the
notification bar (will be seen by the user as he uses another application). -->
<string name="notification_title">Sign-in error</string>
<!-- Sync notifications --> <skip />
<!-- A notification is shown when there is a sync error. This is the text that will scroll through the notification bar (will be seen by the user as he uses another application). -->
<string name="contentServiceSync">Sync</string>
@@ -276,7 +283,7 @@
<!-- Label for the Android system components when they are shown to the user. -->
<string name="android_system_label">Android System</string>
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgrouplab_costMoney">Services that cost you money</string>
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
@@ -1292,7 +1299,7 @@
<!-- Title of the WebView save password dialog. If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. -->
<string name="save_password_label">Confirm</string>
<!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Text in the save password dialog, asking if the browser should remember a password. -->
<string name="save_password_message">Do you want the browser to remember this password?</string>
<!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Button in the save password dialog, saying not to remember this password. -->
@@ -1320,8 +1327,8 @@
<string name="menu_delete_shortcut_label">delete</string>
<!-- Strings used for search bar --><skip />
<!-- This is the default button label in the system-wide search UI.
<!-- This is the default button label in the system-wide search UI.
It is also used by the home screen's search "widget". It should be short -->
<string name="search_go">Search</string>
@@ -1383,7 +1390,7 @@
<item quantity="one">tomorrow</item>
<item quantity="other">in <xliff:g id="count">%d</xliff:g> days</item>
</plurals>
<!-- This is used to express that something occurred some number of abbreviated seconds in the past (e.g., 5 secs ago). -->
<plurals name="abbrev_num_seconds_ago">
<item quantity="one">1 sec ago</item>
@@ -1511,7 +1518,7 @@
<!-- Numeric form of the day. Example: "12/31/2007" -->
<string name="numeric_date">"<xliff:g id="month" example="12">%m</xliff:g>/<xliff:g id="day" example="31">%d</xliff:g>/<xliff:g id="year" example="2008">%Y</xliff:g>"</string>
<!-- Format indicating a range of time, from a time on one day to a time on another day.
<!-- Format indicating a range of time, from a time on one day to a time on another day.
Example: "Mon, Dec 31, 2007, 8am - Tue, Jan 1, 2008, 5pm" -->
<string name="wday1_date1_time1_wday2_date2_time2">"<xliff:g id="weekday1" example="Monday">%1$s</xliff:g>, <xliff:g id="date1" example="December 31, 2007">%2$s</xliff:g>, <xliff:g id="time1" example="8am">%3$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Tuesday">%4$s</xliff:g>, <xliff:g id="date2" example="January 1, 2008">%5$s</xliff:g>, <xliff:g id="time2" example="5pm">%6$s</xliff:g>"</string>
@@ -1519,7 +1526,7 @@
Example: "Mon, Dec 31, 2007 - Tue, Jan 1, 2008" -->
<string name="wday1_date1_wday2_date2">"<xliff:g id="weekday1" example="Monday">%1$s</xliff:g>, <xliff:g id="date1" example="Dec 31, 2007">%2$s</xliff:g> \u2013 <xliff:g id="weekday2" example="Thursday">%4$s</xliff:g>, <xliff:g id="date2" example="Jan 1, 2008">%5$s</xliff:g>"</string>
<!-- Format indicating a range of time, from a time on one day to a time on another day.
<!-- Format indicating a range of time, from a time on one day to a time on another day.
Example: "Dec 31, 2007, 8am - Jan 1, 2008, 5pm" -->
<string name="date1_time1_date2_time2">"<xliff:g id="date1" example="Dec 31, 2007">%2$s</xliff:g>, <xliff:g id="time1" example="8am">%3$s</xliff:g> \u2013 <xliff:g id="date2" example="Jan 1, 2008">%5$s</xliff:g>, <xliff:g id="time2" example="5pm">%6$s</xliff:g>"</string>
@@ -2216,7 +2223,7 @@
<string name="usb_storage_button_mount">Mount</string>
<!-- See USB_STORAGE. This is the button text to ignore the plugging in of the phone.. -->
<string name="usb_storage_button_unmount">Don\'t mount</string>
<!-- See USB_STORAGE_DIALOG. If there was an error mounting, this is the text. -->
<!-- See USB_STORAGE_DIALOG. If there was an error mounting, this is the text. -->
<string name="usb_storage_error_message">There is a problem using your SD card for USB storage.</string>
<!-- USB_STORAGE: When the user connects the phone to a computer via USB, we show a notification asking if he wants to share files across. This is the title -->
<string name="usb_storage_notification_title">USB connected</string>
@@ -2237,7 +2244,7 @@
<string name="usb_storage_stop_button_mount">Turn Off</string>
<!-- See USB_STORAGE_STOP. This is the button text to cancel stoping usb storage. -->
<string name="usb_storage_stop_button_unmount">Cancel</string>
<!-- See USB_STORAGE_STOP_DIALOG. If there was an error stopping, this is the text. -->
<!-- See USB_STORAGE_STOP_DIALOG. If there was an error stopping, this is the text. -->
<string name="usb_storage_stop_error_message">We've encountered a problem turning off USB storage. Check to make sure you have unmounted the USB host, then try again.</string>
<!-- External media format dialog strings -->
@@ -2258,10 +2265,10 @@
<!-- Title of the pop-up dialog in which the user switches input method components. -->
<string name="select_input_method">Select Input Method</string>
<string name="fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
<string name="fast_scroll_numeric_alphabet">\u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
<string name="candidates_style"><u>candidates</u></string>
<!-- External media notification strings -->
@@ -2308,23 +2315,23 @@
<!-- Long label for a button on a full-screen input method for the "Go" action. -->
<string name="ime_action_go">Go</string>
<!-- Long label for a button on a full-screen input method for the "Search" action. -->
<string name="ime_action_search">Search</string>
<!-- Long label for a button on a full-screen input method for the "Send" action. -->
<string name="ime_action_send">Send</string>
<!-- Long label for a button on a full-screen input method for the "Next" action. -->
<string name="ime_action_next">Next</string>
<!-- Long label for a button on a full-screen input method for the "Done" action. -->
<string name="ime_action_done">Done</string>
<!-- Long label for a button on a full-screen input method for an unknown action. -->
<string name="ime_action_default">Execute</string>
<!-- Strings for search suggestions. These are going here because they are referenced by both
<!-- Strings for search suggestions. These are going here because they are referenced by both
ContactsProvider and GoogleContactsProvider -->
<skip />
@@ -2337,7 +2344,7 @@
<!-- This string array should be overridden by the manufacture to present a list of carrier-id,locale pairs. This is used at startup to set a default locale by checking the system property ro.carrier for the carrier-id and searching through this array -->
<string-array translatable="false" name="carrier_locales">
</string-array>
</string-array>
</resources>