1/*
2 * Copyright (C) 2009 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.loaderapp.model;
18
19import com.google.android.collect.Lists;
20import com.google.android.collect.Maps;
21
22import android.accounts.Account;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.pm.PackageManager;
26import android.database.Cursor;
27import android.graphics.drawable.Drawable;
28import android.provider.ContactsContract.Contacts;
29import android.provider.ContactsContract.Data;
30import android.provider.ContactsContract.RawContacts;
31import android.provider.ContactsContract.CommonDataKinds.Phone;
32import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
33import android.widget.EditText;
34
35import java.util.ArrayList;
36import java.util.Collections;
37import java.util.Comparator;
38import java.util.HashMap;
39import java.util.List;
40
41/**
42 * Internal structure that represents constraints and styles for a specific data
43 * source, such as the various data types they support, including details on how
44 * those types should be rendered and edited.
45 * <p>
46 * In the future this may be inflated from XML defined by a data source.
47 */
48public abstract class ContactsSource {
49    /**
50     * The {@link RawContacts#ACCOUNT_TYPE} these constraints apply to.
51     */
52    public String accountType = null;
53
54    /**
55     * Package that resources should be loaded from, either defined through an
56     * {@link Account} or for matching against {@link Data#RES_PACKAGE}.
57     */
58    public String resPackageName;
59    public String summaryResPackageName;
60
61    public int titleRes;
62    public int iconRes;
63
64    public boolean readOnly;
65
66    /**
67     * Set of {@link DataKind} supported by this source.
68     */
69    private ArrayList<DataKind> mKinds = Lists.newArrayList();
70
71    /**
72     * Lookup map of {@link #mKinds} on {@link DataKind#mimeType}.
73     */
74    private HashMap<String, DataKind> mMimeKinds = Maps.newHashMap();
75
76    public static final int LEVEL_NONE = 0;
77    public static final int LEVEL_SUMMARY = 1;
78    public static final int LEVEL_MIMETYPES = 2;
79    public static final int LEVEL_CONSTRAINTS = 3;
80
81    private int mInflatedLevel = LEVEL_NONE;
82
83    public synchronized boolean isInflated(int inflateLevel) {
84        return mInflatedLevel >= inflateLevel;
85    }
86
87    /** @hide exposed for unit tests */
88    public void setInflatedLevel(int inflateLevel) {
89        mInflatedLevel = inflateLevel;
90    }
91
92    /**
93     * Ensure that this {@link ContactsSource} has been inflated to the
94     * requested level.
95     */
96    public synchronized void ensureInflated(Context context, int inflateLevel) {
97        if (!isInflated(inflateLevel)) {
98            inflate(context, inflateLevel);
99        }
100    }
101
102    /**
103     * Perform the actual inflation to the requested level. Called by
104     * {@link #ensureInflated(Context, int)} when inflation is needed.
105     */
106    protected abstract void inflate(Context context, int inflateLevel);
107
108    /**
109     * Invalidate any cache for this {@link ContactsSource}, removing all
110     * inflated data. Calling {@link #ensureInflated(Context, int)} will
111     * populate again from scratch.
112     */
113    public synchronized void invalidateCache() {
114        this.mKinds.clear();
115        this.mMimeKinds.clear();
116        setInflatedLevel(LEVEL_NONE);
117    }
118
119    public CharSequence getDisplayLabel(Context context) {
120        if (this.titleRes != -1 && this.summaryResPackageName != null) {
121            final PackageManager pm = context.getPackageManager();
122            return pm.getText(this.summaryResPackageName, this.titleRes, null);
123        } else if (this.titleRes != -1) {
124            return context.getText(this.titleRes);
125        } else {
126            return this.accountType;
127        }
128    }
129
130    public Drawable getDisplayIcon(Context context) {
131        if (this.titleRes != -1 && this.summaryResPackageName != null) {
132            final PackageManager pm = context.getPackageManager();
133            return pm.getDrawable(this.summaryResPackageName, this.iconRes, null);
134        } else if (this.titleRes != -1) {
135            return context.getResources().getDrawable(this.iconRes);
136        } else {
137            return null;
138        }
139    }
140
141    abstract public int getHeaderColor(Context context);
142
143    abstract public int getSideBarColor(Context context);
144
145    /**
146     * {@link Comparator} to sort by {@link DataKind#weight}.
147     */
148    private static Comparator<DataKind> sWeightComparator = new Comparator<DataKind>() {
149        public int compare(DataKind object1, DataKind object2) {
150            return object1.weight - object2.weight;
151        }
152    };
153
154    /**
155     * Return list of {@link DataKind} supported, sorted by
156     * {@link DataKind#weight}.
157     */
158    public ArrayList<DataKind> getSortedDataKinds() {
159        // TODO: optimize by marking if already sorted
160        Collections.sort(mKinds, sWeightComparator);
161        return mKinds;
162    }
163
164    /**
165     * Find the {@link DataKind} for a specific MIME-type, if it's handled by
166     * this data source. If you may need a fallback {@link DataKind}, use
167     * {@link Sources#getKindOrFallback(String, String, Context, int)}.
168     */
169    public DataKind getKindForMimetype(String mimeType) {
170        return this.mMimeKinds.get(mimeType);
171    }
172
173    /**
174     * Add given {@link DataKind} to list of those provided by this source.
175     */
176    public DataKind addKind(DataKind kind) {
177        kind.resPackageName = this.resPackageName;
178        this.mKinds.add(kind);
179        this.mMimeKinds.put(kind.mimeType, kind);
180        return kind;
181    }
182
183    /**
184     * Description of a specific data type, usually marked by a unique
185     * {@link Data#MIMETYPE}. Includes details about how to view and edit
186     * {@link Data} rows of this kind, including the possible {@link EditType}
187     * labels and editable {@link EditField}.
188     */
189    public static class DataKind {
190        public String resPackageName;
191        public String mimeType;
192        public int titleRes;
193        public int iconRes;
194        public int iconAltRes;
195        public int weight;
196        public boolean secondary;
197        public boolean editable;
198
199        /**
200         * If this is true (default), the user can add and remove values.
201         * If false, the editor will always show a single field (which might be empty).
202         */
203        public boolean isList;
204
205        public StringInflater actionHeader;
206        public StringInflater actionAltHeader;
207        public StringInflater actionBody;
208
209        public boolean actionBodySocial = false;
210
211        public String typeColumn;
212
213        /**
214         * Maximum number of values allowed in the list. -1 represents infinity.
215         * If {@link DataKind#isList} is false, this value is ignored.
216         */
217        public int typeOverallMax;
218
219        public List<EditType> typeList;
220        public List<EditField> fieldList;
221
222        public ContentValues defaultValues;
223
224        public DataKind() {
225        }
226
227        public DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable) {
228            this.mimeType = mimeType;
229            this.titleRes = titleRes;
230            this.iconRes = iconRes;
231            this.weight = weight;
232            this.editable = editable;
233            this.isList = true;
234            this.typeOverallMax = -1;
235        }
236    }
237
238    /**
239     * Description of a specific "type" or "label" of a {@link DataKind} row,
240     * such as {@link Phone#TYPE_WORK}. Includes constraints on total number of
241     * rows a {@link Contacts} may have of this type, and details on how
242     * user-defined labels are stored.
243     */
244    public static class EditType {
245        public int rawValue;
246        public int labelRes;
247//        public int actionRes;
248//        public int actionAltRes;
249        public boolean secondary;
250        public int specificMax;
251        public String customColumn;
252
253        public EditType(int rawValue, int labelRes) {
254            this.rawValue = rawValue;
255            this.labelRes = labelRes;
256            this.specificMax = -1;
257        }
258
259        public EditType setSecondary(boolean secondary) {
260            this.secondary = secondary;
261            return this;
262        }
263
264        public EditType setSpecificMax(int specificMax) {
265            this.specificMax = specificMax;
266            return this;
267        }
268
269        public EditType setCustomColumn(String customColumn) {
270            this.customColumn = customColumn;
271            return this;
272        }
273
274        @Override
275        public boolean equals(Object object) {
276            if (object instanceof EditType) {
277                final EditType other = (EditType)object;
278                return other.rawValue == rawValue;
279            }
280            return false;
281        }
282
283        @Override
284        public int hashCode() {
285            return rawValue;
286        }
287    }
288
289    /**
290     * Description of a user-editable field on a {@link DataKind} row, such as
291     * {@link Phone#NUMBER}. Includes flags to apply to an {@link EditText}, and
292     * the column where this field is stored.
293     */
294    public static class EditField {
295        public String column;
296        public int titleRes;
297        public int inputType;
298        public int minLines;
299        public boolean optional;
300
301        public EditField(String column, int titleRes) {
302            this.column = column;
303            this.titleRes = titleRes;
304        }
305
306        public EditField(String column, int titleRes, int inputType) {
307            this(column, titleRes);
308            this.inputType = inputType;
309        }
310
311        public EditField setOptional(boolean optional) {
312            this.optional = optional;
313            return this;
314        }
315    }
316
317    /**
318     * Generic method of inflating a given {@link Cursor} into a user-readable
319     * {@link CharSequence}. For example, an inflater could combine the multiple
320     * columns of {@link StructuredPostal} together using a string resource
321     * before presenting to the user.
322     */
323    public interface StringInflater {
324        public CharSequence inflateUsing(Context context, Cursor cursor);
325        public CharSequence inflateUsing(Context context, ContentValues values);
326    }
327
328}
329