Merge change 339
* changes: adding concept of features to accounts
This commit is contained in:
1235
api/current.xml
1235
api/current.xml
File diff suppressed because it is too large
Load Diff
@@ -27,13 +27,13 @@ import android.os.RemoteException;
|
||||
public abstract class AbstractAccountAuthenticator {
|
||||
class Transport extends IAccountAuthenticator.Stub {
|
||||
public void addAccount(IAccountAuthenticatorResponse response, String accountType,
|
||||
String authTokenType, Bundle options)
|
||||
String authTokenType, String[] requiredFeatures, Bundle options)
|
||||
throws RemoteException {
|
||||
final Bundle result;
|
||||
try {
|
||||
result = AbstractAccountAuthenticator.this.addAccount(
|
||||
new AccountAuthenticatorResponse(response),
|
||||
accountType, authTokenType, options);
|
||||
accountType, authTokenType, requiredFeatures, options);
|
||||
} catch (NetworkErrorException e) {
|
||||
response.onError(Constants.ERROR_CODE_NETWORK_ERROR, e.getMessage());
|
||||
return;
|
||||
@@ -133,6 +133,25 @@ public abstract class AbstractAccountAuthenticator {
|
||||
response.onResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
public void hasFeatures(IAccountAuthenticatorResponse response,
|
||||
Account account, String[] features) throws RemoteException {
|
||||
final Bundle result;
|
||||
try {
|
||||
result = AbstractAccountAuthenticator.this.hasFeatures(
|
||||
new AccountAuthenticatorResponse(response), account, features);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
response.onError(Constants.ERROR_CODE_UNSUPPORTED_OPERATION,
|
||||
"hasFeatures not supported");
|
||||
return;
|
||||
} catch (NetworkErrorException e) {
|
||||
response.onError(Constants.ERROR_CODE_NETWORK_ERROR, e.getMessage());
|
||||
return;
|
||||
}
|
||||
if (result != null) {
|
||||
response.onResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Transport mTransport = new Transport();
|
||||
@@ -160,7 +179,8 @@ public abstract class AbstractAccountAuthenticator {
|
||||
public abstract Bundle editProperties(AccountAuthenticatorResponse response,
|
||||
String accountType);
|
||||
public abstract Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
|
||||
String authTokenType, Bundle options) throws NetworkErrorException;
|
||||
String authTokenType, String[] requiredFeatures, Bundle options)
|
||||
throws NetworkErrorException;
|
||||
/* @deprecated */
|
||||
public abstract boolean confirmPassword(AccountAuthenticatorResponse response,
|
||||
Account account, String password) throws NetworkErrorException;
|
||||
@@ -171,4 +191,6 @@ public abstract class AbstractAccountAuthenticator {
|
||||
throws NetworkErrorException;
|
||||
public abstract Bundle updateCredentials(AccountAuthenticatorResponse response,
|
||||
Account account, String authTokenType, Bundle loginOptions);
|
||||
public abstract Bundle hasFeatures(AccountAuthenticatorResponse response,
|
||||
Account account, String[] features) throws NetworkErrorException;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Callable;
|
||||
@@ -33,7 +34,7 @@ import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A class that helps with interactions with the {@link IAccountManager} interface. It provides
|
||||
* A class that helps with interactions with the AccountManagerService. 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:
|
||||
@@ -48,6 +49,9 @@ public class AccountManager {
|
||||
private final Context mContext;
|
||||
private final IAccountManager mService;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public AccountManager(Context context, IAccountManager service) {
|
||||
mContext = context;
|
||||
mService = service;
|
||||
@@ -337,7 +341,7 @@ public class AccountManager {
|
||||
false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
|
||||
loginOptions);
|
||||
}
|
||||
};
|
||||
}.start();
|
||||
}
|
||||
|
||||
public Future2 getAuthToken(
|
||||
@@ -350,18 +354,19 @@ public class AccountManager {
|
||||
mService.getAuthToken(mResponse, account, authTokenType,
|
||||
notifyAuthFailure, false /* expectActivityLaunch */, null /* options */);
|
||||
}
|
||||
};
|
||||
}.start();
|
||||
}
|
||||
|
||||
public Future2 addAccount(final String accountType,
|
||||
final String authTokenType, final Bundle addAccountOptions,
|
||||
final String authTokenType, final String[] requiredFeatures,
|
||||
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);
|
||||
requiredFeatures, activity != null, addAccountOptions);
|
||||
}
|
||||
};
|
||||
}.start();
|
||||
}
|
||||
|
||||
/** @deprecated use {@link #confirmCredentials} instead */
|
||||
@@ -374,6 +379,33 @@ public class AccountManager {
|
||||
};
|
||||
}
|
||||
|
||||
public Account[] blockingGetAccountsWithTypeAndFeatures(String type, String[] features)
|
||||
throws AuthenticatorException, IOException, OperationCanceledException {
|
||||
Future2 future = getAccountsWithTypeAndFeatures(type, features,
|
||||
null /* callback */, null /* handler */);
|
||||
Bundle result = future.getResult();
|
||||
Parcelable[] accountsTemp = result.getParcelableArray(Constants.ACCOUNTS_KEY);
|
||||
if (accountsTemp == null) {
|
||||
throw new AuthenticatorException("accounts should not be null");
|
||||
}
|
||||
Account[] accounts = new Account[accountsTemp.length];
|
||||
for (int i = 0; i < accountsTemp.length; i++) {
|
||||
accounts[i] = (Account) accountsTemp[i];
|
||||
}
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public Future2 getAccountsWithTypeAndFeatures(
|
||||
final String type, final String[] features,
|
||||
Future2Callback callback, Handler handler) {
|
||||
if (type == null) throw new IllegalArgumentException("type is null");
|
||||
return new AmsTask(null /* activity */, handler, callback) {
|
||||
public void doWork() throws RemoteException {
|
||||
mService.getAccountsByTypeAndFeatures(mResponse, type, features);
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
public Future2 confirmCredentials(final Account account, final Activity activity,
|
||||
final Future2Callback callback,
|
||||
final Handler handler) {
|
||||
@@ -381,7 +413,7 @@ public class AccountManager {
|
||||
public void doWork() throws RemoteException {
|
||||
mService.confirmCredentials(mResponse, account, activity != null);
|
||||
}
|
||||
};
|
||||
}.start();
|
||||
}
|
||||
|
||||
public Future2 updateCredentials(final Account account, final String authTokenType,
|
||||
@@ -393,7 +425,7 @@ public class AccountManager {
|
||||
mService.updateCredentials(mResponse, account, authTokenType, activity != null,
|
||||
loginOptions);
|
||||
}
|
||||
};
|
||||
}.start();
|
||||
}
|
||||
|
||||
public Future2 editProperties(final String accountType, final Activity activity,
|
||||
@@ -403,7 +435,7 @@ public class AccountManager {
|
||||
public void doWork() throws RemoteException {
|
||||
mService.editProperties(mResponse, accountType, activity != null);
|
||||
}
|
||||
};
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void ensureNotOnMainThread() {
|
||||
@@ -502,11 +534,12 @@ public class AccountManager {
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AmsTask extends FutureTask<Bundle> implements Future2 {
|
||||
private abstract class AmsTask extends FutureTask<Bundle> implements Future2 {
|
||||
final IAccountManagerResponse mResponse;
|
||||
final Handler mHandler;
|
||||
final Future2Callback mCallback;
|
||||
final Activity mActivity;
|
||||
final Thread mThread;
|
||||
public AmsTask(Activity activity, Handler handler, Future2Callback callback) {
|
||||
super(new Callable<Bundle>() {
|
||||
public Bundle call() throws Exception {
|
||||
@@ -518,8 +551,7 @@ public class AccountManager {
|
||||
mCallback = callback;
|
||||
mActivity = activity;
|
||||
mResponse = new Response();
|
||||
|
||||
new Thread(new Runnable() {
|
||||
mThread = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
doWork();
|
||||
@@ -527,7 +559,12 @@ public class AccountManager {
|
||||
// never happens
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}, "AmsTask");
|
||||
}
|
||||
|
||||
public final Future2 start() {
|
||||
mThread.start();
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract void doWork() throws RemoteException;
|
||||
@@ -609,7 +646,7 @@ public class AccountManager {
|
||||
|
||||
}
|
||||
|
||||
public abstract class AMSTaskBoolean extends FutureTask<Boolean> implements Future1<Boolean> {
|
||||
private abstract class AMSTaskBoolean extends FutureTask<Boolean> implements Future1<Boolean> {
|
||||
final IAccountManagerResponse response;
|
||||
final Handler mHandler;
|
||||
final Future1Callback<Boolean> mCallback;
|
||||
@@ -716,13 +753,159 @@ public class AccountManager {
|
||||
}
|
||||
|
||||
if (code == Constants.ERROR_CODE_UNSUPPORTED_OPERATION) {
|
||||
return new UnsupportedOperationException();
|
||||
return new UnsupportedOperationException(message);
|
||||
}
|
||||
|
||||
if (code == Constants.ERROR_CODE_INVALID_RESPONSE) {
|
||||
return new AuthenticatorException("invalid response");
|
||||
return new AuthenticatorException(message);
|
||||
}
|
||||
|
||||
return new AuthenticatorException("unknown error code");
|
||||
if (code == Constants.ERROR_CODE_BAD_ARGUMENTS) {
|
||||
return new IllegalArgumentException(message);
|
||||
}
|
||||
|
||||
return new AuthenticatorException(message);
|
||||
}
|
||||
|
||||
private class GetAuthTokenByTypeAndFeaturesTask extends AmsTask implements Future2Callback {
|
||||
GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType,
|
||||
final String[] features, Activity activityForPrompting,
|
||||
final Bundle addAccountOptions, final Bundle loginOptions,
|
||||
Future2Callback callback, Handler handler) {
|
||||
super(activityForPrompting, handler, callback);
|
||||
if (accountType == null) throw new IllegalArgumentException("account type is null");
|
||||
mAccountType = accountType;
|
||||
mAuthTokenType = authTokenType;
|
||||
mFeatures = features;
|
||||
mAddAccountOptions = addAccountOptions;
|
||||
mLoginOptions = loginOptions;
|
||||
mMyCallback = this;
|
||||
}
|
||||
volatile Future2 mFuture = null;
|
||||
final String mAccountType;
|
||||
final String mAuthTokenType;
|
||||
final String[] mFeatures;
|
||||
final Bundle mAddAccountOptions;
|
||||
final Bundle mLoginOptions;
|
||||
final Future2Callback mMyCallback;
|
||||
|
||||
public void doWork() throws RemoteException {
|
||||
getAccountsWithTypeAndFeatures(mAccountType, mFeatures, new Future2Callback() {
|
||||
public void run(Future2 future) {
|
||||
Bundle getAccountsResult;
|
||||
try {
|
||||
getAccountsResult = future.getResult();
|
||||
} catch (OperationCanceledException e) {
|
||||
setException(e);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
setException(e);
|
||||
return;
|
||||
} catch (AuthenticatorException e) {
|
||||
setException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
Parcelable[] accounts =
|
||||
getAccountsResult.getParcelableArray(Constants.ACCOUNTS_KEY);
|
||||
if (accounts.length == 0) {
|
||||
if (mActivity != null) {
|
||||
// no accounts, add one now. pretend that the user directly
|
||||
// made this request
|
||||
mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures,
|
||||
mAddAccountOptions, mActivity, mMyCallback, mHandler);
|
||||
} else {
|
||||
// send result since we can't prompt to add an account
|
||||
Bundle result = new Bundle();
|
||||
result.putString(Constants.ACCOUNT_NAME_KEY, null);
|
||||
result.putString(Constants.ACCOUNT_TYPE_KEY, null);
|
||||
result.putString(Constants.AUTHTOKEN_KEY, null);
|
||||
try {
|
||||
mResponse.onResult(result);
|
||||
} catch (RemoteException e) {
|
||||
// this will never happen
|
||||
}
|
||||
// we are done
|
||||
}
|
||||
} else if (accounts.length == 1) {
|
||||
// have a single account, return an authtoken for it
|
||||
if (mActivity == null) {
|
||||
mFuture = getAuthToken((Account) accounts[0], mAuthTokenType,
|
||||
false /* notifyAuthFailure */, mMyCallback, mHandler);
|
||||
} else {
|
||||
mFuture = getAuthToken((Account) accounts[0],
|
||||
mAuthTokenType, mLoginOptions,
|
||||
mActivity, mMyCallback, mHandler);
|
||||
}
|
||||
} else {
|
||||
if (mActivity != null) {
|
||||
IAccountManagerResponse chooseResponse =
|
||||
new IAccountManagerResponse.Stub() {
|
||||
public void onResult(Bundle value) throws RemoteException {
|
||||
Account account = new Account(
|
||||
value.getString(Constants.ACCOUNT_NAME_KEY),
|
||||
value.getString(Constants.ACCOUNT_TYPE_KEY));
|
||||
mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions,
|
||||
mActivity, mMyCallback, mHandler);
|
||||
}
|
||||
|
||||
public void onError(int errorCode, String errorMessage)
|
||||
throws RemoteException {
|
||||
mResponse.onError(errorCode, errorMessage);
|
||||
}
|
||||
};
|
||||
// have many accounts, launch the chooser
|
||||
Intent intent = new Intent();
|
||||
intent.setClassName("android",
|
||||
"android.accounts.ChooseAccountActivity");
|
||||
intent.putExtra(Constants.ACCOUNTS_KEY, accounts);
|
||||
intent.putExtra(Constants.ACCOUNT_MANAGER_RESPONSE_KEY,
|
||||
new AccountManagerResponse(chooseResponse));
|
||||
mActivity.startActivity(intent);
|
||||
// the result will arrive via the IAccountManagerResponse
|
||||
} else {
|
||||
// send result since we can't prompt to select an account
|
||||
Bundle result = new Bundle();
|
||||
result.putString(Constants.ACCOUNTS_KEY, null);
|
||||
try {
|
||||
mResponse.onResult(result);
|
||||
} catch (RemoteException e) {
|
||||
// this will never happen
|
||||
}
|
||||
// we are done
|
||||
}
|
||||
}
|
||||
}}, mHandler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO(fredq) pass through the calls to our implemention of Future2 to the underlying
|
||||
// future that we create. We need to do things like have cancel cancel the mFuture, if set
|
||||
// or to cause this to be canceled if mFuture isn't set.
|
||||
// Once this is done then getAuthTokenByFeatures can be changed to return a Future2.
|
||||
|
||||
public void run(Future2 future) {
|
||||
try {
|
||||
set(future.get());
|
||||
} catch (InterruptedException e) {
|
||||
cancel(true);
|
||||
} catch (CancellationException e) {
|
||||
cancel(true);
|
||||
} catch (ExecutionException e) {
|
||||
setException(e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void getAuthTokenByFeatures(
|
||||
final String accountType, final String authTokenType, final String[] features,
|
||||
final Activity activityForPrompting, final Bundle addAccountOptions,
|
||||
final Bundle loginOptions,
|
||||
final Future2Callback callback, final Handler handler) {
|
||||
if (accountType == null) throw new IllegalArgumentException("account type is null");
|
||||
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
|
||||
new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features,
|
||||
activityForPrompting, addAccountOptions, loginOptions, callback, handler).start();
|
||||
}
|
||||
}
|
||||
|
||||
74
core/java/android/accounts/AccountManagerResponse.java
Normal file
74
core/java/android/accounts/AccountManagerResponse.java
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
|
||||
/**
|
||||
* Object that wraps calls to an {@link android.accounts.IAccountManagerResponse} object.
|
||||
* @hide
|
||||
*/
|
||||
public class AccountManagerResponse implements Parcelable {
|
||||
private IAccountManagerResponse mResponse;
|
||||
|
||||
public AccountManagerResponse(IAccountManagerResponse response) {
|
||||
mResponse = response;
|
||||
}
|
||||
|
||||
public AccountManagerResponse(Parcel parcel) {
|
||||
mResponse =
|
||||
IAccountManagerResponse.Stub.asInterface(parcel.readStrongBinder());
|
||||
}
|
||||
|
||||
public void onResult(Bundle result) {
|
||||
try {
|
||||
mResponse.onResult(result);
|
||||
} catch (RemoteException e) {
|
||||
// this should never happen
|
||||
}
|
||||
}
|
||||
|
||||
public void onError(int errorCode, String errorMessage) {
|
||||
try {
|
||||
mResponse.onError(errorCode, errorMessage);
|
||||
} catch (RemoteException e) {
|
||||
// this should never happen
|
||||
}
|
||||
}
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeStrongBinder(mResponse.asBinder());
|
||||
}
|
||||
|
||||
public static final Creator<AccountManagerResponse> CREATOR =
|
||||
new Creator<AccountManagerResponse>() {
|
||||
public AccountManagerResponse createFromParcel(Parcel source) {
|
||||
return new AccountManagerResponse(source);
|
||||
}
|
||||
|
||||
public AccountManagerResponse[] newArray(int size) {
|
||||
return new AccountManagerResponse[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -59,6 +59,7 @@ import com.google.android.collect.Maps;
|
||||
* instead one uses an instance of {@link AccountManager}, which can be accessed as follows:
|
||||
* AccountManager accountManager =
|
||||
* (AccountManager)context.getSystemService(Context.ACCOUNT_SERVICE)
|
||||
* @hide
|
||||
*/
|
||||
public class AccountManagerService extends IAccountManager.Stub {
|
||||
private static final String TAG = "AccountManagerService";
|
||||
@@ -79,7 +80,6 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
|
||||
private final AccountAuthenticatorCache mAuthenticatorCache;
|
||||
private final AuthenticatorBindHelper mBindHelper;
|
||||
public final HashMap<AuthTokenKey, String> mAuthTokenCache = Maps.newHashMap();
|
||||
private final DatabaseHelper mOpenHelper;
|
||||
private final SimWatcher mSimWatcher;
|
||||
|
||||
@@ -300,7 +300,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
|
||||
sendAccountsChangedBroadcast();
|
||||
return true;
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
@@ -321,22 +321,10 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
public void removeAccount(Account account) {
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
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);
|
||||
}
|
||||
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||
db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
|
||||
new String[]{account.mName, account.mType});
|
||||
sendAccountsChangedBroadcast();
|
||||
} finally {
|
||||
restoreCallingIdentity(identityToken);
|
||||
}
|
||||
@@ -359,31 +347,26 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
|
||||
private void invalidateAuthToken(SQLiteDatabase db, String accountType, String authToken) {
|
||||
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();
|
||||
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);
|
||||
db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,8 +374,18 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||
db.beginTransaction();
|
||||
try {
|
||||
if (saveAuthTokenToDatabase(db, account, type, authToken)) {
|
||||
mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
|
||||
long accountId = getAccountId(db, account);
|
||||
if (accountId < 0) {
|
||||
return false;
|
||||
}
|
||||
db.delete(TABLE_AUTHTOKENS,
|
||||
AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
|
||||
new String[]{type});
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
|
||||
values.put(AUTHTOKENS_TYPE, type);
|
||||
values.put(AUTHTOKENS_AUTHTOKEN, authToken);
|
||||
if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) {
|
||||
db.setTransactionSuccessful();
|
||||
return true;
|
||||
}
|
||||
@@ -402,22 +395,6 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean saveAuthTokenToDatabase(SQLiteDatabase db, Account account,
|
||||
String type, String authToken) {
|
||||
long accountId = getAccountId(db, account);
|
||||
if (accountId < 0) {
|
||||
return false;
|
||||
}
|
||||
db.delete(TABLE_AUTHTOKENS,
|
||||
AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
|
||||
new String[]{type});
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
|
||||
values.put(AUTHTOKENS_TYPE, type);
|
||||
values.put(AUTHTOKENS_AUTHTOKEN, authToken);
|
||||
return db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0;
|
||||
}
|
||||
|
||||
public String readAuthTokenFromDatabase(Account account, String authTokenType) {
|
||||
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
|
||||
db.beginTransaction();
|
||||
@@ -436,13 +413,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
public String peekAuthToken(Account account, String authTokenType) {
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
synchronized (mAuthTokenCache) {
|
||||
AuthTokenKey key = new AuthTokenKey(account, authTokenType);
|
||||
if (mAuthTokenCache.containsKey(key)) {
|
||||
return mAuthTokenCache.get(key);
|
||||
}
|
||||
return readAuthTokenFromDatabase(account, authTokenType);
|
||||
}
|
||||
return readAuthTokenFromDatabase(account, authTokenType);
|
||||
} finally {
|
||||
restoreCallingIdentity(identityToken);
|
||||
}
|
||||
@@ -465,12 +436,16 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
mOpenHelper.getWritableDatabase().update(TABLE_ACCOUNTS, values,
|
||||
ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
|
||||
new String[]{account.mName, account.mType});
|
||||
mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
|
||||
sendAccountsChangedBroadcast();
|
||||
} finally {
|
||||
restoreCallingIdentity(identityToken);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendAccountsChangedBroadcast() {
|
||||
mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
|
||||
}
|
||||
|
||||
public void clearPassword(Account account) {
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
@@ -518,7 +493,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
final boolean expectActivityLaunch, final Bundle loginOptions) {
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
String authToken = getCachedAuthToken(account, authTokenType);
|
||||
String authToken = readAuthTokenFromDatabase(account, authTokenType);
|
||||
if (authToken != null) {
|
||||
try {
|
||||
Bundle result = new Bundle();
|
||||
@@ -578,19 +553,24 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
|
||||
|
||||
public void addAcount(final IAccountManagerResponse response,
|
||||
final String accountType, final String authTokenType,
|
||||
public void addAcount(final IAccountManagerResponse response, final String accountType,
|
||||
final String authTokenType, final String[] requiredFeatures,
|
||||
final boolean expectActivityLaunch, final Bundle options) {
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
new Session(response, accountType, expectActivityLaunch) {
|
||||
public void run() throws RemoteException {
|
||||
mAuthenticator.addAccount(this, mAccountType, authTokenType, options);
|
||||
mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
|
||||
options);
|
||||
}
|
||||
|
||||
protected String toDebugString(long now) {
|
||||
return super.toDebugString(now) + ", addAccount"
|
||||
+ ", accountType " + accountType;
|
||||
+ ", accountType " + accountType
|
||||
+ ", requiredFeatures "
|
||||
+ (requiredFeatures != null
|
||||
? TextUtils.join(",", requiredFeatures)
|
||||
: null);
|
||||
}
|
||||
}.bind();
|
||||
} finally {
|
||||
@@ -674,24 +654,101 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean cacheAuthToken(Account account, String authTokenType, String authToken) {
|
||||
synchronized (mAuthTokenCache) {
|
||||
if (saveAuthTokenToDatabase(account, authTokenType, authToken)) {
|
||||
final AuthTokenKey key = new AuthTokenKey(account, authTokenType);
|
||||
mAuthTokenCache.put(key, authToken);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
private class GetAccountsByTypeAndFeatureSession extends Session {
|
||||
private final String[] mFeatures;
|
||||
private volatile Account[] mAccountsOfType = null;
|
||||
private volatile ArrayList<Account> mAccountsWithFeatures = null;
|
||||
private volatile int mCurrentAccount = 0;
|
||||
|
||||
public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response,
|
||||
String type, String[] features) {
|
||||
super(response, type, false /* expectActivityLaunch */);
|
||||
mFeatures = features;
|
||||
}
|
||||
|
||||
public void run() throws RemoteException {
|
||||
mAccountsOfType = getAccountsByType(mAccountType);
|
||||
// check whether each account matches the requested features
|
||||
mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
|
||||
mCurrentAccount = 0;
|
||||
|
||||
checkAccount();
|
||||
}
|
||||
|
||||
public void checkAccount() {
|
||||
if (mCurrentAccount >= mAccountsOfType.length) {
|
||||
sendResult();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures);
|
||||
} catch (RemoteException e) {
|
||||
onError(Constants.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
|
||||
}
|
||||
}
|
||||
|
||||
public void onResult(Bundle result) {
|
||||
mNumResults++;
|
||||
if (result == null) {
|
||||
onError(Constants.ERROR_CODE_INVALID_RESPONSE, "null bundle");
|
||||
return;
|
||||
}
|
||||
if (result.getBoolean(Constants.BOOLEAN_RESULT_KEY, false)) {
|
||||
mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]);
|
||||
}
|
||||
mCurrentAccount++;
|
||||
checkAccount();
|
||||
}
|
||||
|
||||
public void sendResult() {
|
||||
IAccountManagerResponse response = getResponseAndClose();
|
||||
if (response != null) {
|
||||
try {
|
||||
Account[] accounts = new Account[mAccountsWithFeatures.size()];
|
||||
for (int i = 0; i < accounts.length; i++) {
|
||||
accounts[i] = mAccountsWithFeatures.get(i);
|
||||
}
|
||||
Bundle result = new Bundle();
|
||||
result.putParcelableArray(Constants.ACCOUNTS_KEY, accounts);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected String toDebugString(long now) {
|
||||
return super.toDebugString(now) + ", getAccountsByTypeAndFeatures"
|
||||
+ ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
|
||||
}
|
||||
}
|
||||
public void getAccountsByTypeAndFeatures(IAccountManagerResponse response,
|
||||
String type, String[] features) {
|
||||
if (type == null) {
|
||||
if (response != null) {
|
||||
try {
|
||||
response.onError(Constants.ERROR_CODE_BAD_ARGUMENTS, "type is null");
|
||||
} catch (RemoteException e) {
|
||||
// ignore this
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
new GetAccountsByTypeAndFeatureSession(response, type, features).bind();
|
||||
} finally {
|
||||
restoreCallingIdentity(identityToken);
|
||||
}
|
||||
}
|
||||
|
||||
private String getCachedAuthToken(Account account, String authTokenType) {
|
||||
synchronized (mAuthTokenCache) {
|
||||
final AuthTokenKey key = new AuthTokenKey(account, authTokenType);
|
||||
if (!mAuthTokenCache.containsKey(key)) return null;
|
||||
return mAuthTokenCache.get(key);
|
||||
}
|
||||
private boolean cacheAuthToken(Account account, String authTokenType, String authToken) {
|
||||
return saveAuthTokenToDatabase(account, authTokenType, authToken);
|
||||
}
|
||||
|
||||
private long getAccountId(SQLiteDatabase db, Account account) {
|
||||
@@ -743,7 +800,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
final boolean mExpectActivityLaunch;
|
||||
final long mCreationTime;
|
||||
|
||||
private int mNumResults = 0;
|
||||
public int mNumResults = 0;
|
||||
private int mNumRequestContinued = 0;
|
||||
private int mNumErrors = 0;
|
||||
|
||||
@@ -754,6 +811,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
boolean expectActivityLaunch) {
|
||||
super();
|
||||
if (response == null) throw new IllegalArgumentException("response is null");
|
||||
if (accountType == null) throw new IllegalArgumentException("accountType is null");
|
||||
mResponse = response;
|
||||
mAccountType = accountType;
|
||||
mExpectActivityLaunch = expectActivityLaunch;
|
||||
@@ -1071,7 +1129,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
try {
|
||||
db.execSQL("DELETE from " + TABLE_AUTHTOKENS);
|
||||
db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_PASSWORD + " = ''");
|
||||
mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
|
||||
sendAccountsChangedBroadcast();
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
||||
78
core/java/android/accounts/ChooseAccountActivity.java
Normal file
78
core/java/android/accounts/ChooseAccountActivity.java
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.ListActivity;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.view.View;
|
||||
import android.util.Log;
|
||||
|
||||
public class ChooseAccountActivity extends ListActivity {
|
||||
private static final String TAG = "AccountManager";
|
||||
private Parcelable[] mAccounts = null;
|
||||
private AccountManagerResponse mAccountManagerResponse = null;
|
||||
private Bundle mResult;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
mAccounts = getIntent().getParcelableArrayExtra(Constants.ACCOUNTS_KEY);
|
||||
mAccountManagerResponse =
|
||||
getIntent().getParcelableExtra(Constants.ACCOUNT_MANAGER_RESPONSE_KEY);
|
||||
} else {
|
||||
mAccounts = savedInstanceState.getParcelableArray(Constants.ACCOUNTS_KEY);
|
||||
mAccountManagerResponse =
|
||||
savedInstanceState.getParcelable(Constants.ACCOUNT_MANAGER_RESPONSE_KEY);
|
||||
}
|
||||
|
||||
String[] mAccountNames = new String[mAccounts.length];
|
||||
for (int i = 0; i < mAccounts.length; i++) {
|
||||
mAccountNames[i] = ((Account) mAccounts[i]).mName;
|
||||
}
|
||||
|
||||
// Use an existing ListAdapter that will map an array
|
||||
// of strings to TextViews
|
||||
setListAdapter(new ArrayAdapter<String>(this,
|
||||
android.R.layout.simple_list_item_1, mAccountNames));
|
||||
getListView().setTextFilterEnabled(true);
|
||||
}
|
||||
|
||||
protected void onListItemClick(ListView l, View v, int position, long id) {
|
||||
Account account = (Account) mAccounts[position];
|
||||
Log.d(TAG, "selected account " + account);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(Constants.ACCOUNT_NAME_KEY, account.mName);
|
||||
bundle.putString(Constants.ACCOUNT_TYPE_KEY, account.mType);
|
||||
mResult = bundle;
|
||||
finish();
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
if (mAccountManagerResponse != null) {
|
||||
if (mResult != null) {
|
||||
mAccountManagerResponse.onResult(mResult);
|
||||
} else {
|
||||
mAccountManagerResponse.onError(Constants.ERROR_CODE_CANCELED, "canceled");
|
||||
}
|
||||
}
|
||||
super.finish();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ public class Constants {
|
||||
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 int ERROR_CODE_BAD_ARGUMENTS = 7;
|
||||
|
||||
public static final String ACCOUNTS_KEY = "accounts";
|
||||
public static final String AUTHENTICATOR_TYPES_KEY = "authenticator_types";
|
||||
@@ -37,6 +38,7 @@ public class Constants {
|
||||
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 ACCOUNT_MANAGER_RESPONSE_KEY = "accountManagerResponse";
|
||||
public static final String AUTH_FAILED_MESSAGE_KEY = "authFailedMessage";
|
||||
/**
|
||||
* Action sent as a broadcast Intent by the AccountsService
|
||||
|
||||
@@ -28,7 +28,7 @@ oneway interface IAccountAuthenticator {
|
||||
* prompts the user for account information and adds the result to the IAccountManager
|
||||
*/
|
||||
void addAccount(in IAccountAuthenticatorResponse response, String accountType,
|
||||
String authTokenType, in Bundle options);
|
||||
String authTokenType, in String[] requiredFeatures, in Bundle options);
|
||||
|
||||
/**
|
||||
* Checks that the account/password combination is valid.
|
||||
@@ -58,4 +58,11 @@ oneway interface IAccountAuthenticator {
|
||||
* launches an activity that lets the user edit and set the properties for an authenticator
|
||||
*/
|
||||
void editProperties(in IAccountAuthenticatorResponse response, String accountType);
|
||||
|
||||
/**
|
||||
* returns a Bundle where the boolean value BOOLEAN_RESULT_KEY is set if the account has the
|
||||
* specified features
|
||||
*/
|
||||
void hasFeatures(in IAccountAuthenticatorResponse response, in Account account,
|
||||
in String[] features);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* Central application service that provides account management.
|
||||
* @hide
|
||||
*/
|
||||
interface IAccountManager {
|
||||
String getPassword(in Account account);
|
||||
@@ -42,16 +43,19 @@ interface IAccountManager {
|
||||
String authTokenType, boolean notifyOnAuthFailure, boolean expectActivityLaunch,
|
||||
in Bundle options);
|
||||
void addAcount(in IAccountManagerResponse response, String accountType,
|
||||
String authTokenType, boolean expectActivityLaunch, in Bundle options);
|
||||
String authTokenType, in String[] requiredFeatures, 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 getAccountsByTypeAndFeatures(in IAccountManagerResponse response, String accountType,
|
||||
in String[] features);
|
||||
|
||||
/*
|
||||
* @Deprecated
|
||||
* @deprecated
|
||||
*/
|
||||
void confirmPassword(in IAccountManagerResponse response, in Account account,
|
||||
String password);
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* The interface used to return responses for asynchronous calls to the {@link IAccountManager}
|
||||
* @hide
|
||||
*/
|
||||
oneway interface IAccountManagerResponse {
|
||||
void onResult(in Bundle value);
|
||||
|
||||
@@ -983,6 +983,11 @@
|
||||
android:excludeFromRecents="true">
|
||||
</activity>
|
||||
|
||||
<activity android:name="android.accounts.ChooseAccountActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true">
|
||||
</activity>
|
||||
|
||||
<provider android:name=".content.SyncProvider"
|
||||
android:authorities="sync" android:multiprocess="false" />
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ public class AccountManagerServiceTest extends AndroidTestCase {
|
||||
assertEquals(a22, accounts[4]);
|
||||
assertEquals(a32, accounts[5]);
|
||||
|
||||
accounts = ams.getAccountsByType("type1");
|
||||
accounts = ams.getAccountsByType("type1" );
|
||||
Arrays.sort(accounts, new AccountSorter());
|
||||
assertEquals(3, accounts.length);
|
||||
assertEquals(a11, accounts[0]);
|
||||
@@ -90,7 +90,7 @@ public class AccountManagerServiceTest extends AndroidTestCase {
|
||||
|
||||
ams.removeAccount(a21);
|
||||
|
||||
accounts = ams.getAccountsByType("type1");
|
||||
accounts = ams.getAccountsByType("type1" );
|
||||
Arrays.sort(accounts, new AccountSorter());
|
||||
assertEquals(2, accounts.length);
|
||||
assertEquals(a11, accounts[0]);
|
||||
@@ -176,13 +176,6 @@ public class AccountManagerServiceTest extends AndroidTestCase {
|
||||
assertNull(ams.peekAuthToken(a12, "att2"));
|
||||
assertEquals("a12_att3", ams.peekAuthToken(a12, "att3"));
|
||||
|
||||
// check that the authtoken cache is consistent with the database
|
||||
assertEquals(5, ams.mAuthTokenCache.size());
|
||||
for (Map.Entry<AccountManagerService.AuthTokenKey, String> entry
|
||||
: ams.mAuthTokenCache.entrySet()) {
|
||||
assertEquals(entry.getValue(), ams.readAuthTokenFromDatabase(
|
||||
entry.getKey().mAccount, entry.getKey().mAuthTokenType));
|
||||
}
|
||||
assertNull(ams.readAuthTokenFromDatabase(a12, "att2"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user