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 */
16package android.accounts;
17
18import com.google.android.collect.Sets;
19
20import android.app.Activity;
21import android.app.ActivityManager;
22import android.content.Intent;
23import android.os.Bundle;
24import android.os.IBinder;
25import android.os.Parcelable;
26import android.os.RemoteException;
27import android.os.UserHandle;
28import android.os.UserManager;
29import android.text.TextUtils;
30import android.util.Log;
31import android.view.View;
32import android.view.Window;
33import android.widget.AdapterView;
34import android.widget.ArrayAdapter;
35import android.widget.Button;
36import android.widget.ListView;
37import android.widget.TextView;
38
39import com.android.internal.R;
40
41import java.io.IOException;
42import java.util.ArrayList;
43import java.util.HashSet;
44import java.util.LinkedHashMap;
45import java.util.Map;
46import java.util.Set;
47
48/**
49 * @hide
50 */
51public class ChooseTypeAndAccountActivity extends Activity
52        implements AccountManagerCallback<Bundle> {
53    private static final String TAG = "AccountChooser";
54
55    /**
56     * A Parcelable ArrayList of Account objects that limits the choosable accounts to those
57     * in this list, if this parameter is supplied.
58     */
59    public static final String EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST = "allowableAccounts";
60
61    /**
62     * A Parcelable ArrayList of String objects that limits the accounts to choose to those
63     * that match the types in this list, if this parameter is supplied. This list is also
64     * used to filter the allowable account types if add account is selected.
65     */
66    public static final String EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY = "allowableAccountTypes";
67
68    /**
69     * This is passed as the addAccountOptions parameter in AccountManager.addAccount()
70     * if it is called.
71     */
72    public static final String EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE = "addAccountOptions";
73
74    /**
75     * This is passed as the requiredFeatures parameter in AccountManager.addAccount()
76     * if it is called.
77     */
78    public static final String EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY =
79            "addAccountRequiredFeatures";
80
81    /**
82     * This is passed as the authTokenType string in AccountManager.addAccount()
83     * if it is called.
84     */
85    public static final String EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING = "authTokenType";
86
87    /**
88     * If set then the specified account is already "selected".
89     */
90    public static final String EXTRA_SELECTED_ACCOUNT = "selectedAccount";
91
92    /**
93     * Deprecated. Providing this extra to {@link ChooseTypeAndAccountActivity}
94     * will have no effect.
95     */
96    @Deprecated
97    public static final String EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT =
98            "alwaysPromptForAccount";
99
100    /**
101     * If set then this string will be used as the description rather than
102     * the default.
103     */
104    public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE = "descriptionTextOverride";
105
106    public static final int REQUEST_NULL = 0;
107    public static final int REQUEST_CHOOSE_TYPE = 1;
108    public static final int REQUEST_ADD_ACCOUNT = 2;
109
110    private static final String KEY_INSTANCE_STATE_PENDING_REQUEST = "pendingRequest";
111    private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts";
112    private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName";
113    private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount";
114    private static final String KEY_INSTANCE_STATE_ACCOUNTS_LIST = "accountsList";
115    private static final String KEY_INSTANCE_STATE_VISIBILITY_LIST = "visibilityList";
116
117    private static final int SELECTED_ITEM_NONE = -1;
118
119    private Set<Account> mSetOfAllowableAccounts;
120    private Set<String> mSetOfRelevantAccountTypes;
121    private String mSelectedAccountName = null;
122    private boolean mSelectedAddNewAccount = false;
123    private String mDescriptionOverride;
124
125    private LinkedHashMap<Account, Integer> mAccounts;
126    // TODO Redesign flow to show NOT_VISIBLE accounts
127    // and display a warning if they are selected.
128    // Currently NOT_VISBILE accounts are not shown at all.
129    private ArrayList<Account> mPossiblyVisibleAccounts;
130    private int mPendingRequest = REQUEST_NULL;
131    private Parcelable[] mExistingAccounts = null;
132    private int mSelectedItemIndex;
133    private Button mOkButton;
134    private int mCallingUid;
135    private String mCallingPackage;
136    private boolean mDisallowAddAccounts;
137    private boolean mDontShowPicker;
138
139    @Override
140    public void onCreate(Bundle savedInstanceState) {
141        if (Log.isLoggable(TAG, Log.VERBOSE)) {
142            Log.v(TAG, "ChooseTypeAndAccountActivity.onCreate(savedInstanceState="
143                    + savedInstanceState + ")");
144        }
145
146        String message = null;
147
148        try {
149            IBinder activityToken = getActivityToken();
150            mCallingUid = ActivityManager.getService().getLaunchedFromUid(activityToken);
151            mCallingPackage = ActivityManager.getService().getLaunchedFromPackage(
152                    activityToken);
153            if (mCallingUid != 0 && mCallingPackage != null) {
154                Bundle restrictions = UserManager.get(this)
155                        .getUserRestrictions(new UserHandle(UserHandle.getUserId(mCallingUid)));
156                mDisallowAddAccounts =
157                        restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false);
158            }
159        } catch (RemoteException re) {
160            // Couldn't figure out caller details
161            Log.w(getClass().getSimpleName(), "Unable to get caller identity \n" + re);
162        }
163
164        // save some items we use frequently
165        final Intent intent = getIntent();
166
167        mSetOfAllowableAccounts = getAllowableAccountSet(intent);
168        mSetOfRelevantAccountTypes = getReleventAccountTypes(intent);
169        mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE);
170
171        if (savedInstanceState != null) {
172            mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST);
173            mExistingAccounts =
174                    savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS);
175
176            // Makes sure that any user selection is preserved across orientation changes.
177            mSelectedAccountName =
178                    savedInstanceState.getString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
179            mSelectedAddNewAccount =
180                    savedInstanceState.getBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
181            // restore mAccounts
182            Parcelable[] accounts =
183                savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_ACCOUNTS_LIST);
184            ArrayList<Integer> visibility =
185                savedInstanceState.getIntegerArrayList(KEY_INSTANCE_STATE_VISIBILITY_LIST);
186            mAccounts = new LinkedHashMap<>();
187            for (int i = 0; i < accounts.length; i++) {
188                mAccounts.put((Account) accounts[i], visibility.get(i));
189            }
190        } else {
191            mPendingRequest = REQUEST_NULL;
192            mExistingAccounts = null;
193            // If the selected account as specified in the intent matches one in the list we will
194            // show is as pre-selected.
195            Account selectedAccount = (Account) intent.getParcelableExtra(EXTRA_SELECTED_ACCOUNT);
196            if (selectedAccount != null) {
197                mSelectedAccountName = selectedAccount.name;
198            }
199            mAccounts = getAcceptableAccountChoices(AccountManager.get(this));
200        }
201
202        if (Log.isLoggable(TAG, Log.VERBOSE)) {
203            Log.v(TAG, "selected account name is " + mSelectedAccountName);
204        }
205
206        mPossiblyVisibleAccounts = new ArrayList<>(mAccounts.size());
207        for (Map.Entry<Account, Integer> entry : mAccounts.entrySet()) {
208            if (AccountManager.VISIBILITY_NOT_VISIBLE != entry.getValue()) {
209                mPossiblyVisibleAccounts.add(entry.getKey());
210            }
211        }
212
213        if (mPossiblyVisibleAccounts.isEmpty() && mDisallowAddAccounts) {
214            requestWindowFeature(Window.FEATURE_NO_TITLE);
215            setContentView(R.layout.app_not_authorized);
216            mDontShowPicker = true;
217        }
218
219        if (mDontShowPicker) {
220            super.onCreate(savedInstanceState);
221            return;
222        }
223
224        // In cases where the activity does not need to show an account picker, cut the chase
225        // and return the result directly. Eg:
226        // Single account -> select it directly
227        // No account -> launch add account activity directly
228        if (mPendingRequest == REQUEST_NULL) {
229            // If there are no relevant accounts and only one relevant account type go directly to
230            // add account. Otherwise let the user choose.
231            if (mPossiblyVisibleAccounts.isEmpty()) {
232                setNonLabelThemeAndCallSuperCreate(savedInstanceState);
233                if (mSetOfRelevantAccountTypes.size() == 1) {
234                    runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next());
235                } else {
236                    startChooseAccountTypeActivity();
237                }
238            }
239        }
240
241        String[] listItems = getListOfDisplayableOptions(mPossiblyVisibleAccounts);
242        mSelectedItemIndex = getItemIndexToSelect(mPossiblyVisibleAccounts, mSelectedAccountName,
243                mSelectedAddNewAccount);
244
245        super.onCreate(savedInstanceState);
246        setContentView(R.layout.choose_type_and_account);
247        overrideDescriptionIfSupplied(mDescriptionOverride);
248        populateUIAccountList(listItems);
249
250        // Only enable "OK" button if something has been selected.
251        mOkButton = findViewById(android.R.id.button2);
252        mOkButton.setEnabled(mSelectedItemIndex != SELECTED_ITEM_NONE);
253    }
254
255    @Override
256    protected void onDestroy() {
257        if (Log.isLoggable(TAG, Log.VERBOSE)) {
258            Log.v(TAG, "ChooseTypeAndAccountActivity.onDestroy()");
259        }
260        super.onDestroy();
261    }
262
263    @Override
264    protected void onSaveInstanceState(final Bundle outState) {
265        super.onSaveInstanceState(outState);
266        outState.putInt(KEY_INSTANCE_STATE_PENDING_REQUEST, mPendingRequest);
267        if (mPendingRequest == REQUEST_ADD_ACCOUNT) {
268            outState.putParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS, mExistingAccounts);
269        }
270        if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
271            if (mSelectedItemIndex == mPossiblyVisibleAccounts.size()) {
272                outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, true);
273            } else {
274                outState.putBoolean(KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
275                outState.putString(KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME,
276                        mPossiblyVisibleAccounts.get(mSelectedItemIndex).name);
277            }
278        }
279        // save mAccounts
280        Parcelable[] accounts = new Parcelable[mAccounts.size()];
281        ArrayList<Integer> visibility = new ArrayList<>(mAccounts.size());
282        int i = 0;
283        for (Map.Entry<Account, Integer> e : mAccounts.entrySet()) {
284            accounts[i++] = e.getKey();
285            visibility.add(e.getValue());
286        }
287        outState.putParcelableArray(KEY_INSTANCE_STATE_ACCOUNTS_LIST, accounts);
288        outState.putIntegerArrayList(KEY_INSTANCE_STATE_VISIBILITY_LIST, visibility);
289    }
290
291    public void onCancelButtonClicked(View view) {
292        onBackPressed();
293    }
294
295    public void onOkButtonClicked(View view) {
296        if (mSelectedItemIndex == mPossiblyVisibleAccounts.size()) {
297            // Selected "Add New Account" option
298            startChooseAccountTypeActivity();
299        } else if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
300            onAccountSelected(mPossiblyVisibleAccounts.get(mSelectedItemIndex));
301        }
302    }
303
304    // Called when the choose account type activity (for adding an account) returns.
305    // If it was a success read the account and set it in the result. In all cases
306    // return the result and finish this activity.
307    @Override
308    protected void onActivityResult(final int requestCode, final int resultCode,
309            final Intent data) {
310        if (Log.isLoggable(TAG, Log.VERBOSE)) {
311            if (data != null && data.getExtras() != null) data.getExtras().keySet();
312            Bundle extras = data != null ? data.getExtras() : null;
313            Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult(reqCode=" + requestCode
314                    + ", resCode=" + resultCode + ", extras=" + extras + ")");
315        }
316
317        // we got our result, so clear the fact that we had a pending request
318        mPendingRequest = REQUEST_NULL;
319
320        if (resultCode == RESULT_CANCELED) {
321            // if canceling out of addAccount and the original state caused us to skip this,
322            // finish this activity
323            if (mPossiblyVisibleAccounts.isEmpty()) {
324                setResult(Activity.RESULT_CANCELED);
325                finish();
326            }
327            return;
328        }
329
330        if (resultCode == RESULT_OK) {
331            if (requestCode == REQUEST_CHOOSE_TYPE) {
332                if (data != null) {
333                    String accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
334                    if (accountType != null) {
335                        runAddAccountForAuthenticator(accountType);
336                        return;
337                    }
338                }
339                Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find account "
340                        + "type, pretending the request was canceled");
341            } else if (requestCode == REQUEST_ADD_ACCOUNT) {
342                String accountName = null;
343                String accountType = null;
344
345                if (data != null) {
346                    accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
347                    accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
348                }
349
350                if (accountName == null || accountType == null) {
351                    // new account was added.
352                    Account[] currentAccounts = AccountManager.get(this).getAccountsForPackage(
353                            mCallingPackage, mCallingUid);
354                    Set<Account> preExistingAccounts = new HashSet<Account>();
355                    for (Parcelable accountParcel : mExistingAccounts) {
356                        preExistingAccounts.add((Account) accountParcel);
357                    }
358                    for (Account account : currentAccounts) {
359                        // New account is visible to the app - return it.
360                        if (!preExistingAccounts.contains(account)) {
361                            accountName = account.name;
362                            accountType = account.type;
363                            break;
364                        }
365                    }
366                }
367
368                if (accountName != null || accountType != null) {
369                    setResultAndFinish(accountName, accountType);
370                    return;
371                }
372            }
373            Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find added "
374                    + "account, pretending the request was canceled");
375        }
376        if (Log.isLoggable(TAG, Log.VERBOSE)) {
377            Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult: canceled");
378        }
379        setResult(Activity.RESULT_CANCELED);
380        finish();
381    }
382
383    protected void runAddAccountForAuthenticator(String type) {
384        if (Log.isLoggable(TAG, Log.VERBOSE)) {
385            Log.v(TAG, "runAddAccountForAuthenticator: " + type);
386        }
387        final Bundle options = getIntent().getBundleExtra(
388                ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE);
389        final String[] requiredFeatures = getIntent().getStringArrayExtra(
390                ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY);
391        final String authTokenType = getIntent().getStringExtra(
392                ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING);
393        AccountManager.get(this).addAccount(type, authTokenType, requiredFeatures,
394                options, null /* activity */, this /* callback */, null /* Handler */);
395    }
396
397    @Override
398    public void run(final AccountManagerFuture<Bundle> accountManagerFuture) {
399        try {
400            final Bundle accountManagerResult = accountManagerFuture.getResult();
401            final Intent intent = (Intent)accountManagerResult.getParcelable(
402                    AccountManager.KEY_INTENT);
403            if (intent != null) {
404                mPendingRequest = REQUEST_ADD_ACCOUNT;
405                mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage,
406                        mCallingUid);
407                intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
408                startActivityForResult(intent, REQUEST_ADD_ACCOUNT);
409                return;
410            }
411        } catch (OperationCanceledException e) {
412            setResult(Activity.RESULT_CANCELED);
413            finish();
414            return;
415        } catch (IOException e) {
416        } catch (AuthenticatorException e) {
417        }
418        Bundle bundle = new Bundle();
419        bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "error communicating with server");
420        setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
421        finish();
422    }
423
424    /**
425     * The default activity theme shows label at the top. Set a theme which does
426     * not show label, which effectively makes the activity invisible. Note that
427     * no content is being set. If something gets set, using this theme may be
428     * useless.
429     */
430    private void setNonLabelThemeAndCallSuperCreate(Bundle savedInstanceState) {
431        setTheme(R.style.Theme_DeviceDefault_Light_Dialog_NoActionBar);
432        super.onCreate(savedInstanceState);
433    }
434
435    private void onAccountSelected(Account account) {
436      Log.d(TAG, "selected account " + account);
437      setResultAndFinish(account.name, account.type);
438    }
439
440    private void setResultAndFinish(final String accountName, final String accountType) {
441        // Mark account as visible since user chose it.
442        Account account = new Account(accountName, accountType);
443        Integer oldVisibility =
444            AccountManager.get(this).getAccountVisibility(account, mCallingPackage);
445        if (oldVisibility != null
446                && oldVisibility == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE) {
447            AccountManager.get(this).setAccountVisibility(account, mCallingPackage,
448                    AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
449        }
450
451        if (oldVisibility != null && oldVisibility == AccountManager.VISIBILITY_NOT_VISIBLE) {
452            // Added account is not visible to caller.
453            setResult(Activity.RESULT_CANCELED);
454            finish();
455            return;
456        }
457        Bundle bundle = new Bundle();
458        bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName);
459        bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType);
460        setResult(Activity.RESULT_OK, new Intent().putExtras(bundle));
461        if (Log.isLoggable(TAG, Log.VERBOSE)) {
462            Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: selected account "
463                    + accountName + ", " + accountType);
464        }
465
466        finish();
467    }
468
469    private void startChooseAccountTypeActivity() {
470        if (Log.isLoggable(TAG, Log.VERBOSE)) {
471            Log.v(TAG, "ChooseAccountTypeActivity.startChooseAccountTypeActivity()");
472        }
473        final Intent intent = new Intent(this, ChooseAccountTypeActivity.class);
474        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
475        intent.putExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
476                getIntent().getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY));
477        intent.putExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE,
478                getIntent().getBundleExtra(EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE));
479        intent.putExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY,
480                getIntent().getStringArrayExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY));
481        intent.putExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING,
482                getIntent().getStringExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING));
483        startActivityForResult(intent, REQUEST_CHOOSE_TYPE);
484        mPendingRequest = REQUEST_CHOOSE_TYPE;
485    }
486
487    /**
488     * @return a value between 0 (inclusive) and accounts.size() (inclusive) or SELECTED_ITEM_NONE.
489     *      An index value of accounts.size() indicates 'Add account' option.
490     */
491    private int getItemIndexToSelect(ArrayList<Account> accounts, String selectedAccountName,
492        boolean selectedAddNewAccount) {
493      // If "Add account" option was previously selected by user, preserve it across
494      // orientation changes.
495      if (selectedAddNewAccount) {
496          return accounts.size();
497      }
498      // search for the selected account name if present
499      for (int i = 0; i < accounts.size(); i++) {
500        if (accounts.get(i).name.equals(selectedAccountName)) {
501          return i;
502        }
503      }
504      // no account selected.
505      return SELECTED_ITEM_NONE;
506    }
507
508    private String[] getListOfDisplayableOptions(ArrayList<Account> accounts) {
509      // List of options includes all accounts found together with "Add new account" as the
510      // last item in the list.
511      String[] listItems = new String[accounts.size() + (mDisallowAddAccounts ? 0 : 1)];
512      for (int i = 0; i < accounts.size(); i++) {
513          listItems[i] = accounts.get(i).name;
514      }
515      if (!mDisallowAddAccounts) {
516          listItems[accounts.size()] = getResources().getString(
517                  R.string.add_account_button_label);
518      }
519      return listItems;
520    }
521
522    /**
523     * Create a list of Account objects for each account that is acceptable. Filter out accounts
524     * that don't match the allowable types, if provided, or that don't match the allowable
525     * accounts, if provided.
526     */
527    private LinkedHashMap<Account, Integer> getAcceptableAccountChoices(AccountManager accountManager) {
528        Map<Account, Integer> accountsAndVisibilityForCaller =
529                accountManager.getAccountsAndVisibilityForPackage(mCallingPackage, null);
530        Account[] allAccounts = accountManager.getAccounts();
531        LinkedHashMap<Account, Integer> accountsToPopulate =
532                new LinkedHashMap<>(accountsAndVisibilityForCaller.size());
533        for (Account account : allAccounts) {
534            if (mSetOfAllowableAccounts != null
535                    && !mSetOfAllowableAccounts.contains(account)) {
536                continue;
537            }
538            if (mSetOfRelevantAccountTypes != null
539                    && !mSetOfRelevantAccountTypes.contains(account.type)) {
540                continue;
541            }
542            if (accountsAndVisibilityForCaller.get(account) != null) {
543                accountsToPopulate.put(account, accountsAndVisibilityForCaller.get(account));
544            }
545        }
546        return accountsToPopulate;
547    }
548
549    /**
550     * Return a set of account types specified by the intent as well as supported by the
551     * AccountManager.
552     */
553    private Set<String> getReleventAccountTypes(final Intent intent) {
554      // An account type is relevant iff it is allowed by the caller and supported by the account
555      // manager.
556      Set<String> setOfRelevantAccountTypes = null;
557      final String[] allowedAccountTypes =
558              intent.getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY);
559        AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes();
560        Set<String> supportedAccountTypes = new HashSet<String>(descs.length);
561        for (AuthenticatorDescription desc : descs) {
562            supportedAccountTypes.add(desc.type);
563        }
564        if (allowedAccountTypes != null) {
565            setOfRelevantAccountTypes = Sets.newHashSet(allowedAccountTypes);
566            setOfRelevantAccountTypes.retainAll(supportedAccountTypes);
567        } else {
568            setOfRelevantAccountTypes = supportedAccountTypes;
569      }
570      return setOfRelevantAccountTypes;
571    }
572
573    /**
574     * Returns a set of whitelisted accounts given by the intent or null if none specified by the
575     * intent.
576     */
577    private Set<Account> getAllowableAccountSet(final Intent intent) {
578      Set<Account> setOfAllowableAccounts = null;
579      final ArrayList<Parcelable> validAccounts =
580              intent.getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST);
581      if (validAccounts != null) {
582          setOfAllowableAccounts = new HashSet<Account>(validAccounts.size());
583          for (Parcelable parcelable : validAccounts) {
584              setOfAllowableAccounts.add((Account)parcelable);
585          }
586      }
587      return setOfAllowableAccounts;
588    }
589
590    /**
591     * Overrides the description text view for the picker activity if specified by the intent.
592     * If not specified then makes the description invisible.
593     */
594    private void overrideDescriptionIfSupplied(String descriptionOverride) {
595      TextView descriptionView = findViewById(R.id.description);
596      if (!TextUtils.isEmpty(descriptionOverride)) {
597          descriptionView.setText(descriptionOverride);
598      } else {
599          descriptionView.setVisibility(View.GONE);
600      }
601    }
602
603    /**
604     * Populates the UI ListView with the given list of items and selects an item
605     * based on {@code mSelectedItemIndex} member variable.
606     */
607    private final void populateUIAccountList(String[] listItems) {
608      ListView list = findViewById(android.R.id.list);
609      list.setAdapter(new ArrayAdapter<String>(this,
610              android.R.layout.simple_list_item_single_choice, listItems));
611      list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
612      list.setItemsCanFocus(false);
613      list.setOnItemClickListener(
614              new AdapterView.OnItemClickListener() {
615                  @Override
616                  public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
617                      mSelectedItemIndex = position;
618                      mOkButton.setEnabled(true);
619                  }
620              });
621      if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
622          list.setItemChecked(mSelectedItemIndex, true);
623          if (Log.isLoggable(TAG, Log.VERBOSE)) {
624              Log.v(TAG, "List item " + mSelectedItemIndex + " should be selected");
625          }
626      }
627    }
628}
629