ContactEditorUtils.java revision 558669dab4109afebd19eade1f95a396215fb44d
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 17package com.android.contacts.editor; 18 19import com.android.contacts.model.AccountType; 20import com.android.contacts.model.AccountTypeManager; 21import com.android.contacts.model.AccountWithDataSet; 22import com.android.contacts.test.NeededForTesting; 23import com.google.common.annotations.VisibleForTesting; 24import com.google.common.collect.ImmutableList; 25import com.google.common.collect.Sets; 26 27import android.accounts.Account; 28import android.accounts.AccountManager; 29import android.app.Activity; 30import android.content.Context; 31import android.content.Intent; 32import android.content.SharedPreferences; 33import android.preference.PreferenceManager; 34import android.text.TextUtils; 35 36import java.util.ArrayList; 37import java.util.List; 38import java.util.Set; 39 40/** 41 * Utility methods for the "account changed" notification in the new contact creation flow. 42 * 43 * TODO Remove all the "@VisibleForTesting"s once they're actually used in the app. 44 * (Until then we need them to avoid "no such method" in tests) 45 */ 46public class ContactEditorUtils { 47 private static final String TAG = "ContactEditorUtils"; 48 49 private static final String KEY_DEFAULT_ACCOUNT = "ContactEditorUtils_default_account"; 50 private static final String KEY_KNOWN_ACCOUNTS = "ContactEditorUtils_known_accounts"; 51 // Key to tell the first time launch. 52 private static final String KEY_ANYTHING_SAVED = "ContactEditorUtils_anything_saved"; 53 54 private static final List<AccountWithDataSet> EMPTY_ACCOUNTS = ImmutableList.of(); 55 56 private static ContactEditorUtils sInstance; 57 58 private final Context mContext; 59 private final SharedPreferences mPrefs; 60 private final AccountTypeManager mAccountTypes; 61 62 private ContactEditorUtils(Context context) { 63 this(context, AccountTypeManager.getInstance(context)); 64 } 65 66 @VisibleForTesting 67 ContactEditorUtils(Context context, AccountTypeManager accountTypes) { 68 mContext = context; 69 mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); 70 mAccountTypes = accountTypes; 71 } 72 73 public static synchronized ContactEditorUtils getInstance(Context context) { 74 if (sInstance == null) { 75 sInstance = new ContactEditorUtils(context); 76 } 77 return sInstance; 78 } 79 80 void cleanupForTest() { 81 mPrefs.edit().remove(KEY_DEFAULT_ACCOUNT).remove(KEY_KNOWN_ACCOUNTS) 82 .remove(KEY_ANYTHING_SAVED).apply(); 83 } 84 85 private List<AccountWithDataSet> getWritableAccounts() { 86 return mAccountTypes.getAccounts(true); 87 } 88 89 /** 90 * @return true if it's the first launch and {@link #saveDefaultAndAllAccounts} has never 91 * been called. 92 */ 93 private boolean isFirstLaunch() { 94 return !mPrefs.getBoolean(KEY_ANYTHING_SAVED, false); 95 } 96 97 /** 98 * Saves all writable accounts and the default account, which can later be obtained 99 * with {@link #getDefaultAccount}. 100 * 101 * This should be called when saving a newly created contact. 102 * 103 * @param defaultAccount the account used to save a newly created contact. Or pass {@code null} 104 * If the user selected "local only". 105 */ 106 @NeededForTesting 107 public void saveDefaultAndAllAccounts(AccountWithDataSet defaultAccount) { 108 mPrefs.edit() 109 .putBoolean(KEY_ANYTHING_SAVED, true) 110 .putString( 111 KEY_KNOWN_ACCOUNTS,AccountWithDataSet.stringifyList(getWritableAccounts())) 112 .putString(KEY_DEFAULT_ACCOUNT, 113 (defaultAccount == null) ? "" : defaultAccount.stringify()) 114 .apply(); 115 } 116 117 /** 118 * @return the default account saved with {@link #saveDefaultAndAllAccounts}. 119 * 120 * Note the {@code null} return value can mean either {@link #saveDefaultAndAllAccounts} has 121 * never been called, or {@code null} was passed to {@link #saveDefaultAndAllAccounts} -- 122 * i.e. the user selected "local only". 123 * 124 * Also note that the returned account may have been removed already. 125 */ 126 @NeededForTesting 127 public AccountWithDataSet getDefaultAccount() { 128 final String saved = mPrefs.getString(KEY_DEFAULT_ACCOUNT, null); 129 if (TextUtils.isEmpty(saved)) { 130 return null; 131 } 132 return AccountWithDataSet.unstringify(saved); 133 } 134 135 /** 136 * @return true if an account still exists. {@code null} is considered "local only" here, 137 * so it's valid too. 138 */ 139 @VisibleForTesting 140 boolean isValidAccount(AccountWithDataSet account) { 141 if (account == null) { 142 return true; // It's "local only" account, which is valid. 143 } 144 return getWritableAccounts().contains(account); 145 } 146 147 /** 148 * @return saved known accounts, or an empty list if none has been saved yet. 149 */ 150 @VisibleForTesting 151 List<AccountWithDataSet> getSavedAccounts() { 152 final String saved = mPrefs.getString(KEY_KNOWN_ACCOUNTS, null); 153 if (TextUtils.isEmpty(saved)) { 154 return EMPTY_ACCOUNTS; 155 } 156 return AccountWithDataSet.unstringifyList(saved); 157 } 158 159 /** 160 * @return true if the contact editor should show the "accounts changed" notification, that is: 161 * - If it's the first launch. 162 * - Or, if an account has been added. 163 * - Or, if the default account has been removed. 164 * 165 * Note if this method returns {@code false}, the caller can safely assume that 166 * {@link #getDefaultAccount} will return a valid account. (Either an account which still 167 * exists, or {@code null} which should be interpreted as "local only".) 168 */ 169 @NeededForTesting 170 public boolean shouldShowAccountChangedNotification() { 171 if (isFirstLaunch()) { 172 return true; 173 } 174 175 // Account added? 176 final List<AccountWithDataSet> savedAccounts = getSavedAccounts(); 177 for (AccountWithDataSet account : getWritableAccounts()) { 178 if (!savedAccounts.contains(account)) { 179 return true; // New account found. 180 } 181 } 182 183 // Does default account still exist? 184 if (!isValidAccount(getDefaultAccount())) { 185 return true; 186 } 187 188 // All good. 189 return false; 190 } 191 192 @VisibleForTesting 193 String[] getWritableAccountTypeStrings() { 194 final Set<String> types = Sets.newHashSet(); 195 for (AccountType type : mAccountTypes.getAccountTypes(true)) { 196 types.add(type.accountType); 197 } 198 return types.toArray(new String[types.size()]); 199 } 200 201 /** 202 * Create an {@link Intent} to start "add new account" setup wizard. Selectable account 203 * types will be limited to ones that supports editing contacts. 204 * 205 * Use {@link Activity#startActivityForResult} or 206 * {@link android.app.Fragment#startActivityForResult} to start the wizard, and 207 * {@link Activity#onActivityResult} or {@link android.app.Fragment#onActivityResult} to 208 * get the result. 209 */ 210 @NeededForTesting 211 public Intent createAddWritableAccountIntent() { 212 return AccountManager.newChooseAccountIntent( 213 null, // selectedAccount 214 new ArrayList<Account>(), // allowableAccounts 215 getWritableAccountTypeStrings(), // allowableAccountTypes 216 false, // alwaysPromptForAccount 217 null, // descriptionOverrideText 218 null, // addAccountAuthTokenType 219 null, // addAccountRequiredFeatures 220 null // addAccountOptions 221 ); 222 } 223 224 /** 225 * Parses a result from {@link #createAddWritableAccountIntent} and returns the created 226 * {@link Account}, or null if the user has canceled the wizard. Pass the {@code resultCode} 227 * and {@code data} parameters passed to {@link Activity#onActivityResult} or 228 * {@link android.app.Fragment#onActivityResult}. 229 * 230 * Note although the return type is {@link AccountWithDataSet}, return values from this method 231 * will never have {@link AccountWithDataSet#dataSet} set, as there's no way to create an 232 * extension package account from setup wizard. 233 */ 234 @NeededForTesting 235 public AccountWithDataSet getCreatedAccount(int resultCode, Intent resultData) { 236 // Javadoc doesn't say anything about resultCode but that the data intent will be non null 237 // on success. 238 if (resultData == null) return null; 239 240 final String accountType = resultData.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); 241 final String accountName = resultData.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); 242 243 // Just in case 244 if (TextUtils.isEmpty(accountType) || TextUtils.isEmpty(accountName)) return null; 245 246 return new AccountWithDataSet(accountName, accountType, null); 247 } 248} 249 250