Merge "Do not translate FAB downward when transitioning between tabs." into ub-contactsdialer-b-dev
diff --git a/src/com/android/contacts/common/ContactsUtils.java b/src/com/android/contacts/common/ContactsUtils.java
index 2ef68d4..c37e8f8 100644
--- a/src/com/android/contacts/common/ContactsUtils.java
+++ b/src/com/android/contacts/common/ContactsUtils.java
@@ -22,6 +22,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.support.annotation.IntDef;
 import android.provider.ContactsContract.DisplayPhoto;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
@@ -30,8 +31,13 @@
 import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.common.model.dataitem.ImDataItem;
 import com.android.contacts.common.testing.NeededForTesting;
+import com.android.contacts.common.compat.ContactsCompat;
+import com.android.contacts.common.compat.DirectoryCompat;
+import com.android.contacts.common.compat.SdkSelectionUtils;
 import com.android.contacts.common.model.AccountTypeManager;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 public class ContactsUtils {
@@ -46,10 +52,9 @@
 
     private static int sThumbnailSize = -1;
 
-    public static final boolean FLAG_N_FEATURE =
-            false // Enforce Pre-N behavior in release build
-            && (Build.VERSION.SDK_INT > Build.VERSION_CODES.M
-                    || Build.VERSION.CODENAME.startsWith("N"));
+    public static final boolean FLAG_N_FEATURE = SdkSelectionUtils.TARGET_N_SDK // build-time flag
+            && (Build.VERSION.SDK_INT > Build.VERSION_CODES.M // runtime flag
+                    || Build.VERSION.CODENAME.startsWith("N")); // TODO: remove startsWith("N")
 
     // TODO find a proper place for the canonical version of these
     public interface ProviderNames {
@@ -96,6 +101,20 @@
         return null;
     }
 
+
+    public static final long USER_TYPE_CURRENT = 0;
+    public static final long USER_TYPE_WORK = 1;
+
+    /**
+     * UserType indicates the user type of the contact. If the contact is from Work User (Work
+     * Profile in Android Multi-User System), it's {@link #USER_TYPE_WORK}, otherwise,
+     * {@link #USER_TYPE_CURRENT}. Please note that current user can be in work profile, where the
+     * dialer is running inside Work Profile.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({USER_TYPE_CURRENT, USER_TYPE_WORK})
+    public @interface UserType {}
+
     /**
      * Test if the given {@link CharSequence} contains any graphic characters,
      * first checking {@link TextUtils#isEmpty(CharSequence)} to handle null.
@@ -223,4 +242,49 @@
         }
         return new Pair<>(intent, secondaryIntent);
     }
+
+    /**
+     * Determine UserType from directory id and contact id.
+     *
+     * 3 types of query
+     *
+     * 1. 2 profile query: content://com.android.contacts/phone_lookup_enterprise/1234567890
+     * personal and work contact are mixed into one cursor. no directory id. contact_id indicates if
+     * it's work contact
+     *
+     * 2. work local query:
+     * content://com.android.contacts/phone_lookup_enterprise/1234567890?directory=1000000000
+     * either directory_id or contact_id is enough to identify work contact
+     *
+     * 3. work remote query:
+     * content://com.android.contacts/phone_lookup_enterprise/1234567890?directory=1000000003
+     * contact_id is random. only directory_id is available
+     *
+     * Summary: If directory_id is not null, always use directory_id to identify work contact.
+     * (which is the case here) Otherwise, use contact_id.
+     *
+     * @param directoryId directory id of ContactsProvider query
+     * @param contactId contact id
+     * @return UserType indicates the user type of the contact. A directory id or contact id larger
+     *         than a thredshold indicates that the contact is stored in Work Profile, but not in
+     *         current user. It's a contract by ContactsProvider and check by
+     *         Contacts.isEnterpriseDirectoryId and Contacts.isEnterpriseContactId. Currently, only
+     *         2 kinds of users can be detected from the directoryId and contactId as
+     *         ContactsProvider can only access current and work user's contacts
+     */
+    public static @UserType long determineUserType(Long directoryId, Long contactId) {
+        // First check directory id
+        if (directoryId != null) {
+            return DirectoryCompat.isEnterpriseDirectoryId(directoryId) ? USER_TYPE_WORK
+                    : USER_TYPE_CURRENT;
+        }
+        // Only check contact id if directory id is null
+        if (contactId != null && contactId != 0L
+                && ContactsCompat.isEnterpriseContactId(contactId)) {
+            return USER_TYPE_WORK;
+        } else {
+            return USER_TYPE_CURRENT;
+        }
+
+    }
 }
diff --git a/src/com/android/contacts/common/compat/CallableCompat.java b/src/com/android/contacts/common/compat/CallableCompat.java
index 2979f63..d25d4be 100644
--- a/src/com/android/contacts/common/compat/CallableCompat.java
+++ b/src/com/android/contacts/common/compat/CallableCompat.java
@@ -28,8 +28,7 @@
             Uri.withAppendedPath(Callable.CONTENT_URI, "filter_enterprise");
 
     public static Uri getContentFilterUri() {
-        // TODO: Use N APIs
-        if (ContactsUtils.FLAG_N_FEATURE && android.os.Build.VERSION.CODENAME.startsWith("N")) {
+        if (ContactsUtils.FLAG_N_FEATURE) {
             return ENTERPRISE_CONTENT_FILTER_URI;
         }
         return Callable.CONTENT_FILTER_URI;
diff --git a/src/com/android/contacts/common/compat/ContactsCompat.java b/src/com/android/contacts/common/compat/ContactsCompat.java
index 5b50385..5a5e46a 100644
--- a/src/com/android/contacts/common/compat/ContactsCompat.java
+++ b/src/com/android/contacts/common/compat/ContactsCompat.java
@@ -40,8 +40,7 @@
     private static final long ENTERPRISE_CONTACT_ID_BASE = 1000000000;
 
     public static Uri getContentUri() {
-        // TODO: Use N APIs
-        if (ContactsUtils.FLAG_N_FEATURE && android.os.Build.VERSION.CODENAME.startsWith("N")) {
+        if (ContactsUtils.FLAG_N_FEATURE) {
             return ENTERPRISE_CONTENT_FILTER_URI;
         }
         return Contacts.CONTENT_FILTER_URI;
diff --git a/src/com/android/contacts/common/compat/DirectoryCompat.java b/src/com/android/contacts/common/compat/DirectoryCompat.java
index 5f6d8bf..f100938 100644
--- a/src/com/android/contacts/common/compat/DirectoryCompat.java
+++ b/src/com/android/contacts/common/compat/DirectoryCompat.java
@@ -24,30 +24,31 @@
 
 public class DirectoryCompat {
 
-    // TODO: Use N APIs
-    private static final Uri ENTERPRISE_CONTENT_URI =
-            Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, "directories_enterprise");
-    // TODO: Use N APIs
-    private static final long ENTERPRISE_LOCAL_INVISIBLE = 1000000000L + Directory.LOCAL_INVISIBLE;
-
     public static Uri getContentUri() {
-        // TODO: Use N APIs
-        if (ContactsUtils.FLAG_N_FEATURE && android.os.Build.VERSION.CODENAME.startsWith("N")) {
-            return ENTERPRISE_CONTENT_URI;
+        if (ContactsUtils.FLAG_N_FEATURE) {
+            return DirectorySdkCompat.ENTERPRISE_CONTENT_URI;
         }
         return Directory.CONTENT_URI;
     }
 
     public static boolean isInvisibleDirectory(long directoryId) {
-        return (directoryId == Directory.LOCAL_INVISIBLE
-                || directoryId == ENTERPRISE_LOCAL_INVISIBLE);
+        if (ContactsUtils.FLAG_N_FEATURE) {
+            return (directoryId == Directory.LOCAL_INVISIBLE
+                    || directoryId == DirectorySdkCompat.ENTERPRISE_LOCAL_INVISIBLE);
+        }
+        return directoryId == Directory.LOCAL_INVISIBLE;
     }
 
     public static boolean isRemoteDirectory(long directoryId) {
-        // TODO: Use N APIs
-        if (ContactsUtils.FLAG_N_FEATURE && android.os.Build.VERSION.CODENAME.startsWith("N")) {
+        if (ContactsUtils.FLAG_N_FEATURE) {
             return DirectorySdkCompat.isRemoteDirectory(directoryId);
         }
         return !(directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE);
     }
+
+    public static boolean isEnterpriseDirectoryId(long directoryId) {
+        return ContactsUtils.FLAG_N_FEATURE
+                ? DirectorySdkCompat.isEnterpriseDirectoryId(directoryId)
+                : false;
+    }
 }
diff --git a/src/com/android/contacts/common/compat/PhoneCompat.java b/src/com/android/contacts/common/compat/PhoneCompat.java
index 24600f5..5277761 100644
--- a/src/com/android/contacts/common/compat/PhoneCompat.java
+++ b/src/com/android/contacts/common/compat/PhoneCompat.java
@@ -28,8 +28,7 @@
             Uri.withAppendedPath(Phone.CONTENT_URI, "filter_enterprise");
 
     public static Uri getContentFilterUri() {
-        // TODO: Use N APIs
-        if (ContactsUtils.FLAG_N_FEATURE && android.os.Build.VERSION.CODENAME.startsWith("N")) {
+        if (ContactsUtils.FLAG_N_FEATURE) {
             return ENTERPRISE_CONTENT_FILTER_URI;
         }
         return Phone.CONTENT_FILTER_URI;
diff --git a/src/com/android/contacts/common/list/ContactEntryListAdapter.java b/src/com/android/contacts/common/list/ContactEntryListAdapter.java
index 089288b..a7c3504 100644
--- a/src/com/android/contacts/common/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/common/list/ContactEntryListAdapter.java
@@ -418,9 +418,17 @@
                 DirectoryPartition partition = new DirectoryPartition(false, true);
                 partition.setDirectoryId(id);
                 if (DirectoryCompat.isRemoteDirectory(id)) {
-                    partition.setLabel(mContext.getString(R.string.directory_search_label));
+                    if (DirectoryCompat.isEnterpriseDirectoryId(id)) {
+                        partition.setLabel(mContext.getString(R.string.directory_search_label_work));
+                    } else {
+                        partition.setLabel(mContext.getString(R.string.directory_search_label));
+                    }
                 } else {
-                    partition.setLabel(mDefaultFilterHeaderText.toString());
+                    if (DirectoryCompat.isEnterpriseDirectoryId(id)) {
+                        partition.setLabel(mContext.getString(R.string.list_filter_phones_work));
+                    } else {
+                        partition.setLabel(mDefaultFilterHeaderText.toString());
+                    }
                 }
                 partition.setDirectoryType(cursor.getString(directoryTypeColumnIndex));
                 partition.setDisplayName(cursor.getString(displayNameColumnIndex));
diff --git a/src/com/android/contacts/common/list/ContactListItemView.java b/src/com/android/contacts/common/list/ContactListItemView.java
index bb3fbc9..90b3d60 100644
--- a/src/com/android/contacts/common/list/ContactListItemView.java
+++ b/src/com/android/contacts/common/list/ContactListItemView.java
@@ -187,6 +187,7 @@
     private ImageView mPresenceIcon;
     private AppCompatCheckBox mCheckBox;
     private ImageView mVideoCallIcon;
+    private ImageView mWorkProfileIcon;
 
     private ColorStateList mSecondaryTextColor;
 
@@ -537,6 +538,14 @@
                     MeasureSpec.makeMeasureSpec(mVideoCallIconSize, MeasureSpec.EXACTLY));
         }
 
+        if (isVisible(mWorkProfileIcon)) {
+            mWorkProfileIcon.measure(
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+            mNameTextViewHeight =
+                    Math.max(mNameTextViewHeight, mWorkProfileIcon.getMeasuredHeight());
+        }
+
         if (isVisible(mStatusView)) {
             // Presence and status are in a same row, so status will be affected by icon size.
             final int statusWidth;
@@ -706,11 +715,32 @@
                 mLabelAndDataViewMaxHeight + mSnippetTextViewHeight + mStatusTextViewHeight;
         int textTopBound = (bottomBound + topBound - totalTextHeight) / 2 + mTextOffsetTop;
 
+        // Work Profile icon align top
+        int workProfileIconWidth = 0;
+        if (isVisible(mWorkProfileIcon)) {
+            workProfileIconWidth = mWorkProfileIcon.getMeasuredWidth();
+            final int distanceFromEnd = mCheckBoxWidth > 0
+                    ? mCheckBoxWidth + mGapBetweenImageAndText : 0;
+            if (mPhotoPosition == PhotoPosition.LEFT) {
+                // When photo is on left, label is placed on the right edge of the list item.
+                mWorkProfileIcon.layout(rightBound - workProfileIconWidth - distanceFromEnd,
+                        textTopBound,
+                        rightBound - distanceFromEnd,
+                        textTopBound + mNameTextViewHeight);
+            } else {
+                // When photo is on right, label is placed on the left of data view.
+                mWorkProfileIcon.layout(leftBound + distanceFromEnd,
+                        textTopBound,
+                        leftBound + workProfileIconWidth + distanceFromEnd,
+                        textTopBound + mNameTextViewHeight);
+            }
+        }
+
         // Layout all text view and presence icon
         // Put name TextView first
         if (isVisible(mNameTextView)) {
-            final int distanceFromEnd = mCheckBoxWidth > 0
-                    ? mCheckBoxWidth + mGapBetweenImageAndText : 0;
+            final int distanceFromEnd = workProfileIconWidth
+                    + (mCheckBoxWidth > 0 ? mCheckBoxWidth + mGapBetweenImageAndText : 0);
             if (mPhotoPosition == PhotoPosition.LEFT) {
                 mNameTextView.layout(leftBound,
                         textTopBound,
@@ -722,6 +752,9 @@
                         rightBound,
                         textTopBound + mNameTextViewHeight);
             }
+        }
+
+        if (isVisible(mNameTextView) || isVisible(mWorkProfileIcon)) {
             textTopBound += mNameTextViewHeight;
         }
 
@@ -1318,6 +1351,23 @@
         }
     }
 
+    /**
+     * Set to display work profile icon or not
+     *
+     * @param enabled set to display work profile icon or not
+     */
+    public void setWorkProfileIconEnabled(boolean enabled) {
+        if (mWorkProfileIcon != null) {
+            mWorkProfileIcon.setVisibility(enabled ? View.VISIBLE : View.GONE);
+        } else if (enabled) {
+            mWorkProfileIcon = new ImageView(getContext());
+            addView(mWorkProfileIcon);
+            mWorkProfileIcon.setImageResource(R.drawable.ic_work_profile);
+            mWorkProfileIcon.setScaleType(ScaleType.CENTER_INSIDE);
+            mWorkProfileIcon.setVisibility(View.VISIBLE);
+        }
+    }
+
     private TruncateAt getTextEllipsis() {
         return TruncateAt.MARQUEE;
     }
diff --git a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
index fc90fbf..9e886d9 100644
--- a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
@@ -35,6 +35,7 @@
 
 import com.android.contacts.common.CallUtil;
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
+import com.android.contacts.common.ContactsUtils;
 import com.android.contacts.common.GeoUtil;
 import com.android.contacts.common.R;
 import com.android.contacts.common.compat.CallableCompat;
@@ -321,11 +322,20 @@
     public Uri getDataUri(int partitionIndex, Cursor cursor) {
         final long directoryId =
                 ((DirectoryPartition)getPartition(partitionIndex)).getDirectoryId();
-        if (!DirectoryCompat.isRemoteDirectory(directoryId)) {
+        if (DirectoryCompat.isRemoteDirectory(directoryId)) {
+            return null;
+        } else if (DirectoryCompat.isEnterpriseDirectoryId(directoryId)) {
+            /*
+             * ContentUris.withAppendedId(Data.CONTENT_URI, phoneId), is invalid if
+             * isEnterpriseDirectoryId returns true, because the uri itself will fail since the
+             * ContactsProvider in Android Framework currently doesn't support it. return null until
+             * Android framework has enterprise version of Data.CONTENT_URI
+             */
+            return null;
+        } else {
             final long phoneId = cursor.getLong(PhoneQuery.PHONE_ID);
             return ContentUris.withAppendedId(Data.CONTENT_URI, phoneId);
         }
-        return null;
     }
 
     /**
@@ -427,6 +437,7 @@
 
             view.removePhotoView(true, false);
         }
+        bindWorkProfileIcon(view, partition);
 
         final DirectoryPartition directory = (DirectoryPartition) getPartition(partition);
         bindPhoneNumber(view, cursor, directory.isDisplayNumber(), position);
@@ -485,6 +496,13 @@
         view.hideDisplayName();
     }
 
+    private void bindWorkProfileIcon(final ContactListItemView view, int partition) {
+        final DirectoryPartition directory = (DirectoryPartition) getPartition(partition);
+        final long directoryId = directory.getDirectoryId();
+        final long userType = ContactsUtils.determineUserType(directoryId, null);
+        view.setWorkProfileIconEnabled(userType == ContactsUtils.USER_TYPE_WORK);
+    }
+
     protected void bindPhoto(final ContactListItemView view, int partitionIndex, Cursor cursor) {
         if (!isPhotoSupported(partitionIndex)) {
             view.removePhotoView();