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.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 public StringInflater actionHeader; 200 public StringInflater actionAltHeader; 201 public StringInflater actionBody; 202 203 public boolean actionBodySocial = false; 204 205 public String typeColumn; 206 public int typeOverallMax; 207 208 public List<EditType> typeList; 209 public List<EditField> fieldList; 210 211 public ContentValues defaultValues; 212 DataKind()213 public DataKind() { 214 } 215 DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable)216 public DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable) { 217 this.mimeType = mimeType; 218 this.titleRes = titleRes; 219 this.iconRes = iconRes; 220 this.weight = weight; 221 this.editable = editable; 222 this.typeOverallMax = -1; 223 } 224 } 225 226 /** 227 * Description of a specific "type" or "label" of a {@link DataKind} row, 228 * such as {@link Phone#TYPE_WORK}. Includes constraints on total number of 229 * rows a {@link Contacts} may have of this type, and details on how 230 * user-defined labels are stored. 231 */ 232 public static class EditType { 233 public int rawValue; 234 public int labelRes; 235 // public int actionRes; 236 // public int actionAltRes; 237 public boolean secondary; 238 public int specificMax; 239 public String customColumn; 240 EditType(int rawValue, int labelRes)241 public EditType(int rawValue, int labelRes) { 242 this.rawValue = rawValue; 243 this.labelRes = labelRes; 244 this.specificMax = -1; 245 } 246 setSecondary(boolean secondary)247 public EditType setSecondary(boolean secondary) { 248 this.secondary = secondary; 249 return this; 250 } 251 setSpecificMax(int specificMax)252 public EditType setSpecificMax(int specificMax) { 253 this.specificMax = specificMax; 254 return this; 255 } 256 setCustomColumn(String customColumn)257 public EditType setCustomColumn(String customColumn) { 258 this.customColumn = customColumn; 259 return this; 260 } 261 262 @Override equals(Object object)263 public boolean equals(Object object) { 264 if (object instanceof EditType) { 265 final EditType other = (EditType)object; 266 return other.rawValue == rawValue; 267 } 268 return false; 269 } 270 271 @Override hashCode()272 public int hashCode() { 273 return rawValue; 274 } 275 } 276 277 /** 278 * Description of a user-editable field on a {@link DataKind} row, such as 279 * {@link Phone#NUMBER}. Includes flags to apply to an {@link EditText}, and 280 * the column where this field is stored. 281 */ 282 public static class EditField { 283 public String column; 284 public int titleRes; 285 public int inputType; 286 public int minLines; 287 public boolean optional; 288 EditField(String column, int titleRes)289 public EditField(String column, int titleRes) { 290 this.column = column; 291 this.titleRes = titleRes; 292 } 293 EditField(String column, int titleRes, int inputType)294 public EditField(String column, int titleRes, int inputType) { 295 this(column, titleRes); 296 this.inputType = inputType; 297 } 298 setOptional(boolean optional)299 public EditField setOptional(boolean optional) { 300 this.optional = optional; 301 return this; 302 } 303 } 304 305 /** 306 * Generic method of inflating a given {@link Cursor} into a user-readable 307 * {@link CharSequence}. For example, an inflater could combine the multiple 308 * columns of {@link StructuredPostal} together using a string resource 309 * before presenting to the user. 310 */ 311 public interface StringInflater { inflateUsing(Context context, Cursor cursor)312 public CharSequence inflateUsing(Context context, Cursor cursor); inflateUsing(Context context, ContentValues values)313 public CharSequence inflateUsing(Context context, ContentValues values); 314 } 315 316 } 317