• 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.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
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         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
onDestroy()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
onSaveInstanceState(final Bundle outState)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
onActivityResult(final int requestCode, final int resultCode, final Intent data)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 
runAddAccountForAuthenticator(String type)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 
run(final AccountManagerFuture<Bundle> accountManagerFuture)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 
getDrawableForType( final HashMap<String, AuthenticatorDescription> typeToAuthDescription, String accountType)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 
onListItemClick(ListView l, View v, int position, long id)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 
setResultAndFinish(final String accountName, final String accountType)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 
startChooseAccountTypeActivity()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 
AccountInfo(Account account, Drawable drawable, boolean checked)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 
AccountArrayAdapter(Context context, int textViewResourceId, ArrayList<AccountInfo> infos)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
getView(int position, View convertView, ViewGroup parent)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