Merge "Update list header only when contacts in default directory are loaded" into ub-contactsdialer-f-dev
am: dc36620ee8

Change-Id: I19bc9abeaa17c49eda5e7186fc03087fe500fc45
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index cee45a2..0bedf6b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -16,8 +16,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.contacts"
-    android:versionCode="10513"
-    android:versionName="1.5.13">
+    android:versionCode="10512"
+    android:versionName="1.5.12">
 
     <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="25" />
 
diff --git a/res/drawable-hdpi/ic_add_to_circles_black_24.png b/res/drawable-hdpi/ic_add_to_circles_black_24.png
deleted file mode 100644
index 6eb1fcc..0000000
--- a/res/drawable-hdpi/ic_add_to_circles_black_24.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_add_to_circles_black_24.png b/res/drawable-mdpi/ic_add_to_circles_black_24.png
deleted file mode 100644
index fbffc97..0000000
--- a/res/drawable-mdpi/ic_add_to_circles_black_24.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_add_to_circles_black_24.png b/res/drawable-xhdpi/ic_add_to_circles_black_24.png
deleted file mode 100644
index 79116b5..0000000
--- a/res/drawable-xhdpi/ic_add_to_circles_black_24.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_add_to_circles_black_24.png b/res/drawable-xxhdpi/ic_add_to_circles_black_24.png
deleted file mode 100644
index 23a2084..0000000
--- a/res/drawable-xxhdpi/ic_add_to_circles_black_24.png
+++ /dev/null
Binary files differ
diff --git a/res/layout/editor_save_button.xml b/res/layout/editor_save_button.xml
new file mode 100644
index 0000000..024ddcb
--- /dev/null
+++ b/res/layout/editor_save_button.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    style="?android:attr/buttonBarButtonStyle"
+    android:id="@+id/editor_menu_save_button"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:text="@string/menu_save"
+    android:textColor="@color/action_bar_button_text_color"
+    android:textSize="14sp">
+</Button>
diff --git a/res/menu/edit_contact.xml b/res/menu/edit_contact.xml
index 256edb6..9bf067f 100644
--- a/res/menu/edit_contact.xml
+++ b/res/menu/edit_contact.xml
@@ -18,7 +18,7 @@
     <item
         android:id="@+id/menu_save"
         android:showAsAction="always"
-        android:icon="@drawable/ic_done_wht_24dp"
+        android:actionLayout="@layout/editor_save_button"
         android:title="@string/menu_save" />
 
     <item
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7a81940..909b684 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -663,7 +663,7 @@
     <string name="toast_text_copied">Text copied</string>
 
     <!-- Contents of the alert dialog when the user hits the Cancel button in the editor [CHAR LIMIT=128] -->
-    <string name="cancel_confirmation_dialog_message">Discard your changes and quit editing?</string>
+    <string name="cancel_confirmation_dialog_message">Discard changes?</string>
 
     <!-- Positive button text for the cancel editing confirmation dialog.
       Pushing this button indicates that the user wishes to discard the changes they have already
@@ -673,7 +673,7 @@
     <!-- Negative button text for the cancel editing confirmation dialog.
       Pushing this button indicates that the user wishes to continue editing
       and return to the editor [CHAR LIMIT=30] -->
-    <string name="cancel_confirmation_dialog_keep_editing_button">Keep editing</string>
+    <string name="cancel_confirmation_dialog_keep_editing_button">Cancel</string>
 
     <!-- Description of a call log entry, made of a call type and a date -->
     <string name="call_type_and_date">
@@ -751,8 +751,7 @@
 
     <!-- Toast that appears when you are copying a directory contact into your personal contacts -->
     <string name="toast_making_personal_copy">Creating a personal copy&#8230;</string>
-    <!-- Timestamp string for interactions from yesterday. [CHAR LIMIT=40] -->
-    <string name="yesterday">Yesterday</string>
+    <!-- Timestamp string for interactions from tomorrow. [CHAR LIMIT=40] -->
     <string name="tomorrow">Tomorrow</string>
     <!-- Timestamp string for interactions from today. [CHAR LIMIT=40] -->
     <string name="today">Today</string>
diff --git a/src/com/android/contacts/activities/ContactEditorBaseActivity.java b/src/com/android/contacts/activities/ContactEditorBaseActivity.java
index 97095f0..c4abd58 100644
--- a/src/com/android/contacts/activities/ContactEditorBaseActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorBaseActivity.java
@@ -231,6 +231,7 @@
             actionBar.setTitle(getResources().getString(mActionBarTitleResId));
             actionBar.setDisplayShowHomeEnabled(true);
             actionBar.setDisplayHomeAsUpEnabled(true);
+            actionBar.setHomeAsUpIndicator(R.drawable.ic_close_dk);
         }
     }
 
diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
index c1e5af8..d149f16 100644
--- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
@@ -788,6 +788,15 @@
 
         // Save menu is invisible when there's only one read only contact in the editor.
         saveMenu.setVisible(!mRawContactDisplayAloneIsReadOnly);
+        if (saveMenu.isVisible()) {
+            // Since we're using a custom action layout we have to manually hook up the handler.
+            saveMenu.getActionView().setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    onOptionsItemSelected(saveMenu);
+                }
+            });
+        }
 
         if (mRawContactIdToDisplayAlone != -1 || mIsUserProfile) {
             sendToVoiceMailMenu.setVisible(false);
diff --git a/src/com/android/contacts/interactions/ContactInteractionUtil.java b/src/com/android/contacts/interactions/ContactInteractionUtil.java
index 98d45ee..8ec0547 100644
--- a/src/com/android/contacts/interactions/ContactInteractionUtil.java
+++ b/src/com/android/contacts/interactions/ContactInteractionUtil.java
@@ -26,9 +26,6 @@
 
 import java.util.Calendar;
 
-import com.android.contacts.R;
-
-
 /**
  * Utility methods for interactions and their loaders
  */
@@ -61,8 +58,7 @@
      * compareCalendar.
      * This formats the date based on a few conditions:
      * 1. If the timestamp is today, the time is shown
-     * 2. If the timestamp occurs tomorrow or yesterday, that is displayed
-     * 3. Otherwise {Month Date} format is used
+     * 2. Otherwise show full date and time
      */
     @NeededForTesting
     public static String formatDateStringFromTimestamp(long timestamp, Context context,
@@ -76,19 +72,9 @@
                     interactionCalendar.getTime());
         }
 
-        // Turn compareCalendar to yesterday
-        compareCalendar.add(Calendar.DAY_OF_YEAR, -1);
-        if (compareCalendarDayYear(interactionCalendar, compareCalendar)) {
-            return context.getString(R.string.yesterday);
-        }
-
-        // Turn compareCalendar to tomorrow
-        compareCalendar.add(Calendar.DAY_OF_YEAR, 2);
-        if (compareCalendarDayYear(interactionCalendar, compareCalendar)) {
-            return context.getString(R.string.tomorrow);
-        }
-        return DateUtils.formatDateTime(context, interactionCalendar.getTimeInMillis(),
-                DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_YEAR);
+        return DateUtils.formatDateTime(context, timestamp, DateUtils.FORMAT_SHOW_TIME
+                | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY
+                | DateUtils.FORMAT_SHOW_YEAR);
     }
 
     /**
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index e918233..c595f23 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -239,7 +239,6 @@
 
     private static final String MIMETYPE_GPLUS_PROFILE =
             "vnd.android.cursor.item/vnd.googleplus.profile";
-    private static final String GPLUS_PROFILE_DATA_5_ADD_TO_CIRCLE = "addtocircle";
     private static final String GPLUS_PROFILE_DATA_5_VIEW_PROFILE = "view";
     private static final String MIMETYPE_HANGOUTS =
             "vnd.android.cursor.item/vnd.googleplus.profile.comm";
@@ -2108,30 +2107,7 @@
                 // Build advanced entry for known 3p types. Otherwise default to ResolveCache icon.
                 switch (mimetype) {
                     case MIMETYPE_GPLUS_PROFILE:
-                        // If a secondDataItem is available, use it to build an entry with
-                        // alternate actions
-                        if (secondDataItem != null) {
-                            icon = res.getDrawable(R.drawable.ic_google_plus_black_24dp);
-                            alternateIcon = res.getDrawable(R.drawable.ic_add_to_circles_black_24);
-                            final GPlusOrHangoutsDataItemModel itemModel =
-                                    new GPlusOrHangoutsDataItemModel(intent, alternateIntent,
-                                            dataItem, secondDataItem, alternateContentDescription,
-                                            header, text, context);
-
-                            populateGPlusOrHangoutsDataItemModel(itemModel);
-                            intent = itemModel.intent;
-                            alternateIntent = itemModel.alternateIntent;
-                            alternateContentDescription = itemModel.alternateContentDescription;
-                            header = itemModel.header;
-                            text = itemModel.text;
-                        } else {
-                            if (GPLUS_PROFILE_DATA_5_ADD_TO_CIRCLE.equals(
-                                    intent.getDataString())) {
-                                icon = res.getDrawable(R.drawable.ic_add_to_circles_black_24);
-                            } else {
-                                icon = res.getDrawable(R.drawable.ic_google_plus_black_24dp);
-                            }
-                        }
+                        icon = res.getDrawable(R.drawable.ic_google_plus_black_24dp);
                         break;
                     case MIMETYPE_HANGOUTS:
                         // If a secondDataItem is available, use it to build an entry with
@@ -2139,12 +2115,12 @@
                         if (secondDataItem != null) {
                             icon = res.getDrawable(R.drawable.ic_hangout_24dp);
                             alternateIcon = res.getDrawable(R.drawable.ic_hangout_video_24dp);
-                            final GPlusOrHangoutsDataItemModel itemModel =
-                                    new GPlusOrHangoutsDataItemModel(intent, alternateIntent,
+                            final HangoutsDataItemModel itemModel =
+                                    new HangoutsDataItemModel(intent, alternateIntent,
                                             dataItem, secondDataItem, alternateContentDescription,
                                             header, text, context);
 
-                            populateGPlusOrHangoutsDataItemModel(itemModel);
+                            populateHangoutsDataItemModel(itemModel);
                             intent = itemModel.intent;
                             alternateIntent = itemModel.alternateIntent;
                             alternateContentDescription = itemModel.alternateContentDescription;
@@ -2216,9 +2192,10 @@
     private List<Entry> dataItemsToEntries(List<DataItem> dataItems,
             MutableString aboutCardTitleOut) {
         // Hangouts and G+ use two data items to create one entry.
-        if (dataItems.get(0).getMimeType().equals(MIMETYPE_GPLUS_PROFILE) ||
-                dataItems.get(0).getMimeType().equals(MIMETYPE_HANGOUTS)) {
-            return gPlusOrHangoutsDataItemsToEntries(dataItems);
+        if (dataItems.get(0).getMimeType().equals(MIMETYPE_GPLUS_PROFILE)) {
+            return gPlusDataItemsToEntries(dataItems);
+        } else if (dataItems.get(0).getMimeType().equals(MIMETYPE_HANGOUTS)) {
+            return hangoutsDataItemsToEntries(dataItems);
         } else {
             final List<Entry> entries = new ArrayList<>();
             for (DataItem dataItem : dataItems) {
@@ -2233,15 +2210,10 @@
     }
 
     /**
-     * G+ and Hangout entries are unique in that a single ExpandingEntryCardView.Entry consists
-     * of two data items. This method attempts to build each entry using the two data items if
-     * they are available. If there are more or less than two data items, a fall back is used
-     * and each data item gets its own entry.
+     * Put the data items into buckets based on the raw contact id
      */
-    private List<Entry> gPlusOrHangoutsDataItemsToEntries(List<DataItem> dataItems) {
-        final List<Entry> entries = new ArrayList<>();
+    private Map<Long, List<DataItem>> dataItemsToBucket(List<DataItem> dataItems) {
         final Map<Long, List<DataItem>> buckets = new HashMap<>();
-        // Put the data items into buckets based on the raw contact id
         for (DataItem dataItem : dataItems) {
             List<DataItem> bucket = buckets.get(dataItem.getRawContactId());
             if (bucket == null) {
@@ -2250,10 +2222,43 @@
             }
             bucket.add(dataItem);
         }
+        return buckets;
+    }
+
+    /**
+     * For G+ entries, a single ExpandingEntryCardView.Entry consists of two data items. This
+     * method use only the View profile to build entry.
+     */
+    private List<Entry> gPlusDataItemsToEntries(List<DataItem> dataItems) {
+        final List<Entry> entries = new ArrayList<>();
+
+        for (List<DataItem> bucket : dataItemsToBucket(dataItems).values()) {
+            for (DataItem dataItem : bucket) {
+                if (GPLUS_PROFILE_DATA_5_VIEW_PROFILE.equals(
+                        dataItem.getContentValues().getAsString(Data.DATA5))) {
+                    final Entry entry = dataItemToEntry(dataItem, /* secondDataItem = */ null,
+                            this, mContactData, /* aboutCardName = */ null);
+                    if (entry != null) {
+                        entries.add(entry);
+                    }
+                }
+            }
+        }
+        return entries;
+    }
+
+    /**
+     * For Hangouts entries, a single ExpandingEntryCardView.Entry consists of two data items. This
+     * method attempts to build each entry using the two data items if they are available. If there
+     * are more or less than two data items, a fall back is used and each data item gets its own
+     * entry.
+     */
+    private List<Entry> hangoutsDataItemsToEntries(List<DataItem> dataItems) {
+        final List<Entry> entries = new ArrayList<>();
 
         // Use the buckets to build entries. If a bucket contains two data items, build the special
         // entry, otherwise fall back to the normal entry.
-        for (List<DataItem> bucket : buckets.values()) {
+        for (List<DataItem> bucket : dataItemsToBucket(dataItems).values()) {
             if (bucket.size() == 2) {
                 // Use the pair to build an entry
                 final Entry entry = dataItemToEntry(bucket.get(0),
@@ -2276,10 +2281,10 @@
     }
 
     /**
-     * Used for statically passing around G+ or Hangouts data items and entry fields to
-     * populateGPlusOrHangoutsDataItemModel.
+     * Used for statically passing around Hangouts data items and entry fields to
+     * populateHangoutsDataItemModel.
      */
-    private static final class GPlusOrHangoutsDataItemModel {
+    private static final class HangoutsDataItemModel {
         public Intent intent;
         public Intent alternateIntent;
         public DataItem dataItem;
@@ -2289,7 +2294,7 @@
         public String text;
         public Context context;
 
-        public GPlusOrHangoutsDataItemModel(Intent intent, Intent alternateIntent, DataItem dataItem,
+        public HangoutsDataItemModel(Intent intent, Intent alternateIntent, DataItem dataItem,
                 DataItem secondDataItem, StringBuilder alternateContentDescription, String header,
                 String text, Context context) {
             this.intent = intent;
@@ -2303,18 +2308,16 @@
         }
     }
 
-    private static void populateGPlusOrHangoutsDataItemModel(
-            GPlusOrHangoutsDataItemModel dataModel) {
+    private static void populateHangoutsDataItemModel(
+            HangoutsDataItemModel dataModel) {
         final Intent secondIntent = new Intent(Intent.ACTION_VIEW);
         secondIntent.setDataAndType(ContentUris.withAppendedId(Data.CONTENT_URI,
                 dataModel.secondDataItem.getId()), dataModel.secondDataItem.getMimeType());
         // There is no guarantee the order the data items come in. Second
         // data item does not necessarily mean it's the alternate.
-        // Hangouts video and Add to circles should be alternate. Swap if needed
+        // Hangouts video should be alternate. Swap if needed
         if (HANGOUTS_DATA_5_VIDEO.equals(
-                dataModel.dataItem.getContentValues().getAsString(Data.DATA5)) ||
-                GPLUS_PROFILE_DATA_5_ADD_TO_CIRCLE.equals(
-                        dataModel.dataItem.getContentValues().getAsString(Data.DATA5))) {
+                dataModel.dataItem.getContentValues().getAsString(Data.DATA5))) {
             dataModel.alternateIntent = dataModel.intent;
             dataModel.alternateContentDescription = new StringBuilder(dataModel.header);
 
@@ -2323,9 +2326,7 @@
                     dataModel.secondDataItem.getDataKind());
             dataModel.text = dataModel.secondDataItem.getDataKind().typeColumn;
         } else if (HANGOUTS_DATA_5_MESSAGE.equals(
-                dataModel.dataItem.getContentValues().getAsString(Data.DATA5)) ||
-                GPLUS_PROFILE_DATA_5_VIEW_PROFILE.equals(
-                        dataModel.dataItem.getContentValues().getAsString(Data.DATA5))) {
+                dataModel.dataItem.getContentValues().getAsString(Data.DATA5))) {
             dataModel.alternateIntent = secondIntent;
             dataModel.alternateContentDescription = new StringBuilder(
                     dataModel.secondDataItem.buildDataStringForDisplay(dataModel.context,
diff --git a/tests/src/com/android/contacts/interactions/ContactInteractionUtilTest.java b/tests/src/com/android/contacts/interactions/ContactInteractionUtilTest.java
index 4802b46..86167c1 100644
--- a/tests/src/com/android/contacts/interactions/ContactInteractionUtilTest.java
+++ b/tests/src/com/android/contacts/interactions/ContactInteractionUtilTest.java
@@ -15,12 +15,9 @@
  */
 package com.android.contacts.interactions;
 
-import com.android.contacts.common.R;
-
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.test.AndroidTestCase;
-import android.text.format.DateUtils;
 
 import java.util.Calendar;
 import java.util.Locale;
@@ -80,50 +77,15 @@
                         getContext()));
     }
 
-    public void testFormatDateStringFromTimestamp_yesterday() {
-        // Test yesterday and tomorrow (Yesterday or Tomorrow shown)
-        calendar.add(Calendar.DAY_OF_YEAR, -1);
-        assertEquals(getContext().getResources().getString(R.string.yesterday),
-                ContactInteractionUtil.formatDateStringFromTimestamp(calendar.getTimeInMillis(),
-                        getContext()));
-    }
-
-    public void testFormatDateStringFromTimestamp_yesterdayLastYear() {
-        // Set to non leap year
-        calendar.set(Calendar.YEAR, 1999);
-        calendar.set(Calendar.DAY_OF_YEAR, 365);
-        long lastYear = calendar.getTimeInMillis();
-        calendar.add(Calendar.DAY_OF_YEAR, 1);
-
-        assertEquals(getContext().getResources().getString(R.string.yesterday),
-                ContactInteractionUtil.formatDateStringFromTimestamp(lastYear,
-                        getContext(), calendar));
-    }
-
-    public void testFormatDateStringFromTimestamp_tomorrow() {
-        calendar.add(Calendar.DAY_OF_YEAR, 1);
-        assertEquals(getContext().getResources().getString(R.string.tomorrow),
-                ContactInteractionUtil.formatDateStringFromTimestamp(calendar.getTimeInMillis(),
-                        getContext()));
-    }
-
-    public void testFormatDateStringFromTimestamp_tomorrowNewYear() {
-        calendar.set(Calendar.DAY_OF_YEAR, 1);
-        long thisYear = calendar.getTimeInMillis();
-        calendar.add(Calendar.DAY_OF_YEAR, -1);
-
-        assertEquals(getContext().getResources().getString(R.string.tomorrow),
-                ContactInteractionUtil.formatDateStringFromTimestamp(thisYear,
-                        getContext(), calendar));
-    }
-
     public void testFormatDateStringFromTimestamp_other() {
         // Test other (Month Date)
         calendar.set(
                 /* year = */ 1991,
                 /* month = */ Calendar.MONTH,
-                /* day = */ 11);
-        assertEquals("March 11",
+                /* day = */ 11,
+                /* hourOfDay = */ 8,
+                /* minute = */ 8);
+        assertEquals("Monday, March 11, 1991, 8:08 AM",
                 ContactInteractionUtil.formatDateStringFromTimestamp(calendar.getTimeInMillis(),
                         getContext()));
     }