diff options
Diffstat (limited to 'java/com/android/contacts/common/model/account/AccountType.java')
-rw-r--r-- | java/com/android/contacts/common/model/account/AccountType.java | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/java/com/android/contacts/common/model/account/AccountType.java b/java/com/android/contacts/common/model/account/AccountType.java new file mode 100644 index 000000000..1ae485a5f --- /dev/null +++ b/java/com/android/contacts/common/model/account/AccountType.java @@ -0,0 +1,501 @@ +/* + * 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.contacts.common.model.account; + +import android.content.ContentValues; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.RawContacts; +import android.support.annotation.VisibleForTesting; +import android.util.ArrayMap; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import com.android.contacts.common.R; +import com.android.contacts.common.model.dataitem.DataKind; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +/** + * 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. + * + * <p>In the future this may be inflated from XML defined by a data source. + */ +public abstract class AccountType { + + private static final String TAG = "AccountType"; + /** {@link Comparator} to sort by {@link DataKind#weight}. */ + private static Comparator<DataKind> sWeightComparator = + new Comparator<DataKind>() { + @Override + public int compare(DataKind object1, DataKind object2) { + return object1.weight - object2.weight; + } + }; + /** The {@link RawContacts#ACCOUNT_TYPE} these constraints apply to. */ + public String accountType = null; + /** The {@link RawContacts#DATA_SET} these constraints apply to. */ + public String dataSet = null; + /** + * Package that resources should be loaded from. Will be null for embedded types, in which case + * resources are stored in this package itself. + * + * <p>TODO Clean up {@link #resourcePackageName}, {@link #syncAdapterPackageName} and {@link + * #getViewContactNotifyServicePackageName()}. + * + * <p>There's the following invariants: - {@link #syncAdapterPackageName} is always set to the + * actual sync adapter package name. - {@link #resourcePackageName} too is set to the same value, + * unless {@link #isEmbedded()}, in which case it'll be null. There's an unfortunate exception of + * {@link FallbackAccountType}. Even though it {@link #isEmbedded()}, but we set non-null to + * {@link #resourcePackageName} for unit tests. + */ + public String resourcePackageName; + /** + * The package name for the authenticator (for the embedded types, i.e. Google and Exchange) or + * the sync adapter (for external type, including extensions). + */ + public String syncAdapterPackageName; + + public int titleRes; + public int iconRes; + protected boolean mIsInitialized; + /** Set of {@link DataKind} supported by this source. */ + private ArrayList<DataKind> mKinds = new ArrayList<>(); + /** Lookup map of {@link #mKinds} on {@link DataKind#mimeType}. */ + private Map<String, DataKind> mMimeKinds = new ArrayMap<>(); + + /** + * Return a string resource loaded from the given package (or the current package if {@code + * packageName} is null), unless {@code resId} is -1, in which case it returns {@code + * defaultValue}. + * + * <p>(The behavior is undefined if the resource or package doesn't exist.) + */ + @VisibleForTesting + static CharSequence getResourceText( + Context context, String packageName, int resId, String defaultValue) { + if (resId != -1 && packageName != null) { + final PackageManager pm = context.getPackageManager(); + return pm.getText(packageName, resId, null); + } else if (resId != -1) { + return context.getText(resId); + } else { + return defaultValue; + } + } + + public static Drawable getDisplayIcon( + Context context, int titleRes, int iconRes, String syncAdapterPackageName) { + if (titleRes != -1 && syncAdapterPackageName != null) { + final PackageManager pm = context.getPackageManager(); + return pm.getDrawable(syncAdapterPackageName, iconRes, null); + } else if (titleRes != -1) { + return context.getResources().getDrawable(iconRes); + } else { + return null; + } + } + + /** + * Whether this account type was able to be fully initialized. This may be false if (for example) + * the package name associated with the account type could not be found. + */ + public final boolean isInitialized() { + return mIsInitialized; + } + + /** + * @return Whether this type is an "embedded" type. i.e. any of {@link FallbackAccountType}, + * {@link GoogleAccountType} or {@link ExternalAccountType}. + * <p>If an embedded type cannot be initialized (i.e. if {@link #isInitialized()} returns + * {@code false}) it's considered critical, and the application will crash. On the other hand + * if it's not an embedded type, we just skip loading the type. + */ + public boolean isEmbedded() { + return true; + } + + public boolean isExtension() { + return false; + } + + /** + * @return True if contacts can be created and edited using this app. If false, there could still + * be an external editor as provided by {@link #getEditContactActivityClassName()} or {@link + * #getCreateContactActivityClassName()} + */ + public abstract boolean areContactsWritable(); + + /** + * Returns an optional custom edit activity. + * + * <p>Only makes sense for non-embedded account types. The activity class should reside in the + * sync adapter package as determined by {@link #syncAdapterPackageName}. + */ + public String getEditContactActivityClassName() { + return null; + } + + /** + * Returns an optional custom new contact activity. + * + * <p>Only makes sense for non-embedded account types. The activity class should reside in the + * sync adapter package as determined by {@link #syncAdapterPackageName}. + */ + public String getCreateContactActivityClassName() { + return null; + } + + /** + * Returns an optional custom invite contact activity. + * + * <p>Only makes sense for non-embedded account types. The activity class should reside in the + * sync adapter package as determined by {@link #syncAdapterPackageName}. + */ + public String getInviteContactActivityClassName() { + return null; + } + + /** + * Returns an optional service that can be launched whenever a contact is being looked at. This + * allows the sync adapter to provide more up-to-date information. + * + * <p>The service class should reside in the sync adapter package as determined by {@link + * #getViewContactNotifyServicePackageName()}. + */ + public String getViewContactNotifyServiceClassName() { + return null; + } + + /** + * TODO This is way too hacky should be removed. + * + * <p>This is introduced for {@link GoogleAccountType} where {@link #syncAdapterPackageName} is + * the authenticator package name but the notification service is in the sync adapter package. See + * {@link #resourcePackageName} -- we should clean up those. + */ + public String getViewContactNotifyServicePackageName() { + return syncAdapterPackageName; + } + + /** Returns an optional Activity string that can be used to view the group. */ + public String getViewGroupActivity() { + return null; + } + + public CharSequence getDisplayLabel(Context context) { + // Note this resource is defined in the sync adapter package, not resourcePackageName. + return getResourceText(context, syncAdapterPackageName, titleRes, accountType); + } + + /** @return resource ID for the "invite contact" action label, or -1 if not defined. */ + protected int getInviteContactActionResId() { + return -1; + } + + /** @return resource ID for the "view group" label, or -1 if not defined. */ + protected int getViewGroupLabelResId() { + return -1; + } + + /** Returns {@link AccountTypeWithDataSet} for this type. */ + public AccountTypeWithDataSet getAccountTypeAndDataSet() { + return AccountTypeWithDataSet.get(accountType, dataSet); + } + + /** + * Returns a list of additional package names that should be inspected as additional external + * account types. This allows for a primary account type to indicate other packages that may not + * be sync adapters but which still provide contact data, perhaps under a separate data set within + * the account. + */ + public List<String> getExtensionPackageNames() { + return new ArrayList<String>(); + } + + /** + * Returns an optional custom label for the "invite contact" action, which will be shown on the + * contact card. (If not defined, returns null.) + */ + public CharSequence getInviteContactActionLabel(Context context) { + // Note this resource is defined in the sync adapter package, not resourcePackageName. + return getResourceText(context, syncAdapterPackageName, getInviteContactActionResId(), ""); + } + + /** + * Returns a label for the "view group" action. If not defined, this falls back to our own "View + * Updates" string + */ + public CharSequence getViewGroupLabel(Context context) { + // Note this resource is defined in the sync adapter package, not resourcePackageName. + final CharSequence customTitle = + getResourceText(context, syncAdapterPackageName, getViewGroupLabelResId(), null); + + return customTitle == null ? context.getText(R.string.view_updates_from_group) : customTitle; + } + + public Drawable getDisplayIcon(Context context) { + return getDisplayIcon(context, titleRes, iconRes, syncAdapterPackageName); + } + + /** Whether or not groups created under this account type have editable membership lists. */ + public abstract boolean isGroupMembershipEditable(); + + /** Return list of {@link DataKind} supported, sorted by {@link DataKind#weight}. */ + public ArrayList<DataKind> 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. */ + 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) throws DefinitionException { + if (kind.mimeType == null) { + throw new DefinitionException("null is not a valid mime type"); + } + if (mMimeKinds.get(kind.mimeType) != null) { + throw new DefinitionException("mime type '" + kind.mimeType + "' is already registered"); + } + + kind.resourcePackageName = this.resourcePackageName; + this.mKinds.add(kind); + this.mMimeKinds.put(kind.mimeType, kind); + return kind; + } + + /** + * Generic method of inflating a given {@link ContentValues} 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 { + + CharSequence inflateUsing(Context context, ContentValues values); + } + + protected static class DefinitionException extends Exception { + + public DefinitionException(String message) { + super(message); + } + + public DefinitionException(String message, Exception inner) { + super(message, inner); + } + } + + /** + * 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 boolean secondary; + /** + * The number of entries allowed for the type. -1 if not specified. + * + * @see DataKind#typeOverallMax + */ + 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; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + + " rawValue=" + + rawValue + + " labelRes=" + + labelRes + + " secondary=" + + secondary + + " specificMax=" + + specificMax + + " customColumn=" + + customColumn; + } + } + + public static class EventEditType extends EditType { + + private boolean mYearOptional; + + public EventEditType(int rawValue, int labelRes) { + super(rawValue, labelRes); + } + + public boolean isYearOptional() { + return mYearOptional; + } + + public EventEditType setYearOptional(boolean yearOptional) { + mYearOptional = yearOptional; + return this; + } + + @Override + public String toString() { + return super.toString() + " mYearOptional=" + mYearOptional; + } + } + + /** + * 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 final class EditField { + + public String column; + public int titleRes; + public int inputType; + public int minLines; + public boolean optional; + public boolean shortForm; + public boolean longForm; + + 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; + } + + public EditField setShortForm(boolean shortForm) { + this.shortForm = shortForm; + return this; + } + + public EditField setLongForm(boolean longForm) { + this.longForm = longForm; + return this; + } + + public EditField setMinLines(int minLines) { + this.minLines = minLines; + return this; + } + + public boolean isMultiLine() { + return (inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) != 0; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + + ":" + + " column=" + + column + + " titleRes=" + + titleRes + + " inputType=" + + inputType + + " minLines=" + + minLines + + " optional=" + + optional + + " shortForm=" + + shortForm + + " longForm=" + + longForm; + } + } + + /** + * Compare two {@link AccountType} by their {@link AccountType#getDisplayLabel} with the current + * locale. + */ + public static class DisplayLabelComparator implements Comparator<AccountType> { + + private final Context mContext; + /** {@link Comparator} for the current locale. */ + private final Collator mCollator = Collator.getInstance(); + + public DisplayLabelComparator(Context context) { + mContext = context; + } + + private String getDisplayLabel(AccountType type) { + CharSequence label = type.getDisplayLabel(mContext); + return (label == null) ? "" : label.toString(); + } + + @Override + public int compare(AccountType lhs, AccountType rhs) { + return mCollator.compare(getDisplayLabel(lhs), getDisplayLabel(rhs)); + } + } +} |