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