• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.contacts.model;
18 
19 import com.android.contacts.R;
20 import com.google.android.collect.Lists;
21 import com.google.android.collect.Maps;
22 import com.google.common.annotations.VisibleForTesting;
23 
24 import android.accounts.Account;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.pm.PackageManager;
28 import android.database.Cursor;
29 import android.graphics.drawable.Drawable;
30 import android.provider.ContactsContract.CommonDataKinds.Phone;
31 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
32 import android.provider.ContactsContract.Contacts;
33 import android.provider.ContactsContract.Data;
34 import android.provider.ContactsContract.RawContacts;
35 import android.view.inputmethod.EditorInfo;
36 import android.widget.EditText;
37 
38 import java.text.Collator;
39 import java.util.ArrayList;
40 import java.util.Collections;
41 import java.util.Comparator;
42 import java.util.HashMap;
43 import java.util.List;
44 
45 /**
46  * Internal structure that represents constraints and styles for a specific data
47  * source, such as the various data types they support, including details on how
48  * those types should be rendered and edited.
49  * <p>
50  * In the future this may be inflated from XML defined by a data source.
51  */
52 public abstract class AccountType {
53     private static final String TAG = "AccountType";
54 
55     /**
56      * The {@link RawContacts#ACCOUNT_TYPE} these constraints apply to.
57      */
58     public String accountType = null;
59 
60     /**
61      * The {@link RawContacts#DATA_SET} these constraints apply to.
62      */
63     public String dataSet = null;
64 
65     /**
66      * Package that resources should be loaded from, either defined through an
67      * {@link Account} or for matching against {@link Data#RES_PACKAGE}.
68      */
69     public String resPackageName;
70     public String summaryResPackageName;
71 
72     public int titleRes;
73     public int iconRes;
74 
75     /**
76      * Set of {@link DataKind} supported by this source.
77      */
78     private ArrayList<DataKind> mKinds = Lists.newArrayList();
79 
80     /**
81      * Lookup map of {@link #mKinds} on {@link DataKind#mimeType}.
82      */
83     private HashMap<String, DataKind> mMimeKinds = Maps.newHashMap();
84 
isExtension()85     public boolean isExtension() {
86         return false;
87     }
88 
89     /**
90      * @return True if contacts can be created and edited using this app. If false,
91      * there could still be an external editor as provided by
92      * {@link #getEditContactActivityClassName()} or {@link #getCreateContactActivityClassName()}
93      */
areContactsWritable()94     public abstract boolean areContactsWritable();
95 
96     /**
97      * Returns an optional custom edit activity.  The activity class should reside
98      * in the sync adapter package as determined by {@link #resPackageName}.
99      */
getEditContactActivityClassName()100     public String getEditContactActivityClassName() {
101         return null;
102     }
103 
104     /**
105      * Returns an optional custom new contact activity. The activity class should reside
106      * in the sync adapter package as determined by {@link #resPackageName}.
107      */
getCreateContactActivityClassName()108     public String getCreateContactActivityClassName() {
109         return null;
110     }
111 
112     /**
113      * Returns an optional custom invite contact activity. The activity class should reside
114      * in the sync adapter package as determined by {@link #resPackageName}.
115      */
getInviteContactActivityClassName()116     public String getInviteContactActivityClassName() {
117         return null;
118     }
119 
120     /**
121      * Returns an optional service that can be launched whenever a contact is being looked at.
122      * This allows the sync adapter to provide more up-to-date information.
123      * The service class should reside in the sync adapter package as determined by
124      * {@link #resPackageName}.
125      */
getViewContactNotifyServiceClassName()126     public String getViewContactNotifyServiceClassName() {
127         return null;
128     }
129 
130     /** Returns an optional Activity string that can be used to view the group. */
getViewGroupActivity()131     public String getViewGroupActivity() {
132         return null;
133     }
134 
135     /** Returns an optional Activity string that can be used to view the stream item. */
getViewStreamItemActivity()136     public String getViewStreamItemActivity() {
137         return null;
138     }
139 
140     /** Returns an optional Activity string that can be used to view the stream item photo. */
getViewStreamItemPhotoActivity()141     public String getViewStreamItemPhotoActivity() {
142         return null;
143     }
144 
getDisplayLabel(Context context)145     public CharSequence getDisplayLabel(Context context) {
146         return getResourceText(context, summaryResPackageName, titleRes, accountType);
147     }
148 
149     /**
150      * @return resource ID for the "invite contact" action label, or -1 if not defined.
151      */
getInviteContactActionResId()152     protected int getInviteContactActionResId() {
153         return -1;
154     }
155 
156     /**
157      * @return resource ID for the "view group" label, or -1 if not defined.
158      */
getViewGroupLabelResId()159     protected int getViewGroupLabelResId() {
160         return -1;
161     }
162 
163     /**
164      * Returns {@link AccountTypeWithDataSet} for this type.
165      */
getAccountTypeAndDataSet()166     public AccountTypeWithDataSet getAccountTypeAndDataSet() {
167         return AccountTypeWithDataSet.get(accountType, dataSet);
168     }
169 
170     /**
171      * Returns a list of additional package names that should be inspected as additional
172      * external account types.  This allows for a primary account type to indicate other packages
173      * that may not be sync adapters but which still provide contact data, perhaps under a
174      * separate data set within the account.
175      */
getExtensionPackageNames()176     public List<String> getExtensionPackageNames() {
177         return new ArrayList<String>();
178     }
179 
180     /**
181      * Returns an optional custom label for the "invite contact" action, which will be shown on
182      * the contact card.  (If not defined, returns null.)
183      */
getInviteContactActionLabel(Context context)184     public CharSequence getInviteContactActionLabel(Context context) {
185         return getResourceText(context, summaryResPackageName, getInviteContactActionResId(), "");
186     }
187 
188     /**
189      * Returns a label for the "view group" action. If not defined, this falls back to our
190      * own "View Updates" string
191      */
getViewGroupLabel(Context context)192     public CharSequence getViewGroupLabel(Context context) {
193         final CharSequence customTitle =
194                 getResourceText(context, summaryResPackageName, getViewGroupLabelResId(), null);
195 
196         return customTitle == null
197                 ? context.getText(R.string.view_updates_from_group)
198                 : customTitle;
199     }
200 
201     /**
202      * Return a string resource loaded from the given package (or the current package
203      * if {@code packageName} is null), unless {@code resId} is -1, in which case it returns
204      * {@code defaultValue}.
205      *
206      * (The behavior is undefined if the resource or package doesn't exist.)
207      */
208     @VisibleForTesting
getResourceText(Context context, String packageName, int resId, String defaultValue)209     static CharSequence getResourceText(Context context, String packageName, int resId,
210             String defaultValue) {
211         if (resId != -1 && packageName != null) {
212             final PackageManager pm = context.getPackageManager();
213             return pm.getText(packageName, resId, null);
214         } else if (resId != -1) {
215             return context.getText(resId);
216         } else {
217             return defaultValue;
218         }
219     }
220 
getDisplayIcon(Context context)221     public Drawable getDisplayIcon(Context context) {
222         if (this.titleRes != -1 && this.summaryResPackageName != null) {
223             final PackageManager pm = context.getPackageManager();
224             return pm.getDrawable(this.summaryResPackageName, this.iconRes, null);
225         } else if (this.titleRes != -1) {
226             return context.getResources().getDrawable(this.iconRes);
227         } else {
228             return null;
229         }
230     }
231 
232     /**
233      * Whether or not groups created under this account type have editable membership lists.
234      */
isGroupMembershipEditable()235     abstract public boolean isGroupMembershipEditable();
236 
getHeaderColor(Context context)237     abstract public int getHeaderColor(Context context);
238 
getSideBarColor(Context context)239     abstract public int getSideBarColor(Context context);
240 
241     /**
242      * {@link Comparator} to sort by {@link DataKind#weight}.
243      */
244     private static Comparator<DataKind> sWeightComparator = new Comparator<DataKind>() {
245         public int compare(DataKind object1, DataKind object2) {
246             return object1.weight - object2.weight;
247         }
248     };
249 
250     /**
251      * Return list of {@link DataKind} supported, sorted by
252      * {@link DataKind#weight}.
253      */
getSortedDataKinds()254     public ArrayList<DataKind> getSortedDataKinds() {
255         // TODO: optimize by marking if already sorted
256         Collections.sort(mKinds, sWeightComparator);
257         return mKinds;
258     }
259 
260     /**
261      * Find the {@link DataKind} for a specific MIME-type, if it's handled by
262      * this data source. If you may need a fallback {@link DataKind}, use
263      * {@link AccountTypeManager#getKindOrFallback(String, String, String)}.
264      */
getKindForMimetype(String mimeType)265     public DataKind getKindForMimetype(String mimeType) {
266         return this.mMimeKinds.get(mimeType);
267     }
268 
269     /**
270      * Add given {@link DataKind} to list of those provided by this source.
271      */
addKind(DataKind kind)272     public DataKind addKind(DataKind kind) {
273         kind.resPackageName = this.resPackageName;
274         this.mKinds.add(kind);
275         this.mMimeKinds.put(kind.mimeType, kind);
276         return kind;
277     }
278 
279     /**
280      * Description of a specific "type" or "label" of a {@link DataKind} row,
281      * such as {@link Phone#TYPE_WORK}. Includes constraints on total number of
282      * rows a {@link Contacts} may have of this type, and details on how
283      * user-defined labels are stored.
284      */
285     public static class EditType {
286         public int rawValue;
287         public int labelRes;
288         public boolean secondary;
289         /**
290          * The number of entries allowed for the type. -1 if not specified.
291          * @see DataKind#typeOverallMax
292          */
293         public int specificMax;
294         public String customColumn;
295 
EditType(int rawValue, int labelRes)296         public EditType(int rawValue, int labelRes) {
297             this.rawValue = rawValue;
298             this.labelRes = labelRes;
299             this.specificMax = -1;
300         }
301 
setSecondary(boolean secondary)302         public EditType setSecondary(boolean secondary) {
303             this.secondary = secondary;
304             return this;
305         }
306 
setSpecificMax(int specificMax)307         public EditType setSpecificMax(int specificMax) {
308             this.specificMax = specificMax;
309             return this;
310         }
311 
setCustomColumn(String customColumn)312         public EditType setCustomColumn(String customColumn) {
313             this.customColumn = customColumn;
314             return this;
315         }
316 
317         @Override
equals(Object object)318         public boolean equals(Object object) {
319             if (object instanceof EditType) {
320                 final EditType other = (EditType)object;
321                 return other.rawValue == rawValue;
322             }
323             return false;
324         }
325 
326         @Override
hashCode()327         public int hashCode() {
328             return rawValue;
329         }
330     }
331 
332     public static class EventEditType extends EditType {
333         private boolean mYearOptional;
334 
EventEditType(int rawValue, int labelRes)335         public EventEditType(int rawValue, int labelRes) {
336             super(rawValue, labelRes);
337         }
338 
isYearOptional()339         public boolean isYearOptional() {
340             return mYearOptional;
341         }
342 
setYearOptional(boolean yearOptional)343         public EventEditType setYearOptional(boolean yearOptional) {
344             mYearOptional = yearOptional;
345             return this;
346         }
347     }
348 
349     /**
350      * Description of a user-editable field on a {@link DataKind} row, such as
351      * {@link Phone#NUMBER}. Includes flags to apply to an {@link EditText}, and
352      * the column where this field is stored.
353      */
354     public static class EditField {
355         public String column;
356         public int titleRes;
357         public int inputType;
358         public int minLines;
359         public boolean optional;
360         public boolean shortForm;
361         public boolean longForm;
362 
EditField(String column, int titleRes)363         public EditField(String column, int titleRes) {
364             this.column = column;
365             this.titleRes = titleRes;
366         }
367 
EditField(String column, int titleRes, int inputType)368         public EditField(String column, int titleRes, int inputType) {
369             this(column, titleRes);
370             this.inputType = inputType;
371         }
372 
setOptional(boolean optional)373         public EditField setOptional(boolean optional) {
374             this.optional = optional;
375             return this;
376         }
377 
setShortForm(boolean shortForm)378         public EditField setShortForm(boolean shortForm) {
379             this.shortForm = shortForm;
380             return this;
381         }
382 
setLongForm(boolean longForm)383         public EditField setLongForm(boolean longForm) {
384             this.longForm = longForm;
385             return this;
386         }
387 
setMinLines(int minLines)388         public EditField setMinLines(int minLines) {
389             this.minLines = minLines;
390             return this;
391         }
392 
isMultiLine()393         public boolean isMultiLine() {
394             return (inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) != 0;
395         }
396     }
397 
398     /**
399      * Generic method of inflating a given {@link Cursor} into a user-readable
400      * {@link CharSequence}. For example, an inflater could combine the multiple
401      * columns of {@link StructuredPostal} together using a string resource
402      * before presenting to the user.
403      */
404     public interface StringInflater {
inflateUsing(Context context, Cursor cursor)405         public CharSequence inflateUsing(Context context, Cursor cursor);
inflateUsing(Context context, ContentValues values)406         public CharSequence inflateUsing(Context context, ContentValues values);
407     }
408 
409     /**
410      * Compare two {@link AccountType} by their {@link AccountType#getDisplayLabel} with the
411      * current locale.
412      */
413     public static class DisplayLabelComparator implements Comparator<AccountType> {
414         private final Context mContext;
415         /** {@link Comparator} for the current locale. */
416         private final Collator mCollator = Collator.getInstance();
417 
DisplayLabelComparator(Context context)418         public DisplayLabelComparator(Context context) {
419             mContext = context;
420         }
421 
getDisplayLabel(AccountType type)422         private String getDisplayLabel(AccountType type) {
423             CharSequence label = type.getDisplayLabel(mContext);
424             return (label == null) ? "" : label.toString();
425         }
426 
427         @Override
compare(AccountType lhs, AccountType rhs)428         public int compare(AccountType lhs, AccountType rhs) {
429             return mCollator.compare(getDisplayLabel(lhs), getDisplayLabel(rhs));
430         }
431     }
432 }
433