Merge "Improve analytics" into lmp-mr1-dev
diff --git a/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java b/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java
index 437d855..c6483cc 100644
--- a/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java
+++ b/src/com/android/contacts/common/interactions/ImportExportDialogFragment.java
@@ -304,12 +304,12 @@
     private static class AdapterEntry {
         public final String mLabel;
         public final int mChoiceResourceId;
-        public final long mSubscriptionId;
+        public final int mSubscriptionId;
 
-        public AdapterEntry(String label, int resId, long subscriptionId) {
+        public AdapterEntry(String label, int resId, int subId) {
             mLabel = label;
             mChoiceResourceId = resId;
-            mSubscriptionId = subscriptionId;
+            mSubscriptionId = subId;
         }
 
         public AdapterEntry(String label, int resId) {
diff --git a/src/com/android/contacts/common/list/ContactListItemView.java b/src/com/android/contacts/common/list/ContactListItemView.java
index e5de1f4..d90af13 100644
--- a/src/com/android/contacts/common/list/ContactListItemView.java
+++ b/src/com/android/contacts/common/list/ContactListItemView.java
@@ -49,6 +49,7 @@
 import com.android.contacts.common.ContactStatusUtil;
 import com.android.contacts.common.R;
 import com.android.contacts.common.format.TextHighlighter;
+import com.android.contacts.common.util.ContactDisplayUtils;
 import com.android.contacts.common.util.SearchUtil;
 import com.android.contacts.common.util.ViewUtil;
 
@@ -1038,6 +1039,13 @@
         } else {
             mTextHighlighter.setPrefixText(getSnippetView(), text, mHighlightedPrefix);
             mSnippetView.setVisibility(VISIBLE);
+            if (ContactDisplayUtils.isPossiblePhoneNumber(text)) {
+                // Give the text-to-speech engine a hint that it's a phone number
+                mSnippetView.setContentDescription(
+                        ContactDisplayUtils.getTelephoneTtsSpannable(text));
+            } else {
+                mSnippetView.setContentDescription(null);
+            }
         }
     }
 
@@ -1149,6 +1157,14 @@
             name = mUnknownNameText;
         }
         setMarqueeText(getNameTextView(), name);
+
+        if (ContactDisplayUtils.isPossiblePhoneNumber(name)) {
+            // Give the text-to-speech engine a hint that it's a phone number
+            mNameTextView.setContentDescription(
+                    ContactDisplayUtils.getTelephoneTtsSpannable(name.toString()));
+        } else {
+            mNameTextView.setContentDescription(null);
+        }
     }
 
     public void hideDisplayName() {
diff --git a/src/com/android/contacts/common/list/ViewPagerTabs.java b/src/com/android/contacts/common/list/ViewPagerTabs.java
index ec95de6..006d632 100644
--- a/src/com/android/contacts/common/list/ViewPagerTabs.java
+++ b/src/com/android/contacts/common/list/ViewPagerTabs.java
@@ -198,7 +198,12 @@
     @Override
     public void onPageSelected(int position) {
         position = getRtlPosition(position);
-        if (mPrevSelected >= 0) {
+        int tabStripChildCount = mTabStrip.getChildCount();
+        if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
+            return;
+        }
+
+        if (mPrevSelected >= 0 && mPrevSelected < tabStripChildCount) {
             mTabStrip.getChildAt(mPrevSelected).setSelected(false);
         }
         final View selectedChild = mTabStrip.getChildAt(position);
diff --git a/src/com/android/contacts/common/model/account/BaseAccountType.java b/src/com/android/contacts/common/model/account/BaseAccountType.java
index 984cb78..648a6dc 100644
--- a/src/com/android/contacts/common/model/account/BaseAccountType.java
+++ b/src/com/android/contacts/common/model/account/BaseAccountType.java
@@ -99,20 +99,20 @@
         static final String TYPE = "type";
     }
 
-    private interface Weight {
+    protected interface Weight {
         static final int NONE = -1;
-        static final int ORGANIZATION = 5;
         static final int PHONE = 10;
         static final int EMAIL = 15;
-        static final int IM = 20;
         static final int STRUCTURED_POSTAL = 25;
-        static final int NOTE = 110;
-        static final int NICKNAME = 115;
-        static final int WEBSITE = 120;
-        static final int SIP_ADDRESS = 130;
-        static final int EVENT = 150;
-        static final int RELATIONSHIP = 160;
-        static final int GROUP_MEMBERSHIP = 999;
+        static final int NICKNAME = 111;
+        static final int EVENT = 120;
+        static final int ORGANIZATION = 125;
+        static final int NOTE = 130;
+        static final int IM = 140;
+        static final int SIP_ADDRESS = 145;
+        static final int GROUP_MEMBERSHIP = 150;
+        static final int WEBSITE = 160;
+        static final int RELATIONSHIP = 999;
     }
 
     public BaseAccountType() {
@@ -148,7 +148,7 @@
 
     protected DataKind addDataKindStructuredName(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
-                R.string.nameLabelsGroup, -1, true));
+                R.string.nameLabelsGroup, Weight.NONE, true));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
         kind.typeOverallMax = 1;
@@ -178,7 +178,7 @@
 
     protected DataKind addDataKindDisplayName(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
-                R.string.nameLabelsGroup, -1, true));
+                R.string.nameLabelsGroup, Weight.NONE, true));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
         kind.typeOverallMax = 1;
@@ -219,7 +219,7 @@
 
     protected DataKind addDataKindPhoneticName(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
-                R.string.name_phonetic, -1, true));
+                R.string.name_phonetic, Weight.NONE, true));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
         kind.typeOverallMax = 1;
@@ -239,7 +239,7 @@
 
     protected DataKind addDataKindNickname(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Nickname.CONTENT_ITEM_TYPE,
-                    R.string.nicknameLabelsGroup, 115, true));
+                    R.string.nicknameLabelsGroup, Weight.NICKNAME, true));
         kind.typeOverallMax = 1;
         kind.actionHeader = new SimpleInflater(R.string.nicknameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
@@ -255,7 +255,7 @@
 
     protected DataKind addDataKindPhone(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Phone.CONTENT_ITEM_TYPE, R.string.phoneLabelsGroup,
-                10, true));
+                Weight.PHONE, true));
         kind.iconAltRes = R.drawable.ic_text_holo_light;
         kind.iconAltDescriptionRes = R.string.sms;
         kind.actionHeader = new PhoneActionInflater();
@@ -294,7 +294,7 @@
 
     protected DataKind addDataKindEmail(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Email.CONTENT_ITEM_TYPE, R.string.emailLabelsGroup,
-                15, true));
+                Weight.EMAIL, true));
         kind.actionHeader = new EmailActionInflater();
         kind.actionBody = new SimpleInflater(Email.DATA);
         kind.typeColumn = Email.TYPE;
@@ -314,7 +314,7 @@
 
     protected DataKind addDataKindStructuredPostal(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(StructuredPostal.CONTENT_ITEM_TYPE,
-                R.string.postalLabelsGroup, 25, true));
+                R.string.postalLabelsGroup, Weight.STRUCTURED_POSTAL, true));
         kind.actionHeader = new PostalActionInflater();
         kind.actionBody = new SimpleInflater(StructuredPostal.FORMATTED_ADDRESS);
         kind.typeColumn = StructuredPostal.TYPE;
@@ -336,8 +336,8 @@
     }
 
     protected DataKind addDataKindIm(Context context) throws DefinitionException {
-        DataKind kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup, 20,
-                true));
+        DataKind kind = addKind(new DataKind(Im.CONTENT_ITEM_TYPE, R.string.imLabelsGroup,
+                Weight.IM, true));
         kind.actionHeader = new ImActionInflater();
         kind.actionBody = new SimpleInflater(Im.DATA);
 
@@ -368,7 +368,7 @@
 
     protected DataKind addDataKindOrganization(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Organization.CONTENT_ITEM_TYPE,
-                    R.string.organizationLabelsGroup, 5, true));
+                    R.string.organizationLabelsGroup, Weight.ORGANIZATION, true));
         kind.actionHeader = new SimpleInflater(R.string.organizationLabelsGroup);
         kind.actionBody = ORGANIZATION_BODY_INFLATER;
         kind.typeOverallMax = 1;
@@ -383,7 +383,7 @@
     }
 
     protected DataKind addDataKindPhoto(Context context) throws DefinitionException {
-        DataKind kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, -1, true));
+        DataKind kind = addKind(new DataKind(Photo.CONTENT_ITEM_TYPE, -1, Weight.NONE, true));
         kind.typeOverallMax = 1;
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(Photo.PHOTO, -1, -1));
@@ -391,8 +391,8 @@
     }
 
     protected DataKind addDataKindNote(Context context) throws DefinitionException {
-        DataKind kind = addKind(new DataKind(Note.CONTENT_ITEM_TYPE, R.string.label_notes, 110,
-                true));
+        DataKind kind = addKind(new DataKind(Note.CONTENT_ITEM_TYPE, R.string.label_notes,
+                Weight.NOTE, true));
         kind.typeOverallMax = 1;
         kind.actionHeader = new SimpleInflater(R.string.label_notes);
         kind.actionBody = new SimpleInflater(Note.NOTE);
@@ -406,7 +406,7 @@
 
     protected DataKind addDataKindWebsite(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Website.CONTENT_ITEM_TYPE,
-                R.string.websiteLabelsGroup, 120, true));
+                R.string.websiteLabelsGroup, Weight.WEBSITE, true));
         kind.actionHeader = new SimpleInflater(R.string.websiteLabelsGroup);
         kind.actionBody = new SimpleInflater(Website.URL);
         kind.defaultValues = new ContentValues();
@@ -420,7 +420,7 @@
 
     protected DataKind addDataKindSipAddress(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(SipAddress.CONTENT_ITEM_TYPE,
-                    R.string.label_sip_address, 130, true));
+                    R.string.label_sip_address, Weight.SIP_ADDRESS, true));
 
         kind.typeOverallMax = 1;
         kind.actionHeader = new SimpleInflater(R.string.label_sip_address);
@@ -434,7 +434,7 @@
 
     protected DataKind addDataKindGroupMembership(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(GroupMembership.CONTENT_ITEM_TYPE,
-                R.string.groupsLabel, 999, true));
+                R.string.groupsLabel, Weight.GROUP_MEMBERSHIP, true));
 
         kind.typeOverallMax = 1;
         kind.fieldList = Lists.newArrayList();
diff --git a/src/com/android/contacts/common/model/account/ExchangeAccountType.java b/src/com/android/contacts/common/model/account/ExchangeAccountType.java
index 04b5263..7020836 100644
--- a/src/com/android/contacts/common/model/account/ExchangeAccountType.java
+++ b/src/com/android/contacts/common/model/account/ExchangeAccountType.java
@@ -80,7 +80,7 @@
     @Override
     protected DataKind addDataKindStructuredName(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(StructuredName.CONTENT_ITEM_TYPE,
-                R.string.nameLabelsGroup, -1, true));
+                R.string.nameLabelsGroup, Weight.NONE, true));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
 
@@ -109,7 +109,7 @@
     @Override
     protected DataKind addDataKindDisplayName(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
-                R.string.nameLabelsGroup, -1, true));
+                R.string.nameLabelsGroup, Weight.NONE, true));
 
         boolean displayOrderPrimary =
                 context.getResources().getBoolean(R.bool.config_editor_field_order_primary);
@@ -142,7 +142,7 @@
     @Override
     protected DataKind addDataKindPhoneticName(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
-                R.string.name_phonetic, -1, true));
+                R.string.name_phonetic, Weight.NONE, true));
         kind.actionHeader = new SimpleInflater(R.string.nameLabelsGroup);
         kind.actionBody = new SimpleInflater(Nickname.NAME);
 
@@ -307,7 +307,7 @@
 
     protected DataKind addDataKindEvent(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Event.CONTENT_ITEM_TYPE, R.string.eventLabelsGroup,
-                150, true));
+                Weight.EVENT, true));
         kind.actionHeader = new EventActionInflater();
         kind.actionBody = new SimpleInflater(Event.START_DATE);
 
diff --git a/src/com/android/contacts/common/model/account/GoogleAccountType.java b/src/com/android/contacts/common/model/account/GoogleAccountType.java
index 8705ae3..6877187 100644
--- a/src/com/android/contacts/common/model/account/GoogleAccountType.java
+++ b/src/com/android/contacts/common/model/account/GoogleAccountType.java
@@ -122,7 +122,7 @@
 
     private DataKind addDataKindRelation(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Relation.CONTENT_ITEM_TYPE,
-                R.string.relationLabelsGroup, 160, true));
+                R.string.relationLabelsGroup, Weight.RELATIONSHIP, true));
         kind.actionHeader = new RelationActionInflater();
         kind.actionBody = new SimpleInflater(Relation.NAME);
 
@@ -157,7 +157,7 @@
 
     private DataKind addDataKindEvent(Context context) throws DefinitionException {
         DataKind kind = addKind(new DataKind(Event.CONTENT_ITEM_TYPE,
-                    R.string.eventLabelsGroup, 150, true));
+                    R.string.eventLabelsGroup, Weight.EVENT, true));
         kind.actionHeader = new EventActionInflater();
         kind.actionBody = new SimpleInflater(Event.START_DATE);
 
diff --git a/src/com/android/contacts/common/util/AccountSelectionUtil.java b/src/com/android/contacts/common/util/AccountSelectionUtil.java
index 68217b9..da30e89 100644
--- a/src/com/android/contacts/common/util/AccountSelectionUtil.java
+++ b/src/com/android/contacts/common/util/AccountSelectionUtil.java
@@ -55,12 +55,12 @@
 
         final private Context mContext;
         final private int mResId;
-        final private long mSubscriptionId;
+        final private int mSubscriptionId;
 
         final protected List<AccountWithDataSet> mAccountList;
 
         public AccountSelectedListener(Context context, List<AccountWithDataSet> accountList,
-                int resId, long subscriptionId) {
+                int resId, int subscriptionId) {
             if (accountList == null || accountList.size() == 0) {
                 Log.e(LOG_TAG, "The size of Account list is 0.");
             }
diff --git a/src/com/android/contacts/common/util/ContactDisplayUtils.java b/src/com/android/contacts/common/util/ContactDisplayUtils.java
index 7ec751a..24bb6ef 100644
--- a/src/com/android/contacts/common/util/ContactDisplayUtils.java
+++ b/src/com/android/contacts/common/util/ContactDisplayUtils.java
@@ -19,10 +19,18 @@
 import static android.provider.ContactsContract.CommonDataKinds.Phone;
 
 import android.content.Context;
+import android.telephony.PhoneNumberUtils;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.TtsSpan;
 import android.util.Log;
+import android.util.Patterns;
 
 import com.android.contacts.common.R;
 
+import com.android.i18n.phonenumbers.NumberParseException;
+import com.android.i18n.phonenumbers.PhoneNumberUtil;
+import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
 import com.google.common.base.Preconditions;
 
 /**
@@ -187,4 +195,74 @@
         }
     }
 
+    /**
+     * Whether the given text could be a phone number.
+     *
+     * Note this will miss many things that are legitimate phone numbers, for example,
+     * phone numbers with letters.
+     */
+    public static boolean isPossiblePhoneNumber(CharSequence text) {
+        return text == null ? false : Patterns.PHONE.matcher(text.toString()).matches();
+    }
+
+    /**
+     * Returns a Spannable for the given phone number with a telephone {@link TtsSpan} set over
+     * the entire length of the given phone number.
+     */
+    public static Spannable getTelephoneTtsSpannable(String phoneNumber) {
+        final Spannable spannable = new SpannableString(phoneNumber);
+        final TtsSpan ttsSpan = getTelephoneTtsSpan(phoneNumber);
+        spannable.setSpan(ttsSpan, 0, phoneNumber.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        return spannable;
+    }
+
+    /**
+     * Returns a Spannable for the given message with a telephone {@link TtsSpan} set for
+     * the given phone number text wherever it is found within the message.
+     */
+    public static Spannable getTelephoneTtsSpannable(String message, String phoneNumber) {
+        final Spannable spannable = new SpannableString(message);
+        int start = message.indexOf(phoneNumber);
+        while (start >= 0) {
+            final int end = start + phoneNumber.length();
+            final TtsSpan ttsSpan = getTelephoneTtsSpan(phoneNumber);
+            spannable.setSpan(ttsSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            start = message.indexOf(phoneNumber, end);
+        }
+        return spannable;
+    }
+
+    /**
+     * Returns a telephone {@link TtsSpan} for the given phone number.
+     */
+    public static TtsSpan getTelephoneTtsSpan(String phoneNumberString) {
+        if (phoneNumberString == null) {
+            throw new NullPointerException();
+        }
+
+        // Parse the phone number
+        final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
+        PhoneNumber phoneNumber = null;
+        try {
+            // Don't supply a defaultRegion so this fails for non-international numbers because
+            // we don't want to TalkBalk to read a country code (e.g. +1) if it is not already
+            // present
+            phoneNumber = phoneNumberUtil.parse(phoneNumberString, /* defaultRegion */ null);
+        } catch (NumberParseException ignored) {
+        }
+
+        // Build a telephone tts span
+        final TtsSpan.TelephoneBuilder builder = new TtsSpan.TelephoneBuilder();
+        if (phoneNumber == null) {
+            // Strip separators otherwise TalkBack will be silent
+            // (this behavior was observed with TalkBalk 4.0.2 from their alpha channel)
+            builder.setNumberParts(PhoneNumberUtils.stripSeparators(phoneNumberString));
+        } else {
+            if (phoneNumber.hasCountryCode()) {
+                builder.setCountryCode(Integer.toString(phoneNumber.getCountryCode()));
+            }
+            builder.setNumberParts(Long.toString(phoneNumber.getNationalNumber()));
+        }
+        return builder.build();
+    }
 }
diff --git a/src/com/android/contacts/common/util/MaterialColorMapUtils.java b/src/com/android/contacts/common/util/MaterialColorMapUtils.java
index 9c8862c..1e44643 100644
--- a/src/com/android/contacts/common/util/MaterialColorMapUtils.java
+++ b/src/com/android/contacts/common/util/MaterialColorMapUtils.java
@@ -23,7 +23,6 @@
 import android.os.Trace;
 
 public class MaterialColorMapUtils {
-
     private final TypedArray sPrimaryColors;
     private final TypedArray sSecondaryColors;
 
@@ -41,6 +40,36 @@
         }
         public final int mPrimaryColor;
         public final int mSecondaryColor;
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (getClass() != obj.getClass()) {
+                return false;
+            }
+            MaterialPalette other = (MaterialPalette) obj;
+            if (mPrimaryColor != other.mPrimaryColor) {
+                return false;
+            }
+            if (mSecondaryColor != other.mSecondaryColor) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + mPrimaryColor;
+            result = prime * result + mSecondaryColor;
+            return result;
+        }
     }
 
     /**
diff --git a/src/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java b/src/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java
index 0dbe70a..ca62ce5 100644
--- a/src/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java
+++ b/src/com/android/contacts/common/widget/SelectPhoneAccountDialogFragment.java
@@ -132,20 +132,18 @@
     }
 
     private class SelectAccountListAdapter extends ArrayAdapter<PhoneAccountHandle> {
-        private Context mContext;
         private int mResId;
 
         public SelectAccountListAdapter(
                 Context context, int resource, List<PhoneAccountHandle> accountHandles) {
             super(context, resource, accountHandles);
-            mContext = context;
             mResId = resource;
         }
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
             LayoutInflater inflater = (LayoutInflater)
-                    mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                    getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 
             View rowView;
             final ViewHolder holder;
@@ -166,7 +164,7 @@
             PhoneAccountHandle accountHandle = getItem(position);
             PhoneAccount account = mTelecomManager.getPhoneAccount(accountHandle);
             holder.textView.setText(account.getLabel());
-            holder.imageView.setImageDrawable(account.getIcon(mContext));
+            holder.imageView.setImageDrawable(account.getIcon(getContext()));
             return rowView;
         }
 
@@ -183,4 +181,4 @@
         }
         super.onPause();
     }
-}
\ No newline at end of file
+}