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