Merge "Tweak app makefile with extra resources"
diff --git a/java/com/android/contacts/common/list/PhoneNumberListAdapter.java b/java/com/android/contacts/common/list/PhoneNumberListAdapter.java
index b742798..dee5109 100644
--- a/java/com/android/contacts/common/list/PhoneNumberListAdapter.java
+++ b/java/com/android/contacts/common/list/PhoneNumberListAdapter.java
@@ -27,6 +27,7 @@
 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Directory;
+import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -357,7 +358,8 @@
     bindPhoneNumber(view, cursor, directory.isDisplayNumber(), position);
   }
 
-  protected void bindPhoneNumber(
+  @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+  public void bindPhoneNumber(
       ContactListItemView view, Cursor cursor, boolean displayNumber, int position) {
     CharSequence label = null;
     if (displayNumber && !cursor.isNull(PhoneQuery.PHONE_TYPE)) {
@@ -397,7 +399,8 @@
       }
     }
 
-    if (LightbringerComponent.get(mContext).getLightbringer().isReachable(mContext, number)) {
+    if (action == ContactListItemView.NONE
+        && LightbringerComponent.get(mContext).getLightbringer().isReachable(mContext, number)) {
       action = ContactListItemView.LIGHTBRINGER;
     }
 
diff --git a/java/com/android/contacts/common/res/drawable/ic_work_profile.xml b/java/com/android/contacts/common/res/drawable/ic_work_profile.xml
index fc21100..4452885 100644
--- a/java/com/android/contacts/common/res/drawable/ic_work_profile.xml
+++ b/java/com/android/contacts/common/res/drawable/ic_work_profile.xml
@@ -1,16 +1,26 @@
-<?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.
+-->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-  android:height="16dp"
-  android:viewportHeight="48"
-  android:viewportWidth="48"
-  android:width="16dp">
+        android:width="16dp"
+        android:height="16dp"
+        android:viewportWidth="16.0"
+        android:viewportHeight="16.0">
 
 
-  <path
-    android:fillColor="#757575"
-    android:pathData="M28 33h-8v-3H6v8c0 2.2 1.8 4 4 4h28c2.2 0 4-1.8
-4-4v-8H28v3zm12-21h-7V9l-3-3H18l-3 3.1V12H8c-2.2 0-4 1.8-4 4v8c0 2.2 1.8 4 4
-4h12v-3h8v3h12c2.2 0 4-1.8 4-4v-8c0-2.2-1.8-4-4-4zm-10 0H18V9h12v3z"/>
-  <path
-    android:pathData="M0 0h48v48H0z"/>
+    <path
+        android:pathData="M13.33,4h-2.67V2.67c0,-0.74 -0.59,-1.33 -1.33,-1.33H6.67c-0.74,0 -1.33,0.59 -1.33,1.33V4H2.67C1.93,4 1.34,4.59 1.34,5.33l-0.01,7.33c0,0.74 0.59,1.33 1.33,1.33h10.67c0.74,0 1.33,-0.59 1.33,-1.33V5.33C14.67,4.59 14.07,4 13.33,4zM8,10c-0.73,0 -1.33,-0.6 -1.33,-1.33S7.27,7.33 8,7.33s1.33,0.6 1.33,1.33S8.73,10 8,10zM9.33,4H6.67V2.67h2.67V4z"
+        android:fillColor="#757575"/>
 </vector>
diff --git a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java
index b1ad0d9..78ec7a6 100644
--- a/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java
+++ b/java/com/android/dialer/app/calllog/CallLogAsyncTaskUtil.java
@@ -66,9 +66,8 @@
                     .update(voicemailUri, values, Voicemails.IS_READ + " = 0", null)
                 > 0) {
               uploadVoicemailLocalChangesToServer(context);
+              CallLogNotificationsService.markAllNewVoicemailsAsOld(context);
             }
-
-            CallLogNotificationsService.markAllNewVoicemailsAsOld(context);
             return null;
           }
         });
diff --git a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
index 23a00d7..f7ea63c 100644
--- a/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
+++ b/java/com/android/dialer/app/calllog/CallLogListItemViewHolder.java
@@ -551,12 +551,15 @@
   private void bindActionButtons() {
     boolean canPlaceCallToNumber = PhoneNumberHelper.canPlaceCallsTo(number, numberPresentation);
 
+    // Hide the call buttons by default. We then set it to be visible when appropriate below.
+    // This saves us having to remember to set it to GONE in multiple places.
+    callButtonView.setVisibility(View.GONE);
+    videoCallButtonView.setVisibility(View.GONE);
+
     if (isFullyUndialableVoicemail()) {
       // Sometimes the voicemail server will report the message is from some non phone number
       // source. If the number does not contains any dialable digit treat it as it is from a unknown
       // number, remove all action buttons but still show the voicemail playback layout.
-      callButtonView.setVisibility(View.GONE);
-      videoCallButtonView.setVisibility(View.GONE);
       detailsButtonView.setVisibility(View.GONE);
       createNewContactButtonView.setVisibility(View.GONE);
       addToExistingContactButtonView.setVisibility(View.GONE);
@@ -585,11 +588,7 @@
         ((TextView) callButtonView.findViewById(R.id.call_type_or_location_text));
 
     if (canPlaceCallToNumber) {
-      // Set up the call button but hide it by default (the primary action is to call so it is
-      // redundant). We then set it to be visible when appropriate below. This saves us having to
-      // remember to set it to GONE in multiple places.
       callButtonView.setTag(IntentProvider.getReturnCallIntentProvider(number));
-      callButtonView.setVisibility(View.GONE);
       callTypeOrLocationView.setVisibility(View.GONE);
     }
 
@@ -617,8 +616,6 @@
     } else if (lightbringerReady) {
       videoCallButtonView.setTag(IntentProvider.getLightbringerIntentProvider(number));
       videoCallButtonView.setVisibility(View.VISIBLE);
-    } else {
-      videoCallButtonView.setVisibility(View.GONE);
     }
 
     // For voicemail calls, show the voicemail playback layout; hide otherwise.
@@ -947,12 +944,7 @@
     String accountLabel = mCallLogCache.getAccountLabel(accountHandle);
     if (!TextUtils.isEmpty(accountLabel)) {
       SimDetails.Builder simDetails = SimDetails.newBuilder().setNetwork(accountLabel);
-      int color = mCallLogCache.getAccountColor(accountHandle);
-      if (color == PhoneAccount.NO_HIGHLIGHT_COLOR) {
-        simDetails.setColor(R.color.secondary_text_color);
-      } else {
-        simDetails.setColor(color);
-      }
+      simDetails.setColor(mCallLogCache.getAccountColor(accountHandle));
       contact.setSimDetails(simDetails.build());
     }
     return contact.build();
diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java
index 84aedf8..0490b99 100644
--- a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java
+++ b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java
@@ -25,6 +25,8 @@
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.support.annotation.WorkerThread;
+import android.telecom.PhoneAccountHandle;
+import com.android.dialer.app.voicemail.LegacyVoicemailNotificationReceiver;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.DialerExecutor.Worker;
@@ -48,7 +50,8 @@
  */
 public class CallLogNotificationsService extends IntentService {
 
-  private static final String ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD =
+  @VisibleForTesting
+  static final String ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD =
       "com.android.dialer.calllog.ACTION_MARK_ALL_NEW_VOICEMAILS_AS_OLD";
 
   private static final String ACTION_MARK_SINGLE_NEW_VOICEMAIL_AS_OLD =
@@ -68,6 +71,10 @@
   public static final String ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION =
       "com.android.dialer.calllog.CALL_BACK_FROM_MISSED_CALL_NOTIFICATION";
 
+  /** Action mark legacy voicemail as dismissed. */
+  public static final String ACTION_LEGACY_VOICEMAIL_DISMISSED =
+      "com.android.dialer.calllog.ACTION_LEGACY_VOICEMAIL_DISMISSED";
+
   /**
    * Extra to be included with {@link #ACTION_INCOMING_POST_CALL} to represent a post call note.
    *
@@ -83,6 +90,8 @@
    */
   private static final String EXTRA_POST_CALL_NUMBER = "POST_CALL_NUMBER";
 
+  private static final String EXTRA_PHONE_ACCOUNT_HANDLE = "PHONE_ACCOUNT_HANDLE";
+
   public static final int UNKNOWN_MISSED_CALL_COUNT = -1;
 
   public CallLogNotificationsService() {
@@ -149,6 +158,14 @@
     return PendingIntent.getService(context, 0, intent, 0);
   }
 
+  public static PendingIntent createLegacyVoicemailDismissedPendingIntent(
+      @NonNull Context context, PhoneAccountHandle phoneAccountHandle) {
+    Intent intent = new Intent(context, CallLogNotificationsService.class);
+    intent.setAction(ACTION_LEGACY_VOICEMAIL_DISMISSED);
+    intent.putExtra(EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+    return PendingIntent.getService(context, 0, intent, 0);
+  }
+
   @Override
   protected void onHandleIntent(Intent intent) {
     if (intent == null) {
@@ -174,6 +191,10 @@
         VoicemailQueryHandler.markSingleNewVoicemailAsRead(this, voicemailUri);
         VisualVoicemailNotifier.cancelSingleVoicemailNotification(this, voicemailUri);
         break;
+      case ACTION_LEGACY_VOICEMAIL_DISMISSED:
+        LegacyVoicemailNotificationReceiver.setDismissed(
+            this, intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT_HANDLE), true);
+        break;
       case ACTION_INCOMING_POST_CALL:
         String note = intent.getStringExtra(EXTRA_POST_CALL_NOTE);
         String phoneNumber = intent.getStringExtra(EXTRA_POST_CALL_NUMBER);
diff --git a/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java
index 428c716..c64e03e 100644
--- a/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java
+++ b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java
@@ -122,7 +122,10 @@
             .setSound(pinnedTelephonyManager.getVoicemailRingtoneUri(handle))
             .setOngoing(isOngoing)
             .setOnlyAlertOnce(isRefresh)
-            .setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle));
+            .setChannelId(NotificationChannelManager.getVoicemailChannelId(context, handle))
+            .setDeleteIntent(
+                CallLogNotificationsService.createLegacyVoicemailDismissedPendingIntent(
+                    context, handle));
 
     if (pinnedTelephonyManager.isVoicemailVibrationEnabled(handle)) {
       builder.setDefaults(Notification.DEFAULT_VIBRATE);
diff --git a/java/com/android/dialer/app/list/ListsFragment.java b/java/com/android/dialer/app/list/ListsFragment.java
index 3f03db1..dbb6c8b 100644
--- a/java/com/android/dialer/app/list/ListsFragment.java
+++ b/java/com/android/dialer/app/list/ListsFragment.java
@@ -75,7 +75,6 @@
   private SharedPreferences mPrefs;
   private boolean mHasFetchedVoicemailStatus;
   private boolean mShowVoicemailTabAfterVoicemailStatusIsFetched;
-  private VoicemailStatusHelper mVoicemailStatusHelper;
   private final ArrayList<OnPageChangeListener> mOnPageChangeListeners = new ArrayList<>();
   /** The position of the currently selected tab. */
   private int mTabIndex = TAB_INDEX_SPEED_DIAL;
@@ -99,7 +98,6 @@
     LogUtil.d("ListsFragment.onCreate", null);
     Trace.beginSection(TAG + " onCreate");
     super.onCreate(savedInstanceState);
-    mVoicemailStatusHelper = new VoicemailStatusHelper();
     mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
     Trace.endSection();
   }
@@ -294,7 +292,7 @@
 
     // Update hasActiveVoicemailProvider, which controls the number of tabs displayed.
     boolean hasActiveVoicemailProvider =
-        mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0;
+        VoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0;
     if (hasActiveVoicemailProvider != mAdapter.hasActiveVoicemailProvider()) {
       mAdapter.setHasActiveVoicemailProvider(hasActiveVoicemailProvider);
       mAdapter.notifyDataSetChanged();
diff --git a/java/com/android/dialer/app/list/PhoneFavoriteTileView.java b/java/com/android/dialer/app/list/PhoneFavoriteTileView.java
index 455085d..29147e7 100644
--- a/java/com/android/dialer/app/list/PhoneFavoriteTileView.java
+++ b/java/com/android/dialer/app/list/PhoneFavoriteTileView.java
@@ -18,6 +18,7 @@
 
 import android.content.ClipData;
 import android.content.Context;
+import android.net.Uri;
 import android.provider.ContactsContract.PinnedPositions;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -26,10 +27,12 @@
 import com.android.contacts.common.MoreContactUtils;
 import com.android.contacts.common.list.ContactEntry;
 import com.android.contacts.common.list.ContactTileView;
+import com.android.contacts.common.model.ContactLoader;
 import com.android.dialer.app.R;
 import com.android.dialer.callintent.CallInitiationType;
 import com.android.dialer.callintent.CallSpecificAppData;
 import com.android.dialer.callintent.SpeedDialContactType;
+import com.android.dialer.common.LogUtil;
 import com.android.dialer.contactphoto.ContactPhotoManager.DefaultImageRequest;
 import com.android.dialer.lettertile.LetterTileDrawable;
 import com.android.dialer.logging.InteractionEvent;
@@ -94,6 +97,7 @@
     isPinned = (entry.pinned != PinnedPositions.UNPINNED);
     isStarred = entry.isFavorite;
     if (entry != null) {
+      sendViewNotification(getContext(), entry.lookupUri);
       // Grab the phone-number to call directly. See {@link onClick()}.
       mPhoneNumberString = entry.phoneNumber;
 
@@ -186,4 +190,22 @@
   public void setPosition(int position) {
     this.position = position;
   }
+
+  /**
+   * Send a notification using a {@link ContactLoader} to inform the sync adapter that we are
+   * viewing a particular contact, so that it can download the high-res photo.
+   */
+  private static void sendViewNotification(Context context, Uri contactUri) {
+    ContactLoader loader = new ContactLoader(context, contactUri, true /* postViewNotification */);
+    loader.registerListener(
+        0,
+        (loader1, contact) -> {
+          try {
+            loader1.reset();
+          } catch (RuntimeException e) {
+            LogUtil.e("PhoneFavoriteTileView.onLoadComplete", "error resetting loader", e);
+          }
+        });
+    loader.startLoading();
+  }
 }
diff --git a/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java b/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java
index 4100521..a81d866 100644
--- a/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java
+++ b/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java
@@ -23,15 +23,15 @@
 import android.content.Intent;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
-import android.preference.PreferenceManager;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.os.BuildCompat;
-import android.support.v4.os.UserManagerCompat;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.TelephonyManager;
 import com.android.dialer.app.calllog.LegacyVoicemailNotifier;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.PerAccountSharedPreferences;
+import com.android.dialer.util.DialerUtils;
 import com.android.voicemail.VoicemailComponent;
 
 /**
@@ -43,15 +43,14 @@
 public class LegacyVoicemailNotificationReceiver extends BroadcastReceiver {
 
   private static final String LEGACY_VOICEMAIL_COUNT = "legacy_voicemail_count";
+  @VisibleForTesting static final String LEGACY_VOICEMAIL_DISMISSED = "legacy_voicemail_dismissed";
 
   /**
-   * Hidden extra for {@link TelephonyManager#ACTION_SHOW_VOICEMAIL_NOTIFICATION} for whether the
-   * notification is just a refresh or for a new voicemail. The phone should not play a ringtone or
-   * vibrate during a refresh if the notification is already showing.
-   *
-   * <p>TODO(b/62202833): make public
+   * Whether the notification is just a refresh or for a new voicemail. The phone should not play a
+   * ringtone or vibrate during a refresh if the notification is already showing. This is Hidden in
+   * O and public in O MR1.
    */
-  private static final String EXTRA_IS_REFRESH = "is_refresh";
+  @VisibleForTesting static final String EXTRA_IS_REFRESH = "is_refresh";
 
   @Override
   public void onReceive(Context context, Intent intent) {
@@ -72,8 +71,21 @@
     PhoneAccountHandle phoneAccountHandle =
         Assert.isNotNull(intent.getParcelableExtra(TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE));
     int count = intent.getIntExtra(TelephonyManager.EXTRA_NOTIFICATION_COUNT, -1);
+    boolean isRefresh = intent.getBooleanExtra(EXTRA_IS_REFRESH, false);
+    LogUtil.i("LegacyVoicemailNotificationReceiver.onReceive", "isRefresh: " + isRefresh);
+    PerAccountSharedPreferences preferences = getSharedPreferences(context, phoneAccountHandle);
+    if (isRefresh) {
+      if (preferences.getBoolean(LEGACY_VOICEMAIL_DISMISSED, false)) {
+        LogUtil.i(
+            "LegacyVoicemailNotificationReceiver.onReceive",
+            "notification dismissed, ignoring refresh");
+        return;
+      }
+    } else {
+      setDismissed(context, phoneAccountHandle, false);
+    }
 
-    if (!hasVoicemailCountChanged(context, phoneAccountHandle, count)) {
+    if (!hasVoicemailCountChanged(preferences, count)) {
       LogUtil.i(
           "LegacyVoicemailNotificationReceiver.onReceive",
           "voicemail count hasn't changed, ignoring");
@@ -116,27 +128,24 @@
         voicemailNumber,
         callVoicemailIntent,
         voicemailSettingIntent,
-        intent.getBooleanExtra(EXTRA_IS_REFRESH, false));
+        isRefresh);
+  }
+
+  public static void setDismissed(
+      Context context, PhoneAccountHandle phoneAccountHandle, boolean dismissed) {
+    getSharedPreferences(context, phoneAccountHandle)
+        .edit()
+        .putBoolean(LEGACY_VOICEMAIL_DISMISSED, dismissed)
+        .apply();
   }
 
   private static boolean hasVoicemailCountChanged(
-      Context context, PhoneAccountHandle phoneAccountHandle, int newCount) {
-    // Need credential encrypted storage to access preferences.
-    if (!UserManagerCompat.isUserUnlocked(context)) {
-      LogUtil.i(
-          "LegacyVoicemailNotificationReceiver.onReceive",
-          "User locked, bypassing voicemail count check");
-      return true;
-    }
-
+      PerAccountSharedPreferences preferences, int newCount) {
     if (newCount == -1) {
       // Carrier does not report voicemail count
       return true;
     }
 
-    PerAccountSharedPreferences preferences =
-        new PerAccountSharedPreferences(
-            context, phoneAccountHandle, PreferenceManager.getDefaultSharedPreferences(context));
     // Carriers may send multiple notifications for the same voicemail.
     if (newCount != 0 && newCount == preferences.getInt(LEGACY_VOICEMAIL_COUNT, -1)) {
       return false;
@@ -144,4 +153,13 @@
     preferences.edit().putInt(LEGACY_VOICEMAIL_COUNT, newCount).apply();
     return true;
   }
+
+  @VisibleForTesting
+  static PerAccountSharedPreferences getSharedPreferences(
+      Context context, PhoneAccountHandle phoneAccountHandle) {
+    return new PerAccountSharedPreferences(
+        context,
+        phoneAccountHandle,
+        DialerUtils.getDefaultSharedPreferenceForDeviceProtectedStorageContext(context));
+  }
 }
diff --git a/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java b/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java
index 5c9bc01..6d5015a 100644
--- a/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java
+++ b/java/com/android/dialer/app/voicemail/VoicemailPlaybackPresenter.java
@@ -57,6 +57,7 @@
 import com.android.dialer.logging.DialerImpression;
 import com.android.dialer.logging.Logger;
 import com.android.dialer.phonenumbercache.CallLogQuery;
+import com.android.dialer.telecom.TelecomUtil;
 import com.android.dialer.util.PermissionsUtil;
 import com.google.common.io.ByteStreams;
 import java.io.File;
@@ -515,6 +516,11 @@
     mView.disableUiElements();
     mIsPrepared = false;
 
+    if (mContext != null && TelecomUtil.isInCall(mContext)) {
+      handleError(new IllegalStateException("Cannot play voicemail when call is in progress"));
+      return;
+    }
+
     try {
       mMediaPlayer = new MediaPlayer();
       mMediaPlayer.setOnPreparedListener(this);
diff --git a/java/com/android/dialer/binary/common/DialerApplication.java b/java/com/android/dialer/binary/common/DialerApplication.java
index 08666a2..0d38541 100644
--- a/java/com/android/dialer/binary/common/DialerApplication.java
+++ b/java/com/android/dialer/binary/common/DialerApplication.java
@@ -17,18 +17,17 @@
 package com.android.dialer.binary.common;
 
 import android.app.Application;
-import android.os.StrictMode;
 import android.os.Trace;
 import android.support.annotation.NonNull;
 import android.support.v4.os.BuildCompat;
 import com.android.dialer.blocking.BlockedNumbersAutoMigrator;
 import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler;
-import com.android.dialer.buildtype.BuildType;
 import com.android.dialer.calllog.CallLogComponent;
 import com.android.dialer.common.concurrent.DefaultDialerExecutorFactory;
 import com.android.dialer.inject.HasRootComponent;
 import com.android.dialer.notification.NotificationChannelManager;
 import com.android.dialer.persistentlog.PersistentLogger;
+import com.android.dialer.strictmode.DialerStrictMode;
 
 /** A common application subclass for all Dialer build variants. */
 public abstract class DialerApplication extends Application implements HasRootComponent {
@@ -38,9 +37,8 @@
   @Override
   public void onCreate() {
     Trace.beginSection("DialerApplication.onCreate");
-    if (BuildType.get() == BuildType.BUGFOOD) {
-      enableStrictMode();
-    }
+    DialerStrictMode.onApplicationCreate();
+
     super.onCreate();
     new BlockedNumbersAutoMigrator(
             this.getApplicationContext(),
@@ -56,13 +54,6 @@
     Trace.endSection();
   }
 
-  private void enableStrictMode() {
-    StrictMode.setThreadPolicy(
-        new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().penaltyDeath().build());
-    StrictMode.setVmPolicy(
-        new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().penaltyDeath().build());
-  }
-
   /**
    * Returns a new instance of the root component for the application. Sub classes should define a
    * root component that extends all the sub components "HasComponent" intefaces. The component
diff --git a/java/com/android/dialer/callcomposer/CallComposerActivity.java b/java/com/android/dialer/callcomposer/CallComposerActivity.java
index ddc1e87..e6e5513 100644
--- a/java/com/android/dialer/callcomposer/CallComposerActivity.java
+++ b/java/com/android/dialer/callcomposer/CallComposerActivity.java
@@ -670,12 +670,20 @@
                   public void onAnimationStart(Animator animation) {
                     isSendAndCallHidingOrHidden = shouldHide;
                     sendAndCall.setVisibility(View.VISIBLE);
+                    cameraIcon.setVisibility(View.VISIBLE);
+                    galleryIcon.setVisibility(View.VISIBLE);
+                    messageIcon.setVisibility(View.VISIBLE);
                   }
 
                   @Override
                   public void onAnimationEnd(Animator animation) {
                     if (isSendAndCallHidingOrHidden) {
                       sendAndCall.setVisibility(View.INVISIBLE);
+                    } else {
+                      // hide buttons to prevent overdrawing and talkback discoverability
+                      cameraIcon.setVisibility(View.GONE);
+                      galleryIcon.setVisibility(View.GONE);
+                      messageIcon.setVisibility(View.GONE);
                     }
                   }
 
diff --git a/java/com/android/dialer/callcomposer/GalleryCursorLoader.java b/java/com/android/dialer/callcomposer/GalleryCursorLoader.java
index 39d6a4a..d33bfb3 100644
--- a/java/com/android/dialer/callcomposer/GalleryCursorLoader.java
+++ b/java/com/android/dialer/callcomposer/GalleryCursorLoader.java
@@ -16,7 +16,6 @@
 
 package com.android.dialer.callcomposer;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
 import android.net.Uri;
 import android.provider.MediaStore.Files;
@@ -44,11 +43,10 @@
         SORT_ORDER);
   }
 
-  @SuppressLint("DefaultLocale")
   private static String createSelection() {
-    return String.format(
-        "mime_type IN ('image/jpeg', 'image/jpg', 'image/png', 'image/webp')"
-            + " AND media_type in (%d)",
-        FileColumns.MEDIA_TYPE_IMAGE);
+    return "mime_type IN ('image/jpeg', 'image/jpg', 'image/png', 'image/webp')"
+        + " AND media_type in ("
+        + FileColumns.MEDIA_TYPE_IMAGE
+        + ")";
   }
 }
diff --git a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
index 7d5757b..410a3a0 100644
--- a/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
+++ b/java/com/android/dialer/calldetails/CallDetailsHeaderViewHolder.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.net.Uri;
 import android.support.v7.widget.RecyclerView;
+import android.telecom.PhoneAccount;
 import android.text.TextUtils;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -93,7 +94,9 @@
     if (!TextUtils.isEmpty(contact.getSimDetails().getNetwork())) {
       networkView.setVisibility(View.VISIBLE);
       networkView.setText(contact.getSimDetails().getNetwork());
-      networkView.setTextColor(context.getResources().getColor(contact.getSimDetails().getColor()));
+      if (contact.getSimDetails().getColor() != PhoneAccount.NO_HIGHLIGHT_COLOR) {
+        networkView.setTextColor(contact.getSimDetails().getColor());
+      }
     }
 
     if (TextUtils.isEmpty(contact.getNumber())) {
diff --git a/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java b/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java
index fad25a4..6ee4695 100644
--- a/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java
+++ b/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java
@@ -20,11 +20,11 @@
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
-import android.os.StrictMode;
 import android.support.annotation.Nullable;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.inject.ApplicationContext;
+import com.android.dialer.strictmode.DialerStrictMode;
 import com.android.dialer.util.DialerUtils;
 import javax.inject.Inject;
 
@@ -95,37 +95,26 @@
 
   @Override
   public String getString(String key, String defaultValue) {
-    return bypassStrictMode(
+    // Reading shared prefs on the main thread is generally safe since a single instance is cached.
+    return DialerStrictMode.bypass(
         () -> getSharedPrefs(appContext).getString(PREF_PREFIX + key, defaultValue));
   }
 
   @Override
   public long getLong(String key, long defaultValue) {
-    return bypassStrictMode(
+    // Reading shared prefs on the main thread is generally safe since a single instance is cached.
+    return DialerStrictMode.bypass(
         () -> getSharedPrefs(appContext).getLong(PREF_PREFIX + key, defaultValue));
   }
 
   @Override
   public boolean getBoolean(String key, boolean defaultValue) {
-    return bypassStrictMode(
+    // Reading shared prefs on the main thread is generally safe since a single instance is cached.
+    return DialerStrictMode.bypass(
         () -> getSharedPrefs(appContext).getBoolean(PREF_PREFIX + key, defaultValue));
   }
 
   private static SharedPreferences getSharedPrefs(Context appContext) {
     return DialerUtils.getDefaultSharedPreferenceForDeviceProtectedStorageContext(appContext);
   }
-
-  private interface Provider<T> {
-    T get();
-  }
-
-  // Reading shared prefs on the main thread is generally safe since a single instance is cached.
-  private static <T> T bypassStrictMode(Provider<T> provider) {
-    StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
-    try {
-      return provider.get();
-    } finally {
-      StrictMode.setThreadPolicy(oldPolicy);
-    }
-  }
 }
diff --git a/java/com/android/dialer/contactphoto/ContactPhotoManagerImpl.java b/java/com/android/dialer/contactphoto/ContactPhotoManagerImpl.java
index 4ad7ea4..5dbdf5e 100644
--- a/java/com/android/dialer/contactphoto/ContactPhotoManagerImpl.java
+++ b/java/com/android/dialer/contactphoto/ContactPhotoManagerImpl.java
@@ -566,7 +566,7 @@
     if (request.mIsCircular) {
       final RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(resources, bitmap);
       drawable.setAntiAlias(true);
-      drawable.setCornerRadius(bitmap.getHeight() / 2);
+      drawable.setCornerRadius(drawable.getIntrinsicHeight() / 2);
       return drawable;
     } else {
       return new BitmapDrawable(resources, bitmap);
diff --git a/java/com/android/dialer/lightbringer/Lightbringer.java b/java/com/android/dialer/lightbringer/Lightbringer.java
index 9b8a180..9120b24 100644
--- a/java/com/android/dialer/lightbringer/Lightbringer.java
+++ b/java/com/android/dialer/lightbringer/Lightbringer.java
@@ -28,6 +28,8 @@
 
 public interface Lightbringer {
 
+  boolean isEnabled();
+
   @MainThread
   boolean isReachable(@NonNull Context context, @Nullable String number);
 
diff --git a/java/com/android/dialer/lightbringer/stub/LightbringerStub.java b/java/com/android/dialer/lightbringer/stub/LightbringerStub.java
index 92230a4..c98ae09 100644
--- a/java/com/android/dialer/lightbringer/stub/LightbringerStub.java
+++ b/java/com/android/dialer/lightbringer/stub/LightbringerStub.java
@@ -35,6 +35,11 @@
   @Inject
   public LightbringerStub() {}
 
+  @Override
+  public boolean isEnabled() {
+    return false;
+  }
+
   @MainThread
   @Override
   public boolean isReachable(@NonNull Context context, @Nullable String number) {
diff --git a/java/com/android/dialer/logging/LoggingBindings.java b/java/com/android/dialer/logging/LoggingBindings.java
index 85ccfdf..ca9a053 100644
--- a/java/com/android/dialer/logging/LoggingBindings.java
+++ b/java/com/android/dialer/logging/LoggingBindings.java
@@ -80,4 +80,11 @@
       QuickContactBadge quickContact,
       InteractionEvent.Type interactionEvent,
       boolean shouldPerformClick);
+
+  /** Logs People Api lookup result with error */
+  void logPeopleApiLookupReportWithError(
+      long latency, int httpResponseCode, PeopleApiLookupError.Type errorType);
+
+  /** Logs successful People Api lookup result */
+  void logSuccessfulPeopleApiLookupReport(long latency, int httpResponseCode);
 }
diff --git a/java/com/android/dialer/logging/LoggingBindingsStub.java b/java/com/android/dialer/logging/LoggingBindingsStub.java
index 3892996..2dbcc3f 100644
--- a/java/com/android/dialer/logging/LoggingBindingsStub.java
+++ b/java/com/android/dialer/logging/LoggingBindingsStub.java
@@ -54,4 +54,11 @@
       QuickContactBadge quickContact,
       InteractionEvent.Type interactionEvent,
       boolean shouldPerformClick) {}
+
+  @Override
+  public void logPeopleApiLookupReportWithError(
+      long latency, int httpResponseCode, PeopleApiLookupError.Type errorType) {}
+
+  @Override
+  public void logSuccessfulPeopleApiLookupReport(long latency, int httpResponseCode) {}
 }
diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto
index 8ccaf2d..92ff0a6 100644
--- a/java/com/android/dialer/logging/dialer_impression.proto
+++ b/java/com/android/dialer/logging/dialer_impression.proto
@@ -478,5 +478,10 @@
 
     // In in call UI
     UPGRADE_TO_VIDEO_CALL_BUTTON_SHOWN = 1236;
+
+    // Bubble primary button first click to expand bubble
+    BUBBLE_PRIMARY_BUTTON_EXPAND = 1237;
+    // Bubble prinary button second click to return to call
+    BUBBLE_PRIMARY_BUTTON_RETURN_TO_CALL = 1238;
   }
 }
diff --git a/java/com/android/dialer/logging/people_api_lookup_error.proto b/java/com/android/dialer/logging/people_api_lookup_error.proto
new file mode 100644
index 0000000..e37d10a
--- /dev/null
+++ b/java/com/android/dialer/logging/people_api_lookup_error.proto
@@ -0,0 +1,19 @@
+syntax = "proto2";
+
+package com.android.dialer.logging;
+option java_package = "com.android.dialer.logging";
+option java_multiple_files = true;
+option optimize_for = LITE_RUNTIME;
+
+
+
+
+message PeopleApiLookupError {
+  enum Type {
+    UNKNOWN = 0;
+    HTTP_RESPONSE_ERROR = 1;
+    WRONG_KIND_VALUE = 2;
+    NO_ITEM_FOUND = 3;
+    JSON_PARSING_ERROR = 4;
+  }
+}
diff --git a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java
index ee6e61c..0d22a82 100644
--- a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java
+++ b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java
@@ -60,6 +60,23 @@
     return queryIndex == query.length();
   }
 
+  /**
+   * Returns true if the subparts of the name (split by white space) begin with the query.
+   *
+   * <p>Examples:
+   *
+   * <ul>
+   *   <li>#nameContainsQuery("b", "Brandon") returns true
+   *   <li>#nameContainsQuery("o", "Bob") returns false
+   *   <li>#nameContainsQuery("o", "Bob Olive") returns true
+   * </ul>
+   */
+  public static boolean nameContainsQuery(String query, String name) {
+    return Pattern.compile("(^|\\s)" + Pattern.quote(query.toLowerCase()))
+        .matcher(name.toLowerCase())
+        .find();
+  }
+
   /** @return true if the number belongs to the query. */
   public static boolean numberMatchesNumberQuery(String query, String number) {
     return PhoneNumberUtils.isGlobalPhoneNumber(query)
diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactCursor.java b/java/com/android/dialer/searchfragment/cp2/SearchContactCursor.java
index 5199264..05e98cc 100644
--- a/java/com/android/dialer/searchfragment/cp2/SearchContactCursor.java
+++ b/java/com/android/dialer/searchfragment/cp2/SearchContactCursor.java
@@ -142,7 +142,7 @@
       if (TextUtils.isEmpty(query)
           || QueryFilteringUtil.nameMatchesT9Query(query, previousName)
           || QueryFilteringUtil.numberMatchesNumberQuery(query, previousMostQualifiedNumber)
-          || previousName.contains(query)) {
+          || QueryFilteringUtil.nameContainsQuery(query, previousName)) {
         queryFilteredPositions.add(previousMostQualifiedPosition);
       }
     }
diff --git a/java/com/android/dialer/strictmode/DialerStrictMode.java b/java/com/android/dialer/strictmode/DialerStrictMode.java
new file mode 100644
index 0000000..c9bbeaf
--- /dev/null
+++ b/java/com/android/dialer/strictmode/DialerStrictMode.java
@@ -0,0 +1,95 @@
+/*
+ * 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.strictmode;
+
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
+import android.os.StrictMode.VmPolicy;
+import com.android.dialer.buildtype.BuildType;
+
+/**
+ * Enables strict mode for the application, and provides means of temporarily disabling it.
+ *
+ * <p>NOTE: All methods in this class are stripped by proguard in release builds.
+ */
+public final class DialerStrictMode {
+
+  /** Initializes strict mode on application start. */
+  public static void onApplicationCreate() {
+    enableDeathPenalty();
+  }
+
+  /**
+   * Disables the strict mode death penalty. If strict mode is enabled for the build, warnings are
+   * printed instead of the application crashing.
+   *
+   * <p>You should typically do this only temporarily and restore the death penalty in a finally
+   * block using {@link #enableDeathPenalty()}.
+   */
+  public static void disableDeathPenalty() {
+    if (isStrictModeAllowed()) {
+      StrictMode.setThreadPolicy(threadPolicyTemplate().build());
+      StrictMode.setVmPolicy(vmPolicyTemplate().build());
+    }
+  }
+
+  /**
+   * Restore the death penalty. This should typically be called in a finally block after calling
+   * {@link #disableDeathPenalty()}.
+   */
+  public static void enableDeathPenalty() {
+    if (isStrictModeAllowed()) {
+      StrictMode.setThreadPolicy(threadPolicyTemplate().penaltyDeath().build());
+      StrictMode.setVmPolicy(vmPolicyTemplate().penaltyDeath().build());
+    }
+  }
+
+  private static ThreadPolicy.Builder threadPolicyTemplate() {
+    return new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog();
+  }
+
+  private static VmPolicy.Builder vmPolicyTemplate() {
+    return new StrictMode.VmPolicy.Builder().detectAll().penaltyLog();
+  }
+
+  private static boolean isStrictModeAllowed() {
+    return BuildType.get() == BuildType.BUGFOOD;
+  }
+
+  /** Functional interface intended to be used with {@link #bypass(Provider)}. */
+  public interface Provider<T> {
+    T get();
+  }
+
+  /**
+   * Convenience method for disabling and enabling the death penalty using lambdas.
+   *
+   * <p>For example:
+   *
+   * <p><code>
+   *   DialerStrictMode.bypass(() -> doDiskAccessOnMainThread());
+   * </code>
+   */
+  public static <T> T bypass(Provider<T> provider) {
+    disableDeathPenalty();
+    try {
+      return provider.get();
+    } finally {
+      enableDeathPenalty();
+    }
+  }
+}
diff --git a/java/com/android/dialer/voicemailstatus/VisualVoicemailEnabledChecker.java b/java/com/android/dialer/voicemailstatus/VisualVoicemailEnabledChecker.java
index a1fc29e..3f519ad 100644
--- a/java/com/android/dialer/voicemailstatus/VisualVoicemailEnabledChecker.java
+++ b/java/com/android/dialer/voicemailstatus/VisualVoicemailEnabledChecker.java
@@ -45,7 +45,6 @@
   private SharedPreferences mPrefs;
   private boolean mHasActiveVoicemailProvider;
   private CallLogQueryHandler mCallLogQueryHandler;
-  private VoicemailStatusHelper mVoicemailStatusHelper;
   private Context mContext;
   private Callback mCallback;
 
@@ -53,7 +52,6 @@
     mContext = context;
     mCallback = callback;
     mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
-    mVoicemailStatusHelper = new VoicemailStatusHelper();
     mHasActiveVoicemailProvider = mPrefs.getBoolean(PREF_KEY_HAS_ACTIVE_VOICEMAIL_PROVIDER, false);
   }
 
@@ -77,7 +75,7 @@
   @Override
   public void onVoicemailStatusFetched(Cursor statusCursor) {
     boolean hasActiveVoicemailProvider =
-        mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0;
+        VoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor) > 0;
     if (hasActiveVoicemailProvider != mHasActiveVoicemailProvider) {
       mHasActiveVoicemailProvider = hasActiveVoicemailProvider;
       mPrefs
diff --git a/java/com/android/dialer/voicemailstatus/VoicemailStatusHelper.java b/java/com/android/dialer/voicemailstatus/VoicemailStatusHelper.java
index 9df45c2..313fc1b 100644
--- a/java/com/android/dialer/voicemailstatus/VoicemailStatusHelper.java
+++ b/java/com/android/dialer/voicemailstatus/VoicemailStatusHelper.java
@@ -21,15 +21,17 @@
 import com.android.dialer.database.VoicemailStatusQuery;
 
 /**
- * Interface used by the call log UI to determine what user message, if any, related to voicemail
+ * Utility used by the call log UI to determine what user message, if any, related to voicemail
  * source status needs to be shown. The messages are returned in the order of importance.
  *
- * <p>The implementation of this interface interacts with the voicemail content provider to fetch
- * statuses of all the registered voicemail sources and determines if any status message needs to be
- * shown. The user of this interface must observe/listen to provider changes and invoke this class
- * to check if any message needs to be shown.
+ * <p>This class interacts with the voicemail content provider to fetch statuses of all the
+ * registered voicemail sources and determines if any status message needs to be shown. The user of
+ * this class must observe/listen to provider changes and invoke this class to check if any message
+ * needs to be shown.
  */
-public class VoicemailStatusHelper {
+public final class VoicemailStatusHelper {
+
+  private VoicemailStatusHelper() {}
 
   /**
    * Returns the number of active voicemail sources installed.
@@ -39,7 +41,7 @@
    * @param cursor The caller is responsible for the life cycle of the cursor and resetting the
    *     position
    */
-  public int getNumberActivityVoicemailSources(Cursor cursor) {
+  public static int getNumberActivityVoicemailSources(Cursor cursor) {
     int count = 0;
     if (!cursor.moveToFirst()) {
       return 0;
@@ -60,8 +62,10 @@
    * activation is attempted, it will transition into CONFIGURING then into OK or other error state,
    * NOT_CONFIGURED is never set through an error.
    */
-  private boolean isVoicemailSourceActive(Cursor cursor) {
+  private static boolean isVoicemailSourceActive(Cursor cursor) {
     return cursor.getString(VoicemailStatusQuery.SOURCE_PACKAGE_INDEX) != null
+        // getInt() returns 0 when null
+        && !cursor.isNull(VoicemailStatusQuery.CONFIGURATION_STATE_INDEX)
         && cursor.getInt(VoicemailStatusQuery.CONFIGURATION_STATE_INDEX)
             != Status.CONFIGURATION_STATE_NOT_CONFIGURED;
   }
diff --git a/java/com/android/dialer/widget/MessageFragment.java b/java/com/android/dialer/widget/MessageFragment.java
index 615ad3b..7a0fcfd 100644
--- a/java/com/android/dialer/widget/MessageFragment.java
+++ b/java/com/android/dialer/widget/MessageFragment.java
@@ -134,10 +134,9 @@
 
   @Override
   public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-    if (getMessage() == null) {
-      return false;
+    if (!TextUtils.isEmpty(getMessage())) {
+      getListener().onMessageFragmentSendMessage(getMessage());
     }
-    getListener().onMessageFragmentSendMessage(getMessage());
     return true;
   }
 
diff --git a/java/com/android/dialershared/bubble/Bubble.java b/java/com/android/dialershared/bubble/Bubble.java
index f2ba117..9606f5b 100644
--- a/java/com/android/dialershared/bubble/Bubble.java
+++ b/java/com/android/dialershared/bubble/Bubble.java
@@ -61,6 +61,8 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.ViewAnimator;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
 import com.android.dialershared.bubble.BubbleInfo.Action;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -87,6 +89,8 @@
   private final Context context;
   private final WindowManager windowManager;
 
+  private final Handler handler = new Handler();
+
   private LayoutParams windowParams;
 
   // Initialized in factory method
@@ -100,9 +104,7 @@
   private boolean hideAfterText;
   private int collapseEndAction;
 
-  private final Handler handler = new Handler();
-
-  private ViewHolder viewHolder;
+  @VisibleForTesting ViewHolder viewHolder;
   private ViewPropertyAnimator collapseAnimation;
   private Integer overrideGravity;
   private ViewPropertyAnimator exitAnimator;
@@ -238,47 +240,22 @@
     updatePrimaryIconAnimation();
   }
 
-  /**
-   * Hide the button if visible. Will run a short exit animation before hiding. If the bubble is
-   * currently showing text, will hide after the text is done displaying. If the bubble is not
-   * visible this method does nothing.
-   */
+  /** Hide the bubble. */
   public void hide() {
-    if (visibility == Visibility.HIDDEN || visibility == Visibility.EXITING) {
+    if (hideAfterText) {
+      // hideAndReset() will be called after showing text, do nothing here.
       return;
     }
+    hideHelper(this::defaultAfterHidingAnimation);
+  }
 
-    if (textShowing) {
-      hideAfterText = true;
-      return;
-    }
-
-    if (collapseAnimation != null) {
-      collapseEndAction = CollapseEnd.HIDE;
-      return;
-    }
-
-    if (expanded) {
-      startCollapse(CollapseEnd.HIDE);
-      return;
-    }
-
-    visibility = Visibility.EXITING;
-    exitAnimator =
-        viewHolder
-            .getPrimaryButton()
-            .animate()
-            .setInterpolator(new AnticipateInterpolator())
-            .scaleX(0)
-            .scaleY(0)
-            .withEndAction(
-                () -> {
-                  exitAnimator = null;
-                  windowManager.removeView(viewHolder.getRoot());
-                  visibility = Visibility.HIDDEN;
-                  updatePrimaryIconAnimation();
-                });
-    exitAnimator.start();
+  /** Hide the bubble and reset {@viewHolder} to initial state */
+  public void hideAndReset() {
+    hideHelper(
+        () -> {
+          defaultAfterHidingAnimation();
+          reset();
+        });
   }
 
   /** Returns whether the bubble is currently visible */
@@ -350,8 +327,15 @@
                       public boolean onPreDraw() {
                         primaryButton.getViewTreeObserver().removeOnPreDrawListener(this);
 
-                        // Prepare and capture end values
+                        // Prepare and capture end values, always use the size of primaryText since
+                        // its invisibility makes primaryButton smaller than expected
                         TransitionValues endValues = new TransitionValues();
+                        endValues.values.put(
+                            ChangeOnScreenBounds.PROPNAME_WIDTH,
+                            viewHolder.getPrimaryText().getWidth());
+                        endValues.values.put(
+                            ChangeOnScreenBounds.PROPNAME_HEIGHT,
+                            viewHolder.getPrimaryText().getHeight());
                         endValues.view = primaryButton;
                         transition.addTarget(endValues.view);
                         transition.captureEndValues(endValues);
@@ -377,7 +361,8 @@
         () -> {
           textShowing = false;
           if (hideAfterText) {
-            hide();
+            // Always reset here since text shouldn't keep showing.
+            hideAndReset();
           } else {
             doResize(
                 () -> viewHolder.getPrimaryButton().setDisplayedChild(ViewHolder.CHILD_INDEX_ICON));
@@ -411,6 +396,7 @@
 
   void primaryButtonClick() {
     if (expanded || textShowing || currentInfo.getActions().isEmpty()) {
+      Logger.get(context).logImpression(DialerImpression.Type.BUBBLE_PRIMARY_BUTTON_RETURN_TO_CALL);
       try {
         currentInfo.getPrimaryIntent().send();
       } catch (CanceledException e) {
@@ -419,6 +405,7 @@
       return;
     }
 
+    Logger.get(context).logImpression(DialerImpression.Type.BUBBLE_PRIMARY_BUTTON_EXPAND);
     doResize(
         () -> {
           onLeftRightSwitch(isDrawingFromRight());
@@ -478,6 +465,48 @@
     return viewHolder.getRoot();
   }
 
+  /**
+   * Hide the bubble if visible. Will run a short exit animation and before hiding, and {@code
+   * afterHiding} after hiding. If the bubble is currently showing text, will hide after the text is
+   * done displaying. If the bubble is not visible this method does nothing.
+   */
+  private void hideHelper(Runnable afterHiding) {
+    if (visibility == Visibility.HIDDEN || visibility == Visibility.EXITING) {
+      return;
+    }
+
+    if (textShowing) {
+      hideAfterText = true;
+      return;
+    }
+
+    if (collapseAnimation != null) {
+      collapseEndAction = CollapseEnd.HIDE;
+      return;
+    }
+
+    if (expanded) {
+      startCollapse(CollapseEnd.HIDE);
+      return;
+    }
+
+    visibility = Visibility.EXITING;
+    exitAnimator =
+        viewHolder
+            .getPrimaryButton()
+            .animate()
+            .setInterpolator(new AnticipateInterpolator())
+            .scaleX(0)
+            .scaleY(0)
+            .withEndAction(afterHiding);
+    exitAnimator.start();
+  }
+
+  private void reset() {
+    viewHolder = new ViewHolder(viewHolder.getRoot().getContext());
+    update();
+  }
+
   private void update() {
     RippleDrawable backgroundRipple =
         (RippleDrawable)
@@ -681,7 +710,16 @@
     windowManager.updateViewLayout(getRootView(), windowParams);
   }
 
-  private class ViewHolder {
+  private void defaultAfterHidingAnimation() {
+    exitAnimator = null;
+    windowManager.removeView(viewHolder.getRoot());
+    visibility = Visibility.HIDDEN;
+
+    updatePrimaryIconAnimation();
+  }
+
+  @VisibleForTesting
+  class ViewHolder {
 
     public static final int CHILD_INDEX_ICON = 0;
     public static final int CHILD_INDEX_TEXT = 1;
diff --git a/java/com/android/dialershared/bubble/ChangeOnScreenBounds.java b/java/com/android/dialershared/bubble/ChangeOnScreenBounds.java
index 37c8204..8cd61af 100644
--- a/java/com/android/dialershared/bubble/ChangeOnScreenBounds.java
+++ b/java/com/android/dialershared/bubble/ChangeOnScreenBounds.java
@@ -41,6 +41,9 @@
   @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
   static final String PROPNAME_SCREEN_Y = "bubble:changeScreenBounds:screenY";
 
+  static final String PROPNAME_WIDTH = "bubble:changeScreenBounds:width";
+  static final String PROPNAME_HEIGHT = "bubble:changeScreenBounds:height";
+
   private static final Property<ViewBounds, PointF> TOP_LEFT_PROPERTY =
       new Property<ViewBounds, PointF>(PointF.class, "topLeft") {
         @Override
@@ -70,21 +73,32 @@
 
   @Override
   public void captureStartValues(TransitionValues transitionValues) {
-    captureValues(transitionValues);
+    captureValuesWithSize(transitionValues);
   }
 
   @Override
   public void captureEndValues(TransitionValues transitionValues) {
-    captureValues(transitionValues);
+    captureValuesWithSize(transitionValues);
   }
 
-  private void captureValues(TransitionValues values) {
+  /**
+   * Capture location (left and top) from {@code values.view} and size (width and height) from
+   * {@code values.values}. If size is not set, use the size of {@code values.view}.
+   */
+  private void captureValuesWithSize(TransitionValues values) {
     View view = values.view;
 
     if (view.isLaidOut() || view.getWidth() != 0 || view.getHeight() != 0) {
+      Integer width = (Integer) values.values.get(PROPNAME_WIDTH);
+      Integer height = (Integer) values.values.get(PROPNAME_HEIGHT);
+
       values.values.put(
           PROPNAME_BOUNDS,
-          new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
+          new Rect(
+              view.getLeft(),
+              view.getTop(),
+              width == null ? view.getRight() : view.getLeft() + width,
+              height == null ? view.getBottom() : view.getTop() + height));
       values.view.getLocationOnScreen(tempLocation);
       values.values.put(PROPNAME_SCREEN_X, tempLocation[0]);
       values.values.put(PROPNAME_SCREEN_Y, tempLocation[1]);
diff --git a/java/com/android/incallui/CallButtonPresenter.java b/java/com/android/incallui/CallButtonPresenter.java
index d4b77ad..4da227c 100644
--- a/java/com/android/incallui/CallButtonPresenter.java
+++ b/java/com/android/incallui/CallButtonPresenter.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.os.Bundle;
+import android.os.Trace;
 import android.support.v4.app.Fragment;
 import android.support.v4.os.UserManagerCompat;
 import android.telecom.CallAudioState;
@@ -101,6 +102,7 @@
 
   @Override
   public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
+    Trace.beginSection("CallButtonPresenter.onStateChange");
     if (newState == InCallState.OUTGOING) {
       mCall = callList.getOutgoingCall();
     } else if (newState == InCallState.INCALL) {
@@ -124,6 +126,7 @@
       mCall = null;
     }
     updateUi(newState, mCall);
+    Trace.endSection();
   }
 
   /**
diff --git a/java/com/android/incallui/CallCardPresenter.java b/java/com/android/incallui/CallCardPresenter.java
index 3906832..222b5b8 100644
--- a/java/com/android/incallui/CallCardPresenter.java
+++ b/java/com/android/incallui/CallCardPresenter.java
@@ -28,6 +28,7 @@
 import android.hardware.display.DisplayManager;
 import android.os.BatteryManager;
 import android.os.Handler;
+import android.os.Trace;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v4.app.Fragment;
@@ -247,8 +248,10 @@
 
   @Override
   public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
+    Trace.beginSection("CallCardPresenter.onStateChange");
     LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
     if (mInCallScreen == null) {
+      Trace.endSection();
       return;
     }
 
@@ -345,6 +348,7 @@
             callState != DialerCall.State.INCOMING /* animate */);
 
     maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
+    Trace.endSection();
   }
 
   @Override
@@ -583,8 +587,8 @@
     if (call != null) {
       call.getLogState().contactLookupResult = entry.contactLookupResult;
     }
-    if (entry.contactUri != null) {
-      CallerInfoUtils.sendViewNotification(mContext, entry.contactUri);
+    if (entry.lookupUri != null) {
+      CallerInfoUtils.sendViewNotification(mContext, entry.lookupUri);
     }
   }
 
diff --git a/java/com/android/incallui/ContactInfoCache.java b/java/com/android/incallui/ContactInfoCache.java
index d50a5c2..3530153 100644
--- a/java/com/android/incallui/ContactInfoCache.java
+++ b/java/com/android/incallui/ContactInfoCache.java
@@ -699,8 +699,6 @@
     // Note in cache entry whether this is a pending async loading action to know whether to
     // wait for its callback or not.
     boolean hasPendingQuery;
-    /** This will be used for the "view" notification. */
-    public Uri contactUri;
     /** Either a display photo or a thumbnail URI. */
     Uri displayPhotoUri;
 
@@ -741,8 +739,6 @@
           + photo
           + ", isSipCall="
           + isSipCall
-          + ", contactUri="
-          + contactUri
           + ", displayPhotoUri="
           + displayPhotoUri
           + ", contactLookupResult="
diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java
index c95086c..3ea2b17 100644
--- a/java/com/android/incallui/InCallActivity.java
+++ b/java/com/android/incallui/InCallActivity.java
@@ -21,6 +21,7 @@
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.GradientDrawable.Orientation;
 import android.os.Bundle;
+import android.os.Trace;
 import android.support.annotation.ColorInt;
 import android.support.annotation.FloatRange;
 import android.support.annotation.NonNull;
@@ -112,6 +113,7 @@
 
   @Override
   protected void onCreate(Bundle icicle) {
+    Trace.beginSection("InCallActivity.onCreate");
     LogUtil.i("InCallActivity.onCreate", "");
     super.onCreate(icicle);
 
@@ -129,6 +131,7 @@
             View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
 
     pseudoBlackScreenOverlay = findViewById(R.id.psuedo_black_screen_overlay);
+    Trace.endSection();
   }
 
   @Override
@@ -144,6 +147,7 @@
 
   @Override
   protected void onStart() {
+    Trace.beginSection("InCallActivity.onStart");
     LogUtil.i("InCallActivity.onStart", "");
     super.onStart();
     isVisible = true;
@@ -154,40 +158,49 @@
       // Hide the dialpad because there may not be enough room
       showDialpadFragment(false, false);
     }
+    Trace.endSection();
   }
 
   @Override
   protected void onResume() {
+    Trace.beginSection("InCallActivity.onResume");
     LogUtil.i("InCallActivity.onResume", "");
     super.onResume();
     common.onResume();
     PseudoScreenState pseudoScreenState = InCallPresenter.getInstance().getPseudoScreenState();
     pseudoScreenState.addListener(this);
     onPseudoScreenStateChanged(pseudoScreenState.isOn());
+    Trace.endSection();
   }
 
   /** onPause is guaranteed to be called when the InCallActivity goes in the background. */
   @Override
   protected void onPause() {
+    Trace.beginSection("InCallActivity.onPause");
     LogUtil.i("InCallActivity.onPause", "");
     super.onPause();
     common.onPause();
     InCallPresenter.getInstance().getPseudoScreenState().removeListener(this);
+    Trace.endSection();
   }
 
   @Override
   protected void onStop() {
+    Trace.beginSection("InCallActivity.onStop");
     LogUtil.i("InCallActivity.onStop", "");
     super.onStop();
     common.onStop();
     isVisible = false;
+    Trace.endSection();
   }
 
   @Override
   protected void onDestroy() {
+    Trace.beginSection("InCallActivity.onDestroy");
     LogUtil.i("InCallActivity.onDestroy", "");
     super.onDestroy();
     common.onDestroy();
+    Trace.endSection();
   }
 
   @Override
@@ -476,8 +489,10 @@
   }
 
   public void onPrimaryCallStateChanged() {
+    Trace.beginSection("InCallActivity.onPrimaryCallStateChanged");
     LogUtil.i("InCallActivity.onPrimaryCallStateChanged", "");
     showMainInCallFragment();
+    Trace.endSection();
   }
 
   public void onWiFiToLteHandover(DialerCall call) {
@@ -514,15 +529,18 @@
   }
 
   private void showMainInCallFragment() {
+    Trace.beginSection("InCallActivity.showMainInCallFragment");
     // If the activity's onStart method hasn't been called yet then defer doing any work.
     if (!isVisible) {
       LogUtil.i("InCallActivity.showMainInCallFragment", "not visible yet/anymore");
+      Trace.endSection();
       return;
     }
 
     // Don't let this be reentrant.
     if (isInShowMainInCallFragment) {
       LogUtil.i("InCallActivity.showMainInCallFragment", "already in method, bailing");
+      Trace.endSection();
       return;
     }
 
@@ -560,10 +578,13 @@
     }
 
     if (didChangeInCall || didChangeVideo || didChangeAnswer) {
+      Trace.beginSection("InCallActivity.commitTransaction");
       transaction.commitNow();
+      Trace.endSection();
       Logger.get(this).logScreenView(ScreenEvent.Type.INCALL, this);
     }
     isInShowMainInCallFragment = false;
+    Trace.endSection();
   }
 
   private ShouldShowUiResult getShouldShowAnswerUi() {
diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java
index 0dd6549..6c08c49 100644
--- a/java/com/android/incallui/InCallPresenter.java
+++ b/java/com/android/incallui/InCallPresenter.java
@@ -21,6 +21,7 @@
 import android.graphics.Point;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Trace;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
@@ -41,6 +42,7 @@
 import com.android.dialer.blocking.FilteredNumberCompat;
 import com.android.dialer.blocking.FilteredNumbersUtil;
 import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.DefaultDialerExecutorFactory;
 import com.android.dialer.enrichedcall.EnrichedCallComponent;
 import com.android.dialer.location.GeoUtil;
 import com.android.dialer.logging.InteractionEvent;
@@ -189,6 +191,8 @@
   /** Determines if the InCall UI is in fullscreen mode or not. */
   private boolean mIsFullScreen = false;
 
+  private boolean mScreenTimeoutEnabled = true;
+
   private PhoneStateListener mPhoneStateListener =
       new PhoneStateListener() {
         @Override
@@ -321,11 +325,13 @@
       ContactInfoCache contactInfoCache,
       ProximitySensor proximitySensor,
       FilteredNumberAsyncQueryHandler filteredNumberQueryHandler) {
+    Trace.beginSection("InCallPresenter.setUp");
     if (mServiceConnected) {
       LogUtil.i("InCallPresenter.setUp", "New service connection replacing existing one.");
       if (context != mContext || callList != mCallList) {
         throw new IllegalStateException();
       }
+      Trace.endSection();
       return;
     }
 
@@ -360,7 +366,7 @@
     mCallList.addListener(this);
 
     // Create spam call list listener and add it to the list of listeners
-    mSpamCallListListener = new SpamCallListListener(context);
+    mSpamCallListListener = new SpamCallListListener(context, new DefaultDialerExecutorFactory());
     mCallList.addListener(mSpamCallListListener);
 
     VideoPauseController.getInstance().setUp(this);
@@ -371,6 +377,7 @@
         .listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
 
     LogUtil.d("InCallPresenter.setUp", "Finished InCallPresenter.setUp");
+    Trace.endSection();
   }
 
   /**
@@ -395,6 +402,7 @@
   }
 
   private void attemptFinishActivity() {
+    mScreenTimeoutEnabled = true;
     final boolean doFinish = (mInCallActivity != null && isActivityStarted());
     LogUtil.i("InCallPresenter.attemptFinishActivity", "Hide in call UI: " + doFinish);
     if (doFinish) {
@@ -510,6 +518,7 @@
   }
 
   public void onCallAdded(final android.telecom.Call call) {
+    Trace.beginSection("InCallPresenter.onCallAdded");
     LatencyReport latencyReport = new LatencyReport(call);
     if (shouldAttemptBlocking(call)) {
       maybeBlockCall(call, latencyReport);
@@ -525,6 +534,7 @@
     // Since a call has been added we are no longer waiting for Telecom to send us a call.
     setBoundAndWaitingForOutgoingCall(false, null);
     call.registerCallback(mCallCallback);
+    Trace.endSection();
   }
 
   private boolean shouldAttemptBlocking(android.telecom.Call call) {
@@ -687,11 +697,14 @@
    */
   @Override
   public void onCallListChange(CallList callList) {
+    Trace.beginSection("InCallPresenter.onCallListChange");
     if (mInCallActivity != null && mInCallActivity.isInCallScreenAnimating()) {
       mAwaitingCallListUpdate = true;
+      Trace.endSection();
       return;
     }
     if (callList == null) {
+      Trace.endSection();
       return;
     }
 
@@ -741,11 +754,13 @@
           callList.getActiveOrBackgroundCall() != null || callList.getOutgoingCall() != null;
       mInCallActivity.dismissKeyguard(hasCall);
     }
+    Trace.endSection();
   }
 
   /** Called when there is a new incoming call. */
   @Override
   public void onIncomingCall(DialerCall call) {
+    Trace.beginSection("InCallPresenter.onIncomingCall");
     InCallState newState = startOrFinishUi(InCallState.INCOMING);
     InCallState oldState = mInCallState;
 
@@ -761,6 +776,7 @@
       // Re-evaluate which fragment is being shown.
       mInCallActivity.onPrimaryCallStateChanged();
     }
+    Trace.endSection();
   }
 
   @Override
@@ -1064,6 +1080,7 @@
       // TODO(maxwelb) - b/36649622: Investigate this redundant call
       mStatusBarNotifier.updateNotification(mCallList);
     }
+    applyScreenTimeout();
   }
 
   /*package*/
@@ -1577,13 +1594,18 @@
 
   public void enableScreenTimeout(boolean enable) {
     LogUtil.v("InCallPresenter.enableScreenTimeout", "enableScreenTimeout: value=" + enable);
+    mScreenTimeoutEnabled = enable;
+    applyScreenTimeout();
+  }
+
+  private void applyScreenTimeout() {
     if (mInCallActivity == null) {
-      LogUtil.e("InCallPresenter.enableScreenTimeout", "InCallActivity is null.");
+      LogUtil.e("InCallPresenter.applyScreenTimeout", "InCallActivity is null.");
       return;
     }
 
     final Window window = mInCallActivity.getWindow();
-    if (enable) {
+    if (mScreenTimeoutEnabled) {
       window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
     } else {
       window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
diff --git a/java/com/android/incallui/InCallServiceImpl.java b/java/com/android/incallui/InCallServiceImpl.java
index d2b0297..a2e2432 100644
--- a/java/com/android/incallui/InCallServiceImpl.java
+++ b/java/com/android/incallui/InCallServiceImpl.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.IBinder;
+import android.os.Trace;
 import android.telecom.Call;
 import android.telecom.CallAudioState;
 import android.telecom.InCallService;
@@ -45,26 +46,35 @@
 
   @Override
   public void onBringToForeground(boolean showDialpad) {
+    Trace.beginSection("InCallServiceImpl.onBringToForeground");
     InCallPresenter.getInstance().onBringToForeground(showDialpad);
+    Trace.endSection();
   }
 
   @Override
   public void onCallAdded(Call call) {
+    Trace.beginSection("InCallServiceImpl.onCallAdded");
     InCallPresenter.getInstance().onCallAdded(call);
+    Trace.endSection();
   }
 
   @Override
   public void onCallRemoved(Call call) {
+    Trace.beginSection("InCallServiceImpl.onCallRemoved");
     InCallPresenter.getInstance().onCallRemoved(call);
+    Trace.endSection();
   }
 
   @Override
   public void onCanAddCallChanged(boolean canAddCall) {
+    Trace.beginSection("InCallServiceImpl.onCanAddCallChanged");
     InCallPresenter.getInstance().onCanAddCallChanged(canAddCall);
+    Trace.endSection();
   }
 
   @Override
   public IBinder onBind(Intent intent) {
+    Trace.beginSection("InCallServiceImpl.onBind");
     final Context context = getApplicationContext();
     final ContactInfoCache contactInfoCache = ContactInfoCache.getInstance(context);
     InCallPresenter.getInstance()
@@ -85,20 +95,25 @@
       returnToCallController = new ReturnToCallController(this);
     }
 
-    return super.onBind(intent);
+    IBinder iBinder = super.onBind(intent);
+    Trace.endSection();
+    return iBinder;
   }
 
   @Override
   public boolean onUnbind(Intent intent) {
+    Trace.beginSection("InCallServiceImpl.onUnbind");
     super.onUnbind(intent);
 
     InCallPresenter.getInstance().onServiceUnbind();
     tearDown();
 
+    Trace.endSection();
     return false;
   }
 
   private void tearDown() {
+    Trace.beginSection("InCallServiceImpl.tearDown");
     Log.v(this, "tearDown");
     // Tear down the InCall system
     TelecomAdapter.getInstance().clearInCallService();
@@ -107,5 +122,6 @@
       returnToCallController.tearDown();
       returnToCallController = null;
     }
+    Trace.endSection();
   }
 }
diff --git a/java/com/android/incallui/ReturnToCallController.java b/java/com/android/incallui/ReturnToCallController.java
index 978b140..8e4b9cc 100644
--- a/java/com/android/incallui/ReturnToCallController.java
+++ b/java/com/android/incallui/ReturnToCallController.java
@@ -102,6 +102,14 @@
     }
   }
 
+  private void hideAndReset() {
+    if (bubble != null) {
+      bubble.hideAndReset();
+    } else {
+      LogUtil.i("ReturnToCallController.reset", "reset() called without calling show()");
+    }
+  }
+
   private void show() {
     if (bubble == null) {
       bubble = startNewBubble();
@@ -141,7 +149,7 @@
       bubble.showText(context.getText(R.string.incall_call_ended));
     }
     if (!hasAnotherCall) {
-      hide();
+      hideAndReset();
     }
   }
 
diff --git a/java/com/android/incallui/VideoCallPresenter.java b/java/com/android/incallui/VideoCallPresenter.java
index 233b2b5..89cddb9 100644
--- a/java/com/android/incallui/VideoCallPresenter.java
+++ b/java/com/android/incallui/VideoCallPresenter.java
@@ -867,7 +867,7 @@
         false /* isRemotelyHeld */);
     enableCamera(mVideoCall, false);
     InCallPresenter.getInstance().setFullScreen(false);
-
+    InCallPresenter.getInstance().enableScreenTimeout(false);
     mIsVideoMode = false;
   }
 
diff --git a/java/com/android/incallui/call/CallList.java b/java/com/android/incallui/call/CallList.java
index 269fcd6..d932c24 100644
--- a/java/com/android/incallui/call/CallList.java
+++ b/java/com/android/incallui/call/CallList.java
@@ -114,7 +114,7 @@
 
   public void onCallAdded(
       final Context context, final android.telecom.Call telecomCall, LatencyReport latencyReport) {
-    Trace.beginSection("onCallAdded");
+    Trace.beginSection("CallList.onCallAdded");
     final DialerCall call =
         new DialerCall(context, this, telecomCall, latencyReport, true /* registerCallback */);
     logSecondIncomingCall(context, call);
@@ -123,6 +123,7 @@
     manager.registerCapabilitiesListener(call);
     manager.registerStateChangedListener(call);
 
+    Trace.beginSection("checkSpam");
     final DialerCallListenerImpl dialerCallListener = new DialerCallListenerImpl(call);
     call.addListener(dialerCallListener);
     LogUtil.d("CallList.onCallAdded", "callState=" + call.getState());
@@ -169,7 +170,9 @@
 
       updateUserMarkedSpamStatus(call, context, number, dialerCallListener);
     }
+    Trace.endSection();
 
+    Trace.beginSection("checkBlock");
     FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler =
         new FilteredNumberAsyncQueryHandler(context);
 
@@ -185,6 +188,7 @@
         },
         call.getNumber(),
         GeoUtil.getCurrentCountryIso(context));
+    Trace.endSection();
 
     if (call.getState() == DialerCall.State.INCOMING
         || call.getState() == DialerCall.State.CALL_WAITING) {
@@ -353,6 +357,7 @@
 
   /** Called when a single call has changed. */
   private void onIncoming(DialerCall call) {
+    Trace.beginSection("CallList.onIncoming");
     if (updateCallInMap(call)) {
       LogUtil.i("CallList.onIncoming", String.valueOf(call));
     }
@@ -360,6 +365,7 @@
     for (Listener listener : mListeners) {
       listener.onIncomingCall(call);
     }
+    Trace.endSection();
   }
 
   public void addListener(@NonNull Listener listener) {
@@ -570,6 +576,7 @@
    */
   @VisibleForTesting
   void onUpdateCall(DialerCall call) {
+    Trace.beginSection("CallList.onUpdateCall");
     LogUtil.d("CallList.onUpdateCall", String.valueOf(call));
     if (!mCallById.containsKey(call.getId()) && call.isExternalCall()) {
       // When a regular call becomes external, it is removed from the call list, and there may be
@@ -582,6 +589,7 @@
     if (updateCallInMap(call)) {
       LogUtil.i("CallList.onUpdateCall", String.valueOf(call));
     }
+    Trace.endSection();
   }
 
   /**
@@ -606,6 +614,7 @@
    * @return false if no call previously existed and no call was added, otherwise true.
    */
   private boolean updateCallInMap(DialerCall call) {
+    Trace.beginSection("CallList.updateCallInMap");
     Objects.requireNonNull(call);
 
     boolean updated = false;
@@ -635,6 +644,7 @@
       updated = true;
     }
 
+    Trace.endSection();
     return updated;
   }
 
@@ -764,7 +774,7 @@
 
     @Override
     public void onDialerCallUpdate() {
-      Trace.beginSection("onUpdate");
+      Trace.beginSection("CallList.onDialerCallUpdate");
       onUpdateCall(mCall);
       notifyGenericListeners();
       Trace.endSection();
diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java
index 2e15264..6ba0c81 100644
--- a/java/com/android/incallui/call/DialerCall.java
+++ b/java/com/android/incallui/call/DialerCall.java
@@ -430,7 +430,7 @@
   }
 
   private void update() {
-    Trace.beginSection("Update");
+    Trace.beginSection("DialerCall.update");
     int oldState = getState();
     // Clear any cache here that could potentially change on update.
     videoTech = null;
@@ -455,6 +455,7 @@
   }
 
   private void updateFromTelecomCall() {
+    Trace.beginSection("DialerCall.updateFromTelecomCall");
     LogUtil.v("DialerCall.updateFromTelecomCall", mTelecomCall.toString());
 
     mVideoTechManager.dispatchCallStateChanged(mTelecomCall.getState());
@@ -503,6 +504,7 @@
         }
       }
     }
+    Trace.endSection();
   }
 
   /**
@@ -680,13 +682,23 @@
   }
 
   public void setState(int state) {
-    mState = state;
-    if (mState == State.INCOMING) {
+    if (state == State.INCOMING) {
       mLogState.isIncoming = true;
-    } else if (mState == State.DISCONNECTED) {
-      mLogState.duration =
+    } else if (state == State.DISCONNECTED) {
+      long newDuration =
           getConnectTimeMillis() == 0 ? 0 : System.currentTimeMillis() - getConnectTimeMillis();
+      if (mState != state) {
+        mLogState.duration = newDuration;
+      } else {
+        LogUtil.i(
+            "DialerCall.setState",
+            "ignoring state transition from DISCONNECTED to DISCONNECTED."
+                + " Duration would have changed from %s to %s",
+            mLogState.duration,
+            newDuration);
+      }
     }
+    mState = state;
   }
 
   public int getNumberPresentation() {
diff --git a/java/com/android/incallui/calllocation/impl/DownloadMapImageTask.java b/java/com/android/incallui/calllocation/impl/DownloadMapImageTask.java
index b093a1b..035f5cd 100644
--- a/java/com/android/incallui/calllocation/impl/DownloadMapImageTask.java
+++ b/java/com/android/incallui/calllocation/impl/DownloadMapImageTask.java
@@ -50,9 +50,9 @@
 
     try {
       URL mapUrl = new URL(LocationUrlBuilder.getStaticMapUrl(ui.getContext(), locations[0]));
+      TrafficStats.setThreadStatsTag(TrafficStatsTags.DOWNLOAD_LOCATION_MAP_TAG);
       InputStream content = (InputStream) mapUrl.getContent();
 
-      TrafficStats.setThreadStatsTag(TrafficStatsTags.DOWNLOAD_LOCATION_MAP_TAG);
       return Drawable.createFromStream(content, STATIC_MAP_SRC_NAME);
     } catch (Exception ex) {
       LogUtil.e("DownloadMapImageTask.doInBackground", "Exception!!!", ex);
diff --git a/java/com/android/incallui/contactgrid/ContactGridManager.java b/java/com/android/incallui/contactgrid/ContactGridManager.java
index a6d7d95..e4a8a1c 100644
--- a/java/com/android/incallui/contactgrid/ContactGridManager.java
+++ b/java/com/android/incallui/contactgrid/ContactGridManager.java
@@ -31,6 +31,7 @@
 import android.widget.ViewAnimator;
 import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
 import com.android.dialer.common.Assert;
+import com.android.dialer.common.LogUtil;
 import com.android.dialer.lettertile.LetterTileDrawable;
 import com.android.dialer.util.DrawableConverter;
 import com.android.incallui.incall.protocol.ContactPhotoType;
@@ -347,12 +348,16 @@
     }
 
     if (info.isTimerVisible) {
+      bottomTextSwitcher.setDisplayedChild(1);
+      bottomTimerView.setBase(
+          primaryCallState.connectTimeMillis
+              - System.currentTimeMillis()
+              + SystemClock.elapsedRealtime());
       if (!isTimerStarted) {
-        bottomTextSwitcher.setDisplayedChild(1);
-        bottomTimerView.setBase(
-            primaryCallState.connectTimeMillis
-                - System.currentTimeMillis()
-                + SystemClock.elapsedRealtime());
+        LogUtil.i(
+            "ContactGridManager.updateBottomRow",
+            "starting timer with base: %d",
+            bottomTimerView.getBase());
         bottomTimerView.start();
         isTimerStarted = true;
       }
diff --git a/java/com/android/incallui/incall/impl/ButtonChooser.java b/java/com/android/incallui/incall/impl/ButtonChooser.java
index 55b82f0..095a8be 100644
--- a/java/com/android/incallui/incall/impl/ButtonChooser.java
+++ b/java/com/android/incallui/incall/impl/ButtonChooser.java
@@ -18,6 +18,7 @@
 
 import android.support.annotation.NonNull;
 import com.android.dialer.common.Assert;
+import com.android.incallui.incall.impl.MappedButtonConfig.MappingInfo;
 import com.android.incallui.incall.protocol.InCallButtonIds;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -103,12 +104,29 @@
       if (placedButtons.size() >= numUiButtons) {
         return;
       }
+
       // If the conflict button is allowed but disabled, don't place it since it probably will
       // move when it's enabled.
       if (!allowedButtons.contains(conflict) || disabledButtons.contains(conflict)) {
         continue;
       }
+
+      if (isMutuallyExclusiveButtonAvailable(
+          config.lookupMappingInfo(conflict).getMutuallyExclusiveButton(), allowedButtons)) {
+        continue;
+      }
       placedButtons.add(conflict);
     }
   }
+
+  private boolean isMutuallyExclusiveButtonAvailable(
+      int mutuallyExclusiveButton, @NonNull Set<Integer> allowedButtons) {
+    if (mutuallyExclusiveButton == MappingInfo.NO_MUTUALLY_EXCLUSIVE_BUTTON_SET) {
+      return false;
+    }
+    if (allowedButtons.contains(mutuallyExclusiveButton)) {
+      return true;
+    }
+    return false;
+  }
 }
diff --git a/java/com/android/incallui/incall/impl/ButtonChooserFactory.java b/java/com/android/incallui/incall/impl/ButtonChooserFactory.java
index 99364e2..0f4a95d 100644
--- a/java/com/android/incallui/incall/impl/ButtonChooserFactory.java
+++ b/java/com/android/incallui/incall/impl/ButtonChooserFactory.java
@@ -74,6 +74,12 @@
     mapping.put(
         InCallButtonIds.BUTTON_UPGRADE_TO_VIDEO, MappingInfo.builder(4).setSlotOrder(10).build());
     mapping.put(InCallButtonIds.BUTTON_SWAP, MappingInfo.builder(5).setSlotOrder(0).build());
+    mapping.put(
+        InCallButtonIds.BUTTON_SWITCH_TO_SECONDARY,
+        MappingInfo.builder(5)
+            .setSlotOrder(Integer.MAX_VALUE)
+            .setMutuallyExclusiveButton(InCallButtonIds.BUTTON_SWAP)
+            .build());
 
     return new ButtonChooser(new MappedButtonConfig(mapping));
   }
diff --git a/java/com/android/incallui/incall/impl/MappedButtonConfig.java b/java/com/android/incallui/incall/impl/MappedButtonConfig.java
index 7229837..67c4137 100644
--- a/java/com/android/incallui/incall/impl/MappedButtonConfig.java
+++ b/java/com/android/incallui/incall/impl/MappedButtonConfig.java
@@ -141,7 +141,7 @@
   }
 
   @NonNull
-  private MappingInfo lookupMappingInfo(@InCallButtonIds int button) {
+  public MappingInfo lookupMappingInfo(@InCallButtonIds int button) {
     MappingInfo info = mapping.get(button);
     if (info == null) {
       throw new IllegalArgumentException(
@@ -154,6 +154,8 @@
   @AutoValue
   abstract static class MappingInfo {
 
+    public static final int NO_MUTUALLY_EXCLUSIVE_BUTTON_SET = -1;
+
     /** The Ui slot into which a given button desires to be placed. */
     public abstract int getSlot();
 
@@ -171,11 +173,20 @@
      */
     public abstract int getConflictOrder();
 
+    /**
+     * Returns an integer representing a button for which the given button conflicts. Defaults to
+     * {@link NO_MUTUALLY_EXCLUSIVE_BUTTON_SET}.
+     *
+     * <p>If the mutually exclusive button is chosen, the associated button should never be chosen.
+     */
+    public abstract @InCallButtonIds int getMutuallyExclusiveButton();
+
     static Builder builder(int slot) {
       return new AutoValue_MappedButtonConfig_MappingInfo.Builder()
           .setSlot(slot)
           .setSlotOrder(Integer.MAX_VALUE)
-          .setConflictOrder(Integer.MAX_VALUE);
+          .setConflictOrder(Integer.MAX_VALUE)
+          .setMutuallyExclusiveButton(NO_MUTUALLY_EXCLUSIVE_BUTTON_SET);
     }
 
     /** Class used to build instances of {@link MappingInfo}. */
@@ -187,6 +198,8 @@
 
       public abstract Builder setConflictOrder(int conflictOrder);
 
+      public abstract Builder setMutuallyExclusiveButton(@InCallButtonIds int button);
+
       public abstract MappingInfo build();
     }
   }
diff --git a/java/com/android/incallui/spam/NumberInCallHistoryTask.java b/java/com/android/incallui/spam/NumberInCallHistoryTask.java
deleted file mode 100644
index 886933f..0000000
--- a/java/com/android/incallui/spam/NumberInCallHistoryTask.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.incallui.spam;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.os.AsyncTask;
-import android.os.Build.VERSION_CODES;
-import android.provider.CallLog;
-import android.provider.CallLog.Calls;
-import android.support.annotation.NonNull;
-import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.common.concurrent.AsyncTaskExecutor;
-import com.android.dialer.common.concurrent.AsyncTaskExecutors;
-import com.android.dialer.telecom.TelecomUtil;
-import com.android.dialer.util.PermissionsUtil;
-import com.android.incallui.call.DialerCall;
-import com.android.incallui.call.DialerCall.CallHistoryStatus;
-import java.util.Objects;
-
-/** Checks if the number is in the call history. */
-@TargetApi(VERSION_CODES.M)
-public class NumberInCallHistoryTask extends AsyncTask<Void, Void, Integer> {
-
-  public static final String TASK_ID = "number_in_call_history_status";
-
-  private final Context context;
-  private final Listener listener;
-  private final String number;
-  private final String countryIso;
-
-  public NumberInCallHistoryTask(
-      @NonNull Context context, @NonNull Listener listener, String number, String countryIso) {
-    this.context = Objects.requireNonNull(context);
-    this.listener = Objects.requireNonNull(listener);
-    this.number = number;
-    this.countryIso = countryIso;
-  }
-
-  public void submitTask() {
-    if (!PermissionsUtil.hasPhonePermissions(context)) {
-      return;
-    }
-    AsyncTaskExecutor asyncTaskExecutor = AsyncTaskExecutors.createThreadPoolExecutor();
-    asyncTaskExecutor.submit(TASK_ID, this);
-  }
-
-  @Override
-  @CallHistoryStatus
-  public Integer doInBackground(Void... params) {
-    String numberToQuery = number;
-    String fieldToQuery = Calls.NUMBER;
-    String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso);
-
-    // If we can normalize the number successfully, look in "normalized_number"
-    // field instead. Otherwise, look for number in "number" field.
-    if (!TextUtils.isEmpty(normalizedNumber)) {
-      numberToQuery = normalizedNumber;
-      fieldToQuery = Calls.CACHED_NORMALIZED_NUMBER;
-    }
-    try (Cursor cursor =
-        context
-            .getContentResolver()
-            .query(
-                TelecomUtil.getCallLogUri(context),
-                new String[] {CallLog.Calls._ID},
-                fieldToQuery + " = ?",
-                new String[] {numberToQuery},
-                null)) {
-      return cursor != null && cursor.getCount() > 0
-          ? DialerCall.CALL_HISTORY_STATUS_PRESENT
-          : DialerCall.CALL_HISTORY_STATUS_NOT_PRESENT;
-    } catch (SQLiteException e) {
-      LogUtil.e("NumberInCallHistoryTask.doInBackground", "query call log error", e);
-      return DialerCall.CALL_HISTORY_STATUS_UNKNOWN;
-    }
-  }
-
-  @Override
-  public void onPostExecute(@CallHistoryStatus Integer callHistoryStatus) {
-    listener.onComplete(callHistoryStatus);
-  }
-
-  /** Callback for the async task. */
-  public interface Listener {
-
-    void onComplete(@CallHistoryStatus int callHistoryStatus);
-  }
-}
diff --git a/java/com/android/incallui/spam/SpamCallListListener.java b/java/com/android/incallui/spam/SpamCallListListener.java
index 56ae558..7ca3fbb 100644
--- a/java/com/android/incallui/spam/SpamCallListListener.java
+++ b/java/com/android/incallui/spam/SpamCallListListener.java
@@ -16,14 +16,21 @@
 
 package com.android.incallui.spam;
 
+import android.annotation.TargetApi;
 import android.app.Notification;
 import android.app.Notification.Builder;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
 import android.graphics.drawable.Icon;
+import android.os.Build.VERSION_CODES;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.os.BuildCompat;
 import android.telecom.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
@@ -31,17 +38,23 @@
 import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
 import com.android.dialer.blocking.FilteredNumberCompat;
 import com.android.dialer.blocking.FilteredNumbersUtil;
+import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.DialerExecutor.Worker;
+import com.android.dialer.common.concurrent.DialerExecutorFactory;
 import com.android.dialer.location.GeoUtil;
 import com.android.dialer.logging.ContactLookupResult;
 import com.android.dialer.logging.DialerImpression;
 import com.android.dialer.logging.Logger;
 import com.android.dialer.notification.NotificationChannelId;
 import com.android.dialer.spam.Spam;
+import com.android.dialer.telecom.TelecomUtil;
+import com.android.dialer.util.PermissionsUtil;
 import com.android.incallui.R;
 import com.android.incallui.call.CallList;
 import com.android.incallui.call.DialerCall;
 import com.android.incallui.call.DialerCall.CallHistoryStatus;
+import java.util.Arrays;
 import java.util.Random;
 
 /**
@@ -53,15 +66,67 @@
 
   private final Context context;
   private final Random random;
+  private final DialerExecutorFactory dialerExecutorFactory;
 
-  public SpamCallListListener(Context context) {
-    this.context = context;
-    this.random = new Random();
+  public SpamCallListListener(Context context, @NonNull DialerExecutorFactory factory) {
+    this(context, new Random(), factory);
   }
 
-  public SpamCallListListener(Context context, Random rand) {
+  public SpamCallListListener(
+      Context context, Random rand, @NonNull DialerExecutorFactory factory) {
     this.context = context;
     this.random = rand;
+    Assert.isNotNull(factory);
+    this.dialerExecutorFactory = factory;
+  }
+
+  /** Checks if the number is in the call history. */
+  @TargetApi(VERSION_CODES.M)
+  private final class NumberInCallHistoryWorker implements Worker<Void, Integer> {
+
+    private final Context appContext;
+    private final String number;
+    private final String countryIso;
+
+    public NumberInCallHistoryWorker(
+        @NonNull Context appContext, String number, String countryIso) {
+      this.appContext = Assert.isNotNull(appContext);
+      this.number = number;
+      this.countryIso = countryIso;
+    }
+
+    @Override
+    @NonNull
+    @CallHistoryStatus
+    public Integer doInBackground(@Nullable Void input) throws Throwable {
+      String numberToQuery = number;
+      String fieldToQuery = Calls.NUMBER;
+      String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso);
+
+      // If we can normalize the number successfully, look in "normalized_number"
+      // field instead. Otherwise, look for number in "number" field.
+      if (!TextUtils.isEmpty(normalizedNumber)) {
+        numberToQuery = normalizedNumber;
+        fieldToQuery = Calls.CACHED_NORMALIZED_NUMBER;
+      }
+
+      try (Cursor cursor =
+          appContext
+              .getContentResolver()
+              .query(
+                  TelecomUtil.getCallLogUri(appContext),
+                  new String[] {CallLog.Calls._ID},
+                  fieldToQuery + " = ?",
+                  new String[] {numberToQuery},
+                  null)) {
+        return cursor != null && cursor.getCount() > 0
+            ? DialerCall.CALL_HISTORY_STATUS_PRESENT
+            : DialerCall.CALL_HISTORY_STATUS_NOT_PRESENT;
+      } catch (SQLiteException e) {
+        LogUtil.e("NumberInCallHistoryWorker.doInBackground", "query call log error", e);
+        return DialerCall.CALL_HISTORY_STATUS_UNKNOWN;
+      }
+    }
   }
 
   @Override
@@ -70,15 +135,24 @@
     if (TextUtils.isEmpty(number)) {
       return;
     }
-    NumberInCallHistoryTask.Listener listener =
-        new NumberInCallHistoryTask.Listener() {
-          @Override
-          public void onComplete(@CallHistoryStatus int callHistoryStatus) {
-            call.setCallHistoryStatus(callHistoryStatus);
-          }
-        };
-    new NumberInCallHistoryTask(context, listener, number, GeoUtil.getCurrentCountryIso(context))
-        .submitTask();
+
+    String[] deniedPhonePermissions =
+        PermissionsUtil.getPermissionsCurrentlyDenied(
+            context, PermissionsUtil.allPhoneGroupPermissionsUsedInDialer);
+    if (deniedPhonePermissions.length > 0) {
+      LogUtil.i(
+          "NumberInCallHistoryWorker.submitTask",
+          "Need phone permissions: " + Arrays.toString(deniedPhonePermissions));
+      return;
+    }
+
+    NumberInCallHistoryWorker historyTask =
+        new NumberInCallHistoryWorker(context, number, GeoUtil.getCurrentCountryIso(context));
+    dialerExecutorFactory
+        .createNonUiTaskBuilder(historyTask)
+        .onSuccess((result) -> call.setCallHistoryStatus(result))
+        .build()
+        .executeParallel(null);
   }
 
   @Override
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
index 3e80a7f..9a781e6 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionService.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
-import android.os.StrictMode;
 import android.support.annotation.MainThread;
 import android.support.annotation.VisibleForTesting;
 import android.support.v4.os.BuildCompat;
@@ -32,6 +31,7 @@
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.constants.ScheduledJobIds;
+import com.android.dialer.strictmode.DialerStrictMode;
 import com.android.voicemail.impl.transcribe.grpc.TranscriptionClientFactory;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -48,7 +48,6 @@
   private JobParameters jobParameters;
   private TranscriptionClientFactory clientFactory;
   private TranscriptionConfigProvider configProvider;
-  private StrictMode.VmPolicy originalPolicy;
 
   /** Callback used by a task to indicate it has finished processing its work item */
   interface JobCallback {
@@ -111,8 +110,7 @@
       LogUtil.i(
           "TranscriptionService.onStartJob",
           "transcription server address: " + configProvider.getServerAddress());
-      originalPolicy = StrictMode.getVmPolicy();
-      StrictMode.enableDefaults();
+      DialerStrictMode.disableDeathPenalty(); // Re-enabled in cleanup.
       jobParameters = params;
       return checkForWork();
     }
@@ -142,10 +140,7 @@
       executorService.shutdownNow();
       executorService = null;
     }
-    if (originalPolicy != null) {
-      StrictMode.setVmPolicy(originalPolicy);
-      originalPolicy = null;
-    }
+    DialerStrictMode.enableDeathPenalty();
   }
 
   @MainThread