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 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