Home | History | Annotate | Download | only in development
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.development;
     18 
     19 import android.accounts.Account;
     20 import android.accounts.AccountManager;
     21 import android.accounts.AccountManagerCallback;
     22 import android.accounts.AccountManagerFuture;
     23 import android.accounts.AuthenticatorDescription;
     24 import android.accounts.AuthenticatorException;
     25 import android.accounts.OnAccountsUpdateListener;
     26 import android.accounts.OperationCanceledException;
     27 import android.app.Activity;
     28 import android.app.AlertDialog;
     29 import android.app.Dialog;
     30 import android.content.Context;
     31 import android.content.DialogInterface;
     32 import android.content.pm.PackageManager;
     33 import android.content.res.Resources;
     34 import android.os.Bundle;
     35 import android.os.Handler;
     36 import android.os.Parcelable;
     37 import android.text.TextUtils;
     38 import android.util.Log;
     39 import android.view.ContextMenu;
     40 import android.view.LayoutInflater;
     41 import android.view.MenuInflater;
     42 import android.view.MenuItem;
     43 import android.view.View;
     44 import android.view.ViewGroup;
     45 import android.widget.AdapterView;
     46 import android.widget.ArrayAdapter;
     47 import android.widget.EditText;
     48 import android.widget.ImageView;
     49 import android.widget.ListView;
     50 import android.widget.Spinner;
     51 import android.widget.TextView;
     52 
     53 import java.io.IOException;
     54 import java.util.ArrayList;
     55 import java.util.List;
     56 
     57 public class AccountsTester extends Activity implements OnAccountsUpdateListener {
     58     private static final String TAG = "AccountsTester";
     59     private Spinner mAccountTypesSpinner;
     60     private ListView mAccountsListView;
     61     private AccountManager mAccountManager;
     62     private Account mLongPressedAccount = null;
     63     private AuthenticatorDescription[] mAuthenticatorDescs;
     64 
     65     private static final int GET_AUTH_TOKEN_DIALOG_ID = 1;
     66     private static final int UPDATE_CREDENTIALS_DIALOG_ID = 2;
     67     private static final int INVALIDATE_AUTH_TOKEN_DIALOG_ID = 3;
     68     private static final int TEST_HAS_FEATURES_DIALOG_ID = 4;
     69     private static final int MESSAGE_DIALOG_ID = 5;
     70     private static final int GET_AUTH_TOKEN_BY_TYPE_AND_FEATURE_DIALOG_ID = 6;
     71 
     72     private EditText mDesiredAuthTokenTypeEditText;
     73     private EditText mDesiredFeaturesEditText;
     74     private volatile CharSequence mDialogMessage;
     75 
     76     @Override
     77     protected void onCreate(Bundle savedInstanceState) {
     78         super.onCreate(savedInstanceState);
     79         mAccountManager = AccountManager.get(this);
     80         setContentView(R.layout.accounts_tester);
     81         ButtonClickListener buttonClickListener = new ButtonClickListener();
     82 
     83         mAccountTypesSpinner = (Spinner) findViewById(R.id.accounts_tester_account_types_spinner);
     84         mAccountsListView = (ListView) findViewById(R.id.accounts_tester_accounts_list);
     85         registerForContextMenu(mAccountsListView);
     86         initializeAuthenticatorsSpinner();
     87         findViewById(R.id.accounts_tester_get_all_accounts).setOnClickListener(buttonClickListener);
     88         findViewById(R.id.accounts_tester_get_accounts_by_type).setOnClickListener(
     89                 buttonClickListener);
     90         findViewById(R.id.accounts_tester_add_account).setOnClickListener(buttonClickListener);
     91         findViewById(R.id.accounts_tester_edit_properties).setOnClickListener(buttonClickListener);
     92         findViewById(R.id.accounts_tester_get_auth_token_by_type_and_feature).setOnClickListener(
     93                 buttonClickListener);
     94         mDesiredAuthTokenTypeEditText =
     95                 (EditText) findViewById(R.id.accounts_tester_desired_authtokentype);
     96         mDesiredFeaturesEditText = (EditText) findViewById(R.id.accounts_tester_desired_features);
     97     }
     98 
     99     private class AccountArrayAdapter extends ArrayAdapter<Account> {
    100         protected LayoutInflater mInflater;
    101 
    102         public AccountArrayAdapter(Context context, Account[] accounts) {
    103             super(context, R.layout.account_list_item, accounts);
    104             mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    105         }
    106 
    107         class ViewHolder {
    108             TextView name;
    109             ImageView icon;
    110             Account account;
    111         }
    112 
    113         @Override
    114         public View getView(int position, View convertView, ViewGroup parent) {
    115             // A ViewHolder keeps references to children views to avoid unneccessary calls
    116             // to findViewById() on each row.
    117             ViewHolder holder;
    118 
    119             // When convertView is not null, we can reuse it directly, there is no need
    120             // to reinflate it. We only inflate a new View when the convertView supplied
    121             // by ListView is null.
    122             if (convertView == null) {
    123                 convertView = mInflater.inflate(R.layout.account_list_item, null);
    124 
    125                 // Creates a ViewHolder and store references to the two children views
    126                 // we want to bind data to.
    127                 holder = new ViewHolder();
    128                 holder.name = (TextView) convertView.findViewById(
    129                         R.id.accounts_tester_account_name);
    130                 holder.icon = (ImageView) convertView.findViewById(
    131                         R.id.accounts_tester_account_type_icon);
    132 
    133                 convertView.setTag(holder);
    134             } else {
    135                 // Get the ViewHolder back to get fast access to the TextView
    136                 // and the ImageView.
    137                 holder = (ViewHolder) convertView.getTag();
    138             }
    139 
    140             final Account account = getItem(position);
    141             holder.account = account;
    142             holder.icon.setVisibility(View.INVISIBLE);
    143             for (AuthenticatorDescription desc : mAuthenticatorDescs) {
    144                 if (desc.type.equals(account.type)) {
    145                     final String packageName = desc.packageName;
    146                     try {
    147                         final Context authContext = getContext().createPackageContext(packageName,
    148                                 0);
    149                         holder.icon.setImageDrawable(authContext.getResources().getDrawable(
    150                                 desc.iconId));
    151                         holder.icon.setVisibility(View.VISIBLE);
    152                     } catch (PackageManager.NameNotFoundException e) {
    153                         Log.d(TAG, "error getting the Package Context for " + packageName, e);
    154                     }
    155                 }
    156             }
    157 
    158             // Set text field
    159             holder.name.setText(account.name);
    160             return convertView;
    161         }
    162     }
    163 
    164     private void initializeAuthenticatorsSpinner() {
    165         mAuthenticatorDescs = mAccountManager.getAuthenticatorTypes();
    166         List<String> names = new ArrayList(mAuthenticatorDescs.length);
    167         for (int i = 0; i < mAuthenticatorDescs.length; i++) {
    168             Context authContext;
    169             try {
    170                 authContext = createPackageContext(mAuthenticatorDescs[i].packageName, 0);
    171             } catch (PackageManager.NameNotFoundException e) {
    172                 continue;
    173             }
    174             try  {
    175                 names.add(authContext.getString(mAuthenticatorDescs[i].labelId));
    176             } catch (Resources.NotFoundException e) {
    177                 continue;
    178             }
    179         }
    180 
    181         String[] namesArray = names.toArray(new String[names.size()]);
    182         ArrayAdapter<String> adapter =
    183                 new ArrayAdapter<String>(AccountsTester.this,
    184                 android.R.layout.simple_spinner_item, namesArray);
    185         mAccountTypesSpinner.setAdapter(adapter);
    186     }
    187 
    188     public void onAccountsUpdated(Account[] accounts) {
    189         Log.d(TAG, "onAccountsUpdated: \n  " + TextUtils.join("\n  ", accounts));
    190         mAccountsListView.setAdapter(new AccountArrayAdapter(this, accounts));
    191     }
    192 
    193     protected void onStart() {
    194         super.onStart();
    195         final Handler mainHandler = new Handler(getMainLooper());
    196         mAccountManager.addOnAccountsUpdatedListener(this, mainHandler,
    197                 true /* updateImmediately */);
    198     }
    199 
    200     protected void onStop() {
    201         super.onStop();
    202         mAccountManager.removeOnAccountsUpdatedListener(this);
    203     }
    204 
    205     class ButtonClickListener implements View.OnClickListener {
    206         public void onClick(View v) {
    207             if (R.id.accounts_tester_get_all_accounts == v.getId()) {
    208                 onAccountsUpdated(mAccountManager.getAccounts());
    209             } else if (R.id.accounts_tester_get_accounts_by_type == v.getId()) {
    210                 String type = getSelectedAuthenticator().type;
    211                 onAccountsUpdated(mAccountManager.getAccountsByType(type));
    212             } else if (R.id.accounts_tester_add_account == v.getId()) {
    213                 String authTokenType = mDesiredAuthTokenTypeEditText.getText().toString();
    214                 if (TextUtils.isEmpty(authTokenType)) {
    215                     authTokenType = null;
    216                 }
    217                 String featureString = mDesiredFeaturesEditText.getText().toString();
    218                 String[] requiredFeatures = TextUtils.split(featureString, " ");
    219                 if (requiredFeatures.length == 0) {
    220                     requiredFeatures = null;
    221                 }
    222                 mAccountManager.addAccount(getSelectedAuthenticator().type,
    223                         authTokenType, requiredFeatures, null /* options */,
    224                         AccountsTester.this,
    225                         new CallbackToDialog(AccountsTester.this, "add account"),
    226                         null /* handler */);
    227             } else if (R.id.accounts_tester_edit_properties == v.getId()) {
    228                 mAccountManager.editProperties(getSelectedAuthenticator().type,
    229                         AccountsTester.this,
    230                         new CallbackToDialog(AccountsTester.this, "edit properties"),
    231                         null /* handler */);
    232             } else if (R.id.accounts_tester_get_auth_token_by_type_and_feature == v.getId()) {
    233                 showDialog(GET_AUTH_TOKEN_BY_TYPE_AND_FEATURE_DIALOG_ID);
    234             } else {
    235                 // unknown button
    236             }
    237         }
    238     }
    239 
    240     private AuthenticatorDescription getSelectedAuthenticator() {
    241         return mAuthenticatorDescs[mAccountTypesSpinner.getSelectedItemPosition()];
    242     }
    243 
    244     @Override
    245     public void onCreateContextMenu(ContextMenu menu, View v,
    246             ContextMenu.ContextMenuInfo menuInfo) {
    247         menu.setHeaderTitle(R.string.accounts_tester_account_context_menu_title);
    248 
    249         AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
    250 
    251         MenuInflater inflater = getMenuInflater();
    252         inflater.inflate(R.layout.account_list_context_menu, menu);
    253         AccountArrayAdapter.ViewHolder holder =
    254                 (AccountArrayAdapter.ViewHolder)info.targetView.getTag();
    255         mLongPressedAccount = holder.account;
    256     }
    257 
    258     protected void onSaveInstanceState(Bundle outState) {
    259         outState.putParcelable("account", mLongPressedAccount);
    260     }
    261 
    262     protected void onRestoreInstanceState(Bundle savedInstanceState) {
    263         mLongPressedAccount = savedInstanceState.getParcelable("account");
    264     }
    265 
    266     @Override
    267     public boolean onContextItemSelected(MenuItem item) {
    268         if (item.getItemId() == R.id.accounts_tester_remove_account) {
    269             final Account account = mLongPressedAccount;
    270             mAccountManager.removeAccount(account, new AccountManagerCallback<Boolean>() {
    271                 public void run(AccountManagerFuture<Boolean> future) {
    272                     try {
    273                         Log.d(TAG, "removeAccount(" + account + ") = " + future.getResult());
    274                     } catch (OperationCanceledException e) {
    275                     } catch (IOException e) {
    276                     } catch (AuthenticatorException e) {
    277                     }
    278                 }
    279             }, null /* handler */);
    280         } else if (item.getItemId() == R.id.accounts_tester_clear_password) {
    281             final Account account = mLongPressedAccount;
    282             mAccountManager.clearPassword(account);
    283             showMessageDialog("cleared");
    284         } else if (item.getItemId() == R.id.accounts_tester_get_auth_token) {
    285             showDialog(GET_AUTH_TOKEN_DIALOG_ID);
    286         } else if (item.getItemId() == R.id.accounts_tester_test_has_features) {
    287             showDialog(TEST_HAS_FEATURES_DIALOG_ID);
    288         } else if (item.getItemId() == R.id.accounts_tester_invalidate_auth_token) {
    289             showDialog(INVALIDATE_AUTH_TOKEN_DIALOG_ID);
    290         } else if (item.getItemId() == R.id.accounts_tester_update_credentials) {
    291             showDialog(UPDATE_CREDENTIALS_DIALOG_ID);
    292         } else if (item.getItemId() == R.id.accounts_tester_confirm_credentials) {
    293             mAccountManager.confirmCredentials(mLongPressedAccount, null,
    294                     AccountsTester.this, new CallbackToDialog(this, "confirm credentials"),
    295                     null /* handler */);
    296         }
    297         return true;
    298     }
    299 
    300     @Override
    301     protected Dialog onCreateDialog(final int id) {
    302         switch (id) {
    303             case GET_AUTH_TOKEN_DIALOG_ID:
    304             case INVALIDATE_AUTH_TOKEN_DIALOG_ID:
    305             case UPDATE_CREDENTIALS_DIALOG_ID:
    306             case TEST_HAS_FEATURES_DIALOG_ID: {
    307                 final View view = LayoutInflater.from(this).inflate(R.layout.get_auth_token_view,
    308                         null);
    309                 AlertDialog.Builder builder = new AlertDialog.Builder(this);
    310                 builder.setPositiveButton(R.string.accounts_tester_ok_button,
    311                         new DialogInterface.OnClickListener() {
    312                             public void onClick(DialogInterface dialog, int which) {
    313                                 EditText value = (EditText) view.findViewById(
    314                                         R.id.accounts_tester_auth_token_type);
    315 
    316                                 String authTokenType = value.getText().toString();
    317                                 final Account account = mLongPressedAccount;
    318                                 if (id == GET_AUTH_TOKEN_DIALOG_ID) {
    319                                     mAccountManager.getAuthToken(account,
    320                                             authTokenType,
    321                                             null /* loginOptions */,
    322                                             AccountsTester.this,
    323                                             new CallbackToDialog(AccountsTester.this,
    324                                                     "get auth token"),
    325                                             null /* handler */);
    326                                 } else if (id == INVALIDATE_AUTH_TOKEN_DIALOG_ID) {
    327                                     mAccountManager.getAuthToken(account, authTokenType, false,
    328                                             new GetAndInvalidateAuthTokenCallback(account), null);
    329                                 } else if (id == TEST_HAS_FEATURES_DIALOG_ID) {
    330                                     String[] features = TextUtils.split(authTokenType, ",");
    331                                     mAccountManager.hasFeatures(account, features,
    332                                             new TestHasFeaturesCallback(), null);
    333                                 } else {
    334                                     mAccountManager.updateCredentials(
    335                                             account,
    336                                             authTokenType, null /* loginOptions */,
    337                                             AccountsTester.this,
    338                                             new CallbackToDialog(AccountsTester.this, "update"),
    339                                             null /* handler */);
    340                                 }
    341                             }
    342                 });
    343                 builder.setView(view);
    344                 return builder.create();
    345             }
    346 
    347             case GET_AUTH_TOKEN_BY_TYPE_AND_FEATURE_DIALOG_ID: {
    348                 final View view = LayoutInflater.from(this).inflate(R.layout.get_features_view,
    349                         null);
    350                 AlertDialog.Builder builder = new AlertDialog.Builder(this);
    351                 builder.setPositiveButton(R.string.accounts_tester_ok_button,
    352                         new DialogInterface.OnClickListener() {
    353                             public void onClick(DialogInterface dialog, int which) {
    354                                 EditText value = (EditText) view.findViewById(
    355                                         R.id.accounts_tester_auth_token_type);
    356 
    357                                 String authTokenType = value.getText().toString();
    358 
    359                                 value = (EditText) view.findViewById(
    360                                         R.id.accounts_tester_features);
    361 
    362                                 String features = value.getText().toString();
    363 
    364                                 final Account account = mLongPressedAccount;
    365                                 mAccountManager.getAuthTokenByFeatures(
    366                                         getSelectedAuthenticator().type,
    367                                         authTokenType,
    368                                         TextUtils.isEmpty(features) ? null : features.split(" "),
    369                                         AccountsTester.this,
    370                                         null /* addAccountOptions */,
    371                                         null /* getAuthTokenOptions */,
    372                                         new CallbackToDialog(AccountsTester.this,
    373                                                 "get auth token by features"),
    374                                         null /* handler */);
    375                             }
    376                 });
    377                 builder.setView(view);
    378                 return builder.create();
    379             }
    380 
    381             case MESSAGE_DIALOG_ID: {
    382                 AlertDialog.Builder builder = new AlertDialog.Builder(this);
    383                 builder.setMessage(mDialogMessage);
    384                 return builder.create();
    385             }
    386         }
    387 
    388         return super.onCreateDialog(id);
    389     }
    390 
    391     AccountManagerCallback<Bundle> newAccountsCallback(String type, String[] features) {
    392         return new GetAccountsCallback(type, features);
    393     }
    394 
    395     class GetAccountsCallback implements AccountManagerCallback<Bundle> {
    396         final String[] mFeatures;
    397         final String mAccountType;
    398 
    399         public GetAccountsCallback(String type, String[] features) {
    400             mFeatures = features;
    401             mAccountType = type;
    402         }
    403 
    404         public void run(AccountManagerFuture<Bundle> future) {
    405             Log.d(TAG, "GetAccountsCallback: type " + mAccountType
    406                     + ", features "
    407                     + (mFeatures == null ? "none" : TextUtils.join(",", mFeatures)));
    408             try {
    409                 Bundle result = future.getResult();
    410                 Parcelable[] accounts = result.getParcelableArray(AccountManager.KEY_ACCOUNTS);
    411                 Log.d(TAG, "found " + accounts.length + " accounts");
    412                 for (Parcelable account : accounts) {
    413                     Log.d(TAG, "  " + account);
    414                 }
    415             } catch (OperationCanceledException e) {
    416                 Log.d(TAG, "failure", e);
    417             } catch (IOException e) {
    418                 Log.d(TAG, "failure", e);
    419             } catch (AuthenticatorException e) {
    420                 Log.d(TAG, "failure", e);
    421             }
    422         }
    423     }
    424 
    425     AccountManagerCallback<Bundle> newAuthTokensCallback(String type, String authTokenType,
    426             String[] features) {
    427         return new GetAuthTokenCallback(type, authTokenType, features);
    428     }
    429 
    430     class GetAuthTokenCallback implements AccountManagerCallback<Bundle> {
    431         final String[] mFeatures;
    432         final String mAccountType;
    433         final String mAuthTokenType;
    434 
    435         public GetAuthTokenCallback(String type, String authTokenType, String[] features) {
    436             mFeatures = features;
    437             mAccountType = type;
    438             mAuthTokenType = authTokenType;
    439         }
    440 
    441         public void run(AccountManagerFuture<Bundle> future) {
    442             Log.d(TAG, "GetAuthTokenCallback: type " + mAccountType
    443                     + ", features "
    444                     + (mFeatures == null ? "none" : TextUtils.join(",", mFeatures)));
    445             getAndLogResult(future, "get auth token");
    446         }
    447     }
    448 
    449     private class GetAndInvalidateAuthTokenCallback implements AccountManagerCallback<Bundle> {
    450         private final Account mAccount;
    451 
    452         private GetAndInvalidateAuthTokenCallback(Account account) {
    453             mAccount = account;
    454         }
    455 
    456         public void run(AccountManagerFuture<Bundle> future) {
    457             Bundle result = getAndLogResult(future, "get and invalidate");
    458             if (result != null) {
    459                 String authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
    460                 mAccountManager.invalidateAuthToken(mAccount.type, authToken);
    461             }
    462         }
    463     }
    464 
    465     private void showMessageDialog(String message) {
    466         mDialogMessage = message;
    467         removeDialog(MESSAGE_DIALOG_ID);
    468         showDialog(MESSAGE_DIALOG_ID);
    469     }
    470 
    471     private class TestHasFeaturesCallback implements AccountManagerCallback<Boolean> {
    472         public void run(AccountManagerFuture<Boolean> future) {
    473             try {
    474                 Boolean hasFeatures = future.getResult();
    475                 Log.d(TAG, "hasFeatures: " + hasFeatures);
    476                 showMessageDialog("hasFeatures: " + hasFeatures);
    477             } catch (OperationCanceledException e) {
    478                 Log.d(TAG, "interrupted");
    479                 showMessageDialog("operation was canceled");
    480             } catch (IOException e) {
    481                 Log.d(TAG, "error", e);
    482                 showMessageDialog("operation got an IOException");
    483             } catch (AuthenticatorException e) {
    484                 Log.d(TAG, "error", e);
    485                 showMessageDialog("operation got an AuthenticationException");
    486             }
    487         }
    488     }
    489 
    490     private static class CallbackToDialog implements AccountManagerCallback<Bundle> {
    491         private final AccountsTester mActivity;
    492         private final String mLabel;
    493 
    494         private CallbackToDialog(AccountsTester activity, String label) {
    495             mActivity = activity;
    496             mLabel = label;
    497         }
    498 
    499         public void run(AccountManagerFuture<Bundle> future) {
    500             mActivity.getAndLogResult(future, mLabel);
    501         }
    502     }
    503 
    504     private Bundle getAndLogResult(AccountManagerFuture<Bundle> future, String label) {
    505         try {
    506             Bundle result = future.getResult();
    507             result.keySet();
    508             Log.d(TAG, label + ": " + result);
    509             StringBuffer sb = new StringBuffer();
    510             sb.append(label).append(" result:");
    511             for (String key : result.keySet()) {
    512                 Object value = result.get(key);
    513                 if (AccountManager.KEY_AUTHTOKEN.equals(key)) {
    514                     value = "<redacted>";
    515                 }
    516                 sb.append("\n  ").append(key).append(" -> ").append(value);
    517             }
    518             showMessageDialog(sb.toString());
    519             return result;
    520         } catch (OperationCanceledException e) {
    521             Log.d(TAG, label + " failed", e);
    522             showMessageDialog(label + " was canceled");
    523             return null;
    524         } catch (IOException e) {
    525             Log.d(TAG, label + " failed", e);
    526             showMessageDialog(label + " failed with IOException: " + e.getMessage());
    527             return null;
    528         } catch (AuthenticatorException e) {
    529             Log.d(TAG, label + " failed", e);
    530             showMessageDialog(label + " failed with an AuthenticatorException: " + e.getMessage());
    531             return null;
    532         }
    533     }
    534 }
    535