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 17 package com.android.contacts.list; 18 19 import android.accounts.Account; 20 import android.content.SharedPreferences; 21 import android.graphics.drawable.Drawable; 22 import android.net.Uri; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.provider.ContactsContract.RawContacts; 26 import android.text.TextUtils; 27 28 import com.android.contacts.logging.ListEvent; 29 import com.android.contacts.model.account.AccountWithDataSet; 30 import com.android.contacts.model.account.GoogleAccountType; 31 32 import java.util.ArrayList; 33 import java.util.List; 34 35 /** 36 * Contact list filter parameters. 37 */ 38 public final class ContactListFilter implements Comparable<ContactListFilter>, Parcelable { 39 40 public static final int FILTER_TYPE_DEFAULT = -1; 41 public static final int FILTER_TYPE_ALL_ACCOUNTS = -2; 42 public static final int FILTER_TYPE_CUSTOM = -3; 43 public static final int FILTER_TYPE_STARRED = -4; 44 public static final int FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY = -5; 45 public static final int FILTER_TYPE_SINGLE_CONTACT = -6; 46 public static final int FILTER_TYPE_GROUP_MEMBERS = -7; 47 public static final int FILTER_TYPE_DEVICE_CONTACTS = -8; 48 public static final int FILTER_TYPE_SIM_CONTACTS = -9; 49 50 public static final int FILTER_TYPE_ACCOUNT = 0; 51 52 /** 53 * Obsolete filter which had been used in Honeycomb. This may be stored in 54 * {@link SharedPreferences}, but should be replaced with ALL filter when it is found. 55 * 56 * TODO: "group" filter and relevant variables are all obsolete. Remove them. 57 */ 58 private static final int FILTER_TYPE_GROUP = 1; 59 60 private static final String KEY_FILTER_TYPE = "filter.type"; 61 private static final String KEY_ACCOUNT_NAME = "filter.accountName"; 62 private static final String KEY_ACCOUNT_TYPE = "filter.accountType"; 63 private static final String KEY_DATA_SET = "filter.dataSet"; 64 65 public final int filterType; 66 public final String accountType; 67 public final String accountName; 68 public final String dataSet; 69 public final Drawable icon; 70 private String mId; 71 ContactListFilter(int filterType, String accountType, String accountName, String dataSet, Drawable icon)72 public ContactListFilter(int filterType, String accountType, String accountName, String dataSet, 73 Drawable icon) { 74 this.filterType = filterType; 75 this.accountType = accountType; 76 this.accountName = accountName; 77 this.dataSet = dataSet; 78 this.icon = icon; 79 } 80 createFilterWithType(int filterType)81 public static ContactListFilter createFilterWithType(int filterType) { 82 return new ContactListFilter(filterType, null, null, null, null); 83 } 84 createAccountFilter(String accountType, String accountName, String dataSet, Drawable icon)85 public static ContactListFilter createAccountFilter(String accountType, String accountName, 86 String dataSet, Drawable icon) { 87 return new ContactListFilter(ContactListFilter.FILTER_TYPE_ACCOUNT, accountType, 88 accountName, dataSet, icon); 89 } 90 createGroupMembersFilter(String accountType, String accountName, String dataSet)91 public static ContactListFilter createGroupMembersFilter(String accountType, String accountName, 92 String dataSet) { 93 return new ContactListFilter(ContactListFilter.FILTER_TYPE_GROUP_MEMBERS, accountType, 94 accountName, dataSet, /* icon */ null); 95 } 96 createDeviceContactsFilter(Drawable icon)97 public static ContactListFilter createDeviceContactsFilter(Drawable icon) { 98 return new ContactListFilter(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS, 99 /* accountType= */ null, /* accountName= */ null, /* dataSet= */ null, icon); 100 } 101 createDeviceContactsFilter(Drawable icon, AccountWithDataSet account)102 public static ContactListFilter createDeviceContactsFilter(Drawable icon, 103 AccountWithDataSet account) { 104 return new ContactListFilter(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS, 105 account.type, account.name, account.dataSet, icon); 106 } 107 createSimContactsFilter(Drawable icon, AccountWithDataSet account)108 public static ContactListFilter createSimContactsFilter(Drawable icon, 109 AccountWithDataSet account) { 110 return new ContactListFilter(ContactListFilter.FILTER_TYPE_SIM_CONTACTS, 111 account.type, account.name, account.dataSet, icon); 112 } 113 114 /** 115 * Whether the given {@link ContactListFilter} has a filter type that should be displayed as 116 * the default contacts list view. 117 */ isContactsFilterType()118 public boolean isContactsFilterType() { 119 return filterType == ContactListFilter.FILTER_TYPE_DEFAULT 120 || filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS 121 || filterType == ContactListFilter.FILTER_TYPE_CUSTOM; 122 } 123 124 /** Returns the {@link ListEvent.ListType} for the type of this filter. */ toListType()125 public int toListType() { 126 switch (filterType) { 127 case FILTER_TYPE_DEFAULT: 128 // Fall through 129 case FILTER_TYPE_ALL_ACCOUNTS: 130 return ListEvent.ListType.ALL_CONTACTS; 131 case FILTER_TYPE_CUSTOM: 132 return ListEvent.ListType.CUSTOM; 133 case FILTER_TYPE_STARRED: 134 return ListEvent.ListType.STARRED; 135 case FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY: 136 return ListEvent.ListType.PHONE_NUMBERS; 137 case FILTER_TYPE_SINGLE_CONTACT: 138 return ListEvent.ListType.SINGLE_CONTACT; 139 case FILTER_TYPE_ACCOUNT: 140 return ListEvent.ListType.ACCOUNT; 141 case FILTER_TYPE_GROUP_MEMBERS: 142 return ListEvent.ListType.GROUP; 143 case FILTER_TYPE_DEVICE_CONTACTS: 144 return ListEvent.ListType.DEVICE; 145 } 146 return ListEvent.ListType.UNKNOWN_LIST; 147 } 148 149 150 /** 151 * Returns true if this filter is based on data and may become invalid over time. 152 */ isValidationRequired()153 public boolean isValidationRequired() { 154 return filterType == FILTER_TYPE_ACCOUNT; 155 } 156 157 @Override toString()158 public String toString() { 159 switch (filterType) { 160 case FILTER_TYPE_DEFAULT: 161 return "default"; 162 case FILTER_TYPE_ALL_ACCOUNTS: 163 return "all_accounts"; 164 case FILTER_TYPE_CUSTOM: 165 return "custom"; 166 case FILTER_TYPE_STARRED: 167 return "starred"; 168 case FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY: 169 return "with_phones"; 170 case FILTER_TYPE_SINGLE_CONTACT: 171 return "single"; 172 case FILTER_TYPE_ACCOUNT: 173 return "account: " + accountType + (dataSet != null ? "/" + dataSet : "") 174 + " " + accountName; 175 case FILTER_TYPE_GROUP_MEMBERS: 176 return "group_members"; 177 case FILTER_TYPE_DEVICE_CONTACTS: 178 return "device_contacts"; 179 } 180 return super.toString(); 181 } 182 183 @Override compareTo(ContactListFilter another)184 public int compareTo(ContactListFilter another) { 185 int res = accountName.compareTo(another.accountName); 186 if (res != 0) { 187 return res; 188 } 189 190 res = accountType.compareTo(another.accountType); 191 if (res != 0) { 192 return res; 193 } 194 195 return filterType - another.filterType; 196 } 197 198 @Override hashCode()199 public int hashCode() { 200 int code = filterType; 201 if (accountType != null) { 202 code = code * 31 + accountType.hashCode(); 203 } 204 if (accountName != null) { 205 code = code * 31 + accountName.hashCode(); 206 } 207 if (dataSet != null) { 208 code = code * 31 + dataSet.hashCode(); 209 } 210 return code; 211 } 212 213 @Override equals(Object other)214 public boolean equals(Object other) { 215 if (this == other) { 216 return true; 217 } 218 219 if (!(other instanceof ContactListFilter)) { 220 return false; 221 } 222 223 ContactListFilter otherFilter = (ContactListFilter) other; 224 if (filterType != otherFilter.filterType 225 || !TextUtils.equals(accountName, otherFilter.accountName) 226 || !TextUtils.equals(accountType, otherFilter.accountType) 227 || !TextUtils.equals(dataSet, otherFilter.dataSet)) { 228 return false; 229 } 230 231 return true; 232 } 233 234 /** 235 * Store the given {@link ContactListFilter} to preferences. If the requested filter is 236 * of type {@link #FILTER_TYPE_SINGLE_CONTACT} then do not save it to preferences because 237 * it is a temporary state. 238 */ storeToPreferences(SharedPreferences prefs, ContactListFilter filter)239 public static void storeToPreferences(SharedPreferences prefs, ContactListFilter filter) { 240 if (filter != null && filter.filterType == FILTER_TYPE_SINGLE_CONTACT) { 241 return; 242 } 243 prefs.edit() 244 .putInt(KEY_FILTER_TYPE, filter == null ? FILTER_TYPE_DEFAULT : filter.filterType) 245 .putString(KEY_ACCOUNT_NAME, filter == null ? null : filter.accountName) 246 .putString(KEY_ACCOUNT_TYPE, filter == null ? null : filter.accountType) 247 .putString(KEY_DATA_SET, filter == null ? null : filter.dataSet) 248 .apply(); 249 } 250 251 /** 252 * Try to obtain ContactListFilter object saved in SharedPreference. 253 * If there's no info there, return ALL filter instead. 254 */ restoreDefaultPreferences(SharedPreferences prefs)255 public static ContactListFilter restoreDefaultPreferences(SharedPreferences prefs) { 256 ContactListFilter filter = restoreFromPreferences(prefs); 257 if (filter == null) { 258 filter = ContactListFilter.createFilterWithType(FILTER_TYPE_ALL_ACCOUNTS); 259 } 260 // "Group" filter is obsolete and thus is not exposed anymore. The "single contact mode" 261 // should also not be stored in preferences anymore since it is a temporary state. 262 if (filter.filterType == FILTER_TYPE_GROUP || 263 filter.filterType == FILTER_TYPE_SINGLE_CONTACT) { 264 filter = ContactListFilter.createFilterWithType(FILTER_TYPE_ALL_ACCOUNTS); 265 } 266 return filter; 267 } 268 restoreFromPreferences(SharedPreferences prefs)269 private static ContactListFilter restoreFromPreferences(SharedPreferences prefs) { 270 int filterType = prefs.getInt(KEY_FILTER_TYPE, FILTER_TYPE_DEFAULT); 271 if (filterType == FILTER_TYPE_DEFAULT) { 272 return null; 273 } 274 275 String accountName = prefs.getString(KEY_ACCOUNT_NAME, null); 276 String accountType = prefs.getString(KEY_ACCOUNT_TYPE, null); 277 String dataSet = prefs.getString(KEY_DATA_SET, null); 278 return new ContactListFilter(filterType, accountType, accountName, dataSet, null); 279 } 280 281 282 @Override writeToParcel(Parcel dest, int flags)283 public void writeToParcel(Parcel dest, int flags) { 284 dest.writeInt(filterType); 285 dest.writeString(accountName); 286 dest.writeString(accountType); 287 dest.writeString(dataSet); 288 } 289 290 public static final Parcelable.Creator<ContactListFilter> CREATOR = 291 new Parcelable.Creator<ContactListFilter>() { 292 @Override 293 public ContactListFilter createFromParcel(Parcel source) { 294 int filterType = source.readInt(); 295 String accountName = source.readString(); 296 String accountType = source.readString(); 297 String dataSet = source.readString(); 298 return new ContactListFilter(filterType, accountType, accountName, dataSet, null); 299 } 300 301 @Override 302 public ContactListFilter[] newArray(int size) { 303 return new ContactListFilter[size]; 304 } 305 }; 306 307 @Override describeContents()308 public int describeContents() { 309 return 0; 310 } 311 312 /** 313 * Returns a string that can be used as a stable persistent identifier for this filter. 314 */ getId()315 public String getId() { 316 if (mId == null) { 317 StringBuilder sb = new StringBuilder(); 318 sb.append(filterType); 319 if (accountType != null) { 320 sb.append('-').append(accountType); 321 } 322 if (dataSet != null) { 323 sb.append('/').append(dataSet); 324 } 325 if (accountName != null) { 326 sb.append('-').append(accountName.replace('-', '_')); 327 } 328 mId = sb.toString(); 329 } 330 return mId; 331 } 332 333 /** 334 * Adds the account query parameters to the given {@code uriBuilder}. 335 * 336 * @throws IllegalStateException if the filter type is not {@link #FILTER_TYPE_ACCOUNT} or 337 * {@link #FILTER_TYPE_GROUP_MEMBERS}. 338 */ addAccountQueryParameterToUrl(Uri.Builder uriBuilder)339 public Uri.Builder addAccountQueryParameterToUrl(Uri.Builder uriBuilder) { 340 if (filterType != FILTER_TYPE_ACCOUNT 341 && filterType != FILTER_TYPE_GROUP_MEMBERS) { 342 throw new IllegalStateException( 343 "filterType must be FILTER_TYPE_ACCOUNT or FILER_TYPE_GROUP_MEMBERS"); 344 } 345 // null account names are not valid, see ContactsProvider2#appendAccountFromParameter 346 if (accountName != null) { 347 uriBuilder.appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName); 348 uriBuilder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType); 349 } 350 if (dataSet != null) { 351 uriBuilder.appendQueryParameter(RawContacts.DATA_SET, dataSet); 352 } 353 return uriBuilder; 354 } 355 toAccountWithDataSet()356 public AccountWithDataSet toAccountWithDataSet() { 357 if (filterType == FILTER_TYPE_ACCOUNT || filterType == FILTER_TYPE_DEVICE_CONTACTS 358 || filterType == FILTER_TYPE_SIM_CONTACTS) { 359 return new AccountWithDataSet(accountName, accountType, dataSet); 360 } else { 361 throw new IllegalStateException("Cannot create Account from filter type " + 362 filterTypeToString(filterType)); 363 } 364 } 365 toDebugString()366 public String toDebugString() { 367 final StringBuilder builder = new StringBuilder(); 368 builder.append("[filter type: " + filterType + " (" + filterTypeToString(filterType) + ")"); 369 if (filterType == FILTER_TYPE_ACCOUNT) { 370 builder.append(", accountType: " + accountType) 371 .append(", accountName: " + accountName) 372 .append(", dataSet: " + dataSet); 373 } 374 builder.append(", icon: " + icon + "]"); 375 return builder.toString(); 376 } 377 filterTypeToString(int filterType)378 public static final String filterTypeToString(int filterType) { 379 switch (filterType) { 380 case FILTER_TYPE_DEFAULT: 381 return "FILTER_TYPE_DEFAULT"; 382 case FILTER_TYPE_ALL_ACCOUNTS: 383 return "FILTER_TYPE_ALL_ACCOUNTS"; 384 case FILTER_TYPE_CUSTOM: 385 return "FILTER_TYPE_CUSTOM"; 386 case FILTER_TYPE_STARRED: 387 return "FILTER_TYPE_STARRED"; 388 case FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY: 389 return "FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY"; 390 case FILTER_TYPE_SINGLE_CONTACT: 391 return "FILTER_TYPE_SINGLE_CONTACT"; 392 case FILTER_TYPE_ACCOUNT: 393 return "FILTER_TYPE_ACCOUNT"; 394 case FILTER_TYPE_GROUP_MEMBERS: 395 return "FILTER_TYPE_GROUP_MEMBERS"; 396 case FILTER_TYPE_DEVICE_CONTACTS: 397 return "FILTER_TYPE_DEVICE_CONTACTS"; 398 default: 399 return "(unknown)"; 400 } 401 } 402 isSyncable()403 public boolean isSyncable() { 404 return isGoogleAccountType() && filterType == FILTER_TYPE_ACCOUNT; 405 } 406 407 /** 408 * Returns true if this ContactListFilter contains at least one Google account. 409 * (see {@link #isGoogleAccountType) 410 */ isSyncable(List<AccountWithDataSet> accounts)411 public boolean isSyncable(List<AccountWithDataSet> accounts) { 412 if (isSyncable()) { 413 return true; 414 } 415 // Since we don't know which group is selected until the actual contacts loading, we 416 // consider a custom filter syncable as long as there is a Google account on the device, 417 // and don't check if there is any group that belongs to a Google account is selected. 418 if (filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS 419 || filterType == ContactListFilter.FILTER_TYPE_CUSTOM 420 || filterType == ContactListFilter.FILTER_TYPE_DEFAULT) { 421 if (accounts != null && accounts.size() > 0) { 422 // If we're showing all contacts and there is any Google account on the device then 423 // we're syncable. 424 for (AccountWithDataSet account : accounts) { 425 if (GoogleAccountType.ACCOUNT_TYPE.equals(account.type) 426 && account.dataSet == null) { 427 return true; 428 } 429 } 430 } 431 } 432 return false; 433 } 434 shouldShowSyncState()435 public boolean shouldShowSyncState() { 436 return (isGoogleAccountType() && filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) 437 || filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS 438 || filterType == ContactListFilter.FILTER_TYPE_CUSTOM 439 || filterType == ContactListFilter.FILTER_TYPE_DEFAULT; 440 } 441 442 /** 443 * Returns the Google accounts (see {@link #isGoogleAccountType) for this ContactListFilter. 444 */ getSyncableAccounts(List<AccountWithDataSet> accounts)445 public List<Account> getSyncableAccounts(List<AccountWithDataSet> accounts) { 446 final List<Account> syncableAccounts = new ArrayList<>(); 447 448 if (isGoogleAccountType() && filterType == ContactListFilter.FILTER_TYPE_ACCOUNT) { 449 syncableAccounts.add(new Account(accountName, accountType)); 450 } else if (filterType == ContactListFilter.FILTER_TYPE_ALL_ACCOUNTS 451 || filterType == ContactListFilter.FILTER_TYPE_CUSTOM 452 || filterType == ContactListFilter.FILTER_TYPE_DEFAULT) { 453 if (accounts != null && accounts.size() > 0) { 454 for (AccountWithDataSet account : accounts) { 455 if (GoogleAccountType.ACCOUNT_TYPE.equals(account.type) 456 && account.dataSet == null) { 457 syncableAccounts.add(new Account(account.name, account.type)); 458 } 459 } 460 } 461 } 462 return syncableAccounts; 463 } 464 465 /** 466 * Returns true if this ContactListFilter is Google account type. (i.e. where 467 * accountType = "com.google" and dataSet = null) 468 */ isGoogleAccountType()469 public boolean isGoogleAccountType() { 470 return GoogleAccountType.ACCOUNT_TYPE.equals(accountType) && dataSet == null; 471 } 472 } 473