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