Merge "Stop using deprecated methods." into ub-contactsdialer-i-dev
am: d051757d49

Change-Id: I2b9285777b04f65c56ed5bad71f462a00fc0af28
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d76b8f3..e24af50 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="10714"
-          android:versionName="1.7.14">
+          android:versionCode="10722"
+          android:versionName="1.7.22">
 
     <uses-sdk
         android:minSdkVersion="21"
diff --git a/res/layout/expanding_entry_card_item.xml b/res/layout/expanding_entry_card_item.xml
index 99f9174..dbbeee5 100644
--- a/res/layout/expanding_entry_card_item.xml
+++ b/res/layout/expanding_entry_card_item.xml
@@ -88,8 +88,8 @@
 
      <ImageView
          android:id="@+id/third_icon"
-         android:layout_width="wrap_content"
-         android:layout_height="wrap_content"
+         android:layout_width="@dimen/default_clickable_icon_size"
+         android:layout_height="@dimen/default_clickable_icon_size"
          android:layout_alignParentTop="true"
          android:layout_toStartOf="@+id/icon_alternate"
          android:layout_alignWithParentIfMissing="true"
@@ -102,8 +102,8 @@
 
      <ImageView
          android:id="@+id/icon_alternate"
-         android:layout_width="wrap_content"
-         android:layout_height="wrap_content"
+         android:layout_width="@dimen/default_clickable_icon_size"
+         android:layout_height="@dimen/default_clickable_icon_size"
          android:layout_alignParentEnd="true"
          android:layout_alignParentTop="true"
          android:visibility="gone"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index e9fe2ad..c14d396 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -14,6 +14,8 @@
      limitations under the License.
 -->
 <resources>
+    <!-- Size of clickable icons that are GAR-4 -->
+    <dimen name="default_clickable_icon_size">48dp</dimen>
     <!-- Copied from java/com/google/android/assets/launchscreens/res/values-port-v21/dimens.xml -->
     <!-- Values adjusted for nav bar size due to windowDrawsSystemBarBackgrounds -->
     <dimen name="launchscreens_product_logo_bottom">64dp</dimen>
diff --git a/src/com/android/contacts/CallUtil.java b/src/com/android/contacts/CallUtil.java
index ddde01c..bba1faa 100644
--- a/src/com/android/contacts/CallUtil.java
+++ b/src/com/android/contacts/CallUtil.java
@@ -19,17 +19,23 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
+import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.contacts.compat.CompatUtils;
 import com.android.contacts.compat.PhoneAccountSdkCompat;
 import com.android.contacts.util.PermissionsUtil;
 import com.android.contacts.util.PhoneNumberHelper;
 import com.android.contactsbind.FeedbackHelper;
+import com.android.contactsbind.experiments.Flags;
 import com.android.phone.common.PhoneConstants;
 
 import java.util.List;
@@ -60,6 +66,14 @@
      */
     public static final int VIDEO_CALLING_PRESENCE = 2;
 
+    /** {@link PhoneAccount#EXTRA_SUPPORTS_VIDEO_CALLING_FALLBACK} */
+    private static final String EXTRA_SUPPORTS_VIDEO_CALLING_FALLBACK =
+            "android.telecom.extra.SUPPORTS_VIDEO_CALLING_FALLBACK";
+
+    /** {@link CarrierConfigManager#CONFIG_ALLOW_VIDEO_CALLING_FALLBACK} */
+    private static final String CONFIG_ALLOW_VIDEO_CALLING_FALLBACK =
+            "allow_video_calling_fallback_bool";
+
     /**
      * Return an Intent for making a phone call. Scheme (e.g. tel, sip) will be determined
      * automatically.
@@ -80,7 +94,17 @@
      * automatically.
      */
     public static Intent getCallIntent(String number) {
-        return getCallIntent(getCallUri(number));
+        Uri uri = getCallUri(number);
+        return PhoneNumberUtils.isEmergencyNumber(number)
+                ? getCallIntentForEmergencyNumber(uri) : getCallIntent(uri);
+    }
+
+    /**
+     * Return an Intent to directly start Dialer when calling an emergency number. Scheme is always
+     * PhoneAccount.SCHEME_TEL.
+     */
+    private static Intent getCallIntentForEmergencyNumber(Uri uri) {
+        return new Intent(Intent.ACTION_DIAL, uri);
     }
 
     /**
@@ -199,4 +223,66 @@
         }
 
     }
+
+    /**
+     * Determines if we're able to use Tachyon as a fallback for video calling.
+     *
+     * @param context The context.
+     * @return {@code true} if there exists a call capable phone account which supports using a
+     * fallback for video calling, the carrier configuration supports a fallback, and the
+     * experiment for using a fallback is enabled. Otherwise {@code false} is returned.
+     */
+    public static boolean isTachyonEnabled(Context context) {
+        // Need to be able to read phone state, and be on at least N to check PhoneAccount extras.
+        if (!PermissionsUtil.hasPermission(context, android.Manifest.permission.READ_PHONE_STATE)
+                || !CompatUtils.isNCompatible()) {
+            return false;
+        }
+        TelecomManager telecommMgr = (TelecomManager)
+                context.getSystemService(Context.TELECOM_SERVICE);
+        if (telecommMgr == null) {
+            return false;
+        }
+        try {
+            List<PhoneAccountHandle> accountHandles = telecommMgr.getCallCapablePhoneAccounts();
+            for (PhoneAccountHandle accountHandle : accountHandles) {
+                PhoneAccount account = telecommMgr.getPhoneAccount(accountHandle);
+                if (account == null) {
+                    continue;
+                }
+                // Check availability for the device config.
+                final Bundle accountExtras = account.getExtras();
+                final boolean deviceEnabled = accountExtras != null && accountExtras.getBoolean(
+                        EXTRA_SUPPORTS_VIDEO_CALLING_FALLBACK);
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Device video fallback config: " + deviceEnabled);
+                }
+
+                // Check availability from carrier config.
+                final PersistableBundle carrierConfig = context.getSystemService(
+                        CarrierConfigManager.class).getConfig();
+                final boolean carrierEnabled =
+                        carrierConfig != null && carrierConfig.getBoolean(
+                                CONFIG_ALLOW_VIDEO_CALLING_FALLBACK);
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Carrier video fallback config: " + carrierEnabled);
+                }
+
+                // Check experiment value.
+                final boolean experimentEnabled = Flags.getInstance().getBoolean(
+                        Experiments.QUICK_CONTACT_VIDEO_CALL);
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Experiment video fallback config: " + experimentEnabled);
+                }
+
+                // All three checks above must be true to enable Tachyon calling.
+                return deviceEnabled && carrierEnabled && experimentEnabled;
+            }
+            return false;
+        } catch (SecurityException e) {
+            FeedbackHelper.sendFeedback(context, TAG,
+                    "Security exception when getting call capable phone accounts", e);
+            return false;
+        }
+    }
 }
diff --git a/src/com/android/contacts/DynamicShortcuts.java b/src/com/android/contacts/DynamicShortcuts.java
index fc0d05a..ac950d9 100644
--- a/src/com/android/contacts/DynamicShortcuts.java
+++ b/src/com/android/contacts/DynamicShortcuts.java
@@ -297,6 +297,7 @@
         final ShortcutInfo.Builder builder = new ShortcutInfo.Builder(mContext, id)
                 .setIntent(action)
                 .setIcon(icon)
+                .setExtras(extras)
                 .setDisabledMessage(mContext.getString(R.string.dynamic_shortcut_disabled_message));
 
         setLabel(builder, label);
@@ -305,6 +306,9 @@
 
     public ShortcutInfo getQuickContactShortcutInfo(long id, String lookupKey, String displayName) {
         final ShortcutInfo.Builder builder = builderForContactShortcut(id, lookupKey, displayName);
+        if (builder == null) {
+            return null;
+        }
         addIconForContact(id, lookupKey, displayName, builder);
         return builder.build();
     }
diff --git a/src/com/android/contacts/Experiments.java b/src/com/android/contacts/Experiments.java
index b53ced3..b66311d 100644
--- a/src/com/android/contacts/Experiments.java
+++ b/src/com/android/contacts/Experiments.java
@@ -39,6 +39,12 @@
             "Shortcuts__dynamic_min_content_change_update_delay_millis";
 
     /**
+     * Flags for enabling video call from quick contact.
+     */
+    public static final String QUICK_CONTACT_VIDEO_CALL =
+            "QuickContact__video_call_integration";
+
+    /**
      * Flags for maximum time to show spinner for a contacts sync.
      */
     public static final String PULL_TO_REFRESH_CANCEL_REFRESH_MILLIS =
diff --git a/src/com/android/contacts/ShortcutIntentBuilder.java b/src/com/android/contacts/ShortcutIntentBuilder.java
index e90e786..b3bd85d 100644
--- a/src/com/android/contacts/ShortcutIntentBuilder.java
+++ b/src/com/android/contacts/ShortcutIntentBuilder.java
@@ -276,6 +276,9 @@
     private void createContactShortcutIntent(Uri contactUri, String contentType, String displayName,
             String lookupKey, byte[] bitmapData) {
         Intent intent = null;
+        if (TextUtils.isEmpty(displayName)) {
+            displayName = mContext.getResources().getString(R.string.missing_name);
+        }
         if (BuildCompat.isAtLeastO()) {
             final long contactId = ContentUris.parseId(contactUri);
             final ShortcutManager sm = (ShortcutManager)
@@ -283,12 +286,11 @@
             final DynamicShortcuts dynamicShortcuts = new DynamicShortcuts(mContext);
             final ShortcutInfo shortcutInfo = dynamicShortcuts.getQuickContactShortcutInfo(
                     contactId, lookupKey, displayName);
-            intent = sm.createShortcutResultIntent(shortcutInfo);
+            if (shortcutInfo != null) {
+                intent = sm.createShortcutResultIntent(shortcutInfo);
+            }
         }
         final Drawable drawable = getPhotoDrawable(bitmapData, displayName, lookupKey);
-        if (TextUtils.isEmpty(displayName)) {
-            displayName = mContext.getResources().getString(R.string.missing_name);
-        }
 
         final Intent shortcutIntent = ImplicitIntentsUtil.getIntentForQuickContactLauncherShortcut(
                 mContext, contactUri);
@@ -346,7 +348,9 @@
             final DynamicShortcuts dynamicShortcuts = new DynamicShortcuts(mContext);
             final ShortcutInfo shortcutInfo = dynamicShortcuts.getActionShortcutInfo(
                     id, displayName, shortcutIntent, compatAdaptiveIcon.toIcon());
-            intent = sm.createShortcutResultIntent(shortcutInfo);
+            if (shortcutInfo != null) {
+                intent = sm.createShortcutResultIntent(shortcutInfo);
+            }
         }
 
         intent = intent == null ? new Intent() : intent;
diff --git a/src/com/android/contacts/datepicker/DatePickerDialog.java b/src/com/android/contacts/datepicker/DatePickerDialog.java
index 82eed24..de04af9 100644
--- a/src/com/android/contacts/datepicker/DatePickerDialog.java
+++ b/src/com/android/contacts/datepicker/DatePickerDialog.java
@@ -78,21 +78,6 @@
     /**
      * @param context The context the dialog is to run in.
      * @param callBack How the parent is notified that the date is set.
-     * @param year The initial year of the dialog
-     * @param monthOfYear The initial month of the dialog.
-     * @param dayOfMonth The initial day of the dialog.
-     */
-    public DatePickerDialog(Context context,
-            OnDateSetListener callBack,
-            int year,
-            int monthOfYear,
-            int dayOfMonth) {
-        this(context, callBack, year, monthOfYear, dayOfMonth, false);
-    }
-
-    /**
-     * @param context The context the dialog is to run in.
-     * @param callBack How the parent is notified that the date is set.
      * @param year The initial year of the dialog or {@link DatePickerDialog#NO_YEAR} if no year
      *  has been specified
      * @param monthOfYear The initial month of the dialog.
@@ -106,7 +91,7 @@
             int dayOfMonth,
             boolean yearOptional) {
         // Don't pass a theme id. Instead use the default alert dialog theme.
-        this(context, /* themeId = */ -1, callBack, year, monthOfYear, dayOfMonth,
+        this(context, /* themeId = */ 0, callBack, year, monthOfYear, dayOfMonth,
                 yearOptional);
     }
 
@@ -114,24 +99,6 @@
      * @param context The context the dialog is to run in.
      * @param theme the theme to apply to this dialog
      * @param callBack How the parent is notified that the date is set.
-     * @param year The initial year of the dialog or {@link DatePickerDialog#NO_YEAR} if no year
-     *  has been specified
-     * @param monthOfYear The initial month of the dialog.
-     * @param dayOfMonth The initial day of the dialog.
-     */
-    public DatePickerDialog(Context context,
-            int theme,
-            OnDateSetListener callBack,
-            int year,
-            int monthOfYear,
-            int dayOfMonth) {
-        this(context, theme, callBack, year, monthOfYear, dayOfMonth, false);
-    }
-
-    /**
-     * @param context The context the dialog is to run in.
-     * @param theme the theme to apply to this dialog
-     * @param callBack How the parent is notified that the date is set.
      * @param year The initial year of the dialog or {@link DatePickerDialog#NO_YEAR} if no
      *  year has been specified.
      * @param monthOfYear The initial month of the dialog.
@@ -183,13 +150,6 @@
         updateTitle(year, month, day);
     }
 
-    public void updateDate(int year, int monthOfYear, int dayOfMonth) {
-        mInitialYear = year;
-        mInitialMonth = monthOfYear;
-        mInitialDay = dayOfMonth;
-        mDatePicker.updateDate(year, monthOfYear, dayOfMonth);
-    }
-
     private void updateTitle(int year, int month, int day) {
         final Calendar calendar = Calendar.getInstance();
         calendar.set(Calendar.YEAR, year);
diff --git a/src/com/android/contacts/model/dataitem/PhoneDataItem.java b/src/com/android/contacts/model/dataitem/PhoneDataItem.java
index 8d6cf8e..6355157 100644
--- a/src/com/android/contacts/model/dataitem/PhoneDataItem.java
+++ b/src/com/android/contacts/model/dataitem/PhoneDataItem.java
@@ -31,6 +31,11 @@
 
     public static final String KEY_FORMATTED_PHONE_NUMBER = "formattedPhoneNumber";
 
+    private boolean mTachyonReachable;
+    // Stores the custom reachable data item to provide extra data to the Entry created from this
+    // PhoneDataItem.
+    private DataItem mReachableDataItem;
+
     /* package */ PhoneDataItem(ContentValues values) {
         super(values);
     }
@@ -54,6 +59,22 @@
         return getContentValues().getAsString(Phone.LABEL);
     }
 
+    public void setTachyonReachable(boolean tachyonReachable) {
+        mTachyonReachable = tachyonReachable;
+    }
+
+    public boolean isTachyonReachable() {
+        return mTachyonReachable;
+    }
+
+    public DataItem getReachableDataItem() {
+        return mReachableDataItem;
+    }
+
+    public void setReachableDataItem(DataItem reachableDataItem) {
+        mReachableDataItem = reachableDataItem;
+    }
+
     public void computeFormattedPhoneNumber(String defaultCountryIso) {
         final String phoneNumber = getNumber();
         if (phoneNumber != null) {
diff --git a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
index 49da4c8..b0f7800 100644
--- a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
+++ b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java
@@ -117,6 +117,7 @@
         private final int mIconResourceId;
         private final int mThirdAction;
         private final Bundle mThirdExtras;
+        private final boolean mShouldApplyThirdIconColor;
 
         public Entry(int id, Drawable mainIcon, String header, String subHeader,
                 Drawable subHeaderIcon, String text, Drawable textIcon,
@@ -125,7 +126,7 @@
                 Spannable alternateContentDescription, boolean shouldApplyColor, boolean isEditable,
                 EntryContextMenuInfo entryContextMenuInfo, Drawable thirdIcon, Intent thirdIntent,
                 String thirdContentDescription, int thirdAction, Bundle thirdExtras,
-                int iconResourceId) {
+                boolean shouldApplyThirdIconColor, int iconResourceId) {
             mId = id;
             mIcon = mainIcon;
             mHeader = header;
@@ -146,6 +147,7 @@
             mThirdContentDescription = thirdContentDescription;
             mThirdAction = thirdAction;
             mThirdExtras = thirdExtras;
+            mShouldApplyThirdIconColor = shouldApplyThirdIconColor;
             mIconResourceId = iconResourceId;
         }
 
@@ -232,6 +234,10 @@
         public Bundle getThirdExtras() {
             return mThirdExtras;
         }
+
+        boolean shouldApplyThirdIconColor() {
+            return mShouldApplyThirdIconColor;
+        }
     }
 
     public interface ExpandingEntryCardViewListener {
@@ -607,7 +613,7 @@
                             alternateIcon.setColorFilter(mThemeColorFilter);
                         }
                         Drawable thirdIcon = entry.getThirdIcon();
-                        if (thirdIcon != null) {
+                        if (thirdIcon != null && entry.shouldApplyThirdIconColor()) {
                             thirdIcon.mutate();
                             thirdIcon.setColorFilter(mThemeColorFilter);
                         }
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 014e1d5..5da7502 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -35,6 +35,7 @@
 import android.content.Loader;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -107,6 +108,8 @@
 import com.android.contacts.DynamicShortcuts;
 import com.android.contacts.NfcHandler;
 import com.android.contacts.R;
+import com.android.contacts.ShortcutIntentBuilder;
+import com.android.contacts.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
 import com.android.contacts.activities.ContactEditorActivity;
 import com.android.contacts.activities.ContactSelectionActivity;
 import com.android.contacts.activities.RequestDesiredPermissionsActivity;
@@ -126,8 +129,6 @@
 import com.android.contacts.interactions.SmsInteractionsLoader;
 import com.android.contacts.interactions.TouchPointManager;
 import com.android.contacts.lettertiles.LetterTileDrawable;
-import com.android.contacts.ShortcutIntentBuilder;
-import com.android.contacts.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
 import com.android.contacts.list.UiIntentActions;
 import com.android.contacts.logging.Logger;
 import com.android.contacts.logging.QuickContactEvent.ActionType;
@@ -246,6 +247,10 @@
     @SuppressWarnings("deprecation")
     private static final String LEGACY_AUTHORITY = android.provider.Contacts.AUTHORITY;
 
+    public static final String MIMETYPE_TACHYON =
+            "vnd.android.cursor.item/com.google.android.apps.tachyon.phone";
+    private static final String TACHYON_CALL_ACTION =
+            "com.google.android.apps.tachyon.action.CALL";
     private static final String MIMETYPE_GPLUS_PROFILE =
             "vnd.android.cursor.item/vnd.googleplus.profile";
     private static final String GPLUS_PROFILE_DATA_5_VIEW_PROFILE = "view";
@@ -430,8 +435,6 @@
                 }
             }
 
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
             mHasIntentLaunched = true;
             try {
                 final int actionType = intent.getIntExtra(EXTRA_ACTION_TYPE,
@@ -439,7 +442,15 @@
                 final String thirdPartyAction = intent.getStringExtra(EXTRA_THIRD_PARTY_ACTION);
                 Logger.logQuickContactEvent(mReferrer, mContactType,
                         CardType.UNKNOWN_CARD, actionType, thirdPartyAction);
-                ImplicitIntentsUtil.startActivityInAppIfPossible(QuickContactActivity.this, intent);
+                // For the tachyon call action, we need to use startActivityForResult and not
+                // add FLAG_ACTIVITY_NEW_TASK to the intent.
+                if (TACHYON_CALL_ACTION.equals(intent.getAction())) {
+                    QuickContactActivity.this.startActivityForResult(intent, /* requestCode */ 0);
+                } else {
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    ImplicitIntentsUtil.startActivityInAppIfPossible(QuickContactActivity.this,
+                            intent);
+                }
             } catch (SecurityException ex) {
                 Toast.makeText(QuickContactActivity.this, R.string.missing_app,
                         Toast.LENGTH_SHORT).show();
@@ -1328,6 +1339,7 @@
                     /* thirdContentDescription = */ null,
                     /* thirdAction = */ Entry.ACTION_NONE,
                     /* thirdExtras = */ null,
+                    /* shouldApplyThirdIconColor = */ true,
                     /* iconResourceId = */  0);
             List<Entry> phoneticList = new ArrayList<>();
             phoneticList.add(phoneticEntry);
@@ -1390,6 +1402,7 @@
                 /* thirdContentDescription = */ null,
                 /* thirdAction = */ Entry.ACTION_NONE,
                 /* thirdExtras = */ null,
+                /* shouldApplyThirdIconColor = */ true,
                 R.drawable.quantum_ic_phone_vd_theme_24);
 
         final List<List<Entry>> promptEntries = new ArrayList<>();
@@ -1409,6 +1422,7 @@
                     /* EntryContextMenuInfo = */ null, /* thirdIcon = */ null,
                     /* thirdIntent = */ null, /* thirdContentDescription = */ null,
                     /* thirdAction = */ Entry.ACTION_NONE, /* thirdExtras = */ null,
+                    /* shouldApplyThirdIconColor = */ true,
                     R.drawable.quantum_ic_email_vd_theme_24);
 
             promptEntries.add(new ArrayList<Entry>(1));
@@ -1442,8 +1456,8 @@
         Trace.beginSection("Build data items map");
 
         final Map<String, List<DataItem>> dataItemsMap = new HashMap<>();
+        final boolean tachyonEnabled = CallUtil.isTachyonEnabled(this);
 
-        final ResolveCache cache = ResolveCache.getInstance(this);
         for (RawContact rawContact : data.getRawContacts()) {
             for (DataItem dataItem : rawContact.getDataItems()) {
                 dataItem.setRawContactId(rawContact.getId());
@@ -1451,17 +1465,23 @@
                 final String mimeType = dataItem.getMimeType();
                 if (mimeType == null) continue;
 
-                final AccountType accountType = rawContact.getAccountType(this);
-                final DataKind dataKind = AccountTypeManager.getInstance(this)
-                        .getKindOrFallback(accountType, mimeType);
-                if (dataKind == null) continue;
+                if (!MIMETYPE_TACHYON.equals(mimeType)) {
+                    // Only validate non-Tachyon mimetypes.
+                    final AccountType accountType = rawContact.getAccountType(this);
+                    final DataKind dataKind = AccountTypeManager.getInstance(this)
+                            .getKindOrFallback(accountType, mimeType);
+                    if (dataKind == null) continue;
 
-                dataItem.setDataKind(dataKind);
+                    dataItem.setDataKind(dataKind);
 
-                final boolean hasData = !TextUtils.isEmpty(dataItem.buildDataString(this,
-                        dataKind));
+                    final boolean hasData = !TextUtils.isEmpty(dataItem.buildDataString(this,
+                            dataKind));
 
-                if (isMimeExcluded(mimeType) || !hasData) continue;
+                    if (isMimeExcluded(mimeType) || !hasData) continue;
+                } else if (!tachyonEnabled) {
+                    // If tachyon isn't enabled, skip its mimetypes.
+                    continue;
+                }
 
                 List<DataItem> dataItemListByType = dataItemsMap.get(mimeType);
                 if (dataItemListByType == null) {
@@ -1472,6 +1492,7 @@
             }
         }
         Trace.endSection();
+        bindReachability(dataItemsMap);
 
         Trace.beginSection("sort within mimetypes");
         /*
@@ -1529,6 +1550,28 @@
     }
 
     /**
+     * Bind the custom data items to each {@link PhoneDataItem} that is Tachyon reachable, the data
+     * will be needed when creating the {@link Entry} for the {@link PhoneDataItem}.
+     */
+    private void bindReachability(Map<String, List<DataItem>> dataItemsMap) {
+        final List<DataItem> phoneItems = dataItemsMap.get(Phone.CONTENT_ITEM_TYPE);
+        final List<DataItem> tachyonItems = dataItemsMap.get(MIMETYPE_TACHYON);
+        if (phoneItems != null && tachyonItems != null) {
+            for (DataItem phone : phoneItems) {
+                if (phone instanceof PhoneDataItem && ((PhoneDataItem) phone).getNumber() != null) {
+                    for (DataItem tachyonItem : tachyonItems) {
+                        if (((PhoneDataItem) phone).getNumber().equals(
+                                tachyonItem.getContentValues().getAsString(Data.DATA1))) {
+                            ((PhoneDataItem) phone).setTachyonReachable(true);
+                            ((PhoneDataItem) phone).setReachableDataItem(tachyonItem);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
      * Class used to hold the About card and Contact cards' data model that gets generated
      * on a background thread. All data is from CP2.
      */
@@ -1574,6 +1617,7 @@
         Spannable smsContentDescription = null;
         Intent intent = null;
         boolean shouldApplyColor = true;
+        boolean shouldApplyThirdIconColor = true;
         Drawable alternateIcon = null;
         Intent alternateIntent = null;
         StringBuilder alternateContentDescription = new StringBuilder();
@@ -1746,6 +1790,9 @@
                 boolean isPresenceEnabled =
                         (videoCapability & CallUtil.VIDEO_CALLING_PRESENCE) != 0;
                 boolean isVideoEnabled = (videoCapability & CallUtil.VIDEO_CALLING_ENABLED) != 0;
+                // Check to ensure carrier presence indicates the number supports video calling.
+                int carrierPresence = dataItem.getCarrierPresence();
+                boolean isPresent = (carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) != 0;
 
                 if (CallUtil.isCallWithSubjectSupported(context)) {
                     thirdIcon = res.getDrawable(R.drawable.quantum_ic_perm_phone_msg_vd_theme_24);
@@ -1769,20 +1816,23 @@
                             phone.getFormattedPhoneNumber());
                     thirdExtras.putString(CallSubjectDialog.ARG_NUMBER_LABEL,
                             phoneLabel);
-                } else if (isVideoEnabled) {
-                    // Check to ensure carrier presence indicates the number supports video calling.
-                    int carrierPresence = dataItem.getCarrierPresence();
-                    boolean isPresent = (carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) != 0;
-
-                    if ((isPresenceEnabled && isPresent) || !isPresenceEnabled) {
-                        thirdIcon = res.getDrawable(R.drawable.quantum_ic_videocam_vd_theme_24);
-                        thirdAction = Entry.ACTION_INTENT;
-                        thirdIntent = CallUtil.getVideoCallIntent(phone.getNumber(),
-                                CALL_ORIGIN_QUICK_CONTACTS_ACTIVITY);
-                        thirdIntent.putExtra(EXTRA_ACTION_TYPE, ActionType.VIDEOCALL);
-                        thirdContentDescription =
-                                res.getString(R.string.description_video_call);
-                    }
+                } else if (isVideoEnabled && (!isPresenceEnabled || isPresent)) {
+                    thirdIcon = res.getDrawable(R.drawable.quantum_ic_videocam_vd_theme_24);
+                    thirdAction = Entry.ACTION_INTENT;
+                    thirdIntent = CallUtil.getVideoCallIntent(phone.getNumber(),
+                            CALL_ORIGIN_QUICK_CONTACTS_ACTIVITY);
+                    thirdIntent.putExtra(EXTRA_ACTION_TYPE, ActionType.VIDEOCALL);
+                    thirdContentDescription =
+                            res.getString(R.string.description_video_call);
+                } else if (CallUtil.isTachyonEnabled(context)
+                        && ((PhoneDataItem) dataItem).isTachyonReachable()) {
+                    thirdIcon = res.getDrawable(R.drawable.quantum_ic_videocam_vd_theme_24);
+                    thirdAction = Entry.ACTION_INTENT;
+                    thirdIntent = new Intent(TACHYON_CALL_ACTION);
+                    thirdIntent.setData(
+                            Uri.fromParts(PhoneAccount.SCHEME_TEL, phone.getNumber(), null));
+                    thirdContentDescription = ((PhoneDataItem) dataItem).getReachableDataItem()
+                            .getContentValues().getAsString(Data.DATA2);
                 }
             }
         } else if (dataItem instanceof EmailDataItem) {
@@ -1869,6 +1919,10 @@
                     aboutCardName.value = res.getString(R.string.about_card_title);
                 }
             }
+        } else if (CallUtil.isTachyonEnabled(context) && MIMETYPE_TACHYON.equals(
+                dataItem.getMimeType())) {
+            // Skip these actions. They will be placed by the phone number.
+            return null;
         } else {
             // Custom DataItem
             header = dataItem.buildDataStringForDisplay(context, kind);
@@ -1881,7 +1935,6 @@
 
             if (intent != null) {
                 final String mimetype = intent.getType();
-
                 // Build advanced entry for known 3p types. Otherwise default to ResolveCache icon.
                 if (MIMETYPE_HANGOUTS.equals(mimetype)) {
                     // If a secondDataItem is available, use it to build an entry with
@@ -1963,7 +2016,7 @@
                         : smsContentDescription,
                 shouldApplyColor, isEditable,
                 entryContextMenuInfo, thirdIcon, thirdIntent, thirdContentDescription, thirdAction,
-                thirdExtras, iconResourceId);
+                thirdExtras, shouldApplyThirdIconColor, iconResourceId);
     }
 
     private List<Entry> dataItemsToEntries(List<DataItem> dataItems,
@@ -2264,6 +2317,7 @@
                     /* thirdContentDescription = */ null,
                     /* thirdAction = */ Entry.ACTION_NONE,
                     /* thirdActionExtras = */ null,
+                    /* shouldApplyThirdIconColor = */ true,
                     interaction.getIconResourceId()));
         }
         return entries;
@@ -2496,7 +2550,9 @@
                         /* isEditable = */ false, /* EntryContextMenuInfo = */ null,
                         /* thirdIcon = */ null, /* thirdIntent = */ null,
                         /* thirdContentDescription = */ null, /* thirdAction = */ Entry.ACTION_NONE,
-                        /* thirdExtras = */ null, R.drawable.quantum_ic_history_vd_theme_24);
+                        /* thirdExtras = */ null,
+                        /* shouldApplyThirdIconColor = */ true,
+                        R.drawable.quantum_ic_history_vd_theme_24);
 
                 final List<List<Entry>> permissionExplanationEntries = new ArrayList<>();
                 permissionExplanationEntries.add(new ArrayList<Entry>());
@@ -2642,21 +2698,26 @@
      * Creates a launcher shortcut with the current contact.
      */
     private void createLauncherShortcutWithContact() {
-        final ShortcutIntentBuilder builder = new ShortcutIntentBuilder(this,
-                new OnShortcutIntentCreatedListener() {
+        if (BuildCompat.isAtLeastO()) {
+            final ShortcutManager shortcutManager = (ShortcutManager)
+                    getSystemService(SHORTCUT_SERVICE);
+            final DynamicShortcuts shortcuts =
+                    new DynamicShortcuts(QuickContactActivity.this);
+            String displayName = mContactData.getDisplayName();
+            if (displayName == null) {
+                displayName = getString(R.string.missing_name);
+            }
+            final ShortcutInfo shortcutInfo = shortcuts.getQuickContactShortcutInfo(
+                    mContactData.getId(), mContactData.getLookupKey(), displayName);
+            if (shortcutInfo != null) {
+                shortcutManager.requestPinShortcut(shortcutInfo, null);
+            }
+        } else {
+            final ShortcutIntentBuilder builder = new ShortcutIntentBuilder(this,
+                    new OnShortcutIntentCreatedListener() {
 
-                    @Override
-                    public void onShortcutIntentCreated(Uri uri, Intent shortcutIntent) {
-                        if (BuildCompat.isAtLeastO()) {
-                            final ShortcutManager shortcutManager = (ShortcutManager)
-                                    getSystemService(SHORTCUT_SERVICE);
-                            final DynamicShortcuts shortcuts =
-                                    new DynamicShortcuts(QuickContactActivity.this);
-                            shortcutManager.requestPinShortcut(
-                                    shortcuts.getQuickContactShortcutInfo(
-                                            mContactData.getId(), mContactData.getLookupKey(),
-                                            mContactData.getDisplayName()), null);
-                        } else {
+                        @Override
+                        public void onShortcutIntentCreated(Uri uri, Intent shortcutIntent) {
                             // Broadcast the shortcutIntent to the launcher to create a
                             // shortcut to this contact
                             shortcutIntent.setAction(ACTION_INSTALL_SHORTCUT);
@@ -2672,9 +2733,9 @@
                             Toast.makeText(QuickContactActivity.this, toastMessage,
                                     Toast.LENGTH_SHORT).show();
                         }
-                    }
-                });
-        builder.createContactShortcutIntent(mContactData.getLookupUri());
+                    });
+            builder.createContactShortcutIntent(mContactData.getLookupUri());
+        }
     }
 
     private boolean isShortcutCreatable() {