Merge "Show confirmation dialog when leaving customize screen." into ub-contactsdialer-g-dev
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c7c6bf9..244f5b1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1796,4 +1796,13 @@
 
     <!-- Content description for (...) in no name header [CHAR LIMIT=30]-->
     <string name="description_no_name_header">Ellipsis</string>
+
+    <!-- Formatted call duration displayed in recent card in QuickContact, for duration less than 1 minute -->
+    <string name="callDurationSecondFormat"><xliff:g id="seconds">%s</xliff:g> sec</string>
+
+    <!-- Formatted call duration displayed in recent card in QuickContact, for duration less than 1 hour -->
+    <string name="callDurationMinuteFormat"><xliff:g id="minutes">%s</xliff:g> min <xliff:g id="seconds">%s</xliff:g> sec</string>
+
+    <!-- Formatted call duration displayed in recent card in QuickContact, for duration more than 1 hour -->
+    <string name="callDurationHourFormat"><xliff:g id="minutes">%s</xliff:g> hr <xliff:g id="minutes">%s</xliff:g> min <xliff:g id="seconds">%s</xliff:g> sec</string>
 </resources>
diff --git a/src/com/android/contacts/group/GroupNameEditDialogFragment.java b/src/com/android/contacts/group/GroupNameEditDialogFragment.java
index b2bfd0b..36a4710 100644
--- a/src/com/android/contacts/group/GroupNameEditDialogFragment.java
+++ b/src/com/android/contacts/group/GroupNameEditDialogFragment.java
@@ -25,9 +25,8 @@
 import android.content.Intent;
 import android.content.Loader;
 import android.database.Cursor;
-import android.net.Uri;
 import android.os.Bundle;
-import android.provider.ContactsContract;
+import android.provider.ContactsContract.Groups;
 import android.support.design.widget.TextInputLayout;
 import android.support.v7.app.AlertDialog;
 import android.text.Editable;
@@ -273,16 +272,27 @@
     @Override
     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
         // Only a single loader so id is ignored.
-        return new CursorLoader(getActivity(), GroupNameQuery.URI,
-                GroupNameQuery.PROJECTION, GroupNameQuery.getSelection(mAccount),
-                GroupNameQuery.getSelectionArgs(mAccount), null);
+        return new CursorLoader(getActivity(), Groups.CONTENT_SUMMARY_URI,
+                new String[] { Groups.TITLE, Groups.SYSTEM_ID, Groups.ACCOUNT_TYPE,
+                        Groups.SUMMARY_COUNT, Groups.GROUP_IS_READ_ONLY},
+                getSelection(), getSelectionArgs(), null);
     }
 
     @Override
     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
         mExistingGroups = new HashSet<>();
+        final GroupUtil.GroupsProjection projection = new GroupUtil.GroupsProjection(data);
         while (data.moveToNext()) {
-            mExistingGroups.add(data.getString(GroupNameQuery.TITLE));
+            final String title = projection.getTitle(data);
+            // Empty system groups aren't shown in the nav drawer so it would be confusing to tell
+            // the user that they already exist. Instead we allow them to create a duplicate
+            // group in this case. This is how the web handles this case as well (it creates a
+            // new non-system group if a new group with a title that matches a system group is
+            // create).
+            if (projection.isEmptyFFCGroup(data)) {
+                continue;
+            }
+            mExistingGroups.add(title);
         }
     }
 
@@ -290,38 +300,6 @@
     public void onLoaderReset(Loader<Cursor> loader) {
     }
 
-    /**
-     * Defines the structure of the query performed by the CursorLoader created by
-     * GroupNameEditDialogFragment
-     */
-    private static class GroupNameQuery {
-
-        public static final int TITLE = 0;
-        public static final Uri URI = ContactsContract.Groups.CONTENT_URI;
-        public static final String[] PROJECTION = new String[] { ContactsContract.Groups.TITLE };
-
-        public static String getSelection(AccountWithDataSet account) {
-            final StringBuilder builder = new StringBuilder();
-            builder.append(ContactsContract.Groups.ACCOUNT_NAME).append("=? AND ")
-                    .append(ContactsContract.Groups.ACCOUNT_TYPE).append("=?");
-            if (account.dataSet != null) {
-                builder.append(" AND ").append(ContactsContract.Groups.DATA_SET).append("=?");
-            }
-            return builder.toString();
-        }
-
-        public static String[] getSelectionArgs(AccountWithDataSet account) {
-            final int len = account.dataSet == null ? 2 : 3;
-            final String[] args = new String[len];
-            args[0] = account.name;
-            args[1] = account.type;
-            if (account.dataSet != null) {
-                args[2] = account.dataSet;
-            }
-            return args;
-        }
-    }
-
     private void showInputMethod(View view) {
         final InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(
                 Context.INPUT_METHOD_SERVICE);
@@ -352,4 +330,27 @@
         return mGroupNameEditText == null || mGroupNameEditText.getText() == null
                 ? null : mGroupNameEditText.getText().toString();
     }
+
+    private String getSelection() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append(Groups.ACCOUNT_NAME).append("=? AND ")
+               .append(Groups.ACCOUNT_TYPE).append("=? AND ")
+               .append(Groups.DELETED).append("=?");
+        if (mAccount.dataSet != null) {
+            builder.append(" AND ").append(Groups.DATA_SET).append("=?");
+        }
+        return builder.toString();
+    }
+
+    private String[] getSelectionArgs() {
+        final int len = mAccount.dataSet == null ? 3 : 4;
+        final String[] args = new String[len];
+        args[0] = mAccount.name;
+        args[1] = mAccount.type;
+        args[2] = "0"; // Not deleted
+        if (mAccount.dataSet != null) {
+            args[3] = mAccount.dataSet;
+        }
+        return args;
+    }
 }
diff --git a/src/com/android/contacts/group/GroupUtil.java b/src/com/android/contacts/group/GroupUtil.java
index fd8c03d..eae0217 100644
--- a/src/com/android/contacts/group/GroupUtil.java
+++ b/src/com/android/contacts/group/GroupUtil.java
@@ -219,4 +219,77 @@
         }
         return array;
     }
+
+    /**
+     * Stores column ordering for the projection of a query of ContactsContract.Groups
+     */
+    public static final class GroupsProjection {
+        public final int groupId;
+        public final int title;
+        public final int summaryCount;
+        public final int systemId;
+        public final int accountName;
+        public final int accountType;
+        public final int dataSet;
+        public final int autoAdd;
+        public final int favorites;
+        public final int isReadOnly;
+        public final int deleted;
+
+        public GroupsProjection(Cursor cursor) {
+            groupId = cursor.getColumnIndex(Groups._ID);
+            title = cursor.getColumnIndex(Groups.TITLE);
+            summaryCount = cursor.getColumnIndex(Groups.SUMMARY_COUNT);
+            systemId = cursor.getColumnIndex(Groups.SYSTEM_ID);
+            accountName = cursor.getColumnIndex(Groups.ACCOUNT_NAME);
+            accountType = cursor.getColumnIndex(Groups.ACCOUNT_TYPE);
+            dataSet = cursor.getColumnIndex(Groups.DATA_SET);
+            autoAdd = cursor.getColumnIndex(Groups.AUTO_ADD);
+            favorites = cursor.getColumnIndex(Groups.FAVORITES);
+            isReadOnly = cursor.getColumnIndex(Groups.GROUP_IS_READ_ONLY);
+            deleted = cursor.getColumnIndex(Groups.DELETED);
+        }
+
+        public GroupsProjection(String[] projection) {
+            List<String> list = Arrays.asList(projection);
+            groupId = list.indexOf(Groups._ID);
+            title = list.indexOf(Groups.TITLE);
+            summaryCount = list.indexOf(Groups.SUMMARY_COUNT);
+            systemId = list.indexOf(Groups.SYSTEM_ID);
+            accountName = list.indexOf(Groups.ACCOUNT_NAME);
+            accountType = list.indexOf(Groups.ACCOUNT_TYPE);
+            dataSet = list.indexOf(Groups.DATA_SET);
+            autoAdd = list.indexOf(Groups.AUTO_ADD);
+            favorites = list.indexOf(Groups.FAVORITES);
+            isReadOnly = list.indexOf(Groups.GROUP_IS_READ_ONLY);
+            deleted = list.indexOf(Groups.DELETED);
+        }
+
+        public String getTitle(Cursor cursor) {
+            return cursor.getString(title);
+        }
+
+        public long getId(Cursor cursor) {
+            return cursor.getLong(groupId);
+        }
+
+        public String getSystemId(Cursor cursor) {
+            return cursor.getString(systemId);
+        }
+
+        public int getSummaryCount(Cursor cursor) {
+            return cursor.getInt(summaryCount);
+        }
+
+        public boolean isEmptyFFCGroup(Cursor cursor) {
+            if (accountType == -1 || isReadOnly == -1 ||
+                    systemId == -1 || summaryCount == -1) {
+                throw new IllegalArgumentException("Projection is missing required columns");
+            }
+            return GoogleAccountType.ACCOUNT_TYPE.equals(cursor.getString(accountType))
+                    && cursor.getInt(isReadOnly) != 0
+                    && isSystemIdFFC(cursor.getString(systemId))
+                    && cursor.getInt(summaryCount) <= 0;
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/contacts/interactions/CallLogInteraction.java b/src/com/android/contacts/interactions/CallLogInteraction.java
index 3464c0f..06fd273 100644
--- a/src/com/android/contacts/interactions/CallLogInteraction.java
+++ b/src/com/android/contacts/interactions/CallLogInteraction.java
@@ -87,9 +87,18 @@
 
     @Override
     public String getViewFooter(Context context) {
-        Long date = getDate();
-        return date == null ? null : ContactInteractionUtil.formatDateStringFromTimestamp(
-                date, context);
+        final Long date = getDate();
+        if (date != null) {
+            final StringBuilder callDetail = new StringBuilder();
+            callDetail.append(ContactInteractionUtil.formatDateStringFromTimestamp(date, context));
+            final Long duration = getDuration();
+            if (duration != null) {
+                callDetail.append("\n");
+                callDetail.append(ContactInteractionUtil.formatDuration(duration, context));
+            }
+            return callDetail.toString();
+        }
+        return null;
     }
 
     @Override
diff --git a/src/com/android/contacts/interactions/ContactInteractionUtil.java b/src/com/android/contacts/interactions/ContactInteractionUtil.java
index 8ec0547..b2bda5d 100644
--- a/src/com/android/contacts/interactions/ContactInteractionUtil.java
+++ b/src/com/android/contacts/interactions/ContactInteractionUtil.java
@@ -26,6 +26,8 @@
 
 import java.util.Calendar;
 
+import com.android.contacts.R;
+
 /**
  * Utility methods for interactions and their loaders
  */
@@ -84,4 +86,23 @@
         return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR) &&
                 c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR);
     }
+
+    /**
+     * Takes duration of the call in seconds.
+     * Return the formatted duration in hr, min, sec order if they exist.
+     */
+    @NeededForTesting
+    public static String formatDuration(long callDuration, Context context) {
+        final int hours = (int) callDuration / 3600;
+        final int minutes = (int) (callDuration % 3600) / 60;
+        final int seconds = (int) (callDuration % 60);
+
+        if (hours > 0) {
+            return context.getString(R.string.callDurationHourFormat, hours, minutes, seconds);
+        } else if (minutes > 0) {
+            return context.getString(R.string.callDurationMinuteFormat, minutes, seconds);
+        } else {
+            return context.getString(R.string.callDurationSecondFormat, seconds);
+        }
+    }
 }
diff --git a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
index db6d80a..da017cc 100644
--- a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
+++ b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
@@ -497,12 +497,12 @@
         if (TextUtils.isEmpty(mTitleTextView.getText()) &&
                 entriesViewGroup.size() > 0) {
             final View entry = entriesViewGroup.get(0);
-            entry.setPadding(entry.getPaddingLeft(),
+            entry.setPaddingRelative(entry.getPaddingStart(),
                     getResources().getDimensionPixelSize(
                             R.dimen.expanding_entry_card_item_padding_top) +
                     getResources().getDimensionPixelSize(
                             R.dimen.expanding_entry_card_null_title_top_extra_padding),
-                    entry.getPaddingRight(),
+                    entry.getPaddingEnd(),
                     entry.getPaddingBottom());
         }
     }
diff --git a/tests/src/com/android/contacts/interactions/ContactInteractionUtilTest.java b/tests/src/com/android/contacts/interactions/ContactInteractionUtilTest.java
index 86167c1..07ad722 100644
--- a/tests/src/com/android/contacts/interactions/ContactInteractionUtilTest.java
+++ b/tests/src/com/android/contacts/interactions/ContactInteractionUtilTest.java
@@ -90,6 +90,31 @@
                         getContext()));
     }
 
+    public void testFormatDuration_zero() {
+        assertEquals("0 sec",
+                ContactInteractionUtil.formatDuration(0, getContext()));
+    }
+
+    public void testFormatDuration_minZeroSec() {
+        assertEquals("1 min 0 sec",
+                ContactInteractionUtil.formatDuration(60, getContext()));
+    }
+
+    public void testFormatDuration_minSec() {
+        assertEquals("30 min 9 sec",
+                ContactInteractionUtil.formatDuration(1809, getContext()));
+    }
+
+    public void testFormatDuration_hrZeroMinZeroSec() {
+        assertEquals("1 hr 0 min 0 sec",
+                ContactInteractionUtil.formatDuration(3600, getContext()));
+    }
+
+    public void testFormatDuration_hrMinSec() {
+        assertEquals("2 hr 44 min 36 sec",
+                ContactInteractionUtil.formatDuration(9876, getContext()));
+    }
+
     private void setLocale(Locale locale) {
         Locale.setDefault(locale);
         Resources res = getContext().getResources();