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