/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.loaderapp.model;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import android.accounts.Account;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.widget.EditText;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
/**
* Internal structure that represents constraints and styles for a specific data
* source, such as the various data types they support, including details on how
* those types should be rendered and edited.
*
* In the future this may be inflated from XML defined by a data source.
*/
public abstract class ContactsSource {
/**
* The {@link RawContacts#ACCOUNT_TYPE} these constraints apply to.
*/
public String accountType = null;
/**
* Package that resources should be loaded from, either defined through an
* {@link Account} or for matching against {@link Data#RES_PACKAGE}.
*/
public String resPackageName;
public String summaryResPackageName;
public int titleRes;
public int iconRes;
public boolean readOnly;
/**
* Set of {@link DataKind} supported by this source.
*/
private ArrayList mKinds = Lists.newArrayList();
/**
* Lookup map of {@link #mKinds} on {@link DataKind#mimeType}.
*/
private HashMap mMimeKinds = Maps.newHashMap();
public static final int LEVEL_NONE = 0;
public static final int LEVEL_SUMMARY = 1;
public static final int LEVEL_MIMETYPES = 2;
public static final int LEVEL_CONSTRAINTS = 3;
private int mInflatedLevel = LEVEL_NONE;
public synchronized boolean isInflated(int inflateLevel) {
return mInflatedLevel >= inflateLevel;
}
/** @hide exposed for unit tests */
public void setInflatedLevel(int inflateLevel) {
mInflatedLevel = inflateLevel;
}
/**
* Ensure that this {@link ContactsSource} has been inflated to the
* requested level.
*/
public synchronized void ensureInflated(Context context, int inflateLevel) {
if (!isInflated(inflateLevel)) {
inflate(context, inflateLevel);
}
}
/**
* Perform the actual inflation to the requested level. Called by
* {@link #ensureInflated(Context, int)} when inflation is needed.
*/
protected abstract void inflate(Context context, int inflateLevel);
/**
* Invalidate any cache for this {@link ContactsSource}, removing all
* inflated data. Calling {@link #ensureInflated(Context, int)} will
* populate again from scratch.
*/
public synchronized void invalidateCache() {
this.mKinds.clear();
this.mMimeKinds.clear();
setInflatedLevel(LEVEL_NONE);
}
public CharSequence getDisplayLabel(Context context) {
if (this.titleRes != -1 && this.summaryResPackageName != null) {
final PackageManager pm = context.getPackageManager();
return pm.getText(this.summaryResPackageName, this.titleRes, null);
} else if (this.titleRes != -1) {
return context.getText(this.titleRes);
} else {
return this.accountType;
}
}
public Drawable getDisplayIcon(Context context) {
if (this.titleRes != -1 && this.summaryResPackageName != null) {
final PackageManager pm = context.getPackageManager();
return pm.getDrawable(this.summaryResPackageName, this.iconRes, null);
} else if (this.titleRes != -1) {
return context.getResources().getDrawable(this.iconRes);
} else {
return null;
}
}
abstract public int getHeaderColor(Context context);
abstract public int getSideBarColor(Context context);
/**
* {@link Comparator} to sort by {@link DataKind#weight}.
*/
private static Comparator sWeightComparator = new Comparator() {
public int compare(DataKind object1, DataKind object2) {
return object1.weight - object2.weight;
}
};
/**
* Return list of {@link DataKind} supported, sorted by
* {@link DataKind#weight}.
*/
public ArrayList getSortedDataKinds() {
// TODO: optimize by marking if already sorted
Collections.sort(mKinds, sWeightComparator);
return mKinds;
}
/**
* Find the {@link DataKind} for a specific MIME-type, if it's handled by
* this data source. If you may need a fallback {@link DataKind}, use
* {@link Sources#getKindOrFallback(String, String, Context, int)}.
*/
public DataKind getKindForMimetype(String mimeType) {
return this.mMimeKinds.get(mimeType);
}
/**
* Add given {@link DataKind} to list of those provided by this source.
*/
public DataKind addKind(DataKind kind) {
kind.resPackageName = this.resPackageName;
this.mKinds.add(kind);
this.mMimeKinds.put(kind.mimeType, kind);
return kind;
}
/**
* Description of a specific data type, usually marked by a unique
* {@link Data#MIMETYPE}. Includes details about how to view and edit
* {@link Data} rows of this kind, including the possible {@link EditType}
* labels and editable {@link EditField}.
*/
public static class DataKind {
public String resPackageName;
public String mimeType;
public int titleRes;
public int iconRes;
public int iconAltRes;
public int weight;
public boolean secondary;
public boolean editable;
/**
* If this is true (default), the user can add and remove values.
* If false, the editor will always show a single field (which might be empty).
*/
public boolean isList;
public StringInflater actionHeader;
public StringInflater actionAltHeader;
public StringInflater actionBody;
public boolean actionBodySocial = false;
public String typeColumn;
/**
* Maximum number of values allowed in the list. -1 represents infinity.
* If {@link DataKind#isList} is false, this value is ignored.
*/
public int typeOverallMax;
public List typeList;
public List fieldList;
public ContentValues defaultValues;
public DataKind() {
}
public DataKind(String mimeType, int titleRes, int iconRes, int weight, boolean editable) {
this.mimeType = mimeType;
this.titleRes = titleRes;
this.iconRes = iconRes;
this.weight = weight;
this.editable = editable;
this.isList = true;
this.typeOverallMax = -1;
}
}
/**
* Description of a specific "type" or "label" of a {@link DataKind} row,
* such as {@link Phone#TYPE_WORK}. Includes constraints on total number of
* rows a {@link Contacts} may have of this type, and details on how
* user-defined labels are stored.
*/
public static class EditType {
public int rawValue;
public int labelRes;
// public int actionRes;
// public int actionAltRes;
public boolean secondary;
public int specificMax;
public String customColumn;
public EditType(int rawValue, int labelRes) {
this.rawValue = rawValue;
this.labelRes = labelRes;
this.specificMax = -1;
}
public EditType setSecondary(boolean secondary) {
this.secondary = secondary;
return this;
}
public EditType setSpecificMax(int specificMax) {
this.specificMax = specificMax;
return this;
}
public EditType setCustomColumn(String customColumn) {
this.customColumn = customColumn;
return this;
}
@Override
public boolean equals(Object object) {
if (object instanceof EditType) {
final EditType other = (EditType)object;
return other.rawValue == rawValue;
}
return false;
}
@Override
public int hashCode() {
return rawValue;
}
}
/**
* Description of a user-editable field on a {@link DataKind} row, such as
* {@link Phone#NUMBER}. Includes flags to apply to an {@link EditText}, and
* the column where this field is stored.
*/
public static class EditField {
public String column;
public int titleRes;
public int inputType;
public int minLines;
public boolean optional;
public EditField(String column, int titleRes) {
this.column = column;
this.titleRes = titleRes;
}
public EditField(String column, int titleRes, int inputType) {
this(column, titleRes);
this.inputType = inputType;
}
public EditField setOptional(boolean optional) {
this.optional = optional;
return this;
}
}
/**
* Generic method of inflating a given {@link Cursor} into a user-readable
* {@link CharSequence}. For example, an inflater could combine the multiple
* columns of {@link StructuredPostal} together using a string resource
* before presenting to the user.
*/
public interface StringInflater {
public CharSequence inflateUsing(Context context, Cursor cursor);
public CharSequence inflateUsing(Context context, ContentValues values);
}
}