summaryrefslogtreecommitdiff
path: root/java/com/android/contacts/common/model/account/AccountType.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/contacts/common/model/account/AccountType.java')
-rw-r--r--java/com/android/contacts/common/model/account/AccountType.java501
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));
+ }
+ }
+}