1/*
2 * Copyright (C) 2010 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
17package com.android.settings.accounts;
18
19import android.accounts.AccountManager;
20import android.accounts.AuthenticatorDescription;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.SyncAdapterType;
25import android.content.pm.PackageManager;
26import android.content.res.Resources;
27import android.graphics.drawable.Drawable;
28import android.os.Bundle;
29import android.preference.Preference;
30import android.preference.PreferenceActivity;
31import android.preference.PreferenceGroup;
32import android.preference.PreferenceScreen;
33import android.util.Log;
34import com.android.internal.util.CharSequences;
35import com.android.settings.R;
36import com.google.android.collect.Maps;
37
38import java.util.ArrayList;
39import java.util.Collections;
40import java.util.HashMap;
41import java.util.HashSet;
42import java.util.Map;
43
44/**
45 * Activity asking a user to select an account to be set up.
46 */
47public class ChooseAccountActivity extends PreferenceActivity {
48
49    private static final String TAG = "ChooseAccountActivity";
50    private String[] mAuthorities;
51    private PreferenceGroup mAddAccountGroup;
52    private final ArrayList<ProviderEntry> mProviderList = new ArrayList<ProviderEntry>();
53    public HashSet<String> mAccountTypesFilter;
54    private AuthenticatorDescription[] mAuthDescs;
55    private HashMap<String, ArrayList<String>> mAccountTypeToAuthorities = null;
56    private Map<String, AuthenticatorDescription> mTypeToAuthDescription
57            = new HashMap<String, AuthenticatorDescription>();
58
59    private static class ProviderEntry implements Comparable<ProviderEntry> {
60        private final CharSequence name;
61        private final String type;
62        ProviderEntry(CharSequence providerName, String accountType) {
63            name = providerName;
64            type = accountType;
65        }
66
67        public int compareTo(ProviderEntry another) {
68            if (name == null) {
69                return -1;
70            }
71            if (another.name == null) {
72                return +1;
73            }
74            return CharSequences.compareToIgnoreCase(name, another.name);
75        }
76    }
77
78    @Override
79    protected void onCreate(Bundle icicle) {
80        super.onCreate(icicle);
81
82        setContentView(R.layout.add_account_screen);
83        addPreferencesFromResource(R.xml.add_account_settings);
84        mAuthorities = getIntent().getStringArrayExtra(
85                AccountPreferenceBase.AUTHORITIES_FILTER_KEY);
86        String[] accountTypesFilter = getIntent().getStringArrayExtra(
87                AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY);
88        if (accountTypesFilter != null) {
89            mAccountTypesFilter = new HashSet<String>();
90            for (String accountType : accountTypesFilter) {
91                mAccountTypesFilter.add(accountType);
92            }
93        }
94        mAddAccountGroup = getPreferenceScreen();
95        updateAuthDescriptions();
96    }
97
98    /**
99     * Updates provider icons. Subclasses should call this in onCreate()
100     * and update any UI that depends on AuthenticatorDescriptions in onAuthDescriptionsUpdated().
101     */
102    private void updateAuthDescriptions() {
103        mAuthDescs = AccountManager.get(this).getAuthenticatorTypes();
104        for (int i = 0; i < mAuthDescs.length; i++) {
105            mTypeToAuthDescription.put(mAuthDescs[i].type, mAuthDescs[i]);
106        }
107        onAuthDescriptionsUpdated();
108    }
109
110    private void onAuthDescriptionsUpdated() {
111        // Create list of providers to show on preference screen
112        for (int i = 0; i < mAuthDescs.length; i++) {
113            String accountType = mAuthDescs[i].type;
114            CharSequence providerName = getLabelForType(accountType);
115
116            // Skip preferences for authorities not specified. If no authorities specified,
117            // then include them all.
118            ArrayList<String> accountAuths = getAuthoritiesForAccountType(accountType);
119            boolean addAccountPref = true;
120            if (mAuthorities != null && mAuthorities.length > 0 && accountAuths != null) {
121                addAccountPref = false;
122                for (int k = 0; k < mAuthorities.length; k++) {
123                    if (accountAuths.contains(mAuthorities[k])) {
124                        addAccountPref = true;
125                        break;
126                    }
127                }
128            }
129            if (addAccountPref && mAccountTypesFilter != null
130                    && !mAccountTypesFilter.contains(accountType)) {
131                addAccountPref = false;
132            }
133            if (addAccountPref) {
134                mProviderList.add(new ProviderEntry(providerName, accountType));
135            } else {
136                if (Log.isLoggable(TAG, Log.VERBOSE)) {
137                    Log.v(TAG, "Skipped pref " + providerName + ": has no authority we need");
138                }
139            }
140        }
141
142        if (mProviderList.size() == 1) {
143            // If there's only one provider that matches, just run it.
144            finishWithAccountType(mProviderList.get(0).type);
145        } else if (mProviderList.size() > 0) {
146            Collections.sort(mProviderList);
147            mAddAccountGroup.removeAll();
148            for (ProviderEntry pref : mProviderList) {
149                Drawable drawable = getDrawableForType(pref.type);
150                ProviderPreference p =
151                        new ProviderPreference(this, pref.type, drawable, pref.name);
152                mAddAccountGroup.addPreference(p);
153            }
154        } else {
155            if (Log.isLoggable(TAG, Log.VERBOSE)) {
156                final StringBuilder auths = new StringBuilder();
157                for (String a : mAuthorities) {
158                    auths.append(a);
159                    auths.append(' ');
160                }
161                Log.v(TAG, "No providers found for authorities: " + auths);
162            }
163            setResult(RESULT_CANCELED);
164            finish();
165        }
166    }
167
168    public ArrayList<String> getAuthoritiesForAccountType(String type) {
169        if (mAccountTypeToAuthorities == null) {
170            mAccountTypeToAuthorities = Maps.newHashMap();
171            SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypes();
172            for (int i = 0, n = syncAdapters.length; i < n; i++) {
173                final SyncAdapterType sa = syncAdapters[i];
174                ArrayList<String> authorities = mAccountTypeToAuthorities.get(sa.accountType);
175                if (authorities == null) {
176                    authorities = new ArrayList<String>();
177                    mAccountTypeToAuthorities.put(sa.accountType, authorities);
178                }
179                if (Log.isLoggable(TAG, Log.VERBOSE)) {
180                    Log.d(TAG, "added authority " + sa.authority + " to accountType "
181                            + sa.accountType);
182                }
183                authorities.add(sa.authority);
184            }
185        }
186        return mAccountTypeToAuthorities.get(type);
187    }
188
189    /**
190     * Gets an icon associated with a particular account type. If none found, return null.
191     * @param accountType the type of account
192     * @return a drawable for the icon or null if one cannot be found.
193     */
194    protected Drawable getDrawableForType(final String accountType) {
195        Drawable icon = null;
196        if (mTypeToAuthDescription.containsKey(accountType)) {
197            try {
198                AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
199                Context authContext = createPackageContext(desc.packageName, 0);
200                icon = authContext.getResources().getDrawable(desc.iconId);
201            } catch (PackageManager.NameNotFoundException e) {
202                // TODO: place holder icon for missing account icons?
203                Log.w(TAG, "No icon name for account type " + accountType);
204            } catch (Resources.NotFoundException e) {
205                // TODO: place holder icon for missing account icons?
206                Log.w(TAG, "No icon resource for account type " + accountType);
207            }
208        }
209        return icon;
210    }
211
212    /**
213     * Gets the label associated with a particular account type. If none found, return null.
214     * @param accountType the type of account
215     * @return a CharSequence for the label or null if one cannot be found.
216     */
217    protected CharSequence getLabelForType(final String accountType) {
218        CharSequence label = null;
219        if (mTypeToAuthDescription.containsKey(accountType)) {
220            try {
221                AuthenticatorDescription desc = mTypeToAuthDescription.get(accountType);
222                Context authContext = createPackageContext(desc.packageName, 0);
223                label = authContext.getResources().getText(desc.labelId);
224            } catch (PackageManager.NameNotFoundException e) {
225                Log.w(TAG, "No label name for account type " + accountType);
226            } catch (Resources.NotFoundException e) {
227                Log.w(TAG, "No label resource for account type " + accountType);
228            }
229        }
230        return label;
231    }
232
233    @Override
234    public boolean onPreferenceTreeClick(PreferenceScreen preferences, Preference preference) {
235        if (preference instanceof ProviderPreference) {
236            ProviderPreference pref = (ProviderPreference) preference;
237            if (Log.isLoggable(TAG, Log.VERBOSE)) {
238                Log.v(TAG, "Attempting to add account of type " + pref.getAccountType());
239            }
240            finishWithAccountType(pref.getAccountType());
241        }
242        return true;
243    }
244
245    private void finishWithAccountType(String accountType) {
246        Intent intent = new Intent();
247        intent.putExtra(AddAccountSettings.EXTRA_SELECTED_ACCOUNT, accountType);
248        setResult(RESULT_OK, intent);
249        finish();
250    }
251}
252