Home | History | Annotate | Download | only in model
      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 
     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      */
     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      */
    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      */
    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      */
    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      */
    126     public String getViewContactNotifyServiceClassName() {
    127         return null;
    128     }
    129 
    130     /** Returns an optional Activity string that can be used to view the group. */
    131     public String getViewGroupActivity() {
    132         return null;
    133     }
    134 
    135     /** Returns an optional Activity string that can be used to view the stream item. */
    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. */
    141     public String getViewStreamItemPhotoActivity() {
    142         return null;
    143     }
    144 
    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      */
    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      */
    159     protected int getViewGroupLabelResId() {
    160         return -1;
    161     }
    162 
    163     /**
    164      * Returns {@link AccountTypeWithDataSet} for this type.
    165      */
    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      */
    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      */
    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      */
    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
    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 
    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      */
    235     abstract public boolean isGroupMembershipEditable();
    236 
    237     abstract public int getHeaderColor(Context context);
    238 
    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      */
    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      */
    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      */
    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 
    296         public EditType(int rawValue, int labelRes) {
    297             this.rawValue = rawValue;
    298             this.labelRes = labelRes;
    299             this.specificMax = -1;
    300         }
    301 
    302         public EditType setSecondary(boolean secondary) {
    303             this.secondary = secondary;
    304             return this;
    305         }
    306 
    307         public EditType setSpecificMax(int specificMax) {
    308             this.specificMax = specificMax;
    309             return this;
    310         }
    311 
    312         public EditType setCustomColumn(String customColumn) {
    313             this.customColumn = customColumn;
    314             return this;
    315         }
    316 
    317         @Override
    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
    327         public int hashCode() {
    328             return rawValue;
    329         }
    330     }
    331 
    332     public static class EventEditType extends EditType {
    333         private boolean mYearOptional;
    334 
    335         public EventEditType(int rawValue, int labelRes) {
    336             super(rawValue, labelRes);
    337         }
    338 
    339         public boolean isYearOptional() {
    340             return mYearOptional;
    341         }
    342 
    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 
    363         public EditField(String column, int titleRes) {
    364             this.column = column;
    365             this.titleRes = titleRes;
    366         }
    367 
    368         public EditField(String column, int titleRes, int inputType) {
    369             this(column, titleRes);
    370             this.inputType = inputType;
    371         }
    372 
    373         public EditField setOptional(boolean optional) {
    374             this.optional = optional;
    375             return this;
    376         }
    377 
    378         public EditField setShortForm(boolean shortForm) {
    379             this.shortForm = shortForm;
    380             return this;
    381         }
    382 
    383         public EditField setLongForm(boolean longForm) {
    384             this.longForm = longForm;
    385             return this;
    386         }
    387 
    388         public EditField setMinLines(int minLines) {
    389             this.minLines = minLines;
    390             return this;
    391         }
    392 
    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 {
    405         public CharSequence inflateUsing(Context context, Cursor cursor);
    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 
    418         public DisplayLabelComparator(Context context) {
    419             mContext = context;
    420         }
    421 
    422         private String getDisplayLabel(AccountType type) {
    423             CharSequence label = type.getDisplayLabel(mContext);
    424             return (label == null) ? "" : label.toString();
    425         }
    426 
    427         @Override
    428         public int compare(AccountType lhs, AccountType rhs) {
    429             return mCollator.compare(getDisplayLabel(lhs), getDisplayLabel(rhs));
    430         }
    431     }
    432 }
    433