• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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