| /* | |
| * 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 com.android.development; | |
| import android.accounts.Account; | |
| import android.accounts.AccountManager; | |
| import android.accounts.AccountManagerCallback; | |
| import android.accounts.AccountManagerFuture; | |
| import android.accounts.AuthenticatorDescription; | |
| import android.accounts.AuthenticatorException; | |
| import android.accounts.OnAccountsUpdateListener; | |
| import android.accounts.OperationCanceledException; | |
| import android.app.Activity; | |
| import android.app.AlertDialog; | |
| import android.app.Dialog; | |
| import android.content.Context; | |
| import android.content.DialogInterface; | |
| import android.content.pm.PackageManager; | |
| import android.content.res.Resources; | |
| import android.os.Bundle; | |
| import android.os.Handler; | |
| import android.os.Parcelable; | |
| import android.text.TextUtils; | |
| import android.util.Log; | |
| import android.view.ContextMenu; | |
| import android.view.LayoutInflater; | |
| import android.view.MenuInflater; | |
| import android.view.MenuItem; | |
| import android.view.View; | |
| import android.view.ViewGroup; | |
| import android.widget.AdapterView; | |
| import android.widget.ArrayAdapter; | |
| import android.widget.EditText; | |
| import android.widget.ImageView; | |
| import android.widget.ListView; | |
| import android.widget.Spinner; | |
| import android.widget.TextView; | |
| import java.io.IOException; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| public class AccountsTester extends Activity implements OnAccountsUpdateListener { | |
| private static final String TAG = "AccountsTester"; | |
| private Spinner mAccountTypesSpinner; | |
| private ListView mAccountsListView; | |
| private AccountManager mAccountManager; | |
| private Account mLongPressedAccount = null; | |
| private AuthenticatorDescription[] mAuthenticatorDescs; | |
| private static final int GET_AUTH_TOKEN_DIALOG_ID = 1; | |
| private static final int UPDATE_CREDENTIALS_DIALOG_ID = 2; | |
| private static final int INVALIDATE_AUTH_TOKEN_DIALOG_ID = 3; | |
| private static final int TEST_HAS_FEATURES_DIALOG_ID = 4; | |
| private static final int MESSAGE_DIALOG_ID = 5; | |
| private static final int GET_AUTH_TOKEN_BY_TYPE_AND_FEATURE_DIALOG_ID = 6; | |
| private EditText mDesiredAuthTokenTypeEditText; | |
| private EditText mDesiredFeaturesEditText; | |
| private volatile CharSequence mDialogMessage; | |
| @Override | |
| protected void onCreate(Bundle savedInstanceState) { | |
| super.onCreate(savedInstanceState); | |
| mAccountManager = AccountManager.get(this); | |
| setContentView(R.layout.accounts_tester); | |
| ButtonClickListener buttonClickListener = new ButtonClickListener(); | |
| mAccountTypesSpinner = (Spinner) findViewById(R.id.accounts_tester_account_types_spinner); | |
| mAccountsListView = (ListView) findViewById(R.id.accounts_tester_accounts_list); | |
| registerForContextMenu(mAccountsListView); | |
| initializeAuthenticatorsSpinner(); | |
| findViewById(R.id.accounts_tester_get_all_accounts).setOnClickListener(buttonClickListener); | |
| findViewById(R.id.accounts_tester_get_accounts_by_type).setOnClickListener( | |
| buttonClickListener); | |
| findViewById(R.id.accounts_tester_add_account).setOnClickListener(buttonClickListener); | |
| findViewById(R.id.accounts_tester_edit_properties).setOnClickListener(buttonClickListener); | |
| findViewById(R.id.accounts_tester_get_auth_token_by_type_and_feature).setOnClickListener( | |
| buttonClickListener); | |
| mDesiredAuthTokenTypeEditText = | |
| (EditText) findViewById(R.id.accounts_tester_desired_authtokentype); | |
| mDesiredFeaturesEditText = (EditText) findViewById(R.id.accounts_tester_desired_features); | |
| } | |
| private class AccountArrayAdapter extends ArrayAdapter<Account> { | |
| protected LayoutInflater mInflater; | |
| public AccountArrayAdapter(Context context, Account[] accounts) { | |
| super(context, R.layout.account_list_item, accounts); | |
| mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); | |
| } | |
| class ViewHolder { | |
| TextView name; | |
| ImageView icon; | |
| Account account; | |
| } | |
| @Override | |
| public View getView(int position, View convertView, ViewGroup parent) { | |
| // A ViewHolder keeps references to children views to avoid unneccessary calls | |
| // to findViewById() on each row. | |
| ViewHolder holder; | |
| // When convertView is not null, we can reuse it directly, there is no need | |
| // to reinflate it. We only inflate a new View when the convertView supplied | |
| // by ListView is null. | |
| if (convertView == null) { | |
| convertView = mInflater.inflate(R.layout.account_list_item, null); | |
| // Creates a ViewHolder and store references to the two children views | |
| // we want to bind data to. | |
| holder = new ViewHolder(); | |
| holder.name = (TextView) convertView.findViewById( | |
| R.id.accounts_tester_account_name); | |
| holder.icon = (ImageView) convertView.findViewById( | |
| R.id.accounts_tester_account_type_icon); | |
| convertView.setTag(holder); | |
| } else { | |
| // Get the ViewHolder back to get fast access to the TextView | |
| // and the ImageView. | |
| holder = (ViewHolder) convertView.getTag(); | |
| } | |
| final Account account = getItem(position); | |
| holder.account = account; | |
| holder.icon.setVisibility(View.INVISIBLE); | |
| for (AuthenticatorDescription desc : mAuthenticatorDescs) { | |
| if (desc.type.equals(account.type)) { | |
| final String packageName = desc.packageName; | |
| try { | |
| final Context authContext = getContext().createPackageContext(packageName, | |
| 0); | |
| holder.icon.setImageDrawable(authContext.getResources().getDrawable( | |
| desc.iconId)); | |
| holder.icon.setVisibility(View.VISIBLE); | |
| } catch (PackageManager.NameNotFoundException e) { | |
| Log.d(TAG, "error getting the Package Context for " + packageName, e); | |
| } | |
| } | |
| } | |
| // Set text field | |
| holder.name.setText(account.name); | |
| return convertView; | |
| } | |
| } | |
| private void initializeAuthenticatorsSpinner() { | |
| mAuthenticatorDescs = mAccountManager.getAuthenticatorTypes(); | |
| List<String> names = new ArrayList(mAuthenticatorDescs.length); | |
| for (int i = 0; i < mAuthenticatorDescs.length; i++) { | |
| Context authContext; | |
| try { | |
| authContext = createPackageContext(mAuthenticatorDescs[i].packageName, 0); | |
| } catch (PackageManager.NameNotFoundException e) { | |
| continue; | |
| } | |
| try { | |
| names.add(authContext.getString(mAuthenticatorDescs[i].labelId)); | |
| } catch (Resources.NotFoundException e) { | |
| continue; | |
| } | |
| } | |
| String[] namesArray = names.toArray(new String[names.size()]); | |
| ArrayAdapter<String> adapter = | |
| new ArrayAdapter<String>(AccountsTester.this, | |
| android.R.layout.simple_spinner_item, namesArray); | |
| mAccountTypesSpinner.setAdapter(adapter); | |
| } | |
| public void onAccountsUpdated(Account[] accounts) { | |
| Log.d(TAG, "onAccountsUpdated: \n " + TextUtils.join("\n ", accounts)); | |
| mAccountsListView.setAdapter(new AccountArrayAdapter(this, accounts)); | |
| } | |
| protected void onStart() { | |
| super.onStart(); | |
| final Handler mainHandler = new Handler(getMainLooper()); | |
| mAccountManager.addOnAccountsUpdatedListener(this, mainHandler, | |
| true /* updateImmediately */); | |
| } | |
| protected void onStop() { | |
| super.onStop(); | |
| mAccountManager.removeOnAccountsUpdatedListener(this); | |
| } | |
| class ButtonClickListener implements View.OnClickListener { | |
| public void onClick(View v) { | |
| if (R.id.accounts_tester_get_all_accounts == v.getId()) { | |
| onAccountsUpdated(mAccountManager.getAccounts()); | |
| } else if (R.id.accounts_tester_get_accounts_by_type == v.getId()) { | |
| String type = getSelectedAuthenticator().type; | |
| onAccountsUpdated(mAccountManager.getAccountsByType(type)); | |
| } else if (R.id.accounts_tester_add_account == v.getId()) { | |
| String authTokenType = mDesiredAuthTokenTypeEditText.getText().toString(); | |
| if (TextUtils.isEmpty(authTokenType)) { | |
| authTokenType = null; | |
| } | |
| String featureString = mDesiredFeaturesEditText.getText().toString(); | |
| String[] requiredFeatures = TextUtils.split(featureString, " "); | |
| if (requiredFeatures.length == 0) { | |
| requiredFeatures = null; | |
| } | |
| mAccountManager.addAccount(getSelectedAuthenticator().type, | |
| authTokenType, requiredFeatures, null /* options */, | |
| AccountsTester.this, | |
| new CallbackToDialog(AccountsTester.this, "add account"), | |
| null /* handler */); | |
| } else if (R.id.accounts_tester_edit_properties == v.getId()) { | |
| mAccountManager.editProperties(getSelectedAuthenticator().type, | |
| AccountsTester.this, | |
| new CallbackToDialog(AccountsTester.this, "edit properties"), | |
| null /* handler */); | |
| } else if (R.id.accounts_tester_get_auth_token_by_type_and_feature == v.getId()) { | |
| showDialog(GET_AUTH_TOKEN_BY_TYPE_AND_FEATURE_DIALOG_ID); | |
| } else { | |
| // unknown button | |
| } | |
| } | |
| } | |
| private AuthenticatorDescription getSelectedAuthenticator() { | |
| return mAuthenticatorDescs[mAccountTypesSpinner.getSelectedItemPosition()]; | |
| } | |
| @Override | |
| public void onCreateContextMenu(ContextMenu menu, View v, | |
| ContextMenu.ContextMenuInfo menuInfo) { | |
| menu.setHeaderTitle(R.string.accounts_tester_account_context_menu_title); | |
| AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo; | |
| MenuInflater inflater = getMenuInflater(); | |
| inflater.inflate(R.layout.account_list_context_menu, menu); | |
| AccountArrayAdapter.ViewHolder holder = | |
| (AccountArrayAdapter.ViewHolder)info.targetView.getTag(); | |
| mLongPressedAccount = holder.account; | |
| } | |
| protected void onSaveInstanceState(Bundle outState) { | |
| outState.putParcelable("account", mLongPressedAccount); | |
| } | |
| protected void onRestoreInstanceState(Bundle savedInstanceState) { | |
| mLongPressedAccount = savedInstanceState.getParcelable("account"); | |
| } | |
| @Override | |
| public boolean onContextItemSelected(MenuItem item) { | |
| if (item.getItemId() == R.id.accounts_tester_remove_account) { | |
| final Account account = mLongPressedAccount; | |
| mAccountManager.removeAccount(account, new AccountManagerCallback<Boolean>() { | |
| public void run(AccountManagerFuture<Boolean> future) { | |
| try { | |
| Log.d(TAG, "removeAccount(" + account + ") = " + future.getResult()); | |
| } catch (OperationCanceledException e) { | |
| } catch (IOException e) { | |
| } catch (AuthenticatorException e) { | |
| } | |
| } | |
| }, null /* handler */); | |
| } else if (item.getItemId() == R.id.accounts_tester_clear_password) { | |
| final Account account = mLongPressedAccount; | |
| mAccountManager.clearPassword(account); | |
| showMessageDialog("cleared"); | |
| } else if (item.getItemId() == R.id.accounts_tester_get_auth_token) { | |
| showDialog(GET_AUTH_TOKEN_DIALOG_ID); | |
| } else if (item.getItemId() == R.id.accounts_tester_test_has_features) { | |
| showDialog(TEST_HAS_FEATURES_DIALOG_ID); | |
| } else if (item.getItemId() == R.id.accounts_tester_invalidate_auth_token) { | |
| showDialog(INVALIDATE_AUTH_TOKEN_DIALOG_ID); | |
| } else if (item.getItemId() == R.id.accounts_tester_update_credentials) { | |
| showDialog(UPDATE_CREDENTIALS_DIALOG_ID); | |
| } else if (item.getItemId() == R.id.accounts_tester_confirm_credentials) { | |
| mAccountManager.confirmCredentials(mLongPressedAccount, null, | |
| AccountsTester.this, new CallbackToDialog(this, "confirm credentials"), | |
| null /* handler */); | |
| } | |
| return true; | |
| } | |
| @Override | |
| protected Dialog onCreateDialog(final int id) { | |
| switch (id) { | |
| case GET_AUTH_TOKEN_DIALOG_ID: | |
| case INVALIDATE_AUTH_TOKEN_DIALOG_ID: | |
| case UPDATE_CREDENTIALS_DIALOG_ID: | |
| case TEST_HAS_FEATURES_DIALOG_ID: { | |
| final View view = LayoutInflater.from(this).inflate(R.layout.get_auth_token_view, | |
| null); | |
| AlertDialog.Builder builder = new AlertDialog.Builder(this); | |
| builder.setPositiveButton(R.string.accounts_tester_ok_button, | |
| new DialogInterface.OnClickListener() { | |
| public void onClick(DialogInterface dialog, int which) { | |
| EditText value = (EditText) view.findViewById( | |
| R.id.accounts_tester_auth_token_type); | |
| String authTokenType = value.getText().toString(); | |
| final Account account = mLongPressedAccount; | |
| if (id == GET_AUTH_TOKEN_DIALOG_ID) { | |
| mAccountManager.getAuthToken(account, | |
| authTokenType, | |
| null /* loginOptions */, | |
| AccountsTester.this, | |
| new CallbackToDialog(AccountsTester.this, | |
| "get auth token"), | |
| null /* handler */); | |
| } else if (id == INVALIDATE_AUTH_TOKEN_DIALOG_ID) { | |
| mAccountManager.getAuthToken(account, authTokenType, false, | |
| new GetAndInvalidateAuthTokenCallback(account), null); | |
| } else if (id == TEST_HAS_FEATURES_DIALOG_ID) { | |
| String[] features = TextUtils.split(authTokenType, ","); | |
| mAccountManager.hasFeatures(account, features, | |
| new TestHasFeaturesCallback(), null); | |
| } else { | |
| mAccountManager.updateCredentials( | |
| account, | |
| authTokenType, null /* loginOptions */, | |
| AccountsTester.this, | |
| new CallbackToDialog(AccountsTester.this, "update"), | |
| null /* handler */); | |
| } | |
| } | |
| }); | |
| builder.setView(view); | |
| return builder.create(); | |
| } | |
| case GET_AUTH_TOKEN_BY_TYPE_AND_FEATURE_DIALOG_ID: { | |
| final View view = LayoutInflater.from(this).inflate(R.layout.get_features_view, | |
| null); | |
| AlertDialog.Builder builder = new AlertDialog.Builder(this); | |
| builder.setPositiveButton(R.string.accounts_tester_ok_button, | |
| new DialogInterface.OnClickListener() { | |
| public void onClick(DialogInterface dialog, int which) { | |
| EditText value = (EditText) view.findViewById( | |
| R.id.accounts_tester_auth_token_type); | |
| String authTokenType = value.getText().toString(); | |
| value = (EditText) view.findViewById( | |
| R.id.accounts_tester_features); | |
| String features = value.getText().toString(); | |
| final Account account = mLongPressedAccount; | |
| mAccountManager.getAuthTokenByFeatures( | |
| getSelectedAuthenticator().type, | |
| authTokenType, | |
| TextUtils.isEmpty(features) ? null : features.split(" "), | |
| AccountsTester.this, | |
| null /* addAccountOptions */, | |
| null /* getAuthTokenOptions */, | |
| new CallbackToDialog(AccountsTester.this, | |
| "get auth token by features"), | |
| null /* handler */); | |
| } | |
| }); | |
| builder.setView(view); | |
| return builder.create(); | |
| } | |
| case MESSAGE_DIALOG_ID: { | |
| AlertDialog.Builder builder = new AlertDialog.Builder(this); | |
| builder.setMessage(mDialogMessage); | |
| return builder.create(); | |
| } | |
| } | |
| return super.onCreateDialog(id); | |
| } | |
| AccountManagerCallback<Bundle> newAccountsCallback(String type, String[] features) { | |
| return new GetAccountsCallback(type, features); | |
| } | |
| class GetAccountsCallback implements AccountManagerCallback<Bundle> { | |
| final String[] mFeatures; | |
| final String mAccountType; | |
| public GetAccountsCallback(String type, String[] features) { | |
| mFeatures = features; | |
| mAccountType = type; | |
| } | |
| public void run(AccountManagerFuture<Bundle> future) { | |
| Log.d(TAG, "GetAccountsCallback: type " + mAccountType | |
| + ", features " | |
| + (mFeatures == null ? "none" : TextUtils.join(",", mFeatures))); | |
| try { | |
| Bundle result = future.getResult(); | |
| Parcelable[] accounts = result.getParcelableArray(AccountManager.KEY_ACCOUNTS); | |
| Log.d(TAG, "found " + accounts.length + " accounts"); | |
| for (Parcelable account : accounts) { | |
| Log.d(TAG, " " + account); | |
| } | |
| } catch (OperationCanceledException e) { | |
| Log.d(TAG, "failure", e); | |
| } catch (IOException e) { | |
| Log.d(TAG, "failure", e); | |
| } catch (AuthenticatorException e) { | |
| Log.d(TAG, "failure", e); | |
| } | |
| } | |
| } | |
| AccountManagerCallback<Bundle> newAuthTokensCallback(String type, String authTokenType, | |
| String[] features) { | |
| return new GetAuthTokenCallback(type, authTokenType, features); | |
| } | |
| class GetAuthTokenCallback implements AccountManagerCallback<Bundle> { | |
| final String[] mFeatures; | |
| final String mAccountType; | |
| final String mAuthTokenType; | |
| public GetAuthTokenCallback(String type, String authTokenType, String[] features) { | |
| mFeatures = features; | |
| mAccountType = type; | |
| mAuthTokenType = authTokenType; | |
| } | |
| public void run(AccountManagerFuture<Bundle> future) { | |
| Log.d(TAG, "GetAuthTokenCallback: type " + mAccountType | |
| + ", features " | |
| + (mFeatures == null ? "none" : TextUtils.join(",", mFeatures))); | |
| getAndLogResult(future, "get auth token"); | |
| } | |
| } | |
| private class GetAndInvalidateAuthTokenCallback implements AccountManagerCallback<Bundle> { | |
| private final Account mAccount; | |
| private GetAndInvalidateAuthTokenCallback(Account account) { | |
| mAccount = account; | |
| } | |
| public void run(AccountManagerFuture<Bundle> future) { | |
| Bundle result = getAndLogResult(future, "get and invalidate"); | |
| if (result != null) { | |
| String authToken = result.getString(AccountManager.KEY_AUTHTOKEN); | |
| mAccountManager.invalidateAuthToken(mAccount.type, authToken); | |
| } | |
| } | |
| } | |
| private void showMessageDialog(String message) { | |
| mDialogMessage = message; | |
| removeDialog(MESSAGE_DIALOG_ID); | |
| showDialog(MESSAGE_DIALOG_ID); | |
| } | |
| private class TestHasFeaturesCallback implements AccountManagerCallback<Boolean> { | |
| public void run(AccountManagerFuture<Boolean> future) { | |
| try { | |
| Boolean hasFeatures = future.getResult(); | |
| Log.d(TAG, "hasFeatures: " + hasFeatures); | |
| showMessageDialog("hasFeatures: " + hasFeatures); | |
| } catch (OperationCanceledException e) { | |
| Log.d(TAG, "interrupted"); | |
| showMessageDialog("operation was canceled"); | |
| } catch (IOException e) { | |
| Log.d(TAG, "error", e); | |
| showMessageDialog("operation got an IOException"); | |
| } catch (AuthenticatorException e) { | |
| Log.d(TAG, "error", e); | |
| showMessageDialog("operation got an AuthenticationException"); | |
| } | |
| } | |
| } | |
| private static class CallbackToDialog implements AccountManagerCallback<Bundle> { | |
| private final AccountsTester mActivity; | |
| private final String mLabel; | |
| private CallbackToDialog(AccountsTester activity, String label) { | |
| mActivity = activity; | |
| mLabel = label; | |
| } | |
| public void run(AccountManagerFuture<Bundle> future) { | |
| mActivity.getAndLogResult(future, mLabel); | |
| } | |
| } | |
| private Bundle getAndLogResult(AccountManagerFuture<Bundle> future, String label) { | |
| try { | |
| Bundle result = future.getResult(); | |
| result.keySet(); | |
| Log.d(TAG, label + ": " + result); | |
| StringBuffer sb = new StringBuffer(); | |
| sb.append(label).append(" result:"); | |
| for (String key : result.keySet()) { | |
| Object value = result.get(key); | |
| if (AccountManager.KEY_AUTHTOKEN.equals(key)) { | |
| value = "<redacted>"; | |
| } | |
| sb.append("\n ").append(key).append(" -> ").append(value); | |
| } | |
| showMessageDialog(sb.toString()); | |
| return result; | |
| } catch (OperationCanceledException e) { | |
| Log.d(TAG, label + " failed", e); | |
| showMessageDialog(label + " was canceled"); | |
| return null; | |
| } catch (IOException e) { | |
| Log.d(TAG, label + " failed", e); | |
| showMessageDialog(label + " failed with IOException: " + e.getMessage()); | |
| return null; | |
| } catch (AuthenticatorException e) { | |
| Log.d(TAG, label + " failed", e); | |
| showMessageDialog(label + " failed with an AuthenticatorException: " + e.getMessage()); | |
| return null; | |
| } | |
| } | |
| } |