Merge changes I8cb0c6e7,I03258e86,I0b31bd93,I866708a6,I84f9e19b, ...

* changes:
  Implement change SIM animation
  Implement headless PreCall
  Merge the following methods in InCallActivityCommon into InCallActivity: 1. maybeShowErrorDialogOnDisconnect(DisconnectMessage), 2. onDialogDismissed(), 3. showErrorDialog(Dialog, CharSequence), 4. showInternationalCallOnWifiDialog(DialerCall), 5. showWifiFailedDialog(DialerCall), and 6. showWifiToLteHandoverToast(DialerCall).
  Added PhoneLookupDataSource and implemented isDirty.
  Send SUB_ID_EXTRA to VoicemailSettingsActivity in Telephony
  Add preferred SIM meta-data to dialer manifest
  Remove unused code InCallUiBings.getUiReadyBroadcastIntent().
  Move the following methods from InCallActivityCommon to InCallActivity: 1. enableInCallOrientationEventListener(boolean) 2. isDialpadVisible() 3. getDialpadFragment()
  Keyboard now closes in regular search when user scrolls list.
  Optimize contact search by using a Ternary Search Tree.
  Dialpad FAB no longer loses it's icon on fragment resume.
  Automated rollback
  Move InCallActivityCommon#dismissPendingDialogs to InCallActivity.
  Automated rollback
  Replace NUI voicemail media buttons with image buttons
  Notify content URI when preferred SIM is updated
  Use phone account ID for legacy voicemail notification.
  Move InCallActivityCommon#dismissKeyGuard to InCallActivity.
diff --git a/assets/quantum/res/drawable/quantum_ic_delete_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_delete_vd_theme_24.xml
new file mode 100644
index 0000000..900b559
--- /dev/null
+++ b/assets/quantum/res/drawable/quantum_ic_delete_vd_theme_24.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
+</vector>
\ No newline at end of file
diff --git a/assets/quantum/res/drawable/quantum_ic_play_arrow_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_play_arrow_vd_theme_24.xml
new file mode 100644
index 0000000..e17e625
--- /dev/null
+++ b/assets/quantum/res/drawable/quantum_ic_play_arrow_vd_theme_24.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M8,5v14l11,-7z"/>
+</vector>
\ No newline at end of file
diff --git a/assets/quantum/res/drawable/quantum_ic_volume_up_vd_theme_24.xml b/assets/quantum/res/drawable/quantum_ic_volume_up_vd_theme_24.xml
new file mode 100644
index 0000000..ac14bec
--- /dev/null
+++ b/assets/quantum/res/drawable/quantum_ic_volume_up_vd_theme_24.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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:width="24dp"
+    android:height="24dp"
+    android:tint="?attr/colorControlNormal"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
+</vector>
\ No newline at end of file
diff --git a/java/com/android/dialer/app/AndroidManifest.xml b/java/com/android/dialer/app/AndroidManifest.xml
index ad77149..12abaa6 100644
--- a/java/com/android/dialer/app/AndroidManifest.xml
+++ b/java/com/android/dialer/app/AndroidManifest.xml
@@ -140,5 +140,6 @@
         android:name="android.support.FILE_PROVIDER_PATHS"
         android:resource="@xml/file_paths"/>
     </provider>
+    <meta-data android:name="supports_per_number_preferred_account" android:value="true" />
   </application>
 </manifest>
diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java
index 269e598..d9a63fa 100644
--- a/java/com/android/dialer/app/DialtactsActivity.java
+++ b/java/com/android/dialer/app/DialtactsActivity.java
@@ -96,6 +96,7 @@
 import com.android.dialer.callintent.CallSpecificAppData;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.UiUtil;
 import com.android.dialer.common.concurrent.ThreadUtil;
 import com.android.dialer.configprovider.ConfigProviderBindings;
 import com.android.dialer.constants.ActivityRequestCodes;
@@ -178,6 +179,7 @@
   private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui";
   private static final String KEY_IN_NEW_SEARCH_UI = "in_new_search_ui";
   private static final String KEY_SEARCH_QUERY = "search_query";
+  private static final String KEY_DIALPAD_QUERY = "dialpad_query";
   private static final String KEY_FIRST_LAUNCH = "first_launch";
   private static final String KEY_WAS_CONFIGURATION_CHANGE = "was_configuration_change";
   private static final String KEY_IS_DIALPAD_SHOWN = "is_dialpad_shown";
@@ -439,6 +441,7 @@
           .commit();
     } else {
       mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY);
+      mDialpadQuery = savedInstanceState.getString(KEY_DIALPAD_QUERY);
       mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI);
       mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI);
       mInNewSearch = savedInstanceState.getBoolean(KEY_IN_NEW_SEARCH_UI);
@@ -654,6 +657,7 @@
     LogUtil.enterBlock("DialtactsActivity.onSaveInstanceState");
     super.onSaveInstanceState(outState);
     outState.putString(KEY_SEARCH_QUERY, mSearchQuery);
+    outState.putString(KEY_DIALPAD_QUERY, mDialpadQuery);
     outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch);
     outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch);
     outState.putBoolean(KEY_IN_NEW_SEARCH_UI, mInNewSearch);
@@ -1425,6 +1429,21 @@
   }
 
   @Override
+  public boolean onSearchListTouch(MotionEvent event) {
+    if (mIsDialpadShown) {
+      PerformanceReport.recordClick(UiAction.Type.CLOSE_DIALPAD);
+      hideDialpadFragment(true, false);
+      if (TextUtils.isEmpty(mDialpadQuery)) {
+        exitSearchUi();
+      }
+      return true;
+    } else {
+      UiUtil.hideKeyboardFrom(this, mSearchEditTextLayout);
+    }
+    return false;
+  }
+
+  @Override
   public void onListFragmentScrollStateChange(int scrollState) {
     PerformanceReport.recordScrollStateChange(scrollState);
     if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
@@ -1659,17 +1678,6 @@
   }
 
   @Override
-  public boolean onSearchListTouch(MotionEvent event) {
-    if (mIsDialpadShown) {
-      hideDialpadFragment(true, false);
-      if (TextUtils.isEmpty(mDialpadQuery)) {
-        exitSearchUi();
-      }
-    }
-    return false;
-  }
-
-  @Override
   public void onCallPlaced() {
     if (mIsDialpadShown) {
       hideDialpadFragment(false, true);
diff --git a/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java
index 584f07f..a0bbfa0 100644
--- a/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java
+++ b/java/com/android/dialer/app/calllog/LegacyVoicemailNotifier.java
@@ -40,6 +40,7 @@
 /** Shows a notification in the status bar for legacy vociemail. */
 @TargetApi(VERSION_CODES.O)
 public final class LegacyVoicemailNotifier {
+  private static final String NOTIFICATION_TAG_PREFIX = "LegacyVoicemail_";
   private static final String NOTIFICATION_TAG = "LegacyVoicemail";
   private static final int NOTIFICATION_ID = 1;
 
@@ -77,7 +78,8 @@
             callVoicemailIntent,
             voicemailSettingsIntent,
             isRefresh);
-    DialerNotificationManager.notify(context, NOTIFICATION_TAG, NOTIFICATION_ID, notification);
+    DialerNotificationManager.notify(
+        context, getNotificationTag(context, handle), NOTIFICATION_ID, notification);
   }
 
   @NonNull
@@ -146,10 +148,22 @@
     }
   }
 
-  public static void cancelNotification(@NonNull Context context) {
+  public static void cancelNotification(
+      @NonNull Context context, @NonNull PhoneAccountHandle phoneAccountHandle) {
     LogUtil.enterBlock("LegacyVoicemailNotifier.cancelNotification");
     Assert.checkArgument(BuildCompat.isAtLeastO());
-    DialerNotificationManager.cancel(context, NOTIFICATION_TAG, NOTIFICATION_ID);
+    Assert.isNotNull(phoneAccountHandle);
+    DialerNotificationManager.cancel(
+        context, getNotificationTag(context, phoneAccountHandle), NOTIFICATION_ID);
+  }
+
+  @NonNull
+  private static String getNotificationTag(
+      @NonNull Context context, @NonNull PhoneAccountHandle phoneAccountHandle) {
+    if (context.getSystemService(TelephonyManager.class).getPhoneCount() <= 1) {
+      return NOTIFICATION_TAG;
+    }
+    return NOTIFICATION_TAG_PREFIX + phoneAccountHandle.getId();
   }
 
   private LegacyVoicemailNotifier() {}
diff --git a/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java b/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java
index 3ce837b..fee8454 100644
--- a/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java
+++ b/java/com/android/dialer/app/voicemail/LegacyVoicemailNotificationReceiver.java
@@ -96,7 +96,7 @@
 
     if (count == 0) {
       LogUtil.i("LegacyVoicemailNotificationReceiver.onReceive", "clearing notification");
-      LegacyVoicemailNotifier.cancelNotification(context);
+      LegacyVoicemailNotifier.cancelNotification(context, phoneAccountHandle);
       return;
     }
 
diff --git a/java/com/android/dialer/calllog/CallLogModule.java b/java/com/android/dialer/calllog/CallLogModule.java
index 2f2f16d..9926ceb 100644
--- a/java/com/android/dialer/calllog/CallLogModule.java
+++ b/java/com/android/dialer/calllog/CallLogModule.java
@@ -19,12 +19,11 @@
 import com.android.dialer.calllog.datasources.CallLogDataSource;
 import com.android.dialer.calllog.datasources.DataSources;
 import com.android.dialer.calllog.datasources.contacts.ContactsDataSource;
+import com.android.dialer.calllog.datasources.phonelookup.PhoneLookupDataSource;
 import com.android.dialer.calllog.datasources.systemcalllog.SystemCallLogDataSource;
+import com.google.common.collect.ImmutableList;
 import dagger.Module;
 import dagger.Provides;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
 
 /** Dagger module which satisfies call log dependencies. */
 @Module
@@ -32,10 +31,12 @@
 
   @Provides
   static DataSources provideCallLogDataSources(
-      SystemCallLogDataSource systemCallLogDataSource, ContactsDataSource contactsDataSource) {
+      SystemCallLogDataSource systemCallLogDataSource,
+      ContactsDataSource contactsDataSource,
+      PhoneLookupDataSource phoneLookupDataSource) {
     // System call log must be first, see getDataSourcesExcludingSystemCallLog below.
-    List<CallLogDataSource> allDataSources =
-        Collections.unmodifiableList(Arrays.asList(systemCallLogDataSource, contactsDataSource));
+    ImmutableList<CallLogDataSource> allDataSources =
+        ImmutableList.of(systemCallLogDataSource, contactsDataSource, phoneLookupDataSource);
     return new DataSources() {
       @Override
       public SystemCallLogDataSource getSystemCallLogDataSource() {
@@ -43,12 +44,12 @@
       }
 
       @Override
-      public List<CallLogDataSource> getDataSourcesIncludingSystemCallLog() {
+      public ImmutableList<CallLogDataSource> getDataSourcesIncludingSystemCallLog() {
         return allDataSources;
       }
 
       @Override
-      public List<CallLogDataSource> getDataSourcesExcludingSystemCallLog() {
+      public ImmutableList<CallLogDataSource> getDataSourcesExcludingSystemCallLog() {
         return allDataSources.subList(1, allDataSources.size());
       }
     };
diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
index 3062710..0d8e8ce 100644
--- a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
+++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
@@ -89,6 +89,13 @@
           + AnnotatedCallLog.CALL_TYPE
           + ");";
 
+  private static final String CREATE_INDEX_ON_NUMBER_SQL =
+      "create index number_index on "
+          + AnnotatedCallLog.TABLE
+          + " ("
+          + AnnotatedCallLog.NUMBER
+          + ");";
+
   @Override
   public void onCreate(SQLiteDatabase db) {
     LogUtil.enterBlock("AnnotatedCallLogDatabaseHelper.onCreate");
@@ -96,6 +103,7 @@
     db.execSQL(CREATE_TABLE_SQL);
     db.execSQL(String.format(Locale.US, CREATE_TRIGGER_SQL, maxRows, maxRows));
     db.execSQL(CREATE_INDEX_ON_CALL_TYPE_SQL);
+    db.execSQL(CREATE_INDEX_ON_NUMBER_SQL);
     // TODO(zachh): Consider logging impression.
     LogUtil.i(
         "AnnotatedCallLogDatabaseHelper.onCreate",
diff --git a/java/com/android/dialer/calllog/datasources/DataSources.java b/java/com/android/dialer/calllog/datasources/DataSources.java
index 911ca3f..113a9f7 100644
--- a/java/com/android/dialer/calllog/datasources/DataSources.java
+++ b/java/com/android/dialer/calllog/datasources/DataSources.java
@@ -17,14 +17,14 @@
 package com.android.dialer.calllog.datasources;
 
 import com.android.dialer.calllog.datasources.systemcalllog.SystemCallLogDataSource;
-import java.util.List;
+import com.google.common.collect.ImmutableList;
 
 /** Immutable lists of data sources used to populate the annotated call log. */
 public interface DataSources {
 
   SystemCallLogDataSource getSystemCallLogDataSource();
 
-  List<CallLogDataSource> getDataSourcesIncludingSystemCallLog();
+  ImmutableList<CallLogDataSource> getDataSourcesIncludingSystemCallLog();
 
-  List<CallLogDataSource> getDataSourcesExcludingSystemCallLog();
+  ImmutableList<CallLogDataSource> getDataSourcesExcludingSystemCallLog();
 }
diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
new file mode 100644
index 0000000..90298a1
--- /dev/null
+++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
@@ -0,0 +1,139 @@
+/*
+ * 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.calllog.datasources.phonelookup;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.support.annotation.MainThread;
+import android.support.annotation.WorkerThread;
+import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
+import com.android.dialer.calllog.datasources.CallLogDataSource;
+import com.android.dialer.calllog.datasources.CallLogMutations;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.phonelookup.PhoneLookup;
+import com.android.dialer.storage.Unencrypted;
+import com.google.common.collect.ImmutableSet;
+import com.google.protobuf.InvalidProtocolBufferException;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import javax.inject.Inject;
+
+/**
+ * Responsible for maintaining the columns in the annotated call log which are derived from phone
+ * numbers.
+ */
+public final class PhoneLookupDataSource implements CallLogDataSource {
+  private static final String PREF_LAST_TIMESTAMP_PROCESSED = "phoneLookupLastTimestampProcessed";
+
+  private final PhoneLookup phoneLookup;
+  private final SharedPreferences sharedPreferences;
+
+  @Inject
+  PhoneLookupDataSource(PhoneLookup phoneLookup, @Unencrypted SharedPreferences sharedPreferences) {
+    this.phoneLookup = phoneLookup;
+    this.sharedPreferences = sharedPreferences;
+  }
+
+  @WorkerThread
+  @Override
+  public boolean isDirty(Context appContext) {
+    ImmutableSet<DialerPhoneNumber> uniqueDialerPhoneNumbers =
+        queryDistinctDialerPhoneNumbersFromAnnotatedCallLog(appContext);
+
+    long lastTimestampProcessedSharedPrefValue =
+        sharedPreferences.getLong(PREF_LAST_TIMESTAMP_PROCESSED, 0L);
+    try {
+      // TODO(zachh): Would be good to rework call log architecture to properly use futures.
+      // TODO(zachh): Consider how individual lookups should behave wrt timeouts/exceptions and
+      // handle appropriately here.
+      return phoneLookup
+          .isDirty(uniqueDialerPhoneNumbers, lastTimestampProcessedSharedPrefValue)
+          .get();
+    } catch (InterruptedException | ExecutionException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  @WorkerThread
+  @Override
+  public void fill(Context appContext, CallLogMutations mutations) {
+    // TODO(zachh): Implementation.
+  }
+
+  @WorkerThread
+  @Override
+  public void onSuccessfulFill(Context appContext) {
+    // TODO(zachh): Implementation.
+  }
+
+  @WorkerThread
+  @Override
+  public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) {
+    // TODO(zachh): Implementation.
+    return new ContentValues();
+  }
+
+  @MainThread
+  @Override
+  public void registerContentObservers(
+      Context appContext, ContentObserverCallbacks contentObserverCallbacks) {
+    // No content observers required for this data source.
+  }
+
+  private static ImmutableSet<DialerPhoneNumber>
+      queryDistinctDialerPhoneNumbersFromAnnotatedCallLog(Context appContext) {
+    ImmutableSet.Builder<DialerPhoneNumber> numbers = ImmutableSet.builder();
+
+    try (Cursor cursor =
+        appContext
+            .getContentResolver()
+            .query(
+                AnnotatedCallLog.DISTINCT_NUMBERS_CONTENT_URI,
+                new String[] {AnnotatedCallLog.NUMBER},
+                null,
+                null,
+                null)) {
+
+      if (cursor == null) {
+        LogUtil.e(
+            "PhoneLookupDataSource.queryDistinctDialerPhoneNumbersFromAnnotatedCallLog",
+            "null cursor");
+        return numbers.build();
+      }
+
+      if (cursor.moveToFirst()) {
+        int numberColumn = cursor.getColumnIndexOrThrow(AnnotatedCallLog.NUMBER);
+        do {
+          byte[] blob = cursor.getBlob(numberColumn);
+          if (blob == null) {
+            // Not all [incoming] calls have associated phone numbers.
+            continue;
+          }
+          try {
+            numbers.add(DialerPhoneNumber.parseFrom(blob));
+          } catch (InvalidProtocolBufferException e) {
+            throw new IllegalStateException(e);
+          }
+        } while (cursor.moveToNext());
+      }
+    }
+    return numbers.build();
+  }
+}
diff --git a/java/com/android/dialer/dialpadview/DialpadFragment.java b/java/com/android/dialer/dialpadview/DialpadFragment.java
index e85b57e..8d11bcb 100644
--- a/java/com/android/dialer/dialpadview/DialpadFragment.java
+++ b/java/com/android/dialer/dialpadview/DialpadFragment.java
@@ -643,7 +643,7 @@
       iconId = R.drawable.ic_wifi_calling;
     }
     mFloatingActionButtonController.changeIcon(
-        res.getDrawable(iconId, null), res.getString(R.string.description_dial_button));
+        iconId, res.getString(R.string.description_dial_button));
 
     mDialpadQueryListener =
         FragmentUtils.getParentUnsafe(this, OnDialpadQueryChangedListener.class);
diff --git a/java/com/android/dialer/dialpadview/res/layout/dialpad_fragment.xml b/java/com/android/dialer/dialpadview/res/layout/dialpad_fragment.xml
index 2f62e14..2e6b6ec 100644
--- a/java/com/android/dialer/dialpadview/res/layout/dialpad_fragment.xml
+++ b/java/com/android/dialer/dialpadview/res/layout/dialpad_fragment.xml
@@ -66,6 +66,7 @@
       android:layout_centerHorizontal="true"
       android:contentDescription="@string/description_dial_button"
       android:src="@drawable/quantum_ic_call_vd_theme_24"
+      android:tint="#ffffff"
       app:backgroundTint="@color/dialpad_fab_green"
       app:colorControlNormal="#ffffff"
       app:elevation="@dimen/floating_action_button_translation_z"/>
diff --git a/java/com/android/dialer/phonelookup/testing/FakePhoneLookup.java b/java/com/android/dialer/phonelookup/testing/FakePhoneLookup.java
new file mode 100644
index 0000000..853116f
--- /dev/null
+++ b/java/com/android/dialer/phonelookup/testing/FakePhoneLookup.java
@@ -0,0 +1,83 @@
+/*
+ * 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.phonelookup.testing;
+
+import android.support.annotation.NonNull;
+import android.telecom.Call;
+import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.phonelookup.PhoneLookup;
+import com.android.dialer.phonelookup.PhoneLookupInfo;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+
+/** Fake implementation of {@link PhoneLookup} used for unit tests. */
+@AutoValue
+public abstract class FakePhoneLookup implements PhoneLookup {
+
+  abstract PhoneLookupInfo lookupResult();
+
+  abstract ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> bulkUpdateResult();
+
+  abstract boolean isDirtyResult();
+
+  public static Builder builder() {
+    return new AutoValue_FakePhoneLookup.Builder()
+        .setLookupResult(PhoneLookupInfo.getDefaultInstance())
+        .setBulkUpdateResult(ImmutableMap.of())
+        .setIsDirtyResult(false);
+  }
+
+  /** Builder. */
+  @AutoValue.Builder
+  public abstract static class Builder {
+
+    public abstract Builder setLookupResult(PhoneLookupInfo phoneLookupInfo);
+
+    public abstract Builder setBulkUpdateResult(
+        ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> map);
+
+    public abstract Builder setIsDirtyResult(boolean isDirty);
+
+    public abstract FakePhoneLookup build();
+  }
+
+  @Override
+  public ListenableFuture<PhoneLookupInfo> lookup(@NonNull Call call) {
+    SettableFuture<PhoneLookupInfo> future = SettableFuture.create();
+    future.set(lookupResult());
+    return future;
+  }
+
+  @Override
+  public ListenableFuture<Boolean> isDirty(
+      ImmutableSet<DialerPhoneNumber> phoneNumbers, long lastModified) {
+    SettableFuture<Boolean> future = SettableFuture.create();
+    future.set(isDirtyResult());
+    return future;
+  }
+
+  @Override
+  public ListenableFuture<ImmutableMap<DialerPhoneNumber, PhoneLookupInfo>> bulkUpdate(
+      ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> existingInfoMap, long lastModified) {
+    SettableFuture<ImmutableMap<DialerPhoneNumber, PhoneLookupInfo>> future =
+        SettableFuture.create();
+    future.set(bulkUpdateResult());
+    return future;
+  }
+}
diff --git a/java/com/android/dialer/precall/PreCallAction.java b/java/com/android/dialer/precall/PreCallAction.java
index 9434694..9ddc6f2 100644
--- a/java/com/android/dialer/precall/PreCallAction.java
+++ b/java/com/android/dialer/precall/PreCallAction.java
@@ -16,6 +16,7 @@
 
 package com.android.dialer.precall;
 
+import android.content.Context;
 import android.support.annotation.MainThread;
 import com.android.dialer.callintent.CallIntentBuilder;
 
@@ -28,12 +29,29 @@
 public interface PreCallAction {
 
   /**
+   * Whether the action requires an activity to operate. This method is called on all actions before
+   * {@link #runWithUi(PreCallCoordinator)} is called. If {@link true} is returned, {@link
+   * #runWithUi(PreCallCoordinator)} will be guaranteed to be called on the execution phase.
+   * Otherwise {@link #runWithoutUi(Context, CallIntentBuilder)} may be called instead and the
+   * action will not be able to show UI, perform async task, or abort the call. This method should
+   * not make any state changes.
+   */
+  @MainThread
+  boolean requiresUi(Context context, CallIntentBuilder builder);
+
+  /**
+   * Called when all actions returned {@code false} for {@link #requiresUi(Context,
+   * CallIntentBuilder)}.
+   */
+  void runWithoutUi(Context context, CallIntentBuilder builder);
+
+  /**
    * Runs the action. Should block on the main thread until the action is finished. If the action is
    * not instantaneous, {@link PreCallCoordinator#startPendingAction()} should be called to release
    * the thread and continue later.
    */
   @MainThread
-  void run(PreCallCoordinator coordinator);
+  void runWithUi(PreCallCoordinator coordinator);
 
   /**
    * Called when the UI is being paused when a {@link PreCallCoordinator.PendingAction} is started,
diff --git a/java/com/android/dialer/precall/PreCallCoordinator.java b/java/com/android/dialer/precall/PreCallCoordinator.java
index 40b909a..cb3221a 100644
--- a/java/com/android/dialer/precall/PreCallCoordinator.java
+++ b/java/com/android/dialer/precall/PreCallCoordinator.java
@@ -33,10 +33,7 @@
   @NonNull
   CallIntentBuilder getBuilder();
 
-  /**
-   * @return the activity to attach the UI to. Returns {@link null} if the coordinator is running on
-   *     headless mode. TODO(twyen): implement headless mode.
-   */
+  /** @return the activity to attach the UI to. */
   @NonNull
   Activity getActivity();
 
@@ -60,10 +57,10 @@
    * Called by the current running {@link PreCallAction} to release the main thread and resume
    * pre-call later.
    *
-   * @return a {@link PendingAction} which {@link PendingAction#finish(boolean)} should be called to
-   *     resume pre-call. For example the action shows a dialog to the user, startPendingAction()
-   *     should be called as the action will not be finished immediately. When the dialog is
-   *     completed, {@code finish()} is then called to continue the next step.
+   * @return a {@link PendingAction} which {@link PendingAction#finish()} should be called to resume
+   *     pre-call. For example the action shows a dialog to the user, startPendingAction() should be
+   *     called as the action will not be finished immediately. When the dialog is completed, {@code
+   *     finish()} is then called to continue the next step.
    */
   @MainThread
   @NonNull
diff --git a/java/com/android/dialer/precall/impl/AssistedDialAction.java b/java/com/android/dialer/precall/impl/AssistedDialAction.java
index edf97cc..c4f61d2 100644
--- a/java/com/android/dialer/precall/impl/AssistedDialAction.java
+++ b/java/com/android/dialer/precall/impl/AssistedDialAction.java
@@ -17,6 +17,7 @@
 package com.android.dialer.precall.impl;
 
 import android.annotation.TargetApi;
+import android.content.Context;
 import android.os.Build;
 import android.os.Bundle;
 import android.telecom.PhoneAccount;
@@ -35,18 +36,21 @@
 /** Rewrites the call URI with country code. TODO(erfanian): use phone account for multi SIM */
 public class AssistedDialAction implements PreCallAction {
 
+  @Override
+  public boolean requiresUi(Context context, CallIntentBuilder builder) {
+    return false;
+  }
+
   @SuppressWarnings("AndroidApiChecker") // Use of optional
   @TargetApi(Build.VERSION_CODES.N)
   @Override
-  public void run(PreCallCoordinator coordinator) {
-    CallIntentBuilder builder = coordinator.getBuilder();
+  public void runWithoutUi(Context context, CallIntentBuilder builder) {
     if (!builder.isAssistedDialAllowed()) {
       return;
     }
     AssistedDialingMediator assistedDialingMediator =
         ConcreteCreator.createNewAssistedDialingMediator(
-            coordinator.getActivity().getSystemService(TelephonyManager.class),
-            coordinator.getActivity());
+            context.getSystemService(TelephonyManager.class), context);
     if (!assistedDialingMediator.isPlatformEligible()) {
       return;
     }
@@ -69,5 +73,10 @@
   }
 
   @Override
+  public void runWithUi(PreCallCoordinator coordinator) {
+    runWithoutUi(coordinator.getActivity(), coordinator.getBuilder());
+  }
+
+  @Override
   public void onDiscard() {}
 }
diff --git a/java/com/android/dialer/precall/impl/CallingAccountSelector.java b/java/com/android/dialer/precall/impl/CallingAccountSelector.java
index ca74bef..d763c7a 100644
--- a/java/com/android/dialer/precall/impl/CallingAccountSelector.java
+++ b/java/com/android/dialer/precall/impl/CallingAccountSelector.java
@@ -59,16 +59,27 @@
   private boolean isDiscarding;
 
   @Override
-  @MainThread
-  public void run(PreCallCoordinator coordinator) {
-    CallIntentBuilder builder = coordinator.getBuilder();
+  public boolean requiresUi(Context context, CallIntentBuilder builder) {
     if (builder.getPhoneAccountHandle() != null) {
-      return;
+      return false;
     }
-    Activity activity = coordinator.getActivity();
-    TelecomManager telecomManager = activity.getSystemService(TelecomManager.class);
+    TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
     List<PhoneAccountHandle> accounts = telecomManager.getCallCapablePhoneAccounts();
     if (accounts.size() <= 1) {
+      return false;
+    }
+    return true;
+  }
+
+  @Override
+  public void runWithoutUi(Context context, CallIntentBuilder builder) {
+    // do nothing.
+  }
+
+  @Override
+  public void runWithUi(PreCallCoordinator coordinator) {
+    CallIntentBuilder builder = coordinator.getBuilder();
+    if (!requiresUi(coordinator.getActivity(), builder)) {
       return;
     }
     switch (builder.getUri().getScheme()) {
diff --git a/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java b/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java
index 6302a23..485823e 100644
--- a/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java
+++ b/java/com/android/dialer/precall/impl/PreCallCoordinatorImpl.java
@@ -93,7 +93,7 @@
     }
     LogUtil.i("PreCallCoordinatorImpl.runNextAction", "running " + actions.get(currentActionIndex));
     currentAction = actions.get(currentActionIndex);
-    actions.get(currentActionIndex).run(this);
+    actions.get(currentActionIndex).runWithUi(this);
     if (pendingAction == null) {
       onActionFinished();
     }
diff --git a/java/com/android/dialer/precall/impl/PreCallImpl.java b/java/com/android/dialer/precall/impl/PreCallImpl.java
index 21c5dc9..1c78bb8 100644
--- a/java/com/android/dialer/precall/impl/PreCallImpl.java
+++ b/java/com/android/dialer/precall/impl/PreCallImpl.java
@@ -20,8 +20,10 @@
 import android.content.Intent;
 import android.support.annotation.NonNull;
 import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.common.LogUtil;
 import com.android.dialer.precall.PreCall;
 import com.android.dialer.precall.PreCallAction;
+import com.android.dialer.precall.PreCallComponent;
 import com.android.dialer.precall.PreCallCoordinator;
 import com.google.common.collect.ImmutableList;
 import javax.inject.Inject;
@@ -40,8 +42,26 @@
   @NonNull
   @Override
   public Intent buildIntent(Context context, CallIntentBuilder builder) {
+    if (!requiresUi(context, builder)) {
+      LogUtil.i("PreCallImpl.buildIntent", "No UI requested, running pre-call directly");
+      for (PreCallAction action : PreCallComponent.get(context).getPreCall().getActions()) {
+        action.runWithoutUi(context, builder);
+      }
+      return builder.build();
+    }
+    LogUtil.i("PreCallImpl.buildIntent", "building intent to start activity");
     Intent intent = new Intent(context, PreCallActivity.class);
     intent.putExtra(PreCallCoordinator.EXTRA_CALL_INTENT_BUILDER, builder);
     return intent;
   }
+
+  private boolean requiresUi(Context context, CallIntentBuilder builder) {
+    for (PreCallAction action : PreCallComponent.get(context).getPreCall().getActions()) {
+      if (action.requiresUi(context, builder)) {
+        LogUtil.i("PreCallImpl.requiresUi", action + " requested UI");
+        return true;
+      }
+    }
+    return false;
+  }
 }
diff --git a/java/com/android/dialer/preferredsim/impl/PreferredSimFallbackProvider.java b/java/com/android/dialer/preferredsim/impl/PreferredSimFallbackProvider.java
index 322baa2..1b10765 100644
--- a/java/com/android/dialer/preferredsim/impl/PreferredSimFallbackProvider.java
+++ b/java/com/android/dialer/preferredsim/impl/PreferredSimFallbackProvider.java
@@ -131,6 +131,7 @@
         == -1) {
       throw new IllegalStateException("update failed");
     }
+    getContext().getContentResolver().notifyChange(PreferredSimFallbackContract.CONTENT_URI, null);
     return 1;
   }
 
diff --git a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java
index 1ecb486..c4ff275 100644
--- a/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java
+++ b/java/com/android/dialer/searchfragment/common/QueryFilteringUtil.java
@@ -137,7 +137,7 @@
    * @param context The context
    * @return The original string with characters replaced with T9 representations.
    */
-  static String getT9Representation(String s, Context context) {
+  public static String getT9Representation(String s, Context context) {
     StringBuilder builder = new StringBuilder(s.length());
     for (char c : s.toLowerCase().toCharArray()) {
       builder.append(getDigit(c, context));
diff --git a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java
index 166902b..dc16f9d 100644
--- a/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java
+++ b/java/com/android/dialer/searchfragment/cp2/ContactFilterCursor.java
@@ -39,6 +39,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 
 /**
@@ -52,6 +53,7 @@
   private final Cursor cursor;
   // List of cursor ids that are valid for displaying after filtering.
   private final List<Integer> queryFilteredPositions = new ArrayList<>();
+  private final ContactTernarySearchTree contactTree;
 
   private int currentPosition = 0;
 
@@ -77,6 +79,7 @@
    */
   ContactFilterCursor(Cursor cursor, @Nullable String query, Context context) {
     this.cursor = createCursor(cursor);
+    contactTree = buildContactSearchTree(context, this.cursor);
     filter(query, context);
   }
 
@@ -225,6 +228,69 @@
   }
 
   /**
+   * Returns a ternary search trie based on the contact at the cursor's current position with the
+   * following terms inserted:
+   *
+   * <ul>
+   *   <li>Contact's whole display name, company name and nickname.
+   *   <li>The T9 representations of those values
+   *   <li>The T9 initials of those values
+   *   <li>All possible substrings a contact's phone number
+   * </ul>
+   */
+  private static ContactTernarySearchTree buildContactSearchTree(Context context, Cursor cursor) {
+    ContactTernarySearchTree tree = new ContactTernarySearchTree();
+    cursor.moveToPosition(-1);
+    while (cursor.moveToNext()) {
+      int position = cursor.getPosition();
+      Set<String> queryMatches = new ArraySet<>();
+      addMatches(context, queryMatches, cursor.getString(Projections.DISPLAY_NAME));
+      addMatches(context, queryMatches, cursor.getString(Projections.COMPANY_NAME));
+      addMatches(context, queryMatches, cursor.getString(Projections.NICKNAME));
+      for (String query : queryMatches) {
+        tree.put(query, position);
+      }
+      String number = QueryFilteringUtil.digitsOnly(cursor.getString(Projections.PHONE_NUMBER));
+      Set<String> numberSubstrings = new ArraySet<>();
+      numberSubstrings.add(number);
+      for (int start = 0; start < number.length(); start++) {
+        numberSubstrings.add(number.substring(start, number.length()));
+      }
+      for (String substring : numberSubstrings) {
+        tree.put(substring, position);
+      }
+    }
+    return tree;
+  }
+
+  /**
+   * Returns a set containing:
+   *
+   * <ul>
+   *   <li>The white space divided parts of phrase
+   *   <li>The T9 representation of the white space divided parts of phrase
+   *   <li>The T9 representation of the initials (i.e. first character of each part) of phrase
+   * </ul>
+   */
+  private static void addMatches(Context context, Set<String> existingMatches, String phrase) {
+    if (TextUtils.isEmpty(phrase)) {
+      return;
+    }
+    String initials = "";
+    phrase = phrase.toLowerCase(Locale.getDefault());
+    existingMatches.add(phrase);
+    for (String name : phrase.split("\\s")) {
+      if (TextUtils.isEmpty(name)) {
+        continue;
+      }
+      existingMatches.add(name);
+      existingMatches.add(QueryFilteringUtil.getT9Representation(name, context));
+      initials += name.charAt(0);
+    }
+    existingMatches.add(QueryFilteringUtil.getT9Representation(initials, context));
+  }
+
+  /**
    * Filters out contacts that do not match the query.
    *
    * <p>The query can have at least 1 of 3 forms:
@@ -249,24 +315,14 @@
       query = "";
     }
     queryFilteredPositions.clear();
-    query = query.toLowerCase();
-    cursor.moveToPosition(-1);
-
-    while (cursor.moveToNext()) {
-      int position = cursor.getPosition();
-      String number = cursor.getString(Projections.PHONE_NUMBER);
-      String name = cursor.getString(Projections.DISPLAY_NAME);
-      String companyName = cursor.getString(Projections.COMPANY_NAME);
-      String nickName = cursor.getString(Projections.NICKNAME);
-      if (TextUtils.isEmpty(query)
-          || QueryFilteringUtil.nameMatchesT9Query(query, name, context)
-          || QueryFilteringUtil.numberMatchesNumberQuery(query, number)
-          || QueryFilteringUtil.nameContainsQuery(query, name)
-          || QueryFilteringUtil.nameContainsQuery(query, companyName)
-          || QueryFilteringUtil.nameContainsQuery(query, nickName)) {
-        queryFilteredPositions.add(position);
+    if (TextUtils.isEmpty(query)) {
+      for (int i = 0; i < cursor.getCount(); i++) {
+        queryFilteredPositions.add(i);
       }
+    } else {
+      queryFilteredPositions.addAll(contactTree.get(query.toLowerCase(Locale.getDefault())));
     }
+    Collections.sort(queryFilteredPositions);
     currentPosition = 0;
     cursor.moveToFirst();
   }
diff --git a/java/com/android/dialer/searchfragment/cp2/ContactTernarySearchTree.java b/java/com/android/dialer/searchfragment/cp2/ContactTernarySearchTree.java
new file mode 100644
index 0000000..88738e2
--- /dev/null
+++ b/java/com/android/dialer/searchfragment/cp2/ContactTernarySearchTree.java
@@ -0,0 +1,97 @@
+/*
+ * 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.searchfragment.cp2;
+
+import android.support.v4.util.ArraySet;
+import android.text.TextUtils;
+import java.util.Set;
+
+/** Ternary Search Tree for searching a list of contacts. */
+public class ContactTernarySearchTree {
+
+  private Node root;
+
+  /**
+   * Add {@code value} to all middle and end {@link Node#values} that correspond to {@code key}.
+   *
+   * <p>For example, if {@code key} were "FOO", {@code value} would be added to nodes "F", "O" and
+   * "O". But if the traversal required visiting {@link Node#left} or {@link Node#right}, {@code
+   * value} wouldn't be added to those nodes.
+   */
+  public void put(String key, int value) {
+    if (TextUtils.isEmpty(key)) {
+      return;
+    }
+    root = put(root, key, value, 0);
+  }
+
+  private Node put(Node node, String key, int value, int position) {
+    char c = key.charAt(position);
+    if (node == null) {
+      node = new Node();
+      node.key = c;
+    }
+    if (c < node.key) {
+      node.left = put(node.left, key, value, position);
+    } else if (c > node.key) {
+      node.right = put(node.right, key, value, position);
+    } else if (position < key.length() - 1) {
+      node.values.add(value);
+      node.mid = put(node.mid, key, value, position + 1);
+    } else {
+      node.values.add(value);
+    }
+    return node;
+  }
+
+  /** Returns true if {@code key} is contained in the trie. */
+  public boolean contains(String key) {
+    return !get(key).isEmpty();
+  }
+
+  /** Return value stored at Node (in this case, a set of integers). */
+  public Set<Integer> get(String key) {
+    Node x = get(root, key, 0);
+    return x == null ? new ArraySet<>() : x.values;
+  }
+
+  private Node get(Node node, String key, int position) {
+    if (node == null) {
+      return null;
+    }
+    char c = key.charAt(position);
+    if (c < node.key) {
+      return get(node.left, key, position);
+    } else if (c > node.key) {
+      return get(node.right, key, position);
+    } else if (position < key.length() - 1) {
+      return get(node.mid, key, position + 1);
+    } else {
+      return node;
+    }
+  }
+
+  /** Node in ternary search trie. Children are denoted as left, middle and right nodes. */
+  private static class Node {
+    private char key;
+    private final Set<Integer> values = new ArraySet<>();
+
+    private Node left;
+    private Node mid;
+    private Node right;
+  }
+}
diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
index 8306d37..93263ce 100644
--- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
+++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
@@ -32,6 +32,7 @@
 import android.support.v13.app.FragmentCompat;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
+import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -444,7 +445,9 @@
    * the list of supported actions, see {@link SearchActionViewHolder.Action}.
    */
   private List<Integer> getActions() {
-    if (TextUtils.isEmpty(query) || query.length() == 1 || isRegularSearch()) {
+    boolean nonDialableQueryInRegularSearch =
+        isRegularSearch() && !PhoneNumberUtils.isGlobalPhoneNumber(query);
+    if (TextUtils.isEmpty(query) || query.length() == 1 || nonDialableQueryInRegularSearch) {
       return Collections.emptyList();
     }
 
diff --git a/java/com/android/dialer/telecom/TelecomUtil.java b/java/com/android/dialer/telecom/TelecomUtil.java
index 3bf9b46..c79d901 100644
--- a/java/com/android/dialer/telecom/TelecomUtil.java
+++ b/java/com/android/dialer/telecom/TelecomUtil.java
@@ -33,9 +33,12 @@
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.util.Pair;
 import com.android.dialer.common.LogUtil;
+import com.google.common.base.Optional;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -146,6 +149,24 @@
   }
 
   /**
+   * @return the {@link SubscriptionInfo} of the SIM if {@code phoneAccountHandle} corresponds to a
+   *     valid SIM. Absent otherwise.
+   */
+  public static Optional<SubscriptionInfo> getSubscriptionInfo(
+      @NonNull Context context, @NonNull PhoneAccountHandle phoneAccountHandle) {
+    if (TextUtils.isEmpty(phoneAccountHandle.getId())) {
+      return Optional.absent();
+    }
+    SubscriptionManager subscriptionManager = context.getSystemService(SubscriptionManager.class);
+    for (SubscriptionInfo info : subscriptionManager.getActiveSubscriptionInfoList()) {
+      if (phoneAccountHandle.getId().startsWith(info.getIccId())) {
+        return Optional.of(info);
+      }
+    }
+    return Optional.absent();
+  }
+
+  /**
    * Returns true if there is a dialer managed call in progress. Self managed calls starting from O
    * are not included.
    */
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
index 4629ce2..d5db608 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
@@ -30,7 +30,7 @@
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.Button;
+import android.widget.ImageButton;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 import com.android.dialer.common.Assert;
@@ -45,9 +45,10 @@
  */
 public class NewVoicemailMediaPlayerView extends LinearLayout {
 
-  private Button playButton;
-  private Button speakerButton;
-  private Button deleteButton;
+  private ImageButton playButton;
+  private ImageButton speakerButton;
+  private ImageButton phoneButton;
+  private ImageButton deleteButton;
   private TextView totalDurationView;
   private Uri voicemailUri;
   private FragmentManager fragmentManager;
@@ -72,6 +73,7 @@
   private void initializeMediaPlayerButtonsAndViews() {
     playButton = findViewById(R.id.playButton);
     speakerButton = findViewById(R.id.speakerButton);
+    phoneButton = findViewById(R.id.phoneButton);
     deleteButton = findViewById(R.id.deleteButton);
     totalDurationView = findViewById(R.id.playback_seek_total_duration);
   }
@@ -79,6 +81,7 @@
   private void setupListenersForMediaPlayerButtons() {
     playButton.setOnClickListener(playButtonListener);
     speakerButton.setOnClickListener(speakerButtonListener);
+    phoneButton.setOnClickListener(phoneButtonListener);
     deleteButton.setOnClickListener(deleteButtonListener);
   }
 
@@ -164,6 +167,17 @@
         }
       };
 
+  private final View.OnClickListener phoneButtonListener =
+      new View.OnClickListener() {
+        @Override
+        public void onClick(View view) {
+          LogUtil.i(
+              "NewVoicemailMediaPlayer.phoneButtonListener",
+              "speaker request for voicemailUri: %s",
+              voicemailUri.toString());
+        }
+      };
+
   private final View.OnClickListener deleteButtonListener =
       new View.OnClickListener() {
         @Override
diff --git a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml
index e8e5600..07ce86a 100644
--- a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml
+++ b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml
@@ -67,22 +67,36 @@
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:paddingTop="10dp"
-      android:orientation="horizontal">
-    <!-- TODO(a bug): Remove these buttons as this is a place holder for the Media Player -->
-    <Button
+      android:gravity="center"
+      android:orientation="horizontal"
+      android:weightSum="4">
+
+
+    <ImageButton
         android:id="@+id/playButton"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Play"/>
-    <Button
+        style="@style/voicemail_media_player_buttons"
+        android:layout_weight="1"
+        android:src="@drawable/quantum_ic_play_arrow_vd_theme_24"/>
+
+
+    <ImageButton
         android:id="@+id/speakerButton"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Speaker"/>
-    <Button
+        style="@style/voicemail_media_player_buttons"
+        android:layout_weight="1"
+        android:src="@drawable/quantum_ic_volume_up_vd_theme_24"/>
+
+
+    <ImageButton
+        android:id="@+id/phoneButton"
+        style="@style/voicemail_media_player_buttons"
+        android:layout_weight="1"
+        android:src="@drawable/quantum_ic_phone_vd_theme_24"/>
+
+    <ImageButton
         android:id="@+id/deleteButton"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Delete"/>
+        style="@style/voicemail_media_player_buttons"
+        android:layout_weight="1"
+        android:src="@drawable/quantum_ic_delete_vd_theme_24"/>
+
   </LinearLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/java/com/android/dialer/voicemail/listui/res/values/dimens.xml b/java/com/android/dialer/voicemail/listui/res/values/dimens.xml
index 6c062ae..e37bc65 100644
--- a/java/com/android/dialer/voicemail/listui/res/values/dimens.xml
+++ b/java/com/android/dialer/voicemail/listui/res/values/dimens.xml
@@ -29,6 +29,11 @@
   <dimen name="voicemail_icon_size">16dp</dimen>
 
   <dimen name="voicemail_playback_state_text_size">14sp</dimen>
+  <!-- TODO(uabdullah): Work with UX on this value to ensure proper spacing between
+  the seekbar and transcription -->
   <dimen name="voicemail_media_player_padding_top">20dp</dimen>
   <dimen name="voicemail_duration_size">14sp</dimen>
+  <!-- TODO(uabdullah): Work with UX on these values so that the touch target is not too small -->
+  <dimen name="voicemail_media_player_height">56dp</dimen>
+  <dimen name="voicemail_media_player_width">0dp</dimen>
 </resources>
diff --git a/java/com/android/dialer/voicemail/listui/res/values/styles.xml b/java/com/android/dialer/voicemail/listui/res/values/styles.xml
new file mode 100644
index 0000000..aec4609
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/res/values/styles.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<resources>
+
+  <style name="voicemail_media_player_buttons">
+    <item name="android:layout_width">@dimen/voicemail_media_player_width</item>
+    <item name="android:layout_height">@dimen/voicemail_media_player_height</item>
+    <item name="android:background">?android:attr/selectableItemBackgroundBorderless</item>
+  </style>
+</resources>
\ No newline at end of file
diff --git a/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java b/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java
index 2b496c0..aaa1e15 100644
--- a/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java
+++ b/java/com/android/dialer/voicemail/settings/VoicemailSettingsFragment.java
@@ -24,16 +24,22 @@
 import android.preference.SwitchPreference;
 import android.provider.Settings;
 import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyManager;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.logging.DialerImpression;
 import com.android.dialer.logging.Logger;
 import com.android.dialer.notification.NotificationChannelManager;
+import com.android.dialer.telecom.TelecomUtil;
 import com.android.voicemail.VoicemailClient;
 import com.android.voicemail.VoicemailClient.ActivationStateListener;
 import com.android.voicemail.VoicemailComponent;
+import com.google.common.base.Optional;
 
 /**
  * Fragment for voicemail settings. Requires {@link VoicemailClient#PARAM_PHONE_ACCOUNT_HANDLE} set
@@ -45,6 +51,16 @@
 
   private static final String TAG = "VmSettingsActivity";
 
+  // Extras copied from com.android.phone.settings.VoicemailSettingsActivity,
+  // it does not recognize EXTRA_PHONE_ACCOUNT_HANDLE in O.
+  @VisibleForTesting
+  static final String SUB_ID_EXTRA =
+      "com.android.phone.settings.SubscriptionInfoHelper.SubscriptionId";
+  // Extra on intent containing the label of a subscription.
+  @VisibleForTesting
+  static final String SUB_LABEL_EXTRA =
+      "com.android.phone.settings.SubscriptionInfoHelper.SubscriptionLabel";
+
   @Nullable private PhoneAccountHandle phoneAccountHandle;
 
   private VoicemailClient voicemailClient;
@@ -167,6 +183,19 @@
     advancedSettingsIntent.putExtra(TelephonyManager.EXTRA_HIDE_PUBLIC_SETTINGS, true);
     advancedSettingsIntent.putExtra(
         TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+
+    // (a bug): EXTRA_PHONE_ACCOUNT_HANDLE not implemented in telephony in O.
+    Optional<SubscriptionInfo> subscriptionInfo =
+        TelecomUtil.getSubscriptionInfo(getContext(), phoneAccountHandle);
+    if (subscriptionInfo.isPresent()) {
+      advancedSettingsIntent.putExtra(SUB_ID_EXTRA, subscriptionInfo.get().getSubscriptionId());
+      PhoneAccount phoneAccount =
+          getContext().getSystemService(TelecomManager.class).getPhoneAccount(phoneAccountHandle);
+      if (phoneAccount != null) {
+        advancedSettingsIntent.putExtra(SUB_LABEL_EXTRA, phoneAccount.getLabel());
+      }
+    }
+
     advancedSettings.setIntent(advancedSettingsIntent);
     voicemailChangePinPreference.setOnPreferenceClickListener(
         new OnPreferenceClickListener() {
diff --git a/java/com/android/dialer/widget/FloatingActionButtonController.java b/java/com/android/dialer/widget/FloatingActionButtonController.java
index a0c4e6d..dde4d44 100644
--- a/java/com/android/dialer/widget/FloatingActionButtonController.java
+++ b/java/com/android/dialer/widget/FloatingActionButtonController.java
@@ -18,7 +18,7 @@
 
 import android.app.Activity;
 import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
 import android.support.design.widget.FloatingActionButton;
 import android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener;
 import android.view.View;
@@ -39,6 +39,7 @@
   private final int mFloatingActionButtonMarginRight;
   private final FloatingActionButton mFab;
   private final Interpolator mFabInterpolator;
+  private int mFabIconId = -1;
   private int mScreenWidth;
 
   public FloatingActionButtonController(Activity activity, FloatingActionButton fab) {
@@ -82,9 +83,12 @@
     }
   }
 
-  public void changeIcon(Drawable icon, String description) {
-    if (mFab.getDrawable() != icon || !mFab.getContentDescription().equals(description)) {
-      mFab.setImageDrawable(icon);
+  public void changeIcon(@DrawableRes int iconId, String description) {
+    if (this.mFabIconId != iconId) {
+      mFab.setImageResource(iconId);
+      this.mFabIconId = iconId;
+    }
+    if (!mFab.getContentDescription().equals(description)) {
       mFab.setContentDescription(description);
     }
   }
diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java
index cdab6b4..ed10ed0 100644
--- a/java/com/android/incallui/InCallActivity.java
+++ b/java/com/android/incallui/InCallActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.incallui;
 
+import android.app.AlertDialog;
+import android.app.Dialog;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.GradientDrawable;
@@ -34,6 +36,10 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+import android.widget.Toast;
+import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.ThreadUtil;
@@ -59,6 +65,8 @@
 import com.android.incallui.incall.protocol.InCallScreen;
 import com.android.incallui.incall.protocol.InCallScreenDelegate;
 import com.android.incallui.incall.protocol.InCallScreenDelegateFactory;
+import com.android.incallui.incalluilock.InCallUiLock;
+import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
 import com.android.incallui.video.bindings.VideoBindings;
 import com.android.incallui.video.protocol.VideoCallScreen;
 import com.android.incallui.video.protocol.VideoCallScreenDelegate;
@@ -76,8 +84,10 @@
   public static final int PENDING_INTENT_REQUEST_CODE_FULL_SCREEN = 1;
   public static final int PENDING_INTENT_REQUEST_CODE_BUBBLE = 2;
 
-  private static final String TAG_IN_CALL_SCREEN = "tag_in_call_screen";
   private static final String TAG_ANSWER_SCREEN = "tag_answer_screen";
+  private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
+  private static final String TAG_INTERNATIONAL_CALL_ON_WIFI = "tag_international_call_on_wifi";
+  private static final String TAG_IN_CALL_SCREEN = "tag_in_call_screen";
   private static final String TAG_VIDEO_CALL_SCREEN = "tag_video_call_screen";
 
   private static final String DID_SHOW_ANSWER_SCREEN_KEY = "did_show_answer_screen";
@@ -87,9 +97,11 @@
   private static final String CONFIG_ANSWER_AND_RELEASE_ENABLED = "answer_and_release_enabled";
 
   private final InCallActivityCommon common;
+  private InCallOrientationEventListener inCallOrientationEventListener;
   private boolean didShowAnswerScreen;
   private boolean didShowInCallScreen;
   private boolean didShowVideoCallScreen;
+  private boolean dismissKeyguard;
   private int[] backgroundDrawableColors;
   private GradientDrawable backgroundDrawable;
   private boolean isVisible;
@@ -138,6 +150,7 @@
     }
 
     common.onCreate(icicle);
+    inCallOrientationEventListener = new InCallOrientationEventListener(this);
 
     getWindow()
         .getDecorView()
@@ -340,7 +353,21 @@
   }
 
   public boolean isDialpadVisible() {
-    return common.isDialpadVisible();
+    DialpadFragment dialpadFragment = getDialpadFragment();
+    return dialpadFragment != null && dialpadFragment.isVisible();
+  }
+
+  /**
+   * Returns the {@link DialpadFragment} that's shown by this activity, or {@code null}
+   * TODO(a bug): Make this method private after InCallActivityCommon is deleted.
+   */
+  @Nullable
+  DialpadFragment getDialpadFragment() {
+    FragmentManager fragmentManager = getDialpadFragmentManager();
+    if (fragmentManager == null) {
+      return null;
+    }
+    return (DialpadFragment) fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
   }
 
   public void onForegroundCallChanged(DialerCall newForegroundCall) {
@@ -419,37 +446,109 @@
   }
 
   public void dismissKeyguard(boolean dismiss) {
-    common.dismissKeyguard(dismiss);
+    if (dismissKeyguard == dismiss) {
+      return;
+    }
+
+    dismissKeyguard = dismiss;
+    if (dismiss) {
+      getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+    } else {
+      getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+    }
   }
 
   public void showPostCharWaitDialog(String callId, String chars) {
     common.showPostCharWaitDialog(callId, chars);
   }
 
-  public void maybeShowErrorDialogOnDisconnect(DisconnectMessage disconnectMessage) {
-    common.maybeShowErrorDialogOnDisconnect(disconnectMessage);
+  public void showDialogOrToastForDisconnectedCall(DisconnectMessage disconnectMessage) {
+    LogUtil.i(
+        "InCallActivity.showDialogOrToastForDisconnectedCall",
+        "disconnect cause: %s",
+        disconnectMessage);
+
+    if (disconnectMessage.dialog == null || isFinishing()) {
+      return;
+    }
+
+    dismissPendingDialogs();
+
+    // Show a toast if the app is in background when a dialog can't be visible.
+    if (!isVisible()) {
+      Toast.makeText(getApplicationContext(), disconnectMessage.toastMessage, Toast.LENGTH_LONG)
+          .show();
+      return;
+    }
+
+    // Show the dialog.
+    common.setErrorDialog(disconnectMessage.dialog);
+    InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog");
+    disconnectMessage.dialog.setOnDismissListener(
+        dialogInterface -> {
+          lock.release();
+          onDialogDismissed();
+        });
+    disconnectMessage.dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+    disconnectMessage.dialog.show();
+  }
+
+  private void onDialogDismissed() {
+    common.setErrorDialog(null);
+    CallList.getInstance().onErrorDialogDismissed();
   }
 
   public void dismissPendingDialogs() {
-    if (isVisible) {
-      LogUtil.i("InCallActivity.dismissPendingDialogs", "");
-      common.dismissPendingDialogs();
-      AnswerScreen answerScreen = getAnswerScreen();
-      if (answerScreen != null) {
-        answerScreen.dismissPendingDialogs();
-      }
-      needDismissPendingDialogs = false;
-    } else {
-      // The activity is not visible and onSaveInstanceState may have been called so defer the
-      // dismissing action.
+    LogUtil.i("InCallActivity.dismissPendingDialogs", "");
+
+    if (!isVisible) {
+      // Defer the dismissing action as the activity is not visible and onSaveInstanceState may have
+      // been called.
       LogUtil.i(
           "InCallActivity.dismissPendingDialogs", "defer actions since activity is not visible");
       needDismissPendingDialogs = true;
+      return;
     }
+
+    // Dismiss the error dialog
+    Dialog errorDialog = common.getErrorDialog();
+    if (errorDialog != null) {
+      errorDialog.dismiss();
+      common.setErrorDialog(null);
+    }
+
+    // Dismiss the phone account selection dialog
+    SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment =
+        common.getSelectPhoneAccountDialogFragment();
+    if (selectPhoneAccountDialogFragment != null) {
+      selectPhoneAccountDialogFragment.dismiss();
+      common.setSelectPhoneAccountDialogFragment(null);
+    }
+
+    // Dismiss the dialog for international call on WiFi
+    InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
+        (InternationalCallOnWifiDialogFragment)
+            getSupportFragmentManager().findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
+    if (internationalCallOnWifiFragment != null) {
+      internationalCallOnWifiFragment.dismiss();
+    }
+
+    // Dismiss the answer screen
+    AnswerScreen answerScreen = getAnswerScreen();
+    if (answerScreen != null) {
+      answerScreen.dismissPendingDialogs();
+    }
+
+    needDismissPendingDialogs = false;
   }
 
-  private void enableInCallOrientationEventListener(boolean enable) {
-    common.enableInCallOrientationEventListener(enable);
+  // TODO(a bug): Make this method private after InCallActivityCommon is deleted.
+  void enableInCallOrientationEventListener(boolean enable) {
+    if (enable) {
+      inCallOrientationEventListener.enable(true /* notifyDeviceOrientationChange */);
+    } else {
+      inCallOrientationEventListener.disable();
+    }
   }
 
   public void setExcludeFromRecents(boolean exclude) {
@@ -515,17 +614,67 @@
     Trace.endSection();
   }
 
-  public void onWiFiToLteHandover(DialerCall call) {
-    common.showWifiToLteHandoverToast(call);
+  public void showToastForWiFiToLteHandover(DialerCall call) {
+    if (call.hasShownWiFiToLteHandoverToast()) {
+      return;
+    }
+
+    Toast.makeText(this, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG).show();
+    call.setHasShownWiFiToLteHandoverToast();
   }
 
-  public void onHandoverToWifiFailed(DialerCall call) {
-    common.showWifiFailedDialog(call);
+  public void showDialogOrToastForWifiHandoverFailure(DialerCall call) {
+    if (call.showWifiHandoverAlertAsToast()) {
+      Toast.makeText(this, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
+          .show();
+      return;
+    }
+
+    dismissPendingDialogs();
+
+    AlertDialog.Builder builder =
+        new AlertDialog.Builder(this).setTitle(R.string.video_call_lte_to_wifi_failed_title);
+
+    // This allows us to use the theme of the dialog instead of the activity
+    View dialogCheckBoxView =
+        View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null /* root */);
+    CheckBox wifiHandoverFailureCheckbox =
+        (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
+    wifiHandoverFailureCheckbox.setChecked(false);
+
+    InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog");
+    Dialog errorDialog =
+        builder
+            .setView(dialogCheckBoxView)
+            .setMessage(R.string.video_call_lte_to_wifi_failed_message)
+            .setOnCancelListener(dialogInterface -> onDialogDismissed())
+            .setPositiveButton(
+                android.R.string.ok,
+                (dialogInterface, id) -> {
+                  call.setDoNotShowDialogForHandoffToWifiFailure(
+                      wifiHandoverFailureCheckbox.isChecked());
+                  dialogInterface.cancel();
+                  onDialogDismissed();
+                })
+            .setOnDismissListener(dialogInterface -> lock.release())
+            .create();
+
+    common.setErrorDialog(errorDialog);
+    errorDialog.show();
   }
 
-  public void onInternationalCallOnWifi(@NonNull DialerCall call) {
-    LogUtil.enterBlock("InCallActivity.onInternationalCallOnWifi");
-    common.showInternationalCallOnWifiDialog(call);
+  public void showDialogForInternationalCallOnWifi(@NonNull DialerCall call) {
+    if (!InternationalCallOnWifiDialogFragment.shouldShow(this)) {
+      LogUtil.i(
+          "InCallActivity.showDialogForInternationalCallOnWifi",
+          "InternationalCallOnWifiDialogFragment.shouldShow returned false");
+      return;
+    }
+
+    InternationalCallOnWifiDialogFragment fragment =
+        InternationalCallOnWifiDialogFragment.newInstance(
+            call.getId(), common.getCallbackForInternationalCallOnWifiDialog());
+    fragment.show(getSupportFragmentManager(), TAG_INTERNATIONAL_CALL_ON_WIFI);
   }
 
   @Override
diff --git a/java/com/android/incallui/InCallActivityCommon.java b/java/com/android/incallui/InCallActivityCommon.java
index 5a5d770..8f82295 100644
--- a/java/com/android/incallui/InCallActivityCommon.java
+++ b/java/com/android/incallui/InCallActivityCommon.java
@@ -19,12 +19,8 @@
 import android.app.ActivityManager;
 import android.app.ActivityManager.AppTask;
 import android.app.ActivityManager.TaskDescription;
-import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.KeyguardManager;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnDismissListener;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -46,8 +42,6 @@
 import android.view.WindowManager;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
-import android.widget.CheckBox;
-import android.widget.Toast;
 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
 import com.android.dialer.animation.AnimUtils;
@@ -63,8 +57,6 @@
 import com.android.incallui.call.DialerCall;
 import com.android.incallui.call.DialerCall.State;
 import com.android.incallui.call.TelecomAdapter;
-import com.android.incallui.disconnectdialog.DisconnectMessage;
-import com.android.incallui.incalluilock.InCallUiLock;
 import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
 import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment.Callback;
 import com.google.common.base.Optional;
@@ -102,13 +94,11 @@
   private static Optional<Integer> audioRouteForTesting = Optional.absent();
 
   private final InCallActivity inCallActivity;
-  private boolean dismissKeyguard;
   private boolean showPostCharWaitDialogOnResume;
   private String showPostCharWaitDialogCallId;
   private String showPostCharWaitDialogChars;
-  private Dialog dialog;
+  private Dialog errorDialog;
   private SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment;
-  private InCallOrientationEventListener inCallOrientationEventListener;
   private Animation dialpadSlideInAnimation;
   private Animation dialpadSlideOutAnimation;
   private boolean animateDialpadOnShow;
@@ -243,14 +233,12 @@
           "InCallActivityCommon.onCreate", "international fragment exists attaching callback");
       existingInternationalFragment.setCallback(internationalCallOnWifiCallback);
     }
-
-    inCallOrientationEventListener = new InCallOrientationEventListener(inCallActivity);
   }
 
   public void onSaveInstanceState(Bundle out) {
     // TODO: The dialpad fragment should handle this as part of its own state
-    out.putBoolean(INTENT_EXTRA_SHOW_DIALPAD, isDialpadVisible());
-    DialpadFragment dialpadFragment = getDialpadFragment();
+    out.putBoolean(INTENT_EXTRA_SHOW_DIALPAD, inCallActivity.isDialpadVisible());
+    DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
     if (dialpadFragment != null) {
       out.putString(DIALPAD_TEXT_KEY, dialpadFragment.getDtmfText());
     }
@@ -260,14 +248,11 @@
     Trace.beginSection("InCallActivityCommon.onStart");
     // setting activity should be last thing in setup process
     InCallPresenter.getInstance().setActivity(inCallActivity);
-    enableInCallOrientationEventListener(
+    inCallActivity.enableInCallOrientationEventListener(
         inCallActivity.getRequestedOrientation()
             == InCallOrientationEventListener.ACTIVITY_PREFERENCE_ALLOW_ROTATION);
 
     InCallPresenter.getInstance().onActivityStarted();
-    if (!isRecreating) {
-      InCallPresenter.getInstance().onUiShowing(true);
-    }
     Trace.endSection();
   }
 
@@ -279,6 +264,7 @@
           "InCallPresenter is ready for tear down, not sending updates");
     } else {
       updateTaskDescription();
+      InCallPresenter.getInstance().onUiShowing(true);
     }
 
     // If there is a pending request to show or hide the dialpad, handle that now.
@@ -291,20 +277,20 @@
         inCallActivity.showDialpadFragment(true /* show */, animateDialpadOnShow /* animate */);
         animateDialpadOnShow = false;
 
-        DialpadFragment dialpadFragment = getDialpadFragment();
+        DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
         if (dialpadFragment != null) {
           dialpadFragment.setDtmfText(dtmfTextToPreopulate);
           dtmfTextToPreopulate = null;
         }
       } else {
         LogUtil.i("InCallActivityCommon.onResume", "force hide dialpad");
-        if (getDialpadFragment() != null) {
+        if (inCallActivity.getDialpadFragment() != null) {
           inCallActivity.showDialpadFragment(false /* show */, false /* animate */);
         }
       }
       showDialpadRequest = DIALPAD_REQUEST_NONE;
     }
-    updateNavigationBar(isDialpadVisible());
+    updateNavigationBar(inCallActivity.isDialpadVisible());
 
     if (showPostCharWaitDialogOnResume) {
       showPostCharWaitDialog(showPostCharWaitDialogCallId, showPostCharWaitDialogChars);
@@ -319,10 +305,15 @@
   // onPause is guaranteed to be called when the InCallActivity goes
   // in the background.
   public void onPause() {
-    DialpadFragment dialpadFragment = getDialpadFragment();
+    DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
     if (dialpadFragment != null) {
       dialpadFragment.onDialerKeyUp(null);
     }
+
+    InCallPresenter.getInstance().onUiShowing(false);
+    if (inCallActivity.isFinishing()) {
+      InCallPresenter.getInstance().unsetActivity(inCallActivity);
+    }
   }
 
   public void onStop() {
@@ -338,18 +329,14 @@
       }
     }
 
-    enableInCallOrientationEventListener(false);
+    inCallActivity.enableInCallOrientationEventListener(false);
     InCallPresenter.getInstance().updateIsChangingConfigurations();
     InCallPresenter.getInstance().onActivityStopped();
     if (!isRecreating) {
-      InCallPresenter.getInstance().onUiShowing(false);
-      if (dialog != null) {
-        dialog.dismiss();
+      if (errorDialog != null) {
+        errorDialog.dismiss();
       }
     }
-    if (inCallActivity.isFinishing()) {
-      InCallPresenter.getInstance().unsetActivity(inCallActivity);
-    }
   }
 
   public void onDestroy() {
@@ -394,7 +381,7 @@
       return true;
     }
 
-    DialpadFragment dialpadFragment = getDialpadFragment();
+    DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
     if (dialpadFragment != null && dialpadFragment.isVisible()) {
       inCallActivity.showDialpadFragment(false /* show */, true /* animate */);
       return true;
@@ -412,7 +399,7 @@
   }
 
   public boolean onKeyUp(int keyCode, KeyEvent event) {
-    DialpadFragment dialpadFragment = getDialpadFragment();
+    DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
     // push input to the dialer.
     if (dialpadFragment != null
         && (dialpadFragment.isVisible())
@@ -517,7 +504,7 @@
     // As soon as the user starts typing valid dialable keys on the
     // keyboard (presumably to type DTMF tones) we start passing the
     // key events to the DTMFDialer's onDialerKeyDown.
-    DialpadFragment dialpadFragment = getDialpadFragment();
+    DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
     if (dialpadFragment != null && dialpadFragment.isVisible()) {
       return dialpadFragment.onDialerKeyDown(event);
     }
@@ -525,18 +512,6 @@
     return false;
   }
 
-  public void dismissKeyguard(boolean dismiss) {
-    if (dismissKeyguard == dismiss) {
-      return;
-    }
-    dismissKeyguard = dismiss;
-    if (dismiss) {
-      inCallActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
-    } else {
-      inCallActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
-    }
-  }
-
   public void showPostCharWaitDialog(String callId, String chars) {
     if (inCallActivity.isVisible()) {
       PostCharDialogFragment fragment = new PostCharDialogFragment(callId, chars);
@@ -552,19 +527,6 @@
     }
   }
 
-  public void maybeShowErrorDialogOnDisconnect(DisconnectMessage disconnectMessage) {
-    LogUtil.i(
-        "InCallActivityCommon.maybeShowErrorDialogOnDisconnect",
-        "disconnect cause: %s",
-        disconnectMessage);
-
-    if (!inCallActivity.isFinishing()) {
-      if (disconnectMessage.dialog != null) {
-        showErrorDialog(disconnectMessage.dialog, disconnectMessage.toastMessage);
-      }
-    }
-  }
-
   /**
    * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad should
    * be shown on launch.
@@ -586,67 +548,6 @@
     }
   }
 
-  void dismissPendingDialogs() {
-    if (dialog != null) {
-      dialog.dismiss();
-      dialog = null;
-    }
-    if (selectPhoneAccountDialogFragment != null) {
-      selectPhoneAccountDialogFragment.dismiss();
-      selectPhoneAccountDialogFragment = null;
-    }
-
-    InternationalCallOnWifiDialogFragment internationalCallOnWifiFragment =
-        (InternationalCallOnWifiDialogFragment)
-            inCallActivity
-                .getSupportFragmentManager()
-                .findFragmentByTag(TAG_INTERNATIONAL_CALL_ON_WIFI);
-    if (internationalCallOnWifiFragment != null) {
-      LogUtil.i(
-          "InCallActivityCommon.dismissPendingDialogs",
-          "dismissing InternationalCallOnWifiDialogFragment");
-      internationalCallOnWifiFragment.dismiss();
-    }
-  }
-
-  private void showErrorDialog(Dialog dialog, CharSequence message) {
-    LogUtil.i("InCallActivityCommon.showErrorDialog", "message: %s", message);
-    inCallActivity.dismissPendingDialogs();
-
-    // Show toast if apps is in background when dialog won't be visible.
-    if (!inCallActivity.isVisible()) {
-      Toast.makeText(inCallActivity.getApplicationContext(), message, Toast.LENGTH_LONG).show();
-      return;
-    }
-
-    this.dialog = dialog;
-    InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog");
-    dialog.setOnDismissListener(
-        new OnDismissListener() {
-          @Override
-          public void onDismiss(DialogInterface dialog) {
-            LogUtil.i("InCallActivityCommon.showErrorDialog", "dialog dismissed");
-            lock.release();
-            onDialogDismissed();
-          }
-        });
-    dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
-    dialog.show();
-  }
-
-  private void onDialogDismissed() {
-    dialog = null;
-    CallList.getInstance().onErrorDialogDismissed();
-  }
-
-  public void enableInCallOrientationEventListener(boolean enable) {
-    if (enable) {
-      inCallOrientationEventListener.enable(true);
-    } else {
-      inCallOrientationEventListener.disable();
-    }
-  }
-
   public void setExcludeFromRecents(boolean exclude) {
     List<AppTask> tasks = inCallActivity.getSystemService(ActivityManager.class).getAppTasks();
     int taskId = inCallActivity.getTaskId();
@@ -665,83 +566,6 @@
     }
   }
 
-  void showInternationalCallOnWifiDialog(@NonNull DialerCall call) {
-    LogUtil.enterBlock("InCallActivityCommon.showInternationalCallOnWifiDialog");
-    if (!InternationalCallOnWifiDialogFragment.shouldShow(inCallActivity)) {
-      LogUtil.i(
-          "InCallActivityCommon.showInternationalCallOnWifiDialog",
-          "InternationalCallOnWifiDialogFragment.shouldShow returned false");
-      return;
-    }
-
-    InternationalCallOnWifiDialogFragment fragment =
-        InternationalCallOnWifiDialogFragment.newInstance(
-            call.getId(), internationalCallOnWifiCallback);
-    fragment.show(inCallActivity.getSupportFragmentManager(), TAG_INTERNATIONAL_CALL_ON_WIFI);
-  }
-
-  public void showWifiToLteHandoverToast(DialerCall call) {
-    if (call.hasShownWiFiToLteHandoverToast()) {
-      return;
-    }
-    Toast.makeText(
-            inCallActivity, R.string.video_call_wifi_to_lte_handover_toast, Toast.LENGTH_LONG)
-        .show();
-    call.setHasShownWiFiToLteHandoverToast();
-  }
-
-  public void showWifiFailedDialog(final DialerCall call) {
-    if (call.showWifiHandoverAlertAsToast()) {
-      LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as toast");
-      Toast.makeText(
-              inCallActivity, R.string.video_call_lte_to_wifi_failed_message, Toast.LENGTH_SHORT)
-          .show();
-      return;
-    }
-
-    dismissPendingDialogs();
-
-    AlertDialog.Builder builder =
-        new AlertDialog.Builder(inCallActivity)
-            .setTitle(R.string.video_call_lte_to_wifi_failed_title);
-
-    // This allows us to use the theme of the dialog instead of the activity
-    View dialogCheckBoxView =
-        View.inflate(builder.getContext(), R.layout.video_call_lte_to_wifi_failed, null);
-    final CheckBox wifiHandoverFailureCheckbox =
-        (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
-    wifiHandoverFailureCheckbox.setChecked(false);
-
-    InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog");
-    dialog =
-        builder
-            .setView(dialogCheckBoxView)
-            .setMessage(R.string.video_call_lte_to_wifi_failed_message)
-            .setOnCancelListener(
-                new OnCancelListener() {
-                  @Override
-                  public void onCancel(DialogInterface dialog) {
-                    onDialogDismissed();
-                  }
-                })
-            .setPositiveButton(
-                android.R.string.ok,
-                new DialogInterface.OnClickListener() {
-                  @Override
-                  public void onClick(DialogInterface dialog, int id) {
-                    call.setDoNotShowDialogForHandoffToWifiFailure(
-                        wifiHandoverFailureCheckbox.isChecked());
-                    dialog.cancel();
-                    onDialogDismissed();
-                  }
-                })
-            .setOnDismissListener((dialog) -> lock.release())
-            .create();
-
-    LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as dialog");
-    dialog.show();
-  }
-
   void updateNavigationBar(boolean isDialpadVisible) {
     if (!ActivityCompat.isInMultiWindowMode(inCallActivity)) {
       View navigationBarBackground =
@@ -754,7 +578,7 @@
 
   public boolean showDialpadFragment(boolean show, boolean animate) {
     // If the dialpad is already visible, don't animate in. If it's gone, don't animate out.
-    boolean isDialpadVisible = isDialpadVisible();
+    boolean isDialpadVisible = inCallActivity.isDialpadVisible();
     LogUtil.i(
         "InCallActivityCommon.showDialpadFragment",
         "show: %b, animate: %b, " + "isDialpadVisible: %b",
@@ -783,9 +607,10 @@
     } else {
       if (show) {
         performShowDialpadFragment(dialpadFragmentManager);
-        getDialpadFragment().animateShowDialpad();
+        inCallActivity.getDialpadFragment().animateShowDialpad();
       }
-      getDialpadFragment()
+      inCallActivity
+          .getDialpadFragment()
           .getView()
           .startAnimation(show ? dialpadSlideInAnimation : dialpadSlideOutAnimation);
     }
@@ -800,7 +625,7 @@
 
   private void performShowDialpadFragment(@NonNull FragmentManager dialpadFragmentManager) {
     FragmentTransaction transaction = dialpadFragmentManager.beginTransaction();
-    DialpadFragment dialpadFragment = getDialpadFragment();
+    DialpadFragment dialpadFragment = inCallActivity.getDialpadFragment();
     if (dialpadFragment == null) {
       transaction.add(
           inCallActivity.getDialpadContainerId(), new DialpadFragment(), TAG_DIALPAD_FRAGMENT);
@@ -833,21 +658,6 @@
     updateNavigationBar(false /* isDialpadVisible */);
   }
 
-  public boolean isDialpadVisible() {
-    DialpadFragment dialpadFragment = getDialpadFragment();
-    return dialpadFragment != null && dialpadFragment.isVisible();
-  }
-
-  /** Returns the {@link DialpadFragment} that's shown by this activity, or {@code null} */
-  @Nullable
-  private DialpadFragment getDialpadFragment() {
-    FragmentManager fragmentManager = inCallActivity.getDialpadFragmentManager();
-    if (fragmentManager == null) {
-      return null;
-    }
-    return (DialpadFragment) fragmentManager.findFragmentByTag(TAG_DIALPAD_FRAGMENT);
-  }
-
   public void updateTaskDescription() {
     Resources resources = inCallActivity.getResources();
     int color;
@@ -864,10 +674,6 @@
     inCallActivity.setTaskDescription(td);
   }
 
-  public boolean hasPendingDialogs() {
-    return dialog != null;
-  }
-
   private void internalResolveIntent(Intent intent) {
     if (!intent.getAction().equals(Intent.ACTION_MAIN)) {
       return;
@@ -902,7 +708,7 @@
         outgoingCall.disconnect();
       }
 
-      dismissKeyguard(true);
+      inCallActivity.dismissKeyguard(true);
     }
 
     boolean didShowAccountSelectionDialog = maybeShowAccountSelectionDialog();
@@ -937,4 +743,37 @@
         inCallActivity.getFragmentManager(), TAG_SELECT_ACCOUNT_FRAGMENT);
     return true;
   }
+
+  /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
+  @Deprecated
+  @Nullable
+  Dialog getErrorDialog() {
+    return errorDialog;
+  }
+
+  /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
+  @Deprecated
+  void setErrorDialog(@Nullable Dialog errorDialog) {
+    this.errorDialog = errorDialog;
+  }
+
+  /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
+  @Deprecated
+  @Nullable
+  SelectPhoneAccountDialogFragment getSelectPhoneAccountDialogFragment() {
+    return selectPhoneAccountDialogFragment;
+  }
+
+  /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
+  @Deprecated
+  void setSelectPhoneAccountDialogFragment(
+      @Nullable SelectPhoneAccountDialogFragment selectPhoneAccountDialogFragment) {
+    this.selectPhoneAccountDialogFragment = selectPhoneAccountDialogFragment;
+  }
+
+  /** @deprecated Only for temporary use during the deprecation of {@link InCallActivityCommon} */
+  @Deprecated
+  InternationalCallOnWifiDialogFragment.Callback getCallbackForInternationalCallOnWifiDialog() {
+    return internationalCallOnWifiCallback;
+  }
 }
diff --git a/java/com/android/incallui/InCallOrientationEventListener.java b/java/com/android/incallui/InCallOrientationEventListener.java
index e6b0bc0..8aae6fb 100644
--- a/java/com/android/incallui/InCallOrientationEventListener.java
+++ b/java/com/android/incallui/InCallOrientationEventListener.java
@@ -126,12 +126,13 @@
   }
 
   /**
-   * Enables the OrientationEventListener and notifies listeners of current orientation if notify
-   * flag is true
+   * Enables the OrientationEventListener and optionally notifies listeners of the current
+   * orientation.
    *
-   * @param notify true or false. Notify device orientation changed if true.
+   * @param notifyDeviceOrientationChange Whether to notify listeners that the device orientation is
+   *     changed.
    */
-  public void enable(boolean notify) {
+  public void enable(boolean notifyDeviceOrientationChange) {
     if (mEnabled) {
       Log.v(this, "enable: Orientation listener is already enabled. Ignoring...");
       return;
@@ -139,15 +140,15 @@
 
     super.enable();
     mEnabled = true;
-    if (notify) {
+    if (notifyDeviceOrientationChange) {
       InCallPresenter.getInstance().onDeviceOrientationChange(sCurrentOrientation);
     }
   }
 
-  /** Enables the OrientationEventListener with notify flag defaulting to false. */
+  /** Enables the OrientationEventListener. */
   @Override
   public void enable() {
-    enable(false);
+    enable(false /* notifyDeviceOrientationChange */);
   }
 
   /** Disables the OrientationEventListener. */
diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java
index 6c1c130..3ba2ccf 100644
--- a/java/com/android/incallui/InCallPresenter.java
+++ b/java/com/android/incallui/InCallPresenter.java
@@ -88,8 +88,6 @@
 public class InCallPresenter implements CallList.Listener, AudioModeProvider.AudioModeListener {
   private static final String PIXEL2017_SYSTEM_FEATURE =
       "com.google.android.feature.PIXEL_2017_EXPERIENCE";
-  private static final String EXTRA_FIRST_TIME_SHOWN =
-      "com.android.incallui.intent.extra.FIRST_TIME_SHOWN";
 
   private static final long BLOCK_QUERY_TIMEOUT_MS = 1000;
 
@@ -215,14 +213,7 @@
           }
         }
       };
-  /**
-   * Is true when the activity has been previously started. Some code needs to know not just if the
-   * activity is currently up, but if it had been previously shown in foreground for this in-call
-   * session (e.g., StatusBarNotifier). This gets reset when the session ends in the tear-down
-   * method.
-   */
-  private boolean mIsActivityPreviouslyStarted = false;
-
+  
   /** Whether or not InCallService is bound to Telecom. */
   private boolean mServiceBound = false;
 
@@ -476,7 +467,7 @@
       // By the time the UI finally comes up, the call may already be disconnected.
       // If that's the case, we may need to show an error dialog.
       if (mCallList != null && mCallList.getDisconnectedCall() != null) {
-        maybeShowErrorDialogOnDisconnect(mCallList.getDisconnectedCall());
+        showDialogOrToastForDisconnectedCall(mCallList.getDisconnectedCall());
       }
 
       // When the UI comes up, we need to first check the in-call state.
@@ -690,14 +681,14 @@
   @Override
   public void onWiFiToLteHandover(DialerCall call) {
     if (mInCallActivity != null) {
-      mInCallActivity.onWiFiToLteHandover(call);
+      mInCallActivity.showToastForWiFiToLteHandover(call);
     }
   }
 
   @Override
   public void onHandoverToWifiFailed(DialerCall call) {
     if (mInCallActivity != null) {
-      mInCallActivity.onHandoverToWifiFailed(call);
+      mInCallActivity.showDialogOrToastForWifiHandoverFailure(call);
     }
   }
 
@@ -705,7 +696,7 @@
   public void onInternationalCallOnWifi(@NonNull DialerCall call) {
     LogUtil.enterBlock("InCallPresenter.onInternationalCallOnWifi");
     if (mInCallActivity != null) {
-      mInCallActivity.onInternationalCallOnWifi(call);
+      mInCallActivity.showDialogForInternationalCallOnWifi(call);
     }
   }
 
@@ -841,7 +832,7 @@
    */
   @Override
   public void onDisconnect(DialerCall call) {
-    maybeShowErrorDialogOnDisconnect(call);
+    showDialogOrToastForDisconnectedCall(call);
 
     // We need to do the run the same code as onCallListChange.
     onCallListChange(mCallList);
@@ -1052,22 +1043,7 @@
       mProximitySensor.onInCallShowing(showing);
     }
 
-    Intent broadcastIntent = Bindings.get(mContext).getUiReadyBroadcastIntent(mContext);
-    if (broadcastIntent != null) {
-      broadcastIntent.putExtra(EXTRA_FIRST_TIME_SHOWN, !mIsActivityPreviouslyStarted);
-
-      if (showing) {
-        LogUtil.d("InCallPresenter.onUiShowing", "Sending sticky broadcast: ", broadcastIntent);
-        mContext.sendStickyBroadcast(broadcastIntent);
-      } else {
-        LogUtil.d("InCallPresenter.onUiShowing", "Removing sticky broadcast: ", broadcastIntent);
-        mContext.removeStickyBroadcast(broadcastIntent);
-      }
-    }
-
-    if (showing) {
-      mIsActivityPreviouslyStarted = true;
-    } else {
+    if (!showing) {
       updateIsChangingConfigurations();
     }
 
@@ -1265,19 +1241,19 @@
     }
   }
 
-  /**
-   * For some disconnected causes, we show a dialog. This calls into the activity to show the dialog
-   * if appropriate for the call.
-   */
-  private void maybeShowErrorDialogOnDisconnect(DialerCall call) {
-    // For newly disconnected calls, we may want to show a dialog on specific error conditions
-    if (isActivityStarted() && call.getState() == DialerCall.State.DISCONNECTED) {
-      if (call.getAccountHandle() == null && !call.isConferenceCall()) {
-        setDisconnectCauseForMissingAccounts(call);
-      }
-      mInCallActivity.maybeShowErrorDialogOnDisconnect(
-          new DisconnectMessage(mInCallActivity, call));
+  /** Instruct the in-call activity to show an error dialog or toast for a disconnected call. */
+  private void showDialogOrToastForDisconnectedCall(DialerCall call) {
+    if (!isActivityStarted() || call.getState() != DialerCall.State.DISCONNECTED) {
+      return;
     }
+
+    // For newly disconnected calls, we may want to show a dialog on specific error conditions
+    if (call.getAccountHandle() == null && !call.isConferenceCall()) {
+      setDisconnectCauseForMissingAccounts(call);
+    }
+
+    mInCallActivity.showDialogOrToastForDisconnectedCall(
+        new DisconnectMessage(mInCallActivity, call));
   }
 
   /**
@@ -1449,7 +1425,6 @@
 
       cleanupSurfaces();
 
-      mIsActivityPreviouslyStarted = false;
       mIsChangingConfigurations = false;
 
       // blow away stale contact info so that we get fresh data on
diff --git a/java/com/android/incallui/bindings/InCallUiBindings.java b/java/com/android/incallui/bindings/InCallUiBindings.java
index 5c6aef4..c15b68d 100644
--- a/java/com/android/incallui/bindings/InCallUiBindings.java
+++ b/java/com/android/incallui/bindings/InCallUiBindings.java
@@ -26,10 +26,6 @@
   @Nullable
   PhoneNumberService newPhoneNumberService(Context context);
 
-  /** @return An {@link Intent} to be broadcast when the InCallUI is visible. */
-  @Nullable
-  Intent getUiReadyBroadcastIntent(Context context);
-
   /**
    * @return An {@link Intent} to be broadcast when the call state button in the InCallUI is touched
    *     while in a call.
diff --git a/java/com/android/incallui/bindings/InCallUiBindingsStub.java b/java/com/android/incallui/bindings/InCallUiBindingsStub.java
index 3a005b0..3a9e1dc 100644
--- a/java/com/android/incallui/bindings/InCallUiBindingsStub.java
+++ b/java/com/android/incallui/bindings/InCallUiBindingsStub.java
@@ -31,12 +31,6 @@
 
   @Override
   @Nullable
-  public Intent getUiReadyBroadcastIntent(Context context) {
-    return null;
-  }
-
-  @Override
-  @Nullable
   public Intent getCallStateButtonBroadcastIntent(Context context) {
     return null;
   }
diff --git a/java/com/android/incallui/incall/impl/ButtonController.java b/java/com/android/incallui/incall/impl/ButtonController.java
index cefbd72..5e37a49 100644
--- a/java/com/android/incallui/incall/impl/ButtonController.java
+++ b/java/com/android/incallui/incall/impl/ButtonController.java
@@ -16,6 +16,7 @@
 
 package com.android.incallui.incall.impl;
 
+import android.graphics.drawable.AnimationDrawable;
 import android.support.annotation.CallSuper;
 import android.support.annotation.DrawableRes;
 import android.support.annotation.NonNull;
@@ -569,11 +570,14 @@
           InCallButtonIds.BUTTON_SWAP_SIM,
           R.string.incall_content_description_swap_sim,
           R.string.incall_label_swap_sim,
-          R.drawable.quantum_ic_swap_calls_white_36);
+          R.drawable.ic_sim_change_white);
     }
 
     @Override
     public void onClick(View view) {
+      AnimationDrawable drawable = (AnimationDrawable) button.getIconDrawable();
+      drawable.stop(); // animation is one shot, stop it so it can be started again.
+      drawable.start();
       delegate.swapSimClicked();
     }
   }
diff --git a/java/com/android/incallui/incall/impl/CheckableLabeledButton.java b/java/com/android/incallui/incall/impl/CheckableLabeledButton.java
index 325c3a9..ca018ac 100644
--- a/java/com/android/incallui/incall/impl/CheckableLabeledButton.java
+++ b/java/com/android/incallui/incall/impl/CheckableLabeledButton.java
@@ -47,6 +47,7 @@
   private boolean isChecked;
   private OnCheckedChangeListener onCheckedChangeListener;
   private ImageView iconView;
+  @DrawableRes private int iconResource = 0;
   private TextView labelView;
   private Drawable background;
   private Drawable backgroundMore;
@@ -135,8 +136,15 @@
             new int[] {color, Color.WHITE}));
   }
 
+  public Drawable getIconDrawable() {
+    return iconView.getDrawable();
+  }
+
   public void setIconDrawable(@DrawableRes int drawableRes) {
-    iconView.setImageResource(drawableRes);
+    if (iconResource != drawableRes) {
+      iconView.setImageResource(drawableRes);
+      iconResource = drawableRes;
+    }
   }
 
   public void setLabelText(@StringRes int stringRes) {
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_00.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_00.png
new file mode 100644
index 0000000..4c8b33f
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_00.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_01.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_01.png
new file mode 100644
index 0000000..9101779
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_01.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_02.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_02.png
new file mode 100644
index 0000000..92a27ee
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_02.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_03.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_03.png
new file mode 100644
index 0000000..484058f
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_03.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_04.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_04.png
new file mode 100644
index 0000000..348ae92
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_04.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_05.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_05.png
new file mode 100644
index 0000000..011915a
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_05.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_06.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_06.png
new file mode 100644
index 0000000..c1cc0d6
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_06.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_07.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_07.png
new file mode 100644
index 0000000..75233db
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_07.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_08.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_08.png
new file mode 100644
index 0000000..2918e1a
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_08.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_09.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_09.png
new file mode 100644
index 0000000..7e92767
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_09.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_10.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_10.png
new file mode 100644
index 0000000..0089314
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_10.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_11.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_11.png
new file mode 100644
index 0000000..f66a6b6
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_11.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_12.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_12.png
new file mode 100644
index 0000000..9303d95
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_12.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_13.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_13.png
new file mode 100644
index 0000000..d2bef6e
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_13.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_14.png b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_14.png
new file mode 100644
index 0000000..a5434ec
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable-xxxhdpi/ic_sim_change_white_14.png
Binary files differ
diff --git a/java/com/android/incallui/incall/impl/res/drawable/ic_sim_change_white.xml b/java/com/android/incallui/incall/impl/res/drawable/ic_sim_change_white.xml
new file mode 100644
index 0000000..00b1b7a
--- /dev/null
+++ b/java/com/android/incallui/incall/impl/res/drawable/ic_sim_change_white.xml
@@ -0,0 +1,68 @@
+<?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
+  -->
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/ic_sim_change_white"
+    android:oneshot="true">
+  <item
+      android:drawable="@drawable/ic_sim_change_white_00"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_01"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_02"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_03"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_04"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_05"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_06"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_07"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_08"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_09"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_10"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_11"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_12"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_13"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_14"
+      android:duration="33"/>
+  <item
+      android:drawable="@drawable/ic_sim_change_white_00"
+      android:duration="33"/>
+</animation-list>
\ No newline at end of file