Merge changes I481fbbc7,I57e48b5d,Ieb52489b,I2763ddd9

* changes:
  Translation tweaks.
  Migrated context menu to be a PopupMenu instead.
  Add column for call mapping id to AnnotatedCallLog database.
  Don't force open keyboard when RTT is active.
diff --git a/assets/quantum/res/drawable/quantum_ic_security_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_security_vd_theme_24.xml
new file mode 100644
index 0000000..36ea6eb
--- /dev/null
+++ b/assets/quantum/res/drawable/quantum_ic_security_vd_theme_24.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12L21,5l-9,-4zM12,11.99h7c-0.53,4.12 -3.28,7.79 -7,8.94L12,12L5,12L5,6.3l7,-3.11v8.8z"/>
+</vector>
diff --git a/java/com/android/dialer/app/calllog/CallLogAdapter.java b/java/com/android/dialer/app/calllog/CallLogAdapter.java
index b9bd233..b99cef1 100644
--- a/java/com/android/dialer/app/calllog/CallLogAdapter.java
+++ b/java/com/android/dialer/app/calllog/CallLogAdapter.java
@@ -76,7 +76,6 @@
 import com.android.dialer.configprovider.ConfigProviderBindings;
 import com.android.dialer.duo.Duo;
 import com.android.dialer.duo.DuoComponent;
-import com.android.dialer.duo.DuoConstants;
 import com.android.dialer.duo.DuoListener;
 import com.android.dialer.enrichedcall.EnrichedCallCapabilities;
 import com.android.dialer.enrichedcall.EnrichedCallComponent;
@@ -426,7 +425,10 @@
           if (intentProvider == null) {
             return false;
           }
-          return DuoConstants.PACKAGE_NAME.equals(intentProvider.getIntent(activity).getPackage());
+          return DuoComponent.get(activity)
+              .getDuo()
+              .getIntentType(intentProvider.getIntent(activity))
+              .isPresent();
         }
       };
 
@@ -568,7 +570,7 @@
         new PhoneCallDetailsHelper(this.activity, resources, this.callLogCache);
     callLogListItemHelper =
         new CallLogListItemHelper(phoneCallDetailsHelper, resources, this.callLogCache);
-    callLogGroupBuilder = new CallLogGroupBuilder(this);
+    callLogGroupBuilder = new CallLogGroupBuilder(activity.getApplicationContext(), this);
     this.filteredNumberAsyncQueryHandler = Assert.isNotNull(filteredNumberAsyncQueryHandler);
 
     contactsPreferences = new ContactsPreferences(this.activity);
@@ -1032,9 +1034,7 @@
 
 
       String phoneAccountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME);
-      if (DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME
-          .flattenToString()
-          .equals(phoneAccountComponentName)) {
+      if (DuoComponent.get(activity).getDuo().isDuoAccount(phoneAccountComponentName)) {
         entry.setIsDuoCall(true);
       }
 
diff --git a/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java b/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java
index e4bae5e..26b3a62 100644
--- a/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java
+++ b/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java
@@ -16,6 +16,7 @@
 
 package com.android.dialer.app.calllog;
 
+import android.content.Context;
 import android.database.Cursor;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
@@ -27,6 +28,7 @@
 import com.android.dialer.calllogutils.CallbackActionHelper.CallbackAction;
 import com.android.dialer.compat.AppCompatConstants;
 import com.android.dialer.compat.telephony.TelephonyManagerCompat;
+import com.android.dialer.inject.ApplicationContext;
 import com.android.dialer.phonenumbercache.CallLogQuery;
 import com.android.dialer.phonenumberutil.PhoneNumberHelper;
 import java.util.Objects;
@@ -55,10 +57,13 @@
   public static final int DAY_GROUP_OTHER = 2;
   /** Instance of the time object used for time calculations. */
   private static final Time TIME = new Time();
+
+  private final Context appContext;
   /** The object on which the groups are created. */
   private final GroupCreator groupCreator;
 
-  public CallLogGroupBuilder(GroupCreator groupCreator) {
+  public CallLogGroupBuilder(@ApplicationContext Context appContext, GroupCreator groupCreator) {
+    this.appContext = appContext;
     this.groupCreator = groupCreator;
   }
 
@@ -97,7 +102,7 @@
     int groupFeatures = cursor.getInt(CallLogQuery.FEATURES);
     int groupCallbackAction =
         CallbackActionHelper.getCallbackAction(
-            groupNumber, groupFeatures, groupAccountComponentName);
+            appContext, groupNumber, groupFeatures, groupAccountComponentName);
     groupCreator.setCallbackAction(firstRowId, groupCallbackAction);
 
     // Instantiate other group values to those of the first call in the cursor.
@@ -126,7 +131,8 @@
       accountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME);
       accountId = cursor.getString(CallLogQuery.ACCOUNT_ID);
       callbackAction =
-          CallbackActionHelper.getCallbackAction(number, callFeatures, accountComponentName);
+          CallbackActionHelper.getCallbackAction(
+              appContext, number, callFeatures, accountComponentName);
 
       final boolean isSameNumber = equalNumbers(groupNumber, number);
       final boolean isSamePostDialDigits = groupPostDialDigits.equals(numberPostDialDigits);
diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
index 3f7def9..5474838 100644
--- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
+++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
@@ -78,7 +78,6 @@
 import com.android.dialer.dialercontact.SimDetails;
 import com.android.dialer.duo.Duo;
 import com.android.dialer.duo.DuoComponent;
-import com.android.dialer.duo.DuoConstants;
 import com.android.dialer.lettertile.LetterTileDrawable;
 import com.android.dialer.lettertile.LetterTileDrawable.ContactType;
 import com.android.dialer.logging.ContactSource;
@@ -97,6 +96,7 @@
 import com.android.dialer.util.CallUtil;
 import com.android.dialer.util.DialerUtils;
 import com.android.dialer.util.UriUtils;
+import com.google.common.base.Optional;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
@@ -795,9 +795,10 @@
   }
 
   private boolean showDuoPrimaryButton() {
+    Duo duo = DuoComponent.get(context).getDuo();
     return accountHandle != null
-        && accountHandle.getComponentName().equals(DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME)
-        && DuoComponent.get(context).getDuo().isReachable(context, number);
+        && duo.isDuoAccount(accountHandle)
+        && duo.isReachable(context, number);
   }
 
   private static boolean hasDialableChar(CharSequence number) {
@@ -1032,9 +1033,10 @@
 
     // We check to see if we are starting a Duo intent. The reason is Duo
     // intents need to be started using startActivityForResult instead of the usual startActivity
-    String packageName = intent.getPackage();
-    if (DuoConstants.PACKAGE_NAME.equals(packageName)) {
-      startDuoActivity(intent);
+    Optional<Duo.IntentType> duoIntentType =
+        DuoComponent.get(context).getDuo().getIntentType(intent);
+    if (duoIntentType.isPresent()) {
+      startDuoActivity(intent, duoIntentType.get());
     } else if (OldCallDetailsActivity.isLaunchIntent(intent)) {
       PerformanceReport.recordClick(UiAction.Type.OPEN_CALL_DETAIL);
       ((Activity) context)
@@ -1044,8 +1046,8 @@
           && intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, -1)
               == VideoProfile.STATE_BIDIRECTIONAL) {
         Logger.get(context).logImpression(DialerImpression.Type.IMS_VIDEO_REQUESTED_FROM_CALL_LOG);
-      } else if (intent.getDataString() != null
-          && intent.getDataString().contains(DuoConstants.PACKAGE_NAME)) {
+      } else if (intent.filterEquals(
+          DuoComponent.get(context).getDuo().getInstallDuoIntent().orNull())) {
         Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_SET_UP_INSTALL);
       }
 
@@ -1060,24 +1062,24 @@
     return false;
   }
 
-  private void startDuoActivity(Intent intent) {
-    if (DuoConstants.DUO_ACTIVATE_ACTION.equals(intent.getAction())) {
-      Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_SET_UP_ACTIVATE);
-    } else if (DuoConstants.DUO_INVITE_ACTION.equals(intent.getAction())) {
-      Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_INVITE);
-    } else if (DuoConstants.DUO_CALL_ACTION.equals(intent.getAction())) {
-      Logger.get(context)
-          .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FROM_CALL_LOG);
-      if (isNonContactEntry(info)) {
+  private void startDuoActivity(Intent intent, Duo.IntentType intentType) {
+    switch (intentType) {
+      case CALL:
         Logger.get(context)
-            .logImpression(
-                DialerImpression.Type.LIGHTBRINGER_NON_CONTACT_VIDEO_REQUESTED_FROM_CALL_LOG);
-      }
-    } else {
-      throw Assert.createIllegalStateFailException(
-          "Duo intent with invalid action" + intent.getAction());
+            .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FROM_CALL_LOG);
+        if (isNonContactEntry(info)) {
+          Logger.get(context)
+              .logImpression(
+                  DialerImpression.Type.LIGHTBRINGER_NON_CONTACT_VIDEO_REQUESTED_FROM_CALL_LOG);
+        }
+        break;
+      case INVITE:
+        Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_INVITE);
+        break;
+      case ACTIVATE:
+        Logger.get(context).logImpression(DialerImpression.Type.DUO_CALL_LOG_SET_UP_ACTIVATE);
+        break;
     }
-
     try {
       Activity activity = (Activity) context;
       activity.startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO);
diff --git a/java/com/android/dialer/app/calllog/IntentProvider.java b/java/com/android/dialer/app/calllog/IntentProvider.java
index a794c62..1bc726f 100644
--- a/java/com/android/dialer/app/calllog/IntentProvider.java
+++ b/java/com/android/dialer/app/calllog/IntentProvider.java
@@ -22,7 +22,6 @@
 import android.net.Uri;
 import android.provider.ContactsContract;
 import android.support.annotation.Nullable;
-import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.TelephonyManager;
 import com.android.contacts.common.model.Contact;
@@ -33,7 +32,6 @@
 import com.android.dialer.callintent.CallIntentBuilder;
 import com.android.dialer.dialercontact.DialerContact;
 import com.android.dialer.duo.DuoComponent;
-import com.android.dialer.duo.DuoConstants;
 import com.android.dialer.precall.PreCall;
 import com.android.dialer.util.IntentUtil;
 import java.util.ArrayList;
@@ -99,7 +97,7 @@
     return new IntentProvider() {
       @Override
       public Intent getIntent(Context context) {
-        return DuoComponent.get(context).getDuo().getIntent(context, number);
+        return DuoComponent.get(context).getDuo().getCallIntent(number).orNull();
       }
     };
   }
@@ -108,18 +106,7 @@
     return new IntentProvider() {
       @Override
       public Intent getIntent(Context context) {
-        return new Intent(
-            Intent.ACTION_VIEW,
-            new Uri.Builder()
-                .scheme("https")
-                .authority("play.google.com")
-                .appendEncodedPath("store/apps/details")
-                .appendQueryParameter("id", DuoConstants.PACKAGE_NAME)
-                .appendQueryParameter(
-                    "referrer",
-                    "utm_source=dialer&utm_medium=text&utm_campaign=product") // This string is from
-                // the Duo team
-                .build());
+        return DuoComponent.get(context).getDuo().getInstallDuoIntent().orNull();
       }
     };
   }
@@ -128,7 +115,7 @@
     return new IntentProvider() {
       @Override
       public Intent getIntent(Context context) {
-        return new Intent(DuoConstants.DUO_ACTIVATE_ACTION).setPackage(DuoConstants.PACKAGE_NAME);
+        return DuoComponent.get(context).getDuo().getActivateIntent().orNull();
       }
     };
   }
@@ -137,11 +124,7 @@
     return new IntentProvider() {
       @Override
       public Intent getIntent(Context context) {
-        Intent intent =
-            new Intent(DuoConstants.DUO_INVITE_ACTION)
-                .setPackage(DuoConstants.PACKAGE_NAME)
-                .setData(Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null /* fragment */));
-        return intent;
+        return DuoComponent.get(context).getDuo().getInviteIntent(number).orNull();
       }
     };
   }
diff --git a/java/com/android/dialer/app/calllog/MissedCallNotifier.java b/java/com/android/dialer/app/calllog/MissedCallNotifier.java
index 772feed..2cd0f77 100644
--- a/java/com/android/dialer/app/calllog/MissedCallNotifier.java
+++ b/java/com/android/dialer/app/calllog/MissedCallNotifier.java
@@ -52,7 +52,7 @@
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.DialerExecutor.Worker;
 import com.android.dialer.compat.android.provider.VoicemailCompat;
-import com.android.dialer.duo.DuoConstants;
+import com.android.dialer.duo.DuoComponent;
 import com.android.dialer.enrichedcall.FuzzyPhoneNumberMatcher;
 import com.android.dialer.notification.DialerNotificationManager;
 import com.android.dialer.notification.NotificationChannelId;
@@ -275,7 +275,7 @@
       if (phoneAccount == null) {
         continue;
       }
-      if (DuoConstants.PHONE_ACCOUNT_HANDLE.equals(phoneAccountHandle)) {
+      if (DuoComponent.get(context).getDuo().isDuoAccount(phoneAccountHandle)) {
         iterator.remove();
         continue;
       }
diff --git a/java/com/android/dialer/calldetails/CallDetailsActivity.java b/java/com/android/dialer/calldetails/CallDetailsActivity.java
index f1d0d84..36b8308 100644
--- a/java/com/android/dialer/calldetails/CallDetailsActivity.java
+++ b/java/com/android/dialer/calldetails/CallDetailsActivity.java
@@ -127,7 +127,7 @@
 
     @Override
     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
-      updateCallDetailsEntries(CallDetailsCursorLoader.toCallDetailsEntries(data));
+      updateCallDetailsEntries(CallDetailsCursorLoader.toCallDetailsEntries(activity, data));
       activity.loadRttTranscriptAvailability();
     }
 
diff --git a/java/com/android/dialer/calldetails/CallDetailsActivityCommon.java b/java/com/android/dialer/calldetails/CallDetailsActivityCommon.java
index 8f850ae..80a4191 100644
--- a/java/com/android/dialer/calldetails/CallDetailsActivityCommon.java
+++ b/java/com/android/dialer/calldetails/CallDetailsActivityCommon.java
@@ -63,6 +63,7 @@
 import com.android.dialer.precall.PreCall;
 import com.android.dialer.rtt.RttTranscriptActivity;
 import com.android.dialer.rtt.RttTranscriptUtil;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSet;
 import com.google.i18n.phonenumbers.NumberParseException;
@@ -327,15 +328,15 @@
       Logger.get(getActivity())
           .logImpression(DialerImpression.Type.CALL_DETAILS_LIGHTBRINGER_CALL_BACK);
       Duo duo = DuoComponent.get(getActivity()).getDuo();
-      if (!duo.isReachable(getActivity(), phoneNumber)) {
+      Optional<Intent> intentOptional = duo.getCallIntent(phoneNumber);
+      if (!duo.isReachable(getActivity(), phoneNumber) || !intentOptional.isPresent()) {
         placeImsVideoCall(phoneNumber);
         return;
       }
 
       try {
         getActivity()
-            .startActivityForResult(
-                duo.getIntent(getActivity(), phoneNumber), ActivityRequestCodes.DIALTACTS_DUO);
+            .startActivityForResult(intentOptional.get(), ActivityRequestCodes.DIALTACTS_DUO);
       } catch (ActivityNotFoundException e) {
         Toast.makeText(getActivity(), R.string.activity_not_available, Toast.LENGTH_SHORT).show();
       }
diff --git a/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java b/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java
index a7e1771..9d55b9f 100644
--- a/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java
+++ b/java/com/android/dialer/calldetails/CallDetailsCursorLoader.java
@@ -23,7 +23,7 @@
 import com.android.dialer.calldetails.CallDetailsEntries.CallDetailsEntry;
 import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
 import com.android.dialer.common.Assert;
-import com.android.dialer.duo.DuoConstants;
+import com.android.dialer.duo.DuoComponent;
 
 /**
  * A {@link CursorLoader} that loads call detail entries from {@link AnnotatedCallLog} for {@link
@@ -119,21 +119,21 @@
    *     the cursor is not null and the data set it points to is not empty.
    * @return A {@link CallDetailsEntries} proto.
    */
-  static CallDetailsEntries toCallDetailsEntries(Cursor cursor) {
+  static CallDetailsEntries toCallDetailsEntries(Context context, Cursor cursor) {
     Assert.isNotNull(cursor);
     Assert.checkArgument(cursor.moveToFirst());
 
     CallDetailsEntries.Builder entries = CallDetailsEntries.newBuilder();
 
     do {
-      entries.addEntries(toCallDetailsEntry(cursor));
+      entries.addEntries(toCallDetailsEntry(context, cursor));
     } while (cursor.moveToNext());
 
     return entries.build();
   }
 
   /** Creates a new {@link CallDetailsEntry} from the provided cursor using its current position. */
-  private static CallDetailsEntry toCallDetailsEntry(Cursor cursor) {
+  private static CallDetailsEntry toCallDetailsEntry(Context context, Cursor cursor) {
     CallDetailsEntry.Builder entry = CallDetailsEntry.newBuilder();
     entry
         .setCallId(cursor.getLong(ID))
@@ -145,10 +145,7 @@
         .setCallMappingId(cursor.getString(CALL_MAPPING_ID));
 
     String phoneAccountComponentName = cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME);
-    entry.setIsDuoCall(
-        DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME
-            .flattenToString()
-            .equals(phoneAccountComponentName));
+    entry.setIsDuoCall(DuoComponent.get(context).getDuo().isDuoAccount(phoneAccountComponentName));
 
     return entry.build();
   }
diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
index fb18e89..b5067da 100644
--- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
@@ -47,7 +47,7 @@
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
 import com.android.dialer.compat.android.provider.VoicemailCompat;
-import com.android.dialer.duo.DuoConstants;
+import com.android.dialer.duo.Duo;
 import com.android.dialer.inject.ApplicationContext;
 import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil;
 import com.android.dialer.storage.Unencrypted;
@@ -78,6 +78,7 @@
   private final MarkDirtyObserver markDirtyObserver;
   private final SharedPreferences sharedPreferences;
   private final AnnotatedCallLogDatabaseHelper annotatedCallLogDatabaseHelper;
+  private final Duo duo;
 
   @Nullable private Long lastTimestampProcessed;
 
@@ -87,12 +88,14 @@
       @BackgroundExecutor ListeningExecutorService backgroundExecutorService,
       MarkDirtyObserver markDirtyObserver,
       @Unencrypted SharedPreferences sharedPreferences,
-      AnnotatedCallLogDatabaseHelper annotatedCallLogDatabaseHelper) {
+      AnnotatedCallLogDatabaseHelper annotatedCallLogDatabaseHelper,
+      Duo duo) {
     this.appContext = appContext;
     this.backgroundExecutorService = backgroundExecutorService;
     this.markDirtyObserver = markDirtyObserver;
     this.sharedPreferences = sharedPreferences;
     this.annotatedCallLogDatabaseHelper = annotatedCallLogDatabaseHelper;
+    this.duo = duo;
   }
 
   @Override
@@ -387,18 +390,15 @@
    * <p>Characteristics of a Duo audio call are as follows.
    *
    * <ul>
-   *   <li>The phone account component name is {@link DuoConstants#PHONE_ACCOUNT_COMPONENT_NAME};
-   *       and
+   *   <li>The phone account is {@link Duo#isDuoAccount(String)}; and
    *   <li>The features don't include {@link Calls#FEATURES_VIDEO}.
    * </ul>
    *
    * <p>It is the caller's responsibility to ensure the phone account component name and the
    * features come from the same call log entry.
    */
-  private static boolean isDuoAudioCall(@Nullable String phoneAccountComponentName, int features) {
-    return DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME
-            .flattenToString()
-            .equals(phoneAccountComponentName)
+  private boolean isDuoAudioCall(@Nullable String phoneAccountComponentName, int features) {
+    return duo.isDuoAccount(phoneAccountComponentName)
         && ((features & Calls.FEATURES_VIDEO) != Calls.FEATURES_VIDEO);
   }
 
diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java
index 2f6c1fb..a56d6d5 100644
--- a/java/com/android/dialer/calllog/ui/menu/Modules.java
+++ b/java/com/android/dialer/calllog/ui/menu/Modules.java
@@ -30,7 +30,6 @@
 import com.android.dialer.calllogutils.NumberAttributesConverter;
 import com.android.dialer.duo.Duo;
 import com.android.dialer.duo.DuoComponent;
-import com.android.dialer.duo.DuoConstants;
 import com.android.dialer.glidephotomanager.PhotoInfo;
 import com.android.dialer.historyitemactions.DividerModule;
 import com.android.dialer.historyitemactions.DuoCallModule;
@@ -127,9 +126,7 @@
     }
 
     boolean isDuoCall =
-        DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME
-            .flattenToString()
-            .equals(row.getPhoneAccountComponentName());
+        DuoComponent.get(context).getDuo().isDuoAccount(row.getPhoneAccountComponentName());
 
     List<HistoryItemActionModule> modules = new ArrayList<>();
 
diff --git a/java/com/android/dialer/calllogutils/CallLogEntryText.java b/java/com/android/dialer/calllogutils/CallLogEntryText.java
index a1a2a3b..1b7bb06 100644
--- a/java/com/android/dialer/calllogutils/CallLogEntryText.java
+++ b/java/com/android/dialer/calllogutils/CallLogEntryText.java
@@ -20,7 +20,7 @@
 import android.provider.CallLog.Calls;
 import android.text.TextUtils;
 import com.android.dialer.calllog.model.CoalescedRow;
-import com.android.dialer.duo.DuoConstants;
+import com.android.dialer.duo.DuoComponent;
 import com.android.dialer.time.Clock;
 import com.google.common.base.Optional;
 import com.google.common.collect.Collections2;
@@ -202,9 +202,7 @@
       }
 
       boolean isDuoCall =
-          DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME
-              .flattenToString()
-              .equals(row.getPhoneAccountComponentName());
+          DuoComponent.get(context).getDuo().isDuoAccount(row.getPhoneAccountComponentName());
       secondaryText.append(
           context.getText(
               isDuoCall ? R.string.new_call_log_duo_video : R.string.new_call_log_carrier_video));
diff --git a/java/com/android/dialer/calllogutils/CallbackActionHelper.java b/java/com/android/dialer/calllogutils/CallbackActionHelper.java
index 1e219f1..838bc4f 100644
--- a/java/com/android/dialer/calllogutils/CallbackActionHelper.java
+++ b/java/com/android/dialer/calllogutils/CallbackActionHelper.java
@@ -16,10 +16,11 @@
 
 package com.android.dialer.calllogutils;
 
+import android.content.Context;
 import android.provider.CallLog.Calls;
 import android.support.annotation.IntDef;
 import android.text.TextUtils;
-import com.android.dialer.duo.DuoConstants;
+import com.android.dialer.duo.DuoComponent;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -46,8 +47,8 @@
    * @return One of the values in {@link CallbackAction}
    */
   public static @CallbackAction int getCallbackAction(
-      String number, int features, String phoneAccountComponentName) {
-    return getCallbackAction(number, features, isDuoCall(phoneAccountComponentName));
+      Context context, String number, int features, String phoneAccountComponentName) {
+    return getCallbackAction(number, features, isDuoCall(context, phoneAccountComponentName));
   }
 
   /**
@@ -75,9 +76,7 @@
     return CallbackAction.VOICE;
   }
 
-  private static boolean isDuoCall(String phoneAccountComponentName) {
-    return DuoConstants.PHONE_ACCOUNT_COMPONENT_NAME
-        .flattenToString()
-        .equals(phoneAccountComponentName);
+  private static boolean isDuoCall(Context context, String phoneAccountComponentName) {
+    return DuoComponent.get(context).getDuo().isDuoAccount(phoneAccountComponentName);
   }
 }
diff --git a/java/com/android/dialer/constants/ActivityRequestCodes.java b/java/com/android/dialer/constants/ActivityRequestCodes.java
index c96fe9e..9945080 100644
--- a/java/com/android/dialer/constants/ActivityRequestCodes.java
+++ b/java/com/android/dialer/constants/ActivityRequestCodes.java
@@ -30,9 +30,7 @@
   /** Request code for {@link com.android.dialer.callcomposer.CallComposerActivity} intent. */
   public static final int DIALTACTS_CALL_COMPOSER = 2;
 
-  /**
-   * Request code for {@link com.android.dialer.duo.Duo#getIntent(android.content.Context, String)}.
-   */
+  /** Request code for {@link com.android.dialer.duo.Duo#getCallIntent(String)}. */
   public static final int DIALTACTS_DUO = 3;
 
   /** Request code for {@link com.android.dialer.calldetails.OldCallDetailsActivity} intent. */
diff --git a/java/com/android/dialer/duo/Duo.java b/java/com/android/dialer/duo/Duo.java
index e020c80..06a3db0 100644
--- a/java/com/android/dialer/duo/Duo.java
+++ b/java/com/android/dialer/duo/Duo.java
@@ -30,6 +30,7 @@
 import java.util.List;
 
 /** Interface for Duo video call integration. */
+@SuppressWarnings("Guava")
 public interface Duo {
 
   /** @return true if the Duo integration is enabled on this device. */
@@ -70,11 +71,44 @@
   void reloadReachability(@NonNull Context context);
 
   /**
+   * Get the {@link PhoneAccountHandle} used by duo calls in the connection service and call log.
+   */
+  Optional<PhoneAccountHandle> getPhoneAccountHandle();
+
+  boolean isDuoAccount(PhoneAccountHandle phoneAccountHandle);
+
+  boolean isDuoAccount(String componentName);
+
+  /**
    * @return an Intent to start a Duo video call with the parameter number. Must be started using
    *     startActivityForResult.
    */
   @MainThread
-  Intent getIntent(@NonNull Context context, @NonNull String number);
+  Optional<Intent> getCallIntent(@NonNull String number);
+
+  /** @return an Intent to setup duo. Must be started using startActivityForResult. */
+  Optional<Intent> getActivateIntent();
+
+  /**
+   * @return an Intent to invite the parameter number to use duo. Must be started using
+   *     startActivityForResult.
+   */
+  Optional<Intent> getInviteIntent(String number);
+
+  /** Return value of {@link #getIntentType(Intent)} */
+  enum IntentType {
+    /** The intent is returned by {@link #getCallIntent(String)} */
+    CALL,
+    /** The intent is returned by {@link #getActivateIntent()} */
+    ACTIVATE,
+    /** The intent is returned by {@link #getInviteIntent(String)} */
+    INVITE
+  }
+
+  /** Classifies a Duo intent. Absent if the intent is not a Duo intent. */
+  Optional<IntentType> getIntentType(Intent intent);
+
+  Optional<Intent> getInstallDuoIntent();
 
   /** Requests upgrading the parameter ongoing call to a Duo video call. */
   @MainThread
diff --git a/java/com/android/dialer/duo/DuoConstants.java b/java/com/android/dialer/duo/DuoConstants.java
deleted file mode 100644
index 6eb660d..0000000
--- a/java/com/android/dialer/duo/DuoConstants.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-
-package com.android.dialer.duo;
-
-import android.content.ComponentName;
-import android.telecom.PhoneAccountHandle;
-
-/** Constants to reference the Duo application. */
-public final class DuoConstants {
-  public static final String PACKAGE_NAME = "com.google.android.apps.tachyon";
-
-  public static final String CONNECTION_SERVICE =
-      "com.google.android.apps.tachyon.telecom.TachyonTelecomConnectionService";
-
-  public static final String PHONE_ACCOUNT_ID = "0";
-
-  public static final ComponentName PHONE_ACCOUNT_COMPONENT_NAME =
-      new ComponentName(PACKAGE_NAME, CONNECTION_SERVICE);
-
-  public static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE =
-      new PhoneAccountHandle(PHONE_ACCOUNT_COMPONENT_NAME, PHONE_ACCOUNT_ID);
-
-  public static final String DUO_ACTIVATE_ACTION =
-      "com.google.android.apps.tachyon.action.REGISTER";
-
-  public static final String DUO_INVITE_ACTION = "com.google.android.apps.tachyon.action.INVITE";
-
-  public static final String DUO_CALL_ACTION = "com.google.android.apps.tachyon.action.CALL";
-
-  private DuoConstants() {}
-}
diff --git a/java/com/android/dialer/duo/PlaceDuoCallReceiver.java b/java/com/android/dialer/duo/PlaceDuoCallReceiver.java
index 913132c..f504aef 100644
--- a/java/com/android/dialer/duo/PlaceDuoCallReceiver.java
+++ b/java/com/android/dialer/duo/PlaceDuoCallReceiver.java
@@ -72,6 +72,6 @@
 
     Duo duo = DuoComponent.get(context).getDuo();
     activity.startActivityForResult(
-        duo.getIntent(context, phoneNumber), ActivityRequestCodes.DIALTACTS_DUO);
+        duo.getCallIntent(phoneNumber).orNull(), ActivityRequestCodes.DIALTACTS_DUO);
   }
 }
diff --git a/java/com/android/dialer/duo/stub/DuoStub.java b/java/com/android/dialer/duo/stub/DuoStub.java
index ef78f8b..b086714 100644
--- a/java/com/android/dialer/duo/stub/DuoStub.java
+++ b/java/com/android/dialer/duo/stub/DuoStub.java
@@ -81,12 +81,46 @@
   @Override
   public void reloadReachability(@NonNull Context context) {}
 
+  @Override
+  public Optional<PhoneAccountHandle> getPhoneAccountHandle() {
+    return Optional.absent();
+  }
+
+  @Override
+  public boolean isDuoAccount(PhoneAccountHandle phoneAccountHandle) {
+    return false;
+  }
+
+  @Override
+  public boolean isDuoAccount(String componentName) {
+    return false;
+  }
+
   @MainThread
   @Override
-  public Intent getIntent(@NonNull Context context, @NonNull String number) {
+  public Optional<Intent> getCallIntent(@NonNull String number) {
     Assert.isMainThread();
-    Assert.isNotNull(context);
     Assert.isNotNull(number);
+    return Optional.absent();
+  }
+
+  @Override
+  public Optional<Intent> getActivateIntent() {
+    return Optional.absent();
+  }
+
+  @Override
+  public Optional<Intent> getInviteIntent(String number) {
+    return Optional.absent();
+  }
+
+  @Override
+  public Optional<IntentType> getIntentType(Intent intent) {
+    return Optional.absent();
+  }
+
+  @Override
+  public Optional<Intent> getInstallDuoIntent() {
     return null;
   }
 
diff --git a/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java b/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java
index 562177c..515a3cc 100644
--- a/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java
+++ b/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java
@@ -62,6 +62,10 @@
   @Override
   public void loadContactPhoto(ImageView imageView, PhotoInfo photoInfo) {
     Assert.isMainThread();
+    imageView.setContentDescription(
+        appContext.getString(
+            com.android.dialer.contactphoto.R.string.description_quick_contact_for,
+            photoInfo.getName()));
     GlideRequest<Drawable> request = buildRequest(GlideApp.with(imageView), photoInfo);
     request.into(imageView);
   }
diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
index 51befe8..f1eed91 100644
--- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
+++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
@@ -549,7 +549,7 @@
   public void placeDuoCall(String phoneNumber) {
     Logger.get(getContext())
         .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FROM_SEARCH);
-    Intent intent = DuoComponent.get(getContext()).getDuo().getIntent(getContext(), phoneNumber);
+    Intent intent = DuoComponent.get(getContext()).getDuo().getCallIntent(phoneNumber).orNull();
     getActivity().startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO);
     FragmentUtils.getParentUnsafe(this, SearchFragmentListener.class).onCallPlacedFromSearch();
   }
diff --git a/java/com/android/dialer/spam/promo/SpamBlockingPromoHelper.java b/java/com/android/dialer/spam/promo/SpamBlockingPromoHelper.java
index 6a8cde8..a117e19 100644
--- a/java/com/android/dialer/spam/promo/SpamBlockingPromoHelper.java
+++ b/java/com/android/dialer/spam/promo/SpamBlockingPromoHelper.java
@@ -168,7 +168,9 @@
             .setPriority(Notification.PRIORITY_DEFAULT)
             .setColor(context.getColor(R.color.dialer_theme_color))
             .setSmallIcon(R.drawable.quantum_ic_call_vd_theme_24)
-            .setContentText(context.getString(R.string.spam_blocking_promo_text))
+            .setStyle(
+                new Notification.BigTextStyle()
+                    .bigText(context.getString(R.string.spam_blocking_promo_text)))
             .addAction(
                 new Notification.Action.Builder(
                         R.drawable.quantum_ic_block_vd_theme_24,
diff --git a/java/com/android/dialer/speeddial/DisambigDialog.java b/java/com/android/dialer/speeddial/DisambigDialog.java
index 0d29a9c..a76f648 100644
--- a/java/com/android/dialer/speeddial/DisambigDialog.java
+++ b/java/com/android/dialer/speeddial/DisambigDialog.java
@@ -159,7 +159,7 @@
           .logImpression(
               DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FOR_FAVORITE_CONTACT_DISAMBIG);
       Intent intent =
-          DuoComponent.get(getContext()).getDuo().getIntent(getContext(), channel.number());
+          DuoComponent.get(getContext()).getDuo().getCallIntent(channel.number()).orNull();
       getActivity().startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO);
       return;
     }
diff --git a/java/com/android/dialer/speeddial/FavoritesViewHolder.java b/java/com/android/dialer/speeddial/FavoritesViewHolder.java
index 474516e..f06672d 100644
--- a/java/com/android/dialer/speeddial/FavoritesViewHolder.java
+++ b/java/com/android/dialer/speeddial/FavoritesViewHolder.java
@@ -68,10 +68,15 @@
     Assert.checkArgument(speedDialUiItem.isStarred());
 
     nameView.setText(speedDialUiItem.name());
-    if (speedDialUiItem.defaultChannel() != null) {
-      phoneType.setText(speedDialUiItem.defaultChannel().label());
-      videoCallIcon.setVisibility(
-          speedDialUiItem.defaultChannel().isVideoTechnology() ? View.VISIBLE : View.GONE);
+
+    Channel channel = speedDialUiItem.defaultChannel();
+    if (channel == null) {
+      channel = speedDialUiItem.getDefaultVoiceChannel();
+    }
+
+    if (channel != null) {
+      phoneType.setText(channel.label());
+      videoCallIcon.setVisibility(channel.isVideoTechnology() ? View.VISIBLE : View.GONE);
     } else {
       phoneType.setText("");
       videoCallIcon.setVisibility(View.GONE);
diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java
index f0ba186..db4c024 100644
--- a/java/com/android/dialer/speeddial/SpeedDialFragment.java
+++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java
@@ -114,9 +114,7 @@
             getActivity(),
             getChildFragmentManager(),
             new SpeedDialContextMenuItemListener(
-                getActivity(),
-                new UpdateSpeedDialAdapterListener(),
-                speedDialLoaderListener),
+                getActivity(), new UpdateSpeedDialAdapterListener(), speedDialLoaderListener),
             layoutManager);
     adapter =
         new SpeedDialAdapter(getContext(), favoritesListener, suggestedListener, headerListener);
@@ -246,7 +244,8 @@
       if (channel.technology() == Channel.DUO) {
         Logger.get(activity)
             .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FOR_FAVORITE_CONTACT);
-        Intent intent = DuoComponent.get(activity).getDuo().getIntent(activity, channel.number());
+        Intent intent =
+            DuoComponent.get(activity).getDuo().getCallIntent(channel.number()).orNull();
         activity.startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO);
         return;
       }
@@ -346,7 +345,7 @@
             .logImpression(
                 DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FOR_SUGGESTED_CONTACT);
         Intent intent =
-            DuoComponent.get(getContext()).getDuo().getIntent(getContext(), channel.number());
+            DuoComponent.get(getContext()).getDuo().getCallIntent(channel.number()).orNull();
         getActivity().startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO);
         return;
       }
@@ -430,7 +429,8 @@
       if (channel.technology() == Channel.DUO) {
         Logger.get(activity)
             .logImpression(DialerImpression.Type.LIGHTBRINGER_VIDEO_REQUESTED_FOR_FAVORITE_CONTACT);
-        Intent intent = DuoComponent.get(activity).getDuo().getIntent(activity, channel.number());
+        Intent intent =
+            DuoComponent.get(activity).getDuo().getCallIntent(channel.number()).orNull();
         activity.startActivityForResult(intent, ActivityRequestCodes.DIALTACTS_DUO);
         return;
       }
diff --git a/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java b/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java
index f730d12..365b88f 100644
--- a/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java
+++ b/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java
@@ -16,6 +16,7 @@
 
 package com.android.dialer.speeddial.loader;
 
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.Contacts;
@@ -102,7 +103,7 @@
    * <p>If the cursor started at row X, this method will advance to row Y s.t. rows X, X + 1, ... Y
    * - 1 all belong to the same contact (that is, share the same contact id and lookup key).
    */
-  public static SpeedDialUiItem fromCursor(Cursor cursor) {
+  public static SpeedDialUiItem fromCursor(Resources resources, Cursor cursor) {
     Assert.checkArgument(cursor != null);
     Assert.checkArgument(cursor.getCount() != 0);
     String lookupKey = cursor.getString(LOOKUP_KEY);
@@ -125,7 +126,7 @@
           Channel.builder()
               .setNumber(cursor.getString(NUMBER))
               .setPhoneType(cursor.getInt(TYPE))
-              .setLabel(TextUtils.isEmpty(cursor.getString(LABEL)) ? "" : cursor.getString(LABEL))
+              .setLabel(getLabel(resources, cursor))
               .setTechnology(Channel.VOICE)
               .build();
       channels.add(channel);
@@ -141,6 +142,17 @@
     return builder.build();
   }
 
+  private static String getLabel(Resources resources, Cursor cursor) {
+    int numberType = cursor.getInt(TYPE);
+    String numberLabel = cursor.getString(LABEL);
+
+    // Returns empty label instead of "custom" if the custom label is empty.
+    if (numberType == Phone.TYPE_CUSTOM && TextUtils.isEmpty(numberLabel)) {
+      return "";
+    }
+    return (String) Phone.getTypeLabel(resources, numberType, numberLabel);
+  }
+
   public PhotoInfo getPhotoInfo() {
     return PhotoInfo.newBuilder()
         .setPhotoId(photoId())
diff --git a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java
index 0a2a241..1ad37dc 100644
--- a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java
+++ b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java
@@ -205,7 +205,7 @@
         return loadSpeedDialUiItemsInternal();
       }
       Assert.checkArgument(cursor.moveToFirst(), "Cursor should never be empty");
-      SpeedDialUiItem item = SpeedDialUiItem.fromCursor(cursor);
+      SpeedDialUiItem item = SpeedDialUiItem.fromCursor(appContext.getResources(), cursor);
 
       // Star the contact if it isn't starred already, then return.
       if (!item.isStarred()) {
@@ -410,7 +410,7 @@
                 null)) {
       Map<SpeedDialEntry, SpeedDialUiItem> map = new ArrayMap<>();
       for (cursor.moveToFirst(); !cursor.isAfterLast(); /* Iterate in the loop */ ) {
-        SpeedDialUiItem item = SpeedDialUiItem.fromCursor(cursor);
+        SpeedDialUiItem item = SpeedDialUiItem.fromCursor(appContext.getResources(), cursor);
         for (SpeedDialEntry entry : entries) {
           if (entry.contactId() == item.contactId()) {
             // Update the id and pinned position to match it's corresponding SpeedDialEntry.
@@ -513,7 +513,7 @@
         return contacts;
       }
       for (cursor.moveToFirst(); !cursor.isAfterLast(); /* Iterate in the loop */ ) {
-        contacts.add(SpeedDialUiItem.fromCursor(cursor));
+        contacts.add(SpeedDialUiItem.fromCursor(appContext.getResources(), cursor));
       }
       return contacts;
     }
diff --git a/java/com/android/incallui/AndroidManifest.xml b/java/com/android/incallui/AndroidManifest.xml
index 7286b0d..b3d60d4 100644
--- a/java/com/android/incallui/AndroidManifest.xml
+++ b/java/com/android/incallui/AndroidManifest.xml
@@ -43,6 +43,9 @@
   <!-- Set Bluetooth device -->
   <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
 
+  <!-- Set audio selector window type TYPE_APPLICATION_OVERLAY -->
+  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+
   <!-- Set android:taskAffinity="com.android.incallui" for all activities to ensure proper
   navigation. Otherwise system could bring up DialtactsActivity instead, e.g. when user unmerge a
   call.
diff --git a/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java b/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java
index cd17c25..d6946d8 100644
--- a/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java
+++ b/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java
@@ -24,8 +24,10 @@
 import android.content.res.ColorStateList;
 import android.graphics.PorterDuff.Mode;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.support.annotation.Nullable;
 import android.support.design.widget.BottomSheetDialogFragment;
+import android.support.v4.os.BuildCompat;
 import android.telecom.CallAudioState;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -76,6 +78,14 @@
     LogUtil.i("AudioRouteSelectorDialogFragment.onCreateDialog", null);
     Dialog dialog = super.onCreateDialog(savedInstanceState);
     dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+    if (Settings.canDrawOverlays(getContext())) {
+      dialog
+          .getWindow()
+          .setType(
+              BuildCompat.isAtLeastO()
+                  ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+                  : WindowManager.LayoutParams.TYPE_PHONE);
+    }
     return dialog;
   }
 
@@ -139,6 +149,7 @@
     if ((audioState.getSupportedRouteMask() & itemRoute) == 0) {
       item.setVisibility(View.GONE);
     } else if (audioState.getRoute() == itemRoute) {
+      item.setSelected(true);
       item.setTextColor(selectedColor);
       item.setCompoundDrawableTintList(ColorStateList.valueOf(selectedColor));
       item.setCompoundDrawableTintMode(Mode.SRC_ATOP);
@@ -159,6 +170,7 @@
         (TextView) getLayoutInflater().inflate(R.layout.audioroute_item, null, false);
     textView.setText(getAliasName(bluetoothDevice));
     if (selected) {
+      textView.setSelected(true);
       textView.setTextColor(selectedColor);
       textView.setCompoundDrawableTintList(ColorStateList.valueOf(selectedColor));
       textView.setCompoundDrawableTintMode(Mode.SRC_ATOP);
diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java
index 1c27446..1a0de19 100644
--- a/java/com/android/incallui/call/DialerCall.java
+++ b/java/com/android/incallui/call/DialerCall.java
@@ -27,6 +27,7 @@
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.PersistableBundle;
+import android.os.SystemClock;
 import android.os.Trace;
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
@@ -79,6 +80,7 @@
 import com.android.dialer.telecom.TelecomCallUtil;
 import com.android.dialer.telecom.TelecomUtil;
 import com.android.dialer.theme.R;
+import com.android.dialer.time.Clock;
 import com.android.dialer.util.PermissionsUtil;
 import com.android.incallui.audiomode.AudioModeProvider;
 import com.android.incallui.call.state.DialerCallState;
@@ -194,6 +196,8 @@
 
   private volatile boolean feedbackRequested = false;
 
+  private Clock clock = System::currentTimeMillis;
+
   @Nullable private PreferredAccountRecorder preferredAccountRecorder;
   private boolean isCallRemoved;
 
@@ -415,18 +419,6 @@
     updateEnrichedCallSession();
   }
 
-  /** Test only constructor to avoid initializing dependencies. */
-  @VisibleForTesting
-  DialerCall(Context context) {
-    this.context = context;
-    telecomCall = null;
-    latencyReport = null;
-    id = null;
-    hiddenId = 0;
-    dialerCallDelegate = null;
-    videoTechManager = null;
-  }
-
   private static int translateState(int state) {
     switch (state) {
       case Call.STATE_NEW:
@@ -863,21 +855,49 @@
   public void setState(int state) {
     if (state == DialerCallState.INCOMING) {
       logState.isIncoming = true;
-    } else if (state == DialerCallState.DISCONNECTED) {
+    }
+    updateCallTiming(state);
+
+    this.state = state;
+  }
+
+  private void updateCallTiming(int newState) {
+    if (newState == DialerCallState.ACTIVE) {
+      if (this.state == DialerCallState.ACTIVE) {
+        LogUtil.i("DialerCall.updateCallTiming", "state is already active");
+        return;
+      }
+      logState.dialerConnectTimeMillis = clock.currentTimeMillis();
+      logState.dialerConnectTimeMillisElapsedRealtime = SystemClock.elapsedRealtime();
+    }
+
+    if (newState == DialerCallState.DISCONNECTED) {
       long newDuration =
-          getConnectTimeMillis() == 0 ? 0 : System.currentTimeMillis() - getConnectTimeMillis();
-      if (this.state != state) {
-        logState.duration = newDuration;
-      } else {
+          getConnectTimeMillis() == 0 ? 0 : clock.currentTimeMillis() - getConnectTimeMillis();
+      if (this.state == DialerCallState.DISCONNECTED) {
         LogUtil.i(
             "DialerCall.setState",
             "ignoring state transition from DISCONNECTED to DISCONNECTED."
                 + " Duration would have changed from %s to %s",
-            logState.duration,
+            logState.telecomDurationMillis,
             newDuration);
+        return;
       }
+      logState.telecomDurationMillis = newDuration;
+      logState.dialerDurationMillis =
+          logState.dialerConnectTimeMillis == 0
+              ? 0
+              : clock.currentTimeMillis() - logState.dialerConnectTimeMillis;
+      logState.dialerDurationMillisElapsedRealtime =
+          logState.dialerConnectTimeMillisElapsedRealtime == 0
+              ? 0
+              : SystemClock.elapsedRealtime() - logState.dialerConnectTimeMillisElapsedRealtime;
     }
-    this.state = state;
+  }
+
+  @VisibleForTesting
+  void setClock(Clock clock) {
+    this.clock = clock;
   }
 
   public int getNumberPresentation() {
@@ -1735,9 +1755,25 @@
     public CallSpecificAppData callSpecificAppData;
     // If this was a conference call, the total number of calls involved in the conference.
     public int conferencedCalls = 0;
-    public long duration = 0;
     public boolean isLogged = false;
 
+    // Result of subtracting android.telecom.Call.Details#getConnectTimeMillis from the current time
+    public long telecomDurationMillis = 0;
+
+    // Result of a call to System.currentTimeMillis when Dialer sees that a call
+    // moves to the ACTIVE state
+    long dialerConnectTimeMillis = 0;
+
+    // Same as dialer_connect_time_millis, using SystemClock.elapsedRealtime
+    // instead
+    long dialerConnectTimeMillisElapsedRealtime = 0;
+
+    // Result of subtracting dialer_connect_time_millis from System.currentTimeMillis
+    public long dialerDurationMillis = 0;
+
+    // Same as dialerDurationMillis, using SystemClock.elapsedRealtime instead
+    public long dialerDurationMillisElapsedRealtime = 0;
+
     private static String lookupToString(ContactLookupResult.Type lookupType) {
       switch (lookupType) {
         case LOCAL_CONTACT:
@@ -1806,7 +1842,7 @@
           isIncoming,
           lookupToString(contactLookupResult),
           initiationToString(callSpecificAppData),
-          duration);
+          telecomDurationMillis);
     }
   }
 
diff --git a/java/com/android/incallui/spam/SpamCallListListener.java b/java/com/android/incallui/spam/SpamCallListListener.java
index e852f3d..350dd60 100644
--- a/java/com/android/incallui/spam/SpamCallListListener.java
+++ b/java/com/android/incallui/spam/SpamCallListListener.java
@@ -219,7 +219,7 @@
       return false;
     }
 
-    if (logState.duration <= 0) {
+    if (logState.telecomDurationMillis <= 0) {
       return false;
     }
 
diff --git a/java/com/android/incallui/speakeasy/AndroidManifest.xml b/java/com/android/incallui/speakeasy/AndroidManifest.xml
deleted file mode 100644
index e56b2e7..0000000
--- a/java/com/android/incallui/speakeasy/AndroidManifest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
-  ~ Copyright (C) 2018 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
-  -->
-<manifest
-    package="com.android.incallui.speakeasy"
-    xmlns:android="http://schemas.android.com/apk/res/android">
-  <application android:theme="@style/Theme.AppCompat">
-  <activity
-      android:name=".SpeakEasyActivity"
-      android:exported="false"
-      android:theme="@style/DialerThemeBase.NoActionBar"
-      android:windowSoftInputMode="adjustResize"/>
-  </application>
-</manifest>
diff --git a/java/com/android/incallui/speakeasy/res/layout/activity_speakeasy.xml b/java/com/android/incallui/speakeasy/res/layout/activity_speakeasy.xml
deleted file mode 100644
index 5271d62..0000000
--- a/java/com/android/incallui/speakeasy/res/layout/activity_speakeasy.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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
-  -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-  <FrameLayout
-      android:id="@+id/fragment_speakeasy"
-      android:layout_width="match_parent"
-      android:layout_height="match_parent"/>
-
-</LinearLayout>
diff --git a/java/com/android/incallui/speakeasy/res/values/colors.xml b/java/com/android/incallui/speakeasy/res/values/colors.xml
deleted file mode 100644
index fc4790e..0000000
--- a/java/com/android/incallui/speakeasy/res/values/colors.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 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
-  -->
-
-<resources>
-  <color name="speakeasy_status_bar_color">#E0E0E0</color>
-</resources>