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 
17 package com.android.loaderapp.model;
18 
19 import com.google.android.collect.Lists;
20 import com.google.android.collect.Maps;
21 
22 import android.accounts.Account;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.pm.PackageManager;
26 import android.database.Cursor;
27 import android.graphics.drawable.Drawable;
28 import android.provider.ContactsContract.Contacts;
29 import android.provider.ContactsContract.Data;
30 import android.provider.ContactsContract.RawContacts;
31 import android.provider.ContactsContract.CommonDataKinds.Phone;
32 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
33 import android.widget.EditText;
34 
35 import java.util.ArrayList;
36 import java.util.Collections;
37 import java.util.Comparator;
38 import java.util.HashMap;
39 import 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  */
48 public 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 
isInflated(int inflateLevel)83     public synchronized boolean isInflated(int inflateLevel) {
84         return mInflatedLevel >= inflateLevel;
85     }
86 
87     /** @hide exposed for unit tests */
setInflatedLevel(int inflateLevel)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      */
ensureInflated(Context context, int inflateLevel)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      */
inflate(Context context, int inflateLevel)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      */
invalidateCache()113     public synchronized void invalidateCache() {
114         this.mKinds.clear();
115         this.mMimeKinds.clear();
116         setInflatedLevel(LEVEL_NONE);
117     }
118 
getDisplayLabel(Context context)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 
getDisplayIcon(Context context)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 
getHeaderColor(Context context)141     abstract public int getHeaderColor(Context context);
142 
getSideBarColor(Context context)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      */
getSortedDataKinds()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      */
getKindForMimetype(String mimeType)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      */
addKind(DataKind kind)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 
DataKind()224         public DataKind() {
225         }
226 
DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable)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 
EditType(int rawValue, int labelRes)253         public EditType(int rawValue, int labelRes) {
254             this.rawValue = rawValue;
255             this.labelRes = labelRes;
256             this.specificMax = -1;
257         }
258 
setSecondary(boolean secondary)259         public EditType setSecondary(boolean secondary) {
260             this.secondary = secondary;
261             return this;
262         }
263 
setSpecificMax(int specificMax)264         public EditType setSpecificMax(int specificMax) {
265             this.specificMax = specificMax;
266             return this;
267         }
268 
setCustomColumn(String customColumn)269         public EditType setCustomColumn(String customColumn) {
270             this.customColumn = customColumn;
271             return this;
272         }
273 
274         @Override
equals(Object object)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
hashCode()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 
EditField(String column, int titleRes)301         public EditField(String column, int titleRes) {
302             this.column = column;
303             this.titleRes = titleRes;
304         }
305 
EditField(String column, int titleRes, int inputType)306         public EditField(String column, int titleRes, int inputType) {
307             this(column, titleRes);
308             this.inputType = inputType;
309         }
310 
setOptional(boolean optional)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 {
inflateUsing(Context context, Cursor cursor)324         public CharSequence inflateUsing(Context context, Cursor cursor);
inflateUsing(Context context, ContentValues values)325         public CharSequence inflateUsing(Context context, ContentValues values);
326     }
327 
328 }
329