• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onCreate(Bundle savedInstanceState)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
onDestroy()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
onSaveInstanceState(final Bundle outState)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 
onCancelButtonClicked(View view)320     public void onCancelButtonClicked(View view) {
321         onBackPressed();
322     }
323 
onOkButtonClicked(View view)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
onActivityResult(final int requestCode, final int resultCode, final Intent data)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 
runAddAccountForAuthenticator(String type)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
run(final AccountManagerFuture<Bundle> accountManagerFuture)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 
onAccountSelected(Account account)449     private void onAccountSelected(Account account) {
450       Log.d(TAG, "selected account " + account);
451       setResultAndFinish(account.name, account.type);
452     }
453 
setResultAndFinish(final String accountName, final String accountType)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 
startChooseAccountTypeActivity()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