Merge changes I0ff396b5,I3b668c3a,Iab630f19,I6ef204ac

* changes:
  Use goAsync() and dialer executor to insert post call notifications
  Add the assisted dialing logic module.
  Refactor DialerStrictMode into an interface.
  Adding logging for transcription polling requests
diff --git a/java/com/android/contacts/common/preference/ContactsPreferences.java b/java/com/android/contacts/common/preference/ContactsPreferences.java
index 399700e..d0adb04 100644
--- a/java/com/android/contacts/common/preference/ContactsPreferences.java
+++ b/java/com/android/contacts/common/preference/ContactsPreferences.java
@@ -27,7 +27,7 @@
 import android.text.TextUtils;
 import com.android.contacts.common.R;
 import com.android.contacts.common.model.account.AccountWithDataSet;
-import com.android.dialer.strictmode.DialerStrictMode;
+import com.android.dialer.strictmode.StrictModeUtils;
 
 /** Manages user preferences for contacts. */
 public class ContactsPreferences implements OnSharedPreferenceChangeListener {
@@ -107,7 +107,7 @@
     mSortOrder = sortOrder;
     final Editor editor = mPreferences.edit();
     editor.putInt(SORT_ORDER_KEY, sortOrder);
-    DialerStrictMode.bypass(editor::commit);
+    StrictModeUtils.bypass(editor::commit);
   }
 
   private boolean isDisplayOrderUserChangeable() {
@@ -136,7 +136,7 @@
     mDisplayOrder = displayOrder;
     final Editor editor = mPreferences.edit();
     editor.putInt(DISPLAY_ORDER_KEY, displayOrder);
-    DialerStrictMode.bypass(editor::commit);
+    StrictModeUtils.bypass(editor::commit);
   }
 
   private boolean isDefaultAccountUserChangeable() {
@@ -166,7 +166,7 @@
       editor.putString(mDefaultAccountKey, accountWithDataSet.stringify());
     }
     editor.putBoolean(mDefaultAccountSavedKey, true);
-    DialerStrictMode.bypass(editor::commit);
+    StrictModeUtils.bypass(editor::commit);
   }
 
   public void registerChangeListener(ChangeListener listener) {
diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsActivity.java b/java/com/android/dialer/app/calllog/CallLogNotificationsActivity.java
index 0b4da75..c08d2c0 100644
--- a/java/com/android/dialer/app/calllog/CallLogNotificationsActivity.java
+++ b/java/com/android/dialer/app/calllog/CallLogNotificationsActivity.java
@@ -57,7 +57,7 @@
     String action = intent.getAction();
     switch (action) {
       case ACTION_SEND_SMS_FROM_MISSED_CALL_NOTIFICATION:
-        MissedCallNotifier.getIstance(this)
+        MissedCallNotifier.getInstance(this)
             .sendSmsFromMissedCall(
                 intent.getStringExtra(EXTRA_MISSED_CALL_NUMBER), intent.getData());
         break;
diff --git a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java
index 0490b99..5949141 100644
--- a/java/com/android/dialer/app/calllog/CallLogNotificationsService.java
+++ b/java/com/android/dialer/app/calllog/CallLogNotificationsService.java
@@ -64,9 +64,6 @@
   private static final String ACTION_CANCEL_SINGLE_MISSED_CALL =
       "com.android.dialer.calllog.ACTION_CANCEL_SINGLE_MISSED_CALL";
 
-  private static final String ACTION_INCOMING_POST_CALL =
-      "com.android.dialer.calllog.INCOMING_POST_CALL";
-
   /** Action to call back a missed call. */
   public static final String ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION =
       "com.android.dialer.calllog.CALL_BACK_FROM_MISSED_CALL_NOTIFICATION";
@@ -75,21 +72,6 @@
   public static final String ACTION_LEGACY_VOICEMAIL_DISMISSED =
       "com.android.dialer.calllog.ACTION_LEGACY_VOICEMAIL_DISMISSED";
 
-  /**
-   * Extra to be included with {@link #ACTION_INCOMING_POST_CALL} to represent a post call note.
-   *
-   * <p>It must be a {@link String}
-   */
-  private static final String EXTRA_POST_CALL_NOTE = "POST_CALL_NOTE";
-
-  /**
-   * Extra to be included with {@link #ACTION_INCOMING_POST_CALL} to represent the phone number the
-   * post call note came from.
-   *
-   * <p>It must be a {@link String}
-   */
-  private static final String EXTRA_POST_CALL_NUMBER = "POST_CALL_NUMBER";
-
   private static final String EXTRA_PHONE_ACCOUNT_HANDLE = "PHONE_ACCOUNT_HANDLE";
 
   public static final int UNKNOWN_MISSED_CALL_COUNT = -1;
@@ -98,14 +80,6 @@
     super("CallLogNotificationsService");
   }
 
-  public static void insertPostCallNote(Context context, String number, String postCallNote) {
-    Intent serviceIntent = new Intent(context, CallLogNotificationsService.class);
-    serviceIntent.setAction(ACTION_INCOMING_POST_CALL);
-    serviceIntent.putExtra(EXTRA_POST_CALL_NUMBER, number);
-    serviceIntent.putExtra(EXTRA_POST_CALL_NOTE, postCallNote);
-    context.startService(serviceIntent);
-  }
-
   public static void markAllNewVoicemailsAsOld(Context context) {
     LogUtil.enterBlock("CallLogNotificationsService.markAllNewVoicemailsAsOld");
     Intent serviceIntent = new Intent(context, CallLogNotificationsService.class);
@@ -195,11 +169,6 @@
         LegacyVoicemailNotificationReceiver.setDismissed(
             this, intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT_HANDLE), true);
         break;
-      case ACTION_INCOMING_POST_CALL:
-        String note = intent.getStringExtra(EXTRA_POST_CALL_NOTE);
-        String phoneNumber = intent.getStringExtra(EXTRA_POST_CALL_NUMBER);
-        MissedCallNotifier.getIstance(this).insertPostCallNotification(phoneNumber, note);
-        break;
       case ACTION_CANCEL_ALL_MISSED_CALLS:
         cancelAllMissedCalls(this);
         break;
@@ -210,7 +179,7 @@
         TelecomUtil.cancelMissedCallsNotification(this);
         break;
       case ACTION_CALL_BACK_FROM_MISSED_CALL_NOTIFICATION:
-        MissedCallNotifier.getIstance(this)
+        MissedCallNotifier.getInstance(this)
             .callBackFromMissedCall(
                 intent.getStringExtra(
                     MissedCallNotificationReceiver.EXTRA_NOTIFICATION_PHONE_NUMBER),
diff --git a/java/com/android/dialer/app/calllog/MissedCallNotificationReceiver.java b/java/com/android/dialer/app/calllog/MissedCallNotificationReceiver.java
index 65bb6fa..a1f1c52 100644
--- a/java/com/android/dialer/app/calllog/MissedCallNotificationReceiver.java
+++ b/java/com/android/dialer/app/calllog/MissedCallNotificationReceiver.java
@@ -53,7 +53,7 @@
 
     PendingResult pendingResult = goAsync();
 
-    DialerExecutors.createNonUiTaskBuilder(MissedCallNotifier.getIstance(context))
+    DialerExecutors.createNonUiTaskBuilder(MissedCallNotifier.getInstance(context))
         .onSuccess(
             output -> {
               LogUtil.i(
diff --git a/java/com/android/dialer/app/calllog/MissedCallNotifier.java b/java/com/android/dialer/app/calllog/MissedCallNotifier.java
index de76619..084713f 100644
--- a/java/com/android/dialer/app/calllog/MissedCallNotifier.java
+++ b/java/com/android/dialer/app/calllog/MissedCallNotifier.java
@@ -45,6 +45,7 @@
 import com.android.dialer.app.list.DialtactsPagerAdapter;
 import com.android.dialer.callintent.CallInitiationType;
 import com.android.dialer.callintent.CallIntentBuilder;
+import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.DialerExecutor.Worker;
 import com.android.dialer.compat.android.provider.VoicemailCompat;
@@ -83,7 +84,7 @@
     this.callLogNotificationsQueryHelper = callLogNotificationsQueryHelper;
   }
 
-  static MissedCallNotifier getIstance(Context context) {
+  public static MissedCallNotifier getInstance(Context context) {
     return new MissedCallNotifier(context, CallLogNotificationsQueryHelper.getInstance(context));
   }
 
@@ -259,7 +260,10 @@
     return NOTIFICATION_TAG_PREFIX + callUri;
   }
 
+  @WorkerThread
   public void insertPostCallNotification(@NonNull String number, @NonNull String note) {
+    Assert.isWorkerThread();
+    LogUtil.enterBlock("MissedCallNotifier.insertPostCallNotification");
     List<NewCall> newCalls = callLogNotificationsQueryHelper.getNewMissedCalls();
     if (newCalls != null && !newCalls.isEmpty()) {
       for (NewCall call : newCalls) {
@@ -270,10 +274,11 @@
               getNotificationTagForCall(call),
               NOTIFICATION_ID,
               getNotificationForCall(call, note));
-          break;
+          return;
         }
       }
     }
+    LogUtil.i("MissedCallNotifier.insertPostCallNotification", "notification not found");
   }
 
   private Notification getNotificationForCall(
diff --git a/java/com/android/dialer/assisteddialing/AssistedDialingMediator.java b/java/com/android/dialer/assisteddialing/AssistedDialingMediator.java
new file mode 100644
index 0000000..2613d07
--- /dev/null
+++ b/java/com/android/dialer/assisteddialing/AssistedDialingMediator.java
@@ -0,0 +1,81 @@
+/*
+ * 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.assisteddialing;
+
+import android.annotation.TargetApi;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.NonNull;
+import com.android.dialer.common.LogUtil;
+import java.util.Optional;
+
+/**
+ * The Mediator for Assisted Dialing.
+ *
+ * <p>This class is responsible for mediating location discovery of the user, determining if the
+ * call is eligible for assisted dialing, and performing the transformation of numbers eligible for
+ * assisted dialing.
+ */
+public final class AssistedDialingMediator {
+
+  private final LocationDetector locationDetector;
+  private final NumberTransformer numberTransformer;
+
+  protected AssistedDialingMediator(
+      @NonNull LocationDetector locationDetector, @NonNull NumberTransformer numberTransformer) {
+    if (locationDetector == null) {
+      throw new NullPointerException("locationDetector was null");
+    }
+
+    if (numberTransformer == null) {
+      throw new NullPointerException("numberTransformer was null");
+    }
+    this.locationDetector = locationDetector;
+    this.numberTransformer = numberTransformer;
+  }
+
+  /**
+   * Returns a boolean for callers to quickly determine whether or not the AssistedDialingMediator
+   * thinks an attempt at assisted dialing is likely to succeed.
+   */
+  public boolean conditionsEligibleForAssistedDialing(
+      @NonNull String numberToCheck,
+      @NonNull String userHomeCountryCode,
+      @NonNull String userRoamingCountryCode) {
+    return numberTransformer.canDoAssistedDialingTransformation(
+        numberToCheck, userHomeCountryCode, userRoamingCountryCode);
+  }
+
+  /**
+   * Returns an Optional of type String containing the transformed number that was provided. The
+   * transformed number should be capable of dialing out of the User's current country and
+   * successfully connecting with a contact in the User's home country.
+   */
+  @SuppressWarnings("AndroidApiChecker") // Use of optional
+  @TargetApi(VERSION_CODES.N)
+  public Optional<String> attemptAssistedDial(@NonNull String numberToTransform) {
+    Optional<String> userHomeCountryCode = locationDetector.getUpperCaseUserHomeCountry();
+    Optional<String> userRoamingCountryCode = locationDetector.getUpperCaseUserRoamingCountry();
+
+    if (!userHomeCountryCode.isPresent() || !userRoamingCountryCode.isPresent()) {
+      LogUtil.i("AssistedDialingMediator.attemptAssistedDial", "Unable to determine country codes");
+      return Optional.empty();
+    }
+
+    return numberTransformer.doAssistedDialingTransformation(
+        numberToTransform, userHomeCountryCode.get(), userRoamingCountryCode.get());
+  }
+}
diff --git a/java/com/android/dialer/assisteddialing/ConcreteCreator.java b/java/com/android/dialer/assisteddialing/ConcreteCreator.java
new file mode 100644
index 0000000..f51216a
--- /dev/null
+++ b/java/com/android/dialer/assisteddialing/ConcreteCreator.java
@@ -0,0 +1,58 @@
+/*
+ * 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.assisteddialing;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.NonNull;
+import android.telephony.TelephonyManager;
+import com.android.dialer.common.LogUtil;
+
+/**
+ * A Creator for AssistedDialingMediators.
+ *
+ * <p>This helps keep the dependencies required by AssistedDialingMediator for assisted dialing
+ * explicit.
+ */
+@TargetApi(VERSION_CODES.N)
+public final class ConcreteCreator {
+
+  /**
+   * Creates a new AssistedDialingMediator
+   *
+   * @param telephonyManager The telephony manager used to determine user location.
+   * @param context The context used to determine whether or not a provided number is an emergency
+   *     number.
+   * @return An AssistedDialingMediator
+   */
+  public static AssistedDialingMediator createNewAssistedDialingMediator(
+      @NonNull TelephonyManager telephonyManager, @NonNull Context context) {
+    if (telephonyManager == null) {
+      LogUtil.i(
+          "ConcreteCreator.createNewAssistedDialingMediator", "provided TelephonyManager was null");
+      throw new NullPointerException("Provided TelephonyManager was null");
+    }
+    if (context == null) {
+      LogUtil.i("ConcreteCreator.createNewAssistedDialingMediator", "provided context was null");
+      throw new NullPointerException("Provided context was null");
+    }
+    Constraints constraints = new Constraints(context);
+    return new AssistedDialingMediator(
+        new LocationDetector(telephonyManager), new NumberTransformer(constraints));
+  }
+}
diff --git a/java/com/android/dialer/assisteddialing/Constraints.java b/java/com/android/dialer/assisteddialing/Constraints.java
new file mode 100644
index 0000000..6bcab99
--- /dev/null
+++ b/java/com/android/dialer/assisteddialing/Constraints.java
@@ -0,0 +1,202 @@
+/*
+ * 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.assisteddialing;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.NonNull;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import com.android.dialer.common.LogUtil;
+import com.google.i18n.phonenumbers.NumberParseException;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** Ensures that a number is eligible for Assisted Dialing */
+@TargetApi(VERSION_CODES.N)
+@SuppressWarnings("AndroidApiChecker") // Use of optional
+final class Constraints {
+  private final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
+  private final Context context;
+
+  /**
+   * Create a new instance of Constraints.
+   *
+   * @param context The context used to determine whether or not a number is an emergency number.
+   */
+  public Constraints(@NonNull Context context) {
+    if (context == null) {
+      throw new NullPointerException("Provided context cannot be null");
+    }
+    this.context = context;
+  }
+
+  // TODO(erfanian): Ensure the below standard is consistent between libphonenumber and the
+  // platform.
+  // ISO 3166-1 alpha-2 Country Codes that are eligible for assisted dialing.
+  private final String[] supportedCountryCodeValues =
+      new String[] {
+        "CA" /* Canada */,
+        "GB" /* United Kingdom */,
+        "JP" /* Japan */,
+        "MX" /* Mexico */,
+        "US" /* United States*/,
+      };
+
+  private final Set<String> supportedCountryCodes =
+      Arrays.stream(supportedCountryCodeValues)
+          .map(v -> v.toUpperCase(Locale.US))
+          .collect(Collectors.toCollection(ArraySet::new));
+
+  /**
+   * Determines whether or not we think Assisted Dialing is possible given the provided parameters.
+   *
+   * @param numberToCheck A string containing the phone number.
+   * @param userHomeCountryCode A string containing an ISO 3166-1 alpha-2 country code representing
+   *     the user's home country.
+   * @param userRoamingCountryCode A string containing an ISO 3166-1 alpha-2 country code
+   *     representing the user's roaming country.
+   * @return A boolean indicating whether or not the provided values are eligible for assisted
+   *     dialing.
+   */
+  public boolean meetsPreconditions(
+      @NonNull String numberToCheck,
+      @NonNull String userHomeCountryCode,
+      @NonNull String userRoamingCountryCode) {
+
+    if (TextUtils.isEmpty(numberToCheck)) {
+      LogUtil.i("Constraints.meetsPreconditions", "numberToCheck was empty");
+      return false;
+    }
+
+    if (TextUtils.isEmpty(userHomeCountryCode)) {
+      LogUtil.i("Constraints.meetsPreconditions", "userHomeCountryCode was empty");
+      return false;
+    }
+
+    if (TextUtils.isEmpty(userRoamingCountryCode)) {
+      LogUtil.i("Constraints.meetsPreconditions", "userRoamingCountryCode was empty");
+      return false;
+    }
+
+    userHomeCountryCode = userHomeCountryCode.toUpperCase(Locale.US);
+    userRoamingCountryCode = userRoamingCountryCode.toUpperCase(Locale.US);
+
+    Optional<PhoneNumber> parsedPhoneNumber = parsePhoneNumber(numberToCheck, userHomeCountryCode);
+
+    if (!parsedPhoneNumber.isPresent()) {
+      LogUtil.i("Constraints.meetsPreconditions", "parsedPhoneNumber was empty");
+      return false;
+    }
+
+    return areSupportedCountryCodes(userHomeCountryCode, userRoamingCountryCode)
+        && isUserRoaming(userHomeCountryCode, userRoamingCountryCode)
+        && isNotInternationalNumber(parsedPhoneNumber)
+        && isNotEmergencyNumber(numberToCheck, context)
+        && isValidNumber(parsedPhoneNumber);
+  }
+
+  /** Returns a boolean indicating the value equivalence of the provided country codes. */
+  private boolean isUserRoaming(
+      @NonNull String userHomeCountryCode, @NonNull String userRoamingCountryCode) {
+    boolean result = !userHomeCountryCode.equals(userRoamingCountryCode);
+    LogUtil.i("Constraints.isUserRoaming", String.valueOf(result));
+    return result;
+  }
+
+  /**
+   * Returns a boolean indicating the support of both provided country codes for assisted dialing.
+   * Both country codes must be allowed for the return value to be true.
+   */
+  private boolean areSupportedCountryCodes(
+      @NonNull String userHomeCountryCode, @NonNull String userRoamingCountryCode) {
+    if (TextUtils.isEmpty(userHomeCountryCode)) {
+      LogUtil.i("Constraints.areSupportedCountryCodes", "userHomeCountryCode was empty");
+      return false;
+    }
+
+    if (TextUtils.isEmpty(userRoamingCountryCode)) {
+      LogUtil.i("Constraints.areSupportedCountryCodes", "userRoamingCountryCode was empty");
+      return false;
+    }
+
+    boolean result =
+        supportedCountryCodes.contains(userHomeCountryCode)
+            && supportedCountryCodes.contains(userRoamingCountryCode);
+    LogUtil.i("Constraints.areSupportedCountryCodes", String.valueOf(result));
+    return result;
+  }
+
+  /**
+   * A convenience method to take a number as a String and a specified country code, and return a
+   * PhoneNumber object.
+   */
+  private Optional<PhoneNumber> parsePhoneNumber(
+      @NonNull String numberToParse, @NonNull String userHomeCountryCode) {
+    try {
+      // TODO(erfanian): confirm behavior of blocking the foreground thread when moving to the
+      // framework
+      return Optional.of(phoneNumberUtil.parseAndKeepRawInput(numberToParse, userHomeCountryCode));
+    } catch (NumberParseException e) {
+      LogUtil.i("Constraints.parsePhoneNumber", "could not parse the number");
+      return Optional.empty();
+    }
+  }
+
+  /**
+   * Returns a boolean indicating if the provided number and home country code are already
+   * internationally formatted.
+   */
+  private boolean isNotInternationalNumber(@NonNull Optional<PhoneNumber> parsedPhoneNumber) {
+
+    if (parsedPhoneNumber.get().hasCountryCode()
+        && parsedPhoneNumber.get().getCountryCodeSource()
+            != CountryCodeSource.FROM_DEFAULT_COUNTRY) {
+      LogUtil.i(
+          "Constraints.isNotInternationalNumber", "phone number already provided the country code");
+      return false;
+    }
+    return true;
+  }
+
+  /** Returns a boolean indicating if the provided number is considered to be a valid number. */
+  private boolean isValidNumber(@NonNull Optional<PhoneNumber> parsedPhoneNumber) {
+    boolean result = PhoneNumberUtil.getInstance().isValidNumber(parsedPhoneNumber.get());
+    LogUtil.i("Constraints.isValidNumber", String.valueOf(result));
+
+    return result;
+  }
+
+  /** Returns a boolean indicating if the provided number is an emergency number. */
+  private boolean isNotEmergencyNumber(@NonNull String numberToCheck, @NonNull Context context) {
+    // isEmergencyNumber may depend on network state, so also use isLocalEmergencyNumber when
+    // roaming and out of service.
+    boolean result =
+        !PhoneNumberUtils.isEmergencyNumber(numberToCheck)
+            && !PhoneNumberUtils.isLocalEmergencyNumber(context, numberToCheck);
+    LogUtil.i("Constraints.isNotEmergencyNumber", String.valueOf(result));
+    return result;
+  }
+}
diff --git a/java/com/android/dialer/assisteddialing/LocationDetector.java b/java/com/android/dialer/assisteddialing/LocationDetector.java
new file mode 100644
index 0000000..6840689
--- /dev/null
+++ b/java/com/android/dialer/assisteddialing/LocationDetector.java
@@ -0,0 +1,73 @@
+/*
+ * 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.assisteddialing;
+
+import android.annotation.TargetApi;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.NonNull;
+import android.telephony.TelephonyManager;
+import com.android.dialer.common.LogUtil;
+import java.util.Locale;
+import java.util.Optional;
+
+// TODO(erfanian): Improve definition of roaming and home country in finalized API.
+/**
+ * LocationDetector is responsible for determining the Roaming location of the User, in addition to
+ * User's home country.
+ */
+final class LocationDetector {
+
+  private final TelephonyManager telephonyManager;
+
+  public LocationDetector(@NonNull TelephonyManager telephonyManager) {
+    if (telephonyManager == null) {
+      throw new NullPointerException("Provided TelephonyManager was null");
+    }
+    this.telephonyManager = telephonyManager;
+  }
+
+  // TODO(erfanian):  confirm this is based on ISO 3166-1 alpha-2. libphonenumber expects Unicode's
+  // CLDR
+  // TODO(erfanian):  confirm these are still valid in a multi-sim environment.
+  /**
+   * Returns what we believe to be the User's home country. This should resolve to
+   * PROPERTY_ICC_OPERATOR_ISO_COUNTRY
+   */
+  @SuppressWarnings("AndroidApiChecker") // Use of optional
+  @TargetApi(VERSION_CODES.N)
+  public Optional<String> getUpperCaseUserHomeCountry() {
+    String simCountryIso = telephonyManager.getSimCountryIso();
+    if (simCountryIso != null) {
+      return Optional.of(telephonyManager.getSimCountryIso().toUpperCase(Locale.US));
+    }
+    LogUtil.i("LocationDetector.getUpperCaseUserHomeCountry", "user home country was null");
+    return Optional.empty();
+  }
+
+  /** Returns what we believe to be the User's current (roaming) country */
+  @SuppressWarnings("AndroidApiChecker") // Use of optional
+  @TargetApi(VERSION_CODES.N)
+  public Optional<String> getUpperCaseUserRoamingCountry() {
+    // TODO Increase coverage of location resolution??
+    String networkCountryIso = telephonyManager.getNetworkCountryIso();
+    if (networkCountryIso != null) {
+      return Optional.of(telephonyManager.getNetworkCountryIso().toUpperCase(Locale.US));
+    }
+    LogUtil.i("LocationDetector.getUpperCaseUserRoamingCountry", "user roaming country was null");
+    return Optional.empty();
+  }
+}
diff --git a/java/com/android/dialer/assisteddialing/NumberTransformer.java b/java/com/android/dialer/assisteddialing/NumberTransformer.java
new file mode 100644
index 0000000..f01d1a0
--- /dev/null
+++ b/java/com/android/dialer/assisteddialing/NumberTransformer.java
@@ -0,0 +1,93 @@
+/*
+ * 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.assisteddialing;
+
+import android.annotation.TargetApi;
+import android.os.Build.VERSION_CODES;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import com.android.dialer.common.LogUtil;
+import com.google.i18n.phonenumbers.NumberParseException;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import java.util.Optional;
+
+/** Responsible for transforming numbers to make them dialable and valid when roaming. */
+final class NumberTransformer {
+
+  private final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
+  private final Constraints constraints;
+
+  public NumberTransformer(Constraints constraints) {
+    this.constraints = constraints;
+  }
+
+  /**
+   * Returns a boolean for callers to quickly determine whether or not the AssistedDialingMediator
+   * thinks an attempt at assisted dialing is likely to succeed.
+   */
+  public boolean canDoAssistedDialingTransformation(
+      @NonNull String numberToCheck,
+      @NonNull String userHomeCountryCode,
+      @NonNull String userRoamingCountryCode) {
+    return constraints.meetsPreconditions(
+        numberToCheck, userHomeCountryCode, userRoamingCountryCode);
+  }
+
+  /**
+   * A method to do assisted dialing transformations.
+   *
+   * <p>The library will do its best to attempt a transformation, but, if for any reason the
+   * transformation fails, we return an empty optional. The operation can be considered a success
+   * when the Optional we return has a value set.
+   */
+  @SuppressWarnings("AndroidApiChecker") // Use of optional
+  @TargetApi(VERSION_CODES.N)
+  public Optional<String> doAssistedDialingTransformation(
+      String numbertoTransform, String userHomeCountryCode, String userRoamingCountryCode) {
+
+    if (!constraints.meetsPreconditions(
+        numbertoTransform, userHomeCountryCode, userRoamingCountryCode)) {
+      LogUtil.i(
+          "NumberTransformer.doAssistedDialingTransformation",
+          "assisted dialing failed preconditions");
+      return Optional.empty();
+    }
+
+    PhoneNumber phoneNumber;
+    try {
+      phoneNumber = phoneNumberUtil.parse(numbertoTransform, userHomeCountryCode);
+    } catch (NumberParseException e) {
+      LogUtil.i("NumberTransformer.doAssistedDialingTransformation", "number failed to parse");
+      return Optional.empty();
+    }
+
+    String transformedNumber =
+        phoneNumberUtil.formatNumberForMobileDialing(phoneNumber, userRoamingCountryCode, true);
+
+    // formatNumberForMobileDialing may return an empty String.
+    if (TextUtils.isEmpty(transformedNumber)) {
+      LogUtil.i(
+          "NumberTransformer.doAssistedDialingTransformation",
+          "formatNumberForMobileDialing returned an empty string");
+      return Optional.empty();
+    }
+
+    // TODO Verify the transformed number is still valid?
+    return Optional.of(transformedNumber);
+  }
+}
diff --git a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
index 50db7f7..d61f712 100644
--- a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
+++ b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
@@ -26,6 +26,7 @@
 import com.android.dialer.phonenumbergeoutil.impl.PhoneNumberGeoUtilModule;
 import com.android.dialer.simulator.impl.SimulatorModule;
 import com.android.dialer.storage.StorageModule;
+import com.android.dialer.strictmode.impl.SystemStrictModeModule;
 import com.android.incallui.calllocation.stub.StubCallLocationModule;
 import com.android.incallui.maps.stub.StubMapsModule;
 import com.android.voicemail.impl.VoicemailModule;
@@ -43,6 +44,7 @@
     SharedPrefConfigProviderModule.class,
     SimulatorModule.class,
     StorageModule.class,
+    SystemStrictModeModule.class,
     StubCallLocationModule.class,
     StubEnrichedCallModule.class,
     StubMapsModule.class,
diff --git a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
index 30b1e8a..580eb5d 100644
--- a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
+++ b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
@@ -26,6 +26,7 @@
 import com.android.dialer.phonenumbergeoutil.PhoneNumberGeoUtilComponent;
 import com.android.dialer.simulator.SimulatorComponent;
 import com.android.dialer.storage.StorageComponent;
+import com.android.dialer.strictmode.StrictModeComponent;
 import com.android.incallui.calllocation.CallLocationComponent;
 import com.android.incallui.maps.MapsComponent;
 import com.android.voicemail.VoicemailComponent;
@@ -46,5 +47,6 @@
         PhoneNumberGeoUtilComponent.HasComponent,
         SimulatorComponent.HasComponent,
         StorageComponent.HasComponent,
+        StrictModeComponent.HasComponent,
         VoicemailComponent.HasComponent,
         LightbringerComponent.HasComponent {}
diff --git a/java/com/android/dialer/binary/common/DialerApplication.java b/java/com/android/dialer/binary/common/DialerApplication.java
index 580e0a3..19af575 100644
--- a/java/com/android/dialer/binary/common/DialerApplication.java
+++ b/java/com/android/dialer/binary/common/DialerApplication.java
@@ -27,7 +27,7 @@
 import com.android.dialer.inject.HasRootComponent;
 import com.android.dialer.notification.NotificationChannelManager;
 import com.android.dialer.persistentlog.PersistentLogger;
-import com.android.dialer.strictmode.DialerStrictMode;
+import com.android.dialer.strictmode.StrictModeComponent;
 
 /** A common application subclass for all Dialer build variants. */
 public abstract class DialerApplication extends Application implements HasRootComponent {
@@ -37,7 +37,7 @@
   @Override
   public void onCreate() {
     Trace.beginSection("DialerApplication.onCreate");
-    DialerStrictMode.onApplicationCreate(this);
+    StrictModeComponent.get(this).getDialerStrictMode().onApplicationCreate(this);
 
     super.onCreate();
     new BlockedNumbersAutoMigrator(
diff --git a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
index cf51377..87f09c8 100644
--- a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
+++ b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
@@ -26,6 +26,7 @@
 import com.android.dialer.phonenumbergeoutil.impl.PhoneNumberGeoUtilModule;
 import com.android.dialer.simulator.impl.SimulatorModule;
 import com.android.dialer.storage.StorageModule;
+import com.android.dialer.strictmode.impl.SystemStrictModeModule;
 import com.android.incallui.calllocation.impl.CallLocationModule;
 import com.android.incallui.maps.impl.MapsModule;
 import com.android.voicemail.impl.VoicemailModule;
@@ -47,6 +48,7 @@
     SharedPrefConfigProviderModule.class,
     SimulatorModule.class,
     StorageModule.class,
+    SystemStrictModeModule.class,
     StubEnrichedCallModule.class,
     MapsModule.class,
     VoicemailModule.class,
diff --git a/java/com/android/dialer/blocking/FilteredNumberCompat.java b/java/com/android/dialer/blocking/FilteredNumberCompat.java
index 548c965..a5f3d7e 100644
--- a/java/com/android/dialer/blocking/FilteredNumberCompat.java
+++ b/java/com/android/dialer/blocking/FilteredNumberCompat.java
@@ -38,7 +38,7 @@
 import com.android.dialer.database.FilteredNumberContract.FilteredNumberColumns;
 import com.android.dialer.database.FilteredNumberContract.FilteredNumberSources;
 import com.android.dialer.database.FilteredNumberContract.FilteredNumberTypes;
-import com.android.dialer.strictmode.DialerStrictMode;
+import com.android.dialer.strictmode.StrictModeUtils;
 import com.android.dialer.telecom.TelecomUtil;
 import java.util.ArrayList;
 import java.util.List;
@@ -125,7 +125,7 @@
    *     android.provider.BlockedNumberContract} blocking, {@code false} otherwise.
    */
   public static boolean hasMigratedToNewBlocking(Context context) {
-    return DialerStrictMode.bypass(
+    return StrictModeUtils.bypass(
         () ->
             PreferenceManager.getDefaultSharedPreferences(context)
                 .getBoolean(HAS_MIGRATED_TO_NEW_BLOCKING_KEY, false));
diff --git a/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java b/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java
index 6ee4695..5351698 100644
--- a/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java
+++ b/java/com/android/dialer/configprovider/SharedPrefConfigProvider.java
@@ -24,7 +24,7 @@
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.inject.ApplicationContext;
-import com.android.dialer.strictmode.DialerStrictMode;
+import com.android.dialer.strictmode.StrictModeUtils;
 import com.android.dialer.util.DialerUtils;
 import javax.inject.Inject;
 
@@ -96,21 +96,21 @@
   @Override
   public String getString(String key, String defaultValue) {
     // Reading shared prefs on the main thread is generally safe since a single instance is cached.
-    return DialerStrictMode.bypass(
+    return StrictModeUtils.bypass(
         () -> getSharedPrefs(appContext).getString(PREF_PREFIX + key, defaultValue));
   }
 
   @Override
   public long getLong(String key, long defaultValue) {
     // Reading shared prefs on the main thread is generally safe since a single instance is cached.
-    return DialerStrictMode.bypass(
+    return StrictModeUtils.bypass(
         () -> getSharedPrefs(appContext).getLong(PREF_PREFIX + key, defaultValue));
   }
 
   @Override
   public boolean getBoolean(String key, boolean defaultValue) {
     // Reading shared prefs on the main thread is generally safe since a single instance is cached.
-    return DialerStrictMode.bypass(
+    return StrictModeUtils.bypass(
         () -> getSharedPrefs(appContext).getBoolean(PREF_PREFIX + key, defaultValue));
   }
 
diff --git a/java/com/android/dialer/enrichedcall/EnrichedCallManager.java b/java/com/android/dialer/enrichedcall/EnrichedCallManager.java
index 0606a00..9f68978 100644
--- a/java/com/android/dialer/enrichedcall/EnrichedCallManager.java
+++ b/java/com/android/dialer/enrichedcall/EnrichedCallManager.java
@@ -16,6 +16,7 @@
 
 package com.android.dialer.enrichedcall;
 
+import android.content.BroadcastReceiver.PendingResult;
 import android.support.annotation.MainThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -269,10 +270,17 @@
   /**
    * Called when post call data arrives for the given session.
    *
+   * @param pendingResult PendingResult form a broadcast receiver. The broadcast might be received
+   *     when dialer is not in the foreground, and can not start {@link
+   *     com.android.dialer.app.calllog.CallLogNotificationsService} to handle the event. The
+   *     pendingResult allows dialer to hold on to resources when the event is handled in a
+   *     background thread. TODO(b/67015768): migrate CallLogNotificationsService to a
+   *     JobIntentService so it can be used in the background.
    * @throws IllegalStateException if there's no session for the given id
    */
   @MainThread
-  void onIncomingPostCallData(long sessionId, @NonNull MultimediaData multimediaData);
+  void onIncomingPostCallData(
+      @NonNull PendingResult pendingResult, long sessionId, @NonNull MultimediaData multimediaData);
 
   /**
    * Registers the given {@link VideoShareListener}.
diff --git a/java/com/android/dialer/enrichedcall/stub/EnrichedCallManagerStub.java b/java/com/android/dialer/enrichedcall/stub/EnrichedCallManagerStub.java
index 87d99de..55bc0db 100644
--- a/java/com/android/dialer/enrichedcall/stub/EnrichedCallManagerStub.java
+++ b/java/com/android/dialer/enrichedcall/stub/EnrichedCallManagerStub.java
@@ -16,6 +16,7 @@
 
 package com.android.dialer.enrichedcall.stub;
 
+import android.content.BroadcastReceiver.PendingResult;
 import android.support.annotation.MainThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -150,7 +151,12 @@
   public void onIncomingCallComposerData(long sessionId, @NonNull MultimediaData multimediaData) {}
 
   @Override
-  public void onIncomingPostCallData(long sessionId, @NonNull MultimediaData multimediaData) {}
+  public void onIncomingPostCallData(
+      @NonNull PendingResult pendingResult,
+      long sessionId,
+      @NonNull MultimediaData multimediaData) {
+    pendingResult.finish();
+  }
 
   @Override
   public void registerVideoShareListener(@NonNull VideoShareListener listener) {}
diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto
index d5f1963..fe1c5e9 100644
--- a/java/com/android/dialer/logging/dialer_impression.proto
+++ b/java/com/android/dialer/logging/dialer_impression.proto
@@ -551,5 +551,8 @@
     LIGHTBRINGER_NON_CONTACT_UPGRADE_REQUESTED = 1281;
     LIGHTBRINGER_NON_CONTACT_VIDEO_REQUESTED_FROM_CALL_LOG =
         1282;  // Including call history
+
+    // More voicemail transcription impressions
+    VVM_TRANSCRIPTION_POLL_REQUEST = 1283;
   }
 }
diff --git a/java/com/android/dialer/persistentlog/PersistentLogger.java b/java/com/android/dialer/persistentlog/PersistentLogger.java
index 5fdefd1..608602e 100644
--- a/java/com/android/dialer/persistentlog/PersistentLogger.java
+++ b/java/com/android/dialer/persistentlog/PersistentLogger.java
@@ -26,7 +26,7 @@
 import android.support.v4.os.UserManagerCompat;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
-import com.android.dialer.strictmode.DialerStrictMode;
+import com.android.dialer.strictmode.StrictModeUtils;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
@@ -178,7 +178,7 @@
   }
 
   private static byte[] buildTextLog(String tag, String string) {
-    Calendar c = DialerStrictMode.bypass(() -> Calendar.getInstance());
+    Calendar c = StrictModeUtils.bypass(() -> Calendar.getInstance());
     return String.format("%tm-%td %tH:%tM:%tS.%tL - %s - %s", c, c, c, c, c, c, tag, string)
         .getBytes(StandardCharsets.UTF_8);
   }
diff --git a/java/com/android/dialer/strictmode/DialerStrictMode.java b/java/com/android/dialer/strictmode/DialerStrictMode.java
index f895f7c..462db57 100644
--- a/java/com/android/dialer/strictmode/DialerStrictMode.java
+++ b/java/com/android/dialer/strictmode/DialerStrictMode.java
@@ -17,131 +17,12 @@
 package com.android.dialer.strictmode;
 
 import android.app.Application;
-import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.StrictMode;
-import android.os.StrictMode.ThreadPolicy;
-import android.os.StrictMode.VmPolicy;
-import android.preference.PreferenceManager;
-import android.support.annotation.AnyThread;
 import android.support.annotation.MainThread;
-import android.support.v4.os.UserManagerCompat;
-import com.android.dialer.buildtype.BuildType;
-import com.android.dialer.function.Supplier;
-import com.android.dialer.util.DialerUtils;
 
-/** Enables strict mode for the application, and provides means of temporarily disabling it. */
-public final class DialerStrictMode {
-
-  private static final VmPolicy VM_DEATH_PENALTY =
-      new StrictMode.VmPolicy.Builder().penaltyLog().penaltyDeath().build();
-
-  private static final ThreadPolicy THREAD_LOG_PENALTY =
-      new StrictMode.ThreadPolicy.Builder().penaltyLog().build();
-  private static final ThreadPolicy THREAD_DEATH_PENALTY =
-      new StrictMode.ThreadPolicy.Builder().penaltyLog().penaltyDeath().build();
-
-  private DialerStrictMode() {}
+/** Interface for strict mode to handle strict mode violations. */
+public interface DialerStrictMode {
 
   /** Initializes strict mode on application start. */
   @MainThread
-  public static void onApplicationCreate(Application application) {
-    if (isStrictModeAllowed()) {
-      warmupSharedPrefs(application);
-      StrictModeUtils.setRecommendedMainThreadPolicy(THREAD_DEATH_PENALTY);
-      StrictModeUtils.setRecommendedVMPolicy(VM_DEATH_PENALTY);
-
-      // Because Android resets StrictMode policies after Application.onCreate is done, we set it
-      // again right after.
-      // See cl/105932355 for the discussion.
-      // See b/36951662 for the public bug.
-      Handler handler = new Handler(Looper.myLooper());
-      handler.postAtFrontOfQueue(
-          () -> StrictModeUtils.setRecommendedMainThreadPolicy(THREAD_DEATH_PENALTY));
-    }
-  }
-
-  /**
-   * We frequently access shared preferences on the main thread, which causes strict mode
-   * violations. When strict mode is allowed, warm up the shared preferences so that later uses of
-   * shared preferences access the in-memory versions and we don't have to bypass strict mode at
-   * every point in the application where shared preferences are accessed.
-   */
-  private static void warmupSharedPrefs(Application application) {
-    // From credential-encrypted (CE) storage, i.e.:
-    //    /data/data/com.android.dialer/shared_prefs
-
-    if (UserManagerCompat.isUserUnlocked(application)) {
-      // <package_name>_preferences.xml
-      PreferenceManager.getDefaultSharedPreferences(application);
-
-      // <package_name>.xml
-      application.getSharedPreferences(application.getPackageName(), Context.MODE_PRIVATE);
-    }
-
-    // From device-encrypted (DE) storage, i.e.:
-    //   /data/user_de/0/com.android.dialer/shared_prefs/
-
-    // <package_name>_preferences.xml
-    DialerUtils.getDefaultSharedPreferenceForDeviceProtectedStorageContext(application);
-  }
-
-  private static boolean isStrictModeAllowed() {
-    return BuildType.get() == BuildType.BUGFOOD;
-  }
-
-  private static boolean onMainThread() {
-    return Looper.getMainLooper().equals(Looper.myLooper());
-  }
-
-  /**
-   * Convenience method for disabling and enabling the thread policy death penalty using lambdas.
-   *
-   * <p>For example:
-   *
-   * <p><code>
-   *   Value foo = DialerStrictMode.bypass(() -> doDiskAccessOnMainThreadReturningValue());
-   * </code>
-   *
-   * <p>The thread policy is only mutated if this is called from the main thread.
-   */
-  @AnyThread
-  public static <T> T bypass(Supplier<T> supplier) {
-    if (isStrictModeAllowed() && onMainThread()) {
-      ThreadPolicy originalPolicy = StrictMode.getThreadPolicy();
-      StrictModeUtils.setRecommendedMainThreadPolicy(THREAD_LOG_PENALTY);
-      try {
-        return supplier.get();
-      } finally {
-        StrictMode.setThreadPolicy(originalPolicy);
-      }
-    }
-    return supplier.get();
-  }
-
-  /**
-   * Convenience method for disabling and enabling the thread policy death penalty using lambdas.
-   *
-   * <p>For example:
-   *
-   * <p><code>
-   *   DialerStrictMode.bypass(() -> doDiskAccessOnMainThread());
-   * </code>
-   *
-   * <p>The thread policy is only mutated if this is called from the main thread.
-   */
-  @AnyThread
-  public static void bypass(Runnable runnable) {
-    if (isStrictModeAllowed() && onMainThread()) {
-      ThreadPolicy originalPolicy = StrictMode.getThreadPolicy();
-      StrictModeUtils.setRecommendedMainThreadPolicy(THREAD_LOG_PENALTY);
-      try {
-        runnable.run();
-      } finally {
-        StrictMode.setThreadPolicy(originalPolicy);
-      }
-    }
-    runnable.run();
-  }
+  void onApplicationCreate(Application application);
 }
diff --git a/java/com/android/dialer/strictmode/StrictModeComponent.java b/java/com/android/dialer/strictmode/StrictModeComponent.java
new file mode 100644
index 0000000..7b9f48e
--- /dev/null
+++ b/java/com/android/dialer/strictmode/StrictModeComponent.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.strictmode;
+
+import android.content.Context;
+import com.android.dialer.inject.HasRootComponent;
+import dagger.Subcomponent;
+
+/** Dagger component for DialerStrictMode. */
+@Subcomponent
+public abstract class StrictModeComponent {
+
+  public abstract DialerStrictMode getDialerStrictMode();
+
+  public static StrictModeComponent get(Context context) {
+    return ((StrictModeComponent.HasComponent)
+            ((HasRootComponent) context.getApplicationContext()).component())
+        .strictModeComponent();
+  }
+
+  /** Used to refer to the root application component. */
+  public interface HasComponent {
+    StrictModeComponent strictModeComponent();
+  }
+}
diff --git a/java/com/android/dialer/strictmode/StrictModeUtils.java b/java/com/android/dialer/strictmode/StrictModeUtils.java
index 6944fd4..c83beb0 100644
--- a/java/com/android/dialer/strictmode/StrictModeUtils.java
+++ b/java/com/android/dialer/strictmode/StrictModeUtils.java
@@ -16,104 +16,76 @@
 
 package com.android.dialer.strictmode;
 
-import android.os.Build;
+import android.os.Looper;
 import android.os.StrictMode;
-import android.support.annotation.Nullable;
-import com.android.dialer.common.Assert;
-import com.google.auto.value.AutoValue;
-import java.util.Map;
-import java.util.Map.Entry;
+import android.os.StrictMode.ThreadPolicy;
+import android.support.annotation.AnyThread;
+import com.android.dialer.buildtype.BuildType;
+import com.android.dialer.function.Supplier;
 
 /** Utilities for enforcing strict-mode in an app. */
-final class StrictModeUtils {
+public final class StrictModeUtils {
+
+  private static final ThreadPolicy THREAD_NO_PENALTY =
+      new StrictMode.ThreadPolicy.Builder().permitAll().build();
 
   /**
-   * Set the recommended policy for the app.
+   * Convenience method for disabling and enabling the thread policy death penalty using lambdas.
    *
-   * @param threadPenalties policy with preferred penalties. Detection bits will be ignored.
-   */
-  static void setRecommendedMainThreadPolicy(StrictMode.ThreadPolicy threadPenalties) {
-    StrictMode.ThreadPolicy threadPolicy =
-        new StrictMode.ThreadPolicy.Builder(threadPenalties).detectAll().build();
-    StrictMode.setThreadPolicy(threadPolicy);
-  }
-
-  /**
-   * Set the recommended policy for the app.
+   * <p>For example:
    *
-   * @param vmPenalties policy with preferred penalties. Detection bits should be unset.
-   */
-  static void setRecommendedVMPolicy(StrictMode.VmPolicy vmPenalties) {
-    setRecommendedVMPolicy(vmPenalties, StrictModeVmConfig.empty());
-  }
-
-  /**
-   * Set the recommended policy for the app.
+   * <p><code>
+   *   Value foo = StrictModeUtils.bypass(() -> doDiskAccessOnMainThreadReturningValue());
+   * </code>
    *
-   * @param vmPenalties policy with preferred penalties. Detection bits should be unset.
+   * <p>The thread policy is only mutated if this is called from the main thread.
    */
-  private static void setRecommendedVMPolicy(
-      StrictMode.VmPolicy vmPenalties, StrictModeVmConfig config) {
-    Assert.isNotNull(config);
-    StrictMode.VmPolicy.Builder vmPolicyBuilder =
-        new StrictMode.VmPolicy.Builder(vmPenalties)
-            .detectLeakedClosableObjects()
-            .detectLeakedSqlLiteObjects();
-    if (Build.VERSION.SDK_INT >= 16) {
-      vmPolicyBuilder.detectLeakedRegistrationObjects();
-    }
-    if (Build.VERSION.SDK_INT >= 18) {
-      vmPolicyBuilder.detectFileUriExposure();
-    }
-    if (Build.VERSION.SDK_INT >= 21) {
-      // Even though this API is available earlier, it did not properly run finalizers.
-      // This avoids lots of false positives.
-
-      // TODO(zachh): Use LeakCanary and remove this line.
-      vmPolicyBuilder.detectActivityLeaks();
-
-      if (config.maxInstanceLimits() != null) {
-        for (Entry<Class<?>, Integer> entry : config.maxInstanceLimits().entrySet()) {
-          vmPolicyBuilder.setClassInstanceLimit(entry.getKey(), entry.getValue());
-        }
+  @AnyThread
+  public static <T> T bypass(Supplier<T> supplier) {
+    if (isStrictModeAllowed() && onMainThread()) {
+      ThreadPolicy originalPolicy = StrictMode.getThreadPolicy();
+      StrictMode.setThreadPolicy(THREAD_NO_PENALTY);
+      try {
+        return supplier.get();
+      } finally {
+        StrictMode.setThreadPolicy(originalPolicy);
       }
     }
-    if (Build.VERSION.SDK_INT >= 23) {
-      // TODO(azlatin): Enable clear-text check once b/36730713 is fixed.
-      // vmPolicyBuilder.detectCleartextNetwork();
-    }
-    // Once OC Lands:
-    // .detectContentUriWithoutPermission()
-    // .detectUntaggedSockets()
-    StrictMode.setVmPolicy(vmPolicyBuilder.build());
+    return supplier.get();
   }
 
-  /** VmPolicy configuration. */
-  @AutoValue
-  abstract static class StrictModeVmConfig {
-    /** A map of a class to the maximum number of allowed instances of that class. */
-    @Nullable
-    abstract Map<Class<?>, Integer> maxInstanceLimits();
-
-    public static StrictModeVmConfig empty() {
-      return builder().build();
+  /**
+   * Convenience method for disabling and enabling the thread policy death penalty using lambdas.
+   *
+   * <p>For example:
+   *
+   * <p><code>
+   *   StrictModeUtils.bypass(() -> doDiskAccessOnMainThread());
+   * </code>
+   *
+   * <p>The thread policy is only mutated if this is called from the main thread.
+   */
+  @AnyThread
+  public static void bypass(Runnable runnable) {
+    if (isStrictModeAllowed() && onMainThread()) {
+      ThreadPolicy originalPolicy = StrictMode.getThreadPolicy();
+      StrictMode.setThreadPolicy(THREAD_NO_PENALTY);
+      try {
+        runnable.run();
+      } finally {
+        StrictMode.setThreadPolicy(originalPolicy);
+      }
+    } else {
+      runnable.run();
     }
+  }
 
-    public static Builder builder() {
-      return new AutoValue_StrictModeUtils_StrictModeVmConfig.Builder();
-    }
+  public static boolean isStrictModeAllowed() {
+    return BuildType.get() == BuildType.BUGFOOD;
+  }
 
-    /** VmPolicy configuration builder. */
-    @AutoValue.Builder
-    public abstract static class Builder {
-      public abstract Builder setMaxInstanceLimits(Map<Class<?>, Integer> limits);
-
-      public abstract StrictModeVmConfig build();
-
-      Builder() {}
-    }
-
-    StrictModeVmConfig() {}
+  private static boolean onMainThread() {
+    return Looper.getMainLooper().equals(Looper.myLooper());
   }
 
   private StrictModeUtils() {}
diff --git a/java/com/android/dialer/strictmode/impl/SystemDialerStrictMode.java b/java/com/android/dialer/strictmode/impl/SystemDialerStrictMode.java
new file mode 100644
index 0000000..4d65241
--- /dev/null
+++ b/java/com/android/dialer/strictmode/impl/SystemDialerStrictMode.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.strictmode.impl;
+
+import android.app.Application;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.StrictMode;
+import android.os.StrictMode.ThreadPolicy;
+import android.os.StrictMode.VmPolicy;
+import android.preference.PreferenceManager;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+import android.support.v4.os.UserManagerCompat;
+import com.android.dialer.buildtype.BuildType;
+import com.android.dialer.common.Assert;
+import com.android.dialer.strictmode.DialerStrictMode;
+import com.android.dialer.util.DialerUtils;
+import com.google.auto.value.AutoValue;
+import java.util.Map;
+import javax.inject.Inject;
+
+final class SystemDialerStrictMode implements DialerStrictMode {
+  private static final VmPolicy VM_DEATH_PENALTY =
+      new StrictMode.VmPolicy.Builder().penaltyLog().penaltyDeath().build();
+
+  private static final ThreadPolicy THREAD_DEATH_PENALTY =
+      new StrictMode.ThreadPolicy.Builder().penaltyLog().penaltyDeath().build();
+
+  @Inject
+  public SystemDialerStrictMode() {}
+
+  @MainThread
+  @Override
+  public void onApplicationCreate(Application application) {
+    if (isStrictModeAllowed()) {
+      warmupSharedPrefs(application);
+      setRecommendedMainThreadPolicy(THREAD_DEATH_PENALTY);
+      setRecommendedVMPolicy(VM_DEATH_PENALTY);
+
+      // Because Android resets StrictMode policies after Application.onCreate is done, we set it
+      // again right after.
+      // See cl/105932355 for the discussion.
+      // See b/36951662 for the public bug.
+      Handler handler = new Handler(Looper.myLooper());
+      handler.postAtFrontOfQueue(() -> setRecommendedMainThreadPolicy(THREAD_DEATH_PENALTY));
+    }
+  }
+
+  /**
+   * We frequently access shared preferences on the main thread, which causes strict mode
+   * violations. When strict mode is allowed, warm up the shared preferences so that later uses of
+   * shared preferences access the in-memory versions and we don't have to bypass strict mode at
+   * every point in the application where shared preferences are accessed.
+   */
+  private static void warmupSharedPrefs(Application application) {
+    // From credential-encrypted (CE) storage, i.e.:
+    //    /data/data/com.android.dialer/shared_prefs
+
+    if (UserManagerCompat.isUserUnlocked(application)) {
+      // <package_name>_preferences.xml
+      PreferenceManager.getDefaultSharedPreferences(application);
+
+      // <package_name>.xml
+      application.getSharedPreferences(application.getPackageName(), Context.MODE_PRIVATE);
+    }
+
+    // From device-encrypted (DE) storage, i.e.:
+    //   /data/user_de/0/com.android.dialer/shared_prefs/
+
+    // <package_name>_preferences.xml
+    DialerUtils.getDefaultSharedPreferenceForDeviceProtectedStorageContext(application);
+  }
+
+  private static boolean isStrictModeAllowed() {
+    return BuildType.get() == BuildType.BUGFOOD;
+  }
+
+  /**
+   * Set the recommended policy for the app.
+   *
+   * @param threadPenalties policy with preferred penalties. Detection bits will be ignored.
+   */
+  private static void setRecommendedMainThreadPolicy(StrictMode.ThreadPolicy threadPenalties) {
+    StrictMode.ThreadPolicy threadPolicy =
+        new StrictMode.ThreadPolicy.Builder(threadPenalties).detectAll().build();
+    StrictMode.setThreadPolicy(threadPolicy);
+  }
+
+  /**
+   * Set the recommended policy for the app.
+   *
+   * @param vmPenalties policy with preferred penalties. Detection bits should be unset.
+   */
+  private static void setRecommendedVMPolicy(StrictMode.VmPolicy vmPenalties) {
+    setRecommendedVMPolicy(vmPenalties, StrictModeVmConfig.empty());
+  }
+
+  /**
+   * Set the recommended policy for the app.
+   *
+   * @param vmPenalties policy with preferred penalties. Detection bits should be unset.
+   */
+  private static void setRecommendedVMPolicy(
+      StrictMode.VmPolicy vmPenalties, StrictModeVmConfig config) {
+    Assert.isNotNull(config);
+    StrictMode.VmPolicy.Builder vmPolicyBuilder =
+        new StrictMode.VmPolicy.Builder(vmPenalties)
+            .detectLeakedClosableObjects()
+            .detectLeakedSqlLiteObjects();
+    if (Build.VERSION.SDK_INT >= 26) {
+      vmPolicyBuilder.detectContentUriWithoutPermission();
+      // TODO(azlatin): Enable detecting untagged sockets once: b/64840386 is fixed.
+      // vmPolicyBuilder.detectUntaggedSockets();
+    }
+    StrictMode.setVmPolicy(vmPolicyBuilder.build());
+  }
+
+  /** VmPolicy configuration. */
+  @AutoValue
+  abstract static class StrictModeVmConfig {
+    /** A map of a class to the maximum number of allowed instances of that class. */
+    @Nullable
+    abstract Map<Class<?>, Integer> maxInstanceLimits();
+
+    public static StrictModeVmConfig empty() {
+      return builder().build();
+    }
+
+    public static Builder builder() {
+      return new AutoValue_SystemDialerStrictMode_StrictModeVmConfig.Builder();
+    }
+
+    /** VmPolicy configuration builder. */
+    @AutoValue.Builder
+    public abstract static class Builder {
+      public abstract Builder setMaxInstanceLimits(Map<Class<?>, Integer> limits);
+
+      public abstract StrictModeVmConfig build();
+
+      Builder() {}
+    }
+
+    StrictModeVmConfig() {}
+  }
+}
diff --git a/java/com/android/dialer/strictmode/impl/SystemStrictModeModule.java b/java/com/android/dialer/strictmode/impl/SystemStrictModeModule.java
new file mode 100644
index 0000000..6ece874
--- /dev/null
+++ b/java/com/android/dialer/strictmode/impl/SystemStrictModeModule.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.dialer.strictmode.impl;
+
+import com.android.dialer.strictmode.DialerStrictMode;
+import dagger.Binds;
+import dagger.Module;
+import javax.inject.Singleton;
+
+/** Module which binds {@link SystemDialerStrictMode}. */
+@Module
+public abstract class SystemStrictModeModule {
+
+  @Binds
+  @Singleton
+  public abstract DialerStrictMode bindDialerStrictMode(SystemDialerStrictMode impl);
+}
diff --git a/java/com/android/incallui/CallerInfoAsyncQuery.java b/java/com/android/incallui/CallerInfoAsyncQuery.java
index 86b1b7f..09752c7 100644
--- a/java/com/android/incallui/CallerInfoAsyncQuery.java
+++ b/java/com/android/incallui/CallerInfoAsyncQuery.java
@@ -41,7 +41,7 @@
 import com.android.dialer.phonenumbercache.CachedNumberLookupService.CachedContactInfo;
 import com.android.dialer.phonenumbercache.ContactInfoHelper;
 import com.android.dialer.phonenumbercache.PhoneNumberCache;
-import com.android.dialer.strictmode.DialerStrictMode;
+import com.android.dialer.strictmode.StrictModeUtils;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -191,7 +191,7 @@
       CallerInfo info,
       OnQueryCompleteListener listener,
       Object cookie) {
-    long[] directoryIds = DialerStrictMode.bypass(() -> getDirectoryIds(context));
+    long[] directoryIds = StrictModeUtils.bypass(() -> getDirectoryIds(context));
     int size = directoryIds.length;
     if (size == 0) {
       return false;
diff --git a/java/com/android/incallui/calllocation/impl/GoogleLocationSettingHelper.java b/java/com/android/incallui/calllocation/impl/GoogleLocationSettingHelper.java
index ed2e843..305ab43 100644
--- a/java/com/android/incallui/calllocation/impl/GoogleLocationSettingHelper.java
+++ b/java/com/android/incallui/calllocation/impl/GoogleLocationSettingHelper.java
@@ -26,7 +26,7 @@
 import android.provider.Settings.Secure;
 import android.provider.Settings.SettingNotFoundException;
 import com.android.dialer.common.LogUtil;
-import com.android.dialer.strictmode.DialerStrictMode;
+import com.android.dialer.strictmode.StrictModeUtils;
 
 /**
  * Helper class to check if Google Location Services is enabled. This class is based on
@@ -121,7 +121,7 @@
     if (!isEnforceable(context)) {
       return true;
     }
-    int locationServiceStatus = DialerStrictMode.bypass(() -> getUseLocationForServices(context));
+    int locationServiceStatus = StrictModeUtils.bypass(() -> getUseLocationForServices(context));
     return locationServiceStatus == USE_LOCATION_FOR_SERVICES_ON;
   }
 }
diff --git a/java/com/android/incallui/incall/impl/InCallFragment.java b/java/com/android/incallui/incall/impl/InCallFragment.java
index d91b5f2..1317565 100644
--- a/java/com/android/incallui/incall/impl/InCallFragment.java
+++ b/java/com/android/incallui/incall/impl/InCallFragment.java
@@ -44,7 +44,7 @@
 import com.android.dialer.logging.DialerImpression;
 import com.android.dialer.logging.Logger;
 import com.android.dialer.multimedia.MultimediaData;
-import com.android.dialer.strictmode.DialerStrictMode;
+import com.android.dialer.strictmode.StrictModeUtils;
 import com.android.dialer.widget.LockableViewPager;
 import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment;
 import com.android.incallui.audioroute.AudioRouteSelectorDialogFragment.AudioRouteSelectorPresenter;
@@ -142,7 +142,7 @@
     LogUtil.i("InCallFragment.onCreateView", null);
     // Bypass to avoid StrictModeResourceMismatchViolation
     final View view =
-        DialerStrictMode.bypass(
+        StrictModeUtils.bypass(
             () -> layoutInflater.inflate(R.layout.frag_incall_voice, viewGroup, false));
     contactGridManager =
         new ContactGridManager(
diff --git a/java/com/android/voicemail/impl/VoicemailStatus.java b/java/com/android/voicemail/impl/VoicemailStatus.java
index 5553cf5..a24bad4 100644
--- a/java/com/android/voicemail/impl/VoicemailStatus.java
+++ b/java/com/android/voicemail/impl/VoicemailStatus.java
@@ -24,7 +24,7 @@
 import android.provider.VoicemailContract.Status;
 import android.support.annotation.Nullable;
 import android.telecom.PhoneAccountHandle;
-import com.android.dialer.strictmode.DialerStrictMode;
+import com.android.dialer.strictmode.StrictModeUtils;
 
 public class VoicemailStatus {
 
@@ -100,7 +100,7 @@
       ContentResolver contentResolver = mContext.getContentResolver();
       Uri statusUri = VoicemailContract.Status.buildSourceUri(mContext.getPackageName());
       try {
-        DialerStrictMode.bypass(() -> contentResolver.insert(statusUri, mValues));
+        StrictModeUtils.bypass(() -> contentResolver.insert(statusUri, mValues));
       } catch (IllegalArgumentException iae) {
         VvmLog.e(TAG, "apply :: failed to insert content resolver ", iae);
         mValues.clear();
diff --git a/java/com/android/voicemail/impl/configui/ConfigOverrideFragment.java b/java/com/android/voicemail/impl/configui/ConfigOverrideFragment.java
index caf33df..d05940a 100644
--- a/java/com/android/voicemail/impl/configui/ConfigOverrideFragment.java
+++ b/java/com/android/voicemail/impl/configui/ConfigOverrideFragment.java
@@ -35,7 +35,7 @@
 import android.text.TextUtils;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.concurrent.ThreadUtil;
-import com.android.dialer.strictmode.DialerStrictMode;
+import com.android.dialer.strictmode.StrictModeUtils;
 import com.android.voicemail.VoicemailComponent;
 
 /**
@@ -128,7 +128,7 @@
   }
 
   public static boolean isOverridden(Context context) {
-    return DialerStrictMode.bypass(
+    return StrictModeUtils.bypass(
         () ->
             PreferenceManager.getDefaultSharedPreferences(context)
                 .getBoolean(context.getString(R.string.vvm_config_override_enabled_key), false));
diff --git a/java/com/android/voicemail/impl/scheduling/TaskSchedulerJobService.java b/java/com/android/voicemail/impl/scheduling/TaskSchedulerJobService.java
index baf5804..0e3f27c 100644
--- a/java/com/android/voicemail/impl/scheduling/TaskSchedulerJobService.java
+++ b/java/com/android/voicemail/impl/scheduling/TaskSchedulerJobService.java
@@ -30,7 +30,7 @@
 import android.preference.PreferenceManager;
 import android.support.annotation.MainThread;
 import com.android.dialer.constants.ScheduledJobIds;
-import com.android.dialer.strictmode.DialerStrictMode;
+import com.android.dialer.strictmode.StrictModeUtils;
 import com.android.voicemail.impl.Assert;
 import com.android.voicemail.impl.VvmLog;
 import java.util.ArrayList;
@@ -59,7 +59,7 @@
   public boolean onStartJob(JobParameters params) {
     int jobId = params.getTransientExtras().getInt(EXTRA_JOB_ID);
     int expectedJobId =
-        DialerStrictMode.bypass(
+        StrictModeUtils.bypass(
             () -> PreferenceManager.getDefaultSharedPreferences(this).getInt(EXPECTED_JOB_ID, 0));
     if (jobId != expectedJobId) {
       VvmLog.e(
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
index 60b97da..a93c651 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTask.java
@@ -57,7 +57,7 @@
 public abstract class TranscriptionTask implements Runnable {
   private static final String TAG = "TranscriptionTask";
 
-  private final Context context;
+  protected final Context context;
   private final JobCallback callback;
   private final JobWorkItem workItem;
   private final TranscriptionClientFactory clientFactory;
diff --git a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
index 930d7f1..e757280 100644
--- a/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
+++ b/java/com/android/voicemail/impl/transcribe/TranscriptionTaskAsync.java
@@ -20,6 +20,7 @@
 import android.util.Pair;
 import com.android.dialer.common.Assert;
 import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
 import com.android.voicemail.impl.VvmLog;
 import com.android.voicemail.impl.transcribe.TranscriptionService.JobCallback;
 import com.android.voicemail.impl.transcribe.grpc.GetTranscriptResponseAsync;
@@ -94,6 +95,7 @@
         VvmLog.i(TAG, "pollForTranscription, cancelled.");
         return new Pair<>(null, TranscriptionStatus.FAILED_NO_RETRY);
       }
+      Logger.get(context).logImpression(DialerImpression.Type.VVM_TRANSCRIPTION_POLL_REQUEST);
       GetTranscriptResponseAsync response =
           (GetTranscriptResponseAsync)
               sendRequest((client) -> client.sendGetTranscriptRequest(request));