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
17package com.android.contacts.common.list;
18
19import android.content.SharedPreferences;
20import android.graphics.drawable.Drawable;
21import android.net.Uri;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.provider.ContactsContract.RawContacts;
25import android.text.TextUtils;
26
27/**
28 * Contact list filter parameters.
29 */
30public final class ContactListFilter implements Comparable<ContactListFilter>, Parcelable {
31
32    public static final int FILTER_TYPE_DEFAULT = -1;
33    public static final int FILTER_TYPE_ALL_ACCOUNTS = -2;
34    public static final int FILTER_TYPE_CUSTOM = -3;
35    public static final int FILTER_TYPE_STARRED = -4;
36    public static final int FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY = -5;
37    public static final int FILTER_TYPE_SINGLE_CONTACT = -6;
38
39    public static final int FILTER_TYPE_ACCOUNT = 0;
40
41    /**
42     * Obsolete filter which had been used in Honeycomb. This may be stored in
43     * {@link SharedPreferences}, but should be replaced with ALL filter when it is found.
44     *
45     * TODO: "group" filter and relevant variables are all obsolete. Remove them.
46     */
47    private static final int FILTER_TYPE_GROUP = 1;
48
49    private static final String KEY_FILTER_TYPE = "filter.type";
50    private static final String KEY_ACCOUNT_NAME = "filter.accountName";
51    private static final String KEY_ACCOUNT_TYPE = "filter.accountType";
52    private static final String KEY_DATA_SET = "filter.dataSet";
53
54    public final int filterType;
55    public final String accountType;
56    public final String accountName;
57    public final String dataSet;
58    public final Drawable icon;
59    private String mId;
60
61    public ContactListFilter(int filterType, String accountType, String accountName, String dataSet,
62            Drawable icon) {
63        this.filterType = filterType;
64        this.accountType = accountType;
65        this.accountName = accountName;
66        this.dataSet = dataSet;
67        this.icon = icon;
68    }
69
70    public static ContactListFilter createFilterWithType(int filterType) {
71        return new ContactListFilter(filterType, null, null, null, null);
72    }
73
74    public static ContactListFilter createAccountFilter(String accountType, String accountName,
75            String dataSet, Drawable icon) {
76        return new ContactListFilter(ContactListFilter.FILTER_TYPE_ACCOUNT, accountType,
77                accountName, dataSet, icon);
78    }
79
80    /**
81     * Returns true if this filter is based on data and may become invalid over time.
82     */
83    public boolean isValidationRequired() {
84        return filterType == FILTER_TYPE_ACCOUNT;
85    }
86
87    @Override
88    public String toString() {
89        switch (filterType) {
90            case FILTER_TYPE_DEFAULT:
91                return "default";
92            case FILTER_TYPE_ALL_ACCOUNTS:
93                return "all_accounts";
94            case FILTER_TYPE_CUSTOM:
95                return "custom";
96            case FILTER_TYPE_STARRED:
97                return "starred";
98            case FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY:
99                return "with_phones";
100            case FILTER_TYPE_SINGLE_CONTACT:
101                return "single";
102            case FILTER_TYPE_ACCOUNT:
103                return "account: " + accountType + (dataSet != null ? "/" + dataSet : "")
104                        + " " + accountName;
105        }
106        return super.toString();
107    }
108
109    @Override
110    public int compareTo(ContactListFilter another) {
111        int res = accountName.compareTo(another.accountName);
112        if (res != 0) {
113            return res;
114        }
115
116        res = accountType.compareTo(another.accountType);
117        if (res != 0) {
118            return res;
119        }
120
121        return filterType - another.filterType;
122    }
123
124    @Override
125    public int hashCode() {
126        int code = filterType;
127        if (accountType != null) {
128            code = code * 31 + accountType.hashCode();
129            code = code * 31 + accountName.hashCode();
130        }
131        if (dataSet != null) {
132            code = code * 31 + dataSet.hashCode();
133        }
134        return code;
135    }
136
137    @Override
138    public boolean equals(Object other) {
139        if (this == other) {
140            return true;
141        }
142
143        if (!(other instanceof ContactListFilter)) {
144            return false;
145        }
146
147        ContactListFilter otherFilter = (ContactListFilter) other;
148        if (filterType != otherFilter.filterType
149                || !TextUtils.equals(accountName, otherFilter.accountName)
150                || !TextUtils.equals(accountType, otherFilter.accountType)
151                || !TextUtils.equals(dataSet, otherFilter.dataSet)) {
152            return false;
153        }
154
155        return true;
156    }
157
158    /**
159     * Store the given {@link ContactListFilter} to preferences. If the requested filter is
160     * of type {@link #FILTER_TYPE_SINGLE_CONTACT} then do not save it to preferences because
161     * it is a temporary state.
162     */
163    public static void storeToPreferences(SharedPreferences prefs, ContactListFilter filter) {
164        if (filter != null && filter.filterType == FILTER_TYPE_SINGLE_CONTACT) {
165            return;
166        }
167        prefs.edit()
168            .putInt(KEY_FILTER_TYPE, filter == null ? FILTER_TYPE_DEFAULT : filter.filterType)
169            .putString(KEY_ACCOUNT_NAME, filter == null ? null : filter.accountName)
170            .putString(KEY_ACCOUNT_TYPE, filter == null ? null : filter.accountType)
171            .putString(KEY_DATA_SET, filter == null ? null : filter.dataSet)
172            .apply();
173    }
174
175    /**
176     * Try to obtain ContactListFilter object saved in SharedPreference.
177     * If there's no info there, return ALL filter instead.
178     */
179    public static ContactListFilter restoreDefaultPreferences(SharedPreferences prefs) {
180        ContactListFilter filter = restoreFromPreferences(prefs);
181        if (filter == null) {
182            filter = ContactListFilter.createFilterWithType(FILTER_TYPE_ALL_ACCOUNTS);
183        }
184        // "Group" filter is obsolete and thus is not exposed anymore. The "single contact mode"
185        // should also not be stored in preferences anymore since it is a temporary state.
186        if (filter.filterType == FILTER_TYPE_GROUP ||
187                filter.filterType == FILTER_TYPE_SINGLE_CONTACT) {
188            filter = ContactListFilter.createFilterWithType(FILTER_TYPE_ALL_ACCOUNTS);
189        }
190        return filter;
191    }
192
193    private static ContactListFilter restoreFromPreferences(SharedPreferences prefs) {
194        int filterType = prefs.getInt(KEY_FILTER_TYPE, FILTER_TYPE_DEFAULT);
195        if (filterType == FILTER_TYPE_DEFAULT) {
196            return null;
197        }
198
199        String accountName = prefs.getString(KEY_ACCOUNT_NAME, null);
200        String accountType = prefs.getString(KEY_ACCOUNT_TYPE, null);
201        String dataSet = prefs.getString(KEY_DATA_SET, null);
202        return new ContactListFilter(filterType, accountType, accountName, dataSet, null);
203    }
204
205
206    @Override
207    public void writeToParcel(Parcel dest, int flags) {
208        dest.writeInt(filterType);
209        dest.writeString(accountName);
210        dest.writeString(accountType);
211        dest.writeString(dataSet);
212    }
213
214    public static final Parcelable.Creator<ContactListFilter> CREATOR =
215            new Parcelable.Creator<ContactListFilter>() {
216        @Override
217        public ContactListFilter createFromParcel(Parcel source) {
218            int filterType = source.readInt();
219            String accountName = source.readString();
220            String accountType = source.readString();
221            String dataSet = source.readString();
222            return new ContactListFilter(filterType, accountType, accountName, dataSet, null);
223        }
224
225        @Override
226        public ContactListFilter[] newArray(int size) {
227            return new ContactListFilter[size];
228        }
229    };
230
231    @Override
232    public int describeContents() {
233        return 0;
234    }
235
236    /**
237     * Returns a string that can be used as a stable persistent identifier for this filter.
238     */
239    public String getId() {
240        if (mId == null) {
241            StringBuilder sb = new StringBuilder();
242            sb.append(filterType);
243            if (accountType != null) {
244                sb.append('-').append(accountType);
245            }
246            if (dataSet != null) {
247                sb.append('/').append(dataSet);
248            }
249            if (accountName != null) {
250                sb.append('-').append(accountName.replace('-', '_'));
251            }
252            mId = sb.toString();
253        }
254        return mId;
255    }
256
257    /**
258     * Adds the account query parameters to the given {@code uriBuilder}.
259     *
260     * @throws IllegalStateException if the filter type is not {@link #FILTER_TYPE_ACCOUNT}.
261     */
262    public Uri.Builder addAccountQueryParameterToUrl(Uri.Builder uriBuilder) {
263        if (filterType != FILTER_TYPE_ACCOUNT) {
264            throw new IllegalStateException("filterType must be FILTER_TYPE_ACCOUNT");
265        }
266        uriBuilder.appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName);
267        uriBuilder.appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType);
268        if (!TextUtils.isEmpty(dataSet)) {
269            uriBuilder.appendQueryParameter(RawContacts.DATA_SET, dataSet);
270        }
271        return uriBuilder;
272    }
273
274    public String toDebugString() {
275        final StringBuilder builder = new StringBuilder();
276        builder.append("[filter type: " + filterType + " (" + filterTypeToString(filterType) + ")");
277        if (filterType == FILTER_TYPE_ACCOUNT) {
278            builder.append(", accountType: " + accountType)
279                    .append(", accountName: " + accountName)
280                    .append(", dataSet: " + dataSet);
281        }
282        builder.append(", icon: " + icon + "]");
283        return builder.toString();
284    }
285
286    public static final String filterTypeToString(int filterType) {
287        switch (filterType) {
288            case FILTER_TYPE_DEFAULT:
289                return "FILTER_TYPE_DEFAULT";
290            case FILTER_TYPE_ALL_ACCOUNTS:
291                return "FILTER_TYPE_ALL_ACCOUNTS";
292            case FILTER_TYPE_CUSTOM:
293                return "FILTER_TYPE_CUSTOM";
294            case FILTER_TYPE_STARRED:
295                return "FILTER_TYPE_STARRED";
296            case FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY:
297                return "FILTER_TYPE_WITH_PHONE_NUMBERS_ONLY";
298            case FILTER_TYPE_SINGLE_CONTACT:
299                return "FILTER_TYPE_SINGLE_CONTACT";
300            case FILTER_TYPE_ACCOUNT:
301                return "FILTER_TYPE_ACCOUNT";
302            default:
303                return "(unknown)";
304        }
305    }
306}
307