Home | History | Annotate | Download | only in list
      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 
     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 
     80     public static ContactListFilter createFilterWithType(int filterType) {
     81         return new ContactListFilter(filterType, null, null, null, null);
     82     }
     83 
     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 
     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 
     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 
    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      */
    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. */
    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      */
    146     public boolean isValidationRequired() {
    147         return filterType == FILTER_TYPE_ACCOUNT;
    148     }
    149 
    150     @Override
    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
    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
    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
    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      */
    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      */
    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 
    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
    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
    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      */
    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      */
    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 
    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 
    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 
    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 
    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      */
    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 
    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      */
    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      */
    461     public boolean isGoogleAccountType() {
    462         return GoogleAccountType.ACCOUNT_TYPE.equals(accountType) && dataSet == null;
    463     }
    464 }
    465