Home | History | Annotate | Download | only in accounts
      1 /*
      2  * Copyright (C) 2011 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 package android.accounts;
     17 
     18 import android.app.Activity;
     19 import android.content.Intent;
     20 import android.os.Bundle;
     21 import android.os.Parcelable;
     22 import android.text.TextUtils;
     23 import android.util.Log;
     24 import android.view.View;
     25 import android.widget.AdapterView;
     26 import android.widget.ArrayAdapter;
     27 import android.widget.Button;
     28 import android.widget.ListView;
     29 import android.widget.TextView;
     30 
     31 import com.android.internal.R;
     32 
     33 import java.io.IOException;
     34 import java.util.ArrayList;
     35 import java.util.HashMap;
     36 import java.util.HashSet;
     37 import java.util.Set;
     38 
     39 /**
     40  * @hide
     41  */
     42 public class ChooseTypeAndAccountActivity extends Activity
     43         implements AccountManagerCallback<Bundle> {
     44     private static final String TAG = "AccountChooser";
     45 
     46     /**
     47      * A Parcelable ArrayList of Account objects that limits the choosable accounts to those
     48      * in this list, if this parameter is supplied.
     49      */
     50     public static final String EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST = "allowableAccounts";
     51 
     52     /**
     53      * A Parcelable ArrayList of String objects that limits the accounts to choose to those
     54      * that match the types in this list, if this parameter is supplied. This list is also
     55      * used to filter the allowable account types if add account is selected.
     56      */
     57     public static final String EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY = "allowableAccountTypes";
     58 
     59     /**
     60      * This is passed as the addAccountOptions parameter in AccountManager.addAccount()
     61      * if it is called.
     62      */
     63     public static final String EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE = "addAccountOptions";
     64 
     65     /**
     66      * This is passed as the requiredFeatures parameter in AccountManager.addAccount()
     67      * if it is called.
     68      */
     69     public static final String EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY =
     70             "addAccountRequiredFeatures";
     71 
     72     /**
     73      * This is passed as the authTokenType string in AccountManager.addAccount()
     74      * if it is called.
     75      */
     76     public static final String EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING = "authTokenType";
     77 
     78     /**
     79      * If set then the specified account is already "selected".
     80      */
     81     public static final String EXTRA_SELECTED_ACCOUNT = "selectedAccount";
     82 
     83     /**
     84      * If true then display the account selection list even if there is just
     85      * one account to choose from. boolean.
     86      */
     87     public static final String EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT =
     88             "alwaysPromptForAccount";
     89 
     90     /**
     91      * If set then this string willb e used as the description rather than
     92      * the default.
     93      */
     94     public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE =
     95             "descriptionTextOverride";
     96 
     97     public static final int REQUEST_NULL = 0;
     98     public static final int REQUEST_CHOOSE_TYPE = 1;
     99     public static final int REQUEST_ADD_ACCOUNT = 2;
    100 
    101     private static final String KEY_INSTANCE_STATE_PENDING_REQUEST = "pendingRequest";
    102     private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts";
    103     private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName";
    104     private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount";
    105 
    106     private static final int SELECTED_ITEM_NONE = -1;
    107 
    108     private ArrayList<Account> mAccounts;
    109     private int mPendingRequest = REQUEST_NULL;
    110     private Parcelable[] mExistingAccounts = null;
    111     private int mSelectedItemIndex;
    112     private Button mOkButton;
    113 
    114     @Override
    115     public void onCreate(Bundle savedInstanceState) {
    116         super.onCreate(savedInstanceState);
    117         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    118             Log.v(TAG, "ChooseTypeAndAccountActivity.onCreate(savedInstanceState="
    119                     + savedInstanceState + ")");
    120         }
    121 
    122         // save some items we use frequently
    123         final AccountManager accountManager = AccountManager.get(this);
    124         final Intent intent = getIntent();
    125 
    126         String selectedAccountName = null;
    127         boolean selectedAddNewAccount = false;
    128 
    129         if (savedInstanceState != null) {
    130             mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST);
    131             mExistingAccounts =
    132                     savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS);
    133 
    134             // Makes sure that any user selection is preserved across orientation changes.
    135             selectedAccountName = savedInstanceState.getString(
    136                     KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
    137 
    138             selectedAddNewAccount = savedInstanceState.getBoolean(
    139                     KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
    140         } else {
    141             mPendingRequest = REQUEST_NULL;
    142             mExistingAccounts = null;
    143             // If the selected account as specified in the intent matches one in the list we will
    144             // show is as pre-selected.
    145             Account selectedAccount = (Account) intent.getParcelableExtra(EXTRA_SELECTED_ACCOUNT);
    146             if (selectedAccount != null) {
    147                 selectedAccountName = selectedAccount.name;
    148             }
    149         }
    150 
    151         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    152             Log.v(TAG, "selected account name is " + selectedAccountName);
    153         }
    154 
    155         // build an efficiently queryable map of account types to authenticator descriptions
    156         final HashMap<String, AuthenticatorDescription> typeToAuthDescription =
    157                 new HashMap<String, AuthenticatorDescription>();
    158         for(AuthenticatorDescription desc : accountManager.getAuthenticatorTypes()) {
    159             typeToAuthDescription.put(desc.type, desc);
    160         }
    161 
    162         // Read the validAccounts, if present, and add them to the setOfAllowableAccounts
    163         Set<Account> setOfAllowableAccounts = null;
    164         final ArrayList<Parcelable> validAccounts =
    165                 intent.getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST);
    166         if (validAccounts != null) {
    167             setOfAllowableAccounts = new HashSet<Account>(validAccounts.size());
    168             for (Parcelable parcelable : validAccounts) {
    169                 setOfAllowableAccounts.add((Account)parcelable);
    170             }
    171         }
    172 
    173         // An account type is relevant iff it is allowed by the caller and supported by the account
    174         // manager.
    175         Set<String> setOfRelevantAccountTypes = null;
    176         final String[] allowedAccountTypes =
    177                 intent.getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY);
    178         if (allowedAccountTypes != null) {
    179 
    180             setOfRelevantAccountTypes = new HashSet<String>(allowedAccountTypes.length);
    181             Set<String> setOfAllowedAccountTypes = new HashSet<String>(allowedAccountTypes.length);
    182             for (String type : allowedAccountTypes) {
    183                 setOfAllowedAccountTypes.add(type);
    184             }
    185 
    186             AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes();
    187             Set<String> supportedAccountTypes = new HashSet<String>(descs.length);
    188             for (AuthenticatorDescription desc : descs) {
    189                 supportedAccountTypes.add(desc.type);
    190             }
    191 
    192             for (String acctType : setOfAllowedAccountTypes) {
    193                 if (supportedAccountTypes.contains(acctType)) {
    194                     setOfRelevantAccountTypes.add(acctType);
    195                 }
    196             }
    197         }
    198 
    199         // Create a list of AccountInfo objects for each account that is allowable. Filter out
    200         // accounts that don't match the allowable types, if provided, or that don't match the
    201         // allowable accounts, if provided.
    202         final Account[] accounts = accountManager.getAccounts();
    203         mAccounts = new ArrayList<Account>(accounts.length);
    204         mSelectedItemIndex = SELECTED_ITEM_NONE;
    205         for (Account account : accounts) {
    206             if (setOfAllowableAccounts != null
    207                     && !setOfAllowableAccounts.contains(account)) {
    208                 continue;
    209             }
    210             if (setOfRelevantAccountTypes != null
    211                     && !setOfRelevantAccountTypes.contains(account.type)) {
    212                 continue;
    213             }
    214             if (account.name.equals(selectedAccountName)) {
    215                 mSelectedItemIndex = mAccounts.size();
    216             }
    217             mAccounts.add(account);
    218         }
    219 
    220         if (mPendingRequest == REQUEST_NULL) {
    221             // If there are no relevant accounts and only one relevant account type go directly to
    222             // add account. Otherwise let the user choose.
    223             if (mAccounts.isEmpty()) {
    224                 if (setOfRelevantAccountTypes.size() == 1) {
    225                     runAddAccountForAuthenticator(setOfRelevantAccountTypes.iterator().next());
    226                 } else {
    227                     startChooseAccountTypeActivity();
    228                 }
    229                 return;
    230             }
    231 
    232             // if there is only one allowable account return it
    233             if (!intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false)
    234                     && mAccounts.size() == 1) {
    235                 Account account = mAccounts.get(0);
    236                 setResultAndFinish(account.name, account.type);
    237                 return;
    238             }
    239         }
    240 
    241         // Cannot set content view until we know that mPendingRequest is not null, otherwise
    242         // would cause screen flicker.
    243         setContentView(R.layout.choose_type_and_account);
    244 
    245         // Override the description text if supplied
    246         final String descriptionOverride =
    247                 intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE);
    248         TextView descriptionView = (TextView) findViewById(R.id.description);
    249         if (!TextUtils.isEmpty(descriptionOverride)) {
    250             descriptionView.setText(descriptionOverride);
    251         } else {
    252             descriptionView.setVisibility(View.GONE);
    253         }
    254 
    255         // List of options includes all accounts found together with "Add new account" as the
    256         // last item in the list.
    257         String[] listItems = new String[mAccounts.size() + 1];
    258         for (int i = 0; i < mAccounts.size(); i++) {
    259             listItems[i] = mAccounts.get(i).name;
    260         }
    261         listItems[mAccounts.size()] = getResources().getString(
    262                 R.string.add_account_button_label);
    263 
    264         ListView list = (ListView) findViewById(android.R.id.list);
    265         list.setAdapter(new ArrayAdapter<String>(this,
    266                 android.R.layout.simple_list_item_single_choice, listItems));
    267         list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
    268         list.setItemsCanFocus(false);
    269         list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    270             @Override
    271             public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
    272                 mSelectedItemIndex = position;
    273                 mOkButton.setEnabled(true);
    274             }
    275         });
    276 
    277         // If "Add account" option was previously selected by user, preserve it across
    278         // orientation changes.
    279         if (selectedAddNewAccount) {
    280             mSelectedItemIndex = mAccounts.size();
    281         }
    282         if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
    283             list.setItemChecked(mSelectedItemIndex, true);
    284             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    285                 Log.v(TAG, "List item " + mSelectedItemIndex + " should be selected");
    286             }
    287         }
    288 
    289         // Only enable "OK" button if something has been selected.
    290         mOkButton = (Button) findViewById(android.R.id.button2);
    291         mOkButton.setEnabled(mSelectedItemIndex != SELECTED_ITEM_NONE);
    292     }
    293 
    294     @Override
    295     protected void onDestroy() {
    296         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    297             Log.v(TAG, "ChooseTypeAndAccountActivity.onDestroy()");
    298         }
    299         super.onDestroy();
    300     }
    301 
    302     @Override
    303     protected void onSaveInstanceState(final Bundle outState) {
    304         super.onSaveInstanceState(outState);
    305         outState.putInt(KEY_INSTANCE_STATE_PENDING_REQUEST, mPendingRequest);
    306         if (mPendingRequest == REQUEST_ADD_ACCOUNT) {
    307             outState.putParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS, mExistingAccounts);
    308         }
    309         if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
    310             if (mSelectedItemIndex == mAccounts.size()) {
    311                 outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, true);
    312             } else {
    313                 outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
    314                 outState.putString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME,
    315                         mAccounts.get(mSelectedItemIndex).name);
    316             }
    317         }
    318     }
    319 
    320     public void onCancelButtonClicked(View view) {
    321         onBackPressed();
    322     }
    323 
    324     public void onOkButtonClicked(View view) {
    325         if (mSelectedItemIndex == mAccounts.size()) {
    326             // Selected "Add New Account" option
    327             startChooseAccountTypeActivity();
    328         } else if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
    329             onAccountSelected(mAccounts.get(mSelectedItemIndex));
    330         }
    331     }
    332 
    333     // Called when the choose account type activity (for adding an account) returns.
    334     // If it was a success read the account and set it in the result. In all cases
    335     // return the result and finish this activity.
    336     @Override
    337     protected void onActivityResult(final int requestCode, final int resultCode,
    338             final Intent data) {
    339         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    340             if (data != null && data.getExtras() != null) data.getExtras().keySet();
    341             Bundle extras = data != null ? data.getExtras() : null;
    342             Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult(reqCode=" + requestCode
    343                     + ", resCode=" + resultCode + ", extras=" + extras + ")");
    344         }
    345 
    346         // we got our result, so clear the fact that we had a pending request
    347         mPendingRequest = REQUEST_NULL;
    348 
    349         if (resultCode == RESULT_CANCELED) {
    350             // if canceling out of addAccount and the original state caused us to skip this,
    351             // finish this activity
    352             if (mAccounts.isEmpty()) {
    353                 setResult(Activity.RESULT_CANCELED);
    354                 finish();
    355             }
    356             return;
    357         }
    358 
    359         if (resultCode == RESULT_OK) {
    360             if (requestCode == REQUEST_CHOOSE_TYPE) {
    361                 if (data != null) {
    362                     String accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
    363                     if (accountType != null) {
    364                         runAddAccountForAuthenticator(accountType);
    365                         return;
    366                     }
    367                 }
    368                 Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find account "
    369                         + "type, pretending the request was canceled");
    370             } else if (requestCode == REQUEST_ADD_ACCOUNT) {
    371                 String accountName = null;
    372                 String accountType = null;
    373 
    374                 if (data != null) {
    375                     accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
    376                     accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
    377                 }
    378 
    379                 if (accountName == null || accountType == null) {
    380                     Account[] currentAccounts = AccountManager.get(this).getAccounts();
    381                     Set<Account> preExistingAccounts = new HashSet<Account>();
    382                     for (Parcelable accountParcel : mExistingAccounts) {
    383                         preExistingAccounts.add((Account) accountParcel);
    384                     }
    385                     for (Account account : currentAccounts) {
    386                         if (!preExistingAccounts.contains(account)) {
    387                             accountName = account.name;
    388                             accountType = account.type;
    389                             break;
    390                         }
    391                     }
    392                 }
    393 
    394                 if (accountName != null || accountType != null) {
    395                     setResultAndFinish(accountName, accountType);
    396                     return;
    397                 }
    398             }
    399             Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find added "
    400                     + "account, pretending the request was canceled");
    401         }
    402         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    403             Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult: canceled");
    404         }
    405         setResult(Activity.RESULT_CANCELED);
    406         finish();
    407     }
    408 
    409     protected void runAddAccountForAuthenticator(String type) {
    410         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    411             Log.v(TAG, "runAddAccountForAuthenticator: " + type);
    412         }
    413         final Bundle options = getIntent().getBundleExtra(
    414                 ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE);
    415         final String[] requiredFeatures = getIntent().getStringArrayExtra(
    416                 ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY);
    417         final String authTokenType = getIntent().getStringExtra(
    418                 ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING);
    419         AccountManager.get(this).addAccount(type, authTokenType, requiredFeatures,
    420                 options, null /* activity */, this /* callback */, null /* Handler */);
    421     }
    422 
    423     @Override
    424     public void run(final AccountManagerFuture<Bundle> accountManagerFuture) {
    425         try {
    426             final Bundle accountManagerResult = accountManagerFuture.getResult();
    427             final Intent intent = (Intent)accountManagerResult.getParcelable(
    428                     AccountManager.KEY_INTENT);
    429             if (intent != null) {
    430                 mPendingRequest = REQUEST_ADD_ACCOUNT;
    431                 mExistingAccounts = AccountManager.get(this).getAccounts();
    432                 intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
    433                 startActivityForResult(intent, REQUEST_ADD_ACCOUNT);
    434                 return;
    435             }
    436         } catch (OperationCanceledException e) {
    437             setResult(Activity.RESULT_CANCELED);
    438             finish();
    439             return;
    440         } catch (IOException e) {
    441         } catch (AuthenticatorException e) {
    442         }
    443         Bundle bundle = new Bundle();
    444         bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "error communicating with server");
    445         setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
    446         finish();
    447     }
    448 
    449     private void onAccountSelected(Account account) {
    450       Log.d(TAG, "selected account " + account);
    451       setResultAndFinish(account.name, account.type);
    452     }
    453 
    454     private void setResultAndFinish(final String accountName, final String accountType) {
    455         Bundle bundle = new Bundle();
    456         bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
    457         bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
    458         setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
    459         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    460             Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: "
    461                     + "selected account " + accountName + ", " + accountType);
    462         }
    463         finish();
    464     }
    465 
    466     private void startChooseAccountTypeActivity() {
    467         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    468             Log.v(TAG, "ChooseAccountTypeActivity.startChooseAccountTypeActivity()");
    469         }
    470         final Intent intent = new Intent(this, ChooseAccountTypeActivity.class);
    471         intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
    472         intent.putExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
    473                 getIntent().getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY));
    474         intent.putExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE,
    475                 getIntent().getBundleExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE));
    476         intent.putExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY,
    477                 getIntent().getStringArrayExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY));
    478         intent.putExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING,
    479                 getIntent().getStringExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING));
    480         startActivityForResult(intent, REQUEST_CHOOSE_TYPE);
    481         mPendingRequest = REQUEST_CHOOSE_TYPE;
    482     }
    483 }
    484