Merge "Small changes to ImsTestApp when testing ImsService changes"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a7c2ae3..f68b72e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -22,10 +22,6 @@
         android:sharedUserLabel="@string/phoneAppLabel"
 >
 
-    <uses-sdk
-        android:minSdkVersion="23"
-        android:targetSdkVersion="26" />
-
     <original-package android:name="com.android.phone" />
 
     <protected-broadcast android:name="android.telecom.action.TTY_PREFERRED_MODE_CHANGED" />
@@ -94,6 +90,9 @@
     <protected-broadcast android:name= "android.telephony.action.SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED" />
     <protected-broadcast android:name= "android.telephony.action.NETWORK_COUNTRY_CHANGED" />
 
+    <!-- For Vendor Debugging in Telephony -->
+    <protected-broadcast android:name="android.telephony.debug.action.DEBUG_EVENT" />
+
     <uses-permission android:name="android.permission.BROADCAST_STICKY" />
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.CALL_PRIVILEGED" />
@@ -195,6 +194,7 @@
     <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
     <uses-permission android:name="android.permission.BIND_TELEPHONY_DATA_SERVICE" />
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
+    <uses-permission android:name="android.permission.READ_PRECISE_PHONE_STATE" />
 
     <application android:name="PhoneApp"
             android:persistent="true"
diff --git a/res/layout/pref_dialog_editpin.xml b/res/layout/pref_dialog_editpin.xml
index a278690..94cdadf 100644
--- a/res/layout/pref_dialog_editpin.xml
+++ b/res/layout/pref_dialog_editpin.xml
@@ -22,7 +22,7 @@
     android:orientation="vertical"
     android:padding="?android:attr/dialogPreferredPadding">
 
-    <TextView android:id="@+android:id/message"
+    <TextView android:id="@android:id/message"
         style="?android:attr/textAppearanceSmall"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/res/values/config.xml b/res/values/config.xml
index b1f8ae8..6b6bf04 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -254,6 +254,7 @@
     <!-- Intent action to launch target emergency app. -->
     <string name="config_emergency_app_intent" translatable="false"></string>
 
-    <!-- Flag indicating whether shortcut view of promoted emergency numbers should be enabled. -->
-    <bool name="config_emergency_shortcut_view_enabled">false</bool>
+    <!-- The country list that shortcut view can be enabled. -->
+    <string-array name="config_countries_to_enable_shortcut_view" translatable="false">
+    </string-array>
 </resources>
diff --git a/res/xml/phone_account_settings.xml b/res/xml/phone_account_settings.xml
index ae3e9d9..d230328 100644
--- a/res/xml/phone_account_settings.xml
+++ b/res/xml/phone_account_settings.xml
@@ -29,6 +29,7 @@
             android:order="1" />
 
         <PreferenceScreen
+            android:key="phone_accounts_all_calling_accounts"
             android:title="@string/phone_accounts_all_calling_accounts"
             android:summary="@string/phone_accounts_all_calling_accounts_summary"
             android:persistent="false"
diff --git a/src/com/android/phone/EmergencyDialer.java b/src/com/android/phone/EmergencyDialer.java
index 80b4632..d7443d5 100644
--- a/src/com/android/phone/EmergencyDialer.java
+++ b/src/com/android/phone/EmergencyDialer.java
@@ -810,10 +810,6 @@
     }
 
     private boolean canEnableShortcutView(PersistableBundle carrierConfig) {
-        if (!getResources().getBoolean(R.bool.config_emergency_shortcut_view_enabled)) {
-            // Disables shortcut view by project.
-            return false;
-        }
         if (!carrierConfig.getBoolean(
                 CarrierConfigManager.KEY_SUPPORT_EMERGENCY_DIALER_SHORTCUT_BOOL)) {
             Log.d(LOG_TAG, "Disables shortcut view by carrier requirement");
diff --git a/src/com/android/phone/PhoneApp.java b/src/com/android/phone/PhoneApp.java
index 333e0ec..df151bf 100644
--- a/src/com/android/phone/PhoneApp.java
+++ b/src/com/android/phone/PhoneApp.java
@@ -19,7 +19,6 @@
 import android.app.Application;
 import android.os.UserHandle;
 
-import com.android.phone.ecc.IsoToEccProtobufRepository;
 import com.android.services.telephony.TelecomAccountRegistry;
 
 /**
@@ -41,10 +40,5 @@
 
             TelecomAccountRegistry.getInstance(this).setupOnBoot();
         }
-
-        new Thread(() -> {
-            // Preload ECC table in background.
-            IsoToEccProtobufRepository.getInstance().loadMappingTable(PhoneApp.this);
-        }).start();
     }
 }
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index ccbb4d0..3d82e74 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -44,6 +44,7 @@
 import android.provider.Settings;
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
+import android.telephony.DebugEventReporter;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -283,6 +284,9 @@
         //   getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_VOICE_CALLS);
 
         if (mCM == null) {
+            // Initialize DebugEventReporter early so that it can be used
+            DebugEventReporter.initialize(this);
+
             // Inject telephony component factory if configured using other jars.
             XmlResourceParser parser = getResources().getXml(R.xml.telephony_injection);
             TelephonyComponentFactory.getInstance().injectTheComponentFactory(parser);
@@ -594,14 +598,22 @@
                     airplaneMode = AIRPLANE_ON;
                 }
                 handleAirplaneModeChange(context, airplaneMode);
-            } else if ((action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) &&
-                    (mPUKEntryActivity != null)) {
-                // if an attempt to un-PUK-lock the device was made, while we're
-                // receiving this state change notification, notify the handler.
-                // NOTE: This is ONLY triggered if an attempt to un-PUK-lock has
-                // been attempted.
-                mHandler.sendMessage(mHandler.obtainMessage(EVENT_SIM_STATE_CHANGED,
-                        intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE)));
+            } else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
+                // re-register as it may be a new IccCard
+                int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY,
+                        SubscriptionManager.INVALID_PHONE_INDEX);
+                if (SubscriptionManager.isValidPhoneId(phoneId)) {
+                    PhoneUtils.unregisterIccStatus(mHandler, phoneId);
+                    PhoneUtils.registerIccStatus(mHandler, EVENT_SIM_NETWORK_LOCKED, phoneId);
+                }
+                if (mPUKEntryActivity != null) {
+                    // if an attempt to un-PUK-lock the device was made, while we're
+                    // receiving this state change notification, notify the handler.
+                    // NOTE: This is ONLY triggered if an attempt to un-PUK-lock has
+                    // been attempted.
+                    mHandler.sendMessage(mHandler.obtainMessage(EVENT_SIM_STATE_CHANGED,
+                            intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE)));
+                }
             } else if (action.equals(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED)) {
                 String newPhone = intent.getStringExtra(PhoneConstants.PHONE_NAME_KEY);
                 Log.d(LOG_TAG, "Radio technology switched. Now " + newPhone + " is active.");
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 3078220..13accc9 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -35,6 +35,7 @@
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -135,6 +136,7 @@
 import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.euicc.EuiccConnector;
+import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.uicc.IccIoResult;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.SIMRecords;
@@ -156,6 +158,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -1103,6 +1106,7 @@
                     ar = (AsyncResult) msg.obj;
                     request = (MainThreadRequest) ar.userObj;
                     request.result = (ar.exception == null);
+                    updateModemStateMetrics();
                     notifyRequester(request);
                     break;
                 default:
@@ -1841,9 +1845,21 @@
     public Bundle getCellLocation(String callingPackage) {
         mApp.getSystemService(AppOpsManager.class)
                 .checkPackage(Binder.getCallingUid(), callingPackage);
-        if (!LocationAccessPolicy.canAccessCellLocation(mApp, callingPackage,
-                Binder.getCallingUid(), Binder.getCallingPid(), true)) {
-            return null;
+
+        LocationAccessPolicy.LocationPermissionResult locationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("getCellLocation")
+                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                                .build());
+        switch (locationResult) {
+            case DENIED_HARD:
+                throw new SecurityException("Not allowed to access cell location");
+            case DENIED_SOFT:
+                return new Bundle();
         }
 
         WorkSource workSource = getWorkSource(Binder.getCallingUid());
@@ -1993,9 +2009,21 @@
     public List<CellInfo> getAllCellInfo(String callingPackage) {
         mApp.getSystemService(AppOpsManager.class)
                 .checkPackage(Binder.getCallingUid(), callingPackage);
-        if (!LocationAccessPolicy.canAccessCellLocation(mApp,
-                callingPackage, Binder.getCallingUid(), Binder.getCallingPid(), true)) {
-            return null;
+
+        LocationAccessPolicy.LocationPermissionResult locationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("getAllCellInfo")
+                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                                .build());
+        switch (locationResult) {
+            case DENIED_HARD:
+                throw new SecurityException("Not allowed to access cell info");
+            case DENIED_SOFT:
+                return new ArrayList<>();
         }
 
         final int targetSdk = getTargetSdk(callingPackage);
@@ -2036,9 +2064,21 @@
             int subId, ICellInfoCallback cb, String callingPackage, WorkSource workSource) {
         mApp.getSystemService(AppOpsManager.class)
                 .checkPackage(Binder.getCallingUid(), callingPackage);
-        if (!LocationAccessPolicy.canAccessCellLocation(mApp, callingPackage,
-                Binder.getCallingUid(), Binder.getCallingPid(), true)) {
-            return;
+
+        LocationAccessPolicy.LocationPermissionResult locationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("requestCellInfoUpdate")
+                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                                .build());
+        switch (locationResult) {
+            case DENIED_HARD:
+                throw new SecurityException("Not allowed to access cell info");
+            case DENIED_SOFT:
+                return;
         }
 
         final Phone phone = getPhone(subId);
@@ -2819,6 +2859,9 @@
         } catch (ImsException e) {
             Log.w(LOG_TAG, "IMS isCapable - service unavailable: " + e.getMessage());
             return false;
+        } catch (IllegalArgumentException e) {
+            Log.i(LOG_TAG, "isCapable: " + subId + " is inactive, returning false.");
+            return false;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -3403,7 +3446,7 @@
     private int getSlotIndexOrException(int subId) throws IllegalArgumentException {
         int slotId = SubscriptionManager.getSlotIndex(subId);
         if (!SubscriptionManager.isValidSlotIndex(slotId)) {
-            throw new IllegalArgumentException("Invalid Subscription Id.");
+            throw new IllegalArgumentException("Invalid Subscription Id, subId=" + subId);
         }
         return slotId;
     }
@@ -4184,9 +4227,24 @@
      * Scans for available networks.
      */
     @Override
-    public CellNetworkScanResult getCellNetworkScanResults(int subId) {
+    public CellNetworkScanResult getCellNetworkScanResults(int subId, String callingPackage) {
         TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
                 mApp, subId, "getCellNetworkScanResults");
+        LocationAccessPolicy.LocationPermissionResult locationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("getCellNetworkScanResults")
+                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                                .build());
+        switch (locationResult) {
+            case DENIED_HARD:
+                throw new SecurityException("Not allowed to access scan results -- location");
+            case DENIED_SOFT:
+                return null;
+        }
 
         long identity = Binder.clearCallingIdentity();
         try {
@@ -4209,17 +4267,29 @@
      */
     @Override
     public int requestNetworkScan(int subId, NetworkScanRequest request, Messenger messenger,
-            IBinder binder) {
+            IBinder binder, String callingPackage) {
         TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
                 mApp, subId, "requestNetworkScan");
 
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return mNetworkScanRequestTracker.startNetworkScan(
-                    request, messenger, binder, getPhone(subId));
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        LocationAccessPolicy.LocationPermissionResult locationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("requestNetworkScan")
+                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                                .build());
+        switch (locationResult) {
+            case DENIED_HARD:
+                throw new SecurityException("Not allowed to request network scan -- location");
+            case DENIED_SOFT:
+                return -1;
         }
+
+        return mNetworkScanRequestTracker.startNetworkScan(
+                request, messenger, binder, getPhone(subId),
+                callingPackage);
     }
 
     /**
@@ -4272,8 +4342,9 @@
      */
     @Override
     public int getPreferredNetworkType(int subId) {
-        TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
-                mApp, subId, "getPreferredNetworkType");
+        TelephonyPermissions
+                .enforeceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+                        mApp, subId, "getPreferredNetworkType");
 
         final long identity = Binder.clearCallingIdentity();
         try {
@@ -5259,6 +5330,31 @@
             return null;
         }
 
+        LocationAccessPolicy.LocationPermissionResult fineLocationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("getServiceStateForSubscriber")
+                                .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                                .build());
+
+        LocationAccessPolicy.LocationPermissionResult coarseLocationResult =
+                LocationAccessPolicy.checkLocationPermission(mApp,
+                        new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                                .setCallingPackage(callingPackage)
+                                .setCallingPid(Binder.getCallingPid())
+                                .setCallingUid(Binder.getCallingUid())
+                                .setMethod("getServiceStateForSubscriber")
+                                .setMinSdkVersionForCoarse(Build.VERSION_CODES.Q)
+                                .build());
+        // We don't care about hard or soft here -- all we need to know is how much info to scrub.
+        boolean hasFinePermission =
+                fineLocationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED;
+        boolean hasCoarsePermission =
+                coarseLocationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED;
+
         final long identity = Binder.clearCallingIdentity();
         try {
             final Phone phone = getPhone(subId);
@@ -5266,7 +5362,13 @@
                 return null;
             }
 
-            return phone.getServiceState();
+            ServiceState ss = phone.getServiceState();
+
+            // Scrub out the location info in ServiceState depending on what level of access
+            // the caller has.
+            if (hasFinePermission) return ss;
+            if (hasCoarsePermission) return ss.sanitizeLocationInfo(false);
+            return ss.sanitizeLocationInfo(true);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -5959,9 +6061,15 @@
 
     @Override
     public List<UiccCardInfo> getUiccCardsInfo(String callingPackage) {
-        if (checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
-                != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
-            throw new SecurityException("Caller does not have carrier privileges on any UICC.");
+        try {
+            enforceReadPrivilegedPermission("getUiccCardsInfo");
+        } catch (SecurityException e) {
+            // even without READ_PRIVILEGED_PHONE_STATE, we allow the call to continue if the caller
+            // has carrier privileges on an active UICC
+            if (checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
+                        != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
+                throw new SecurityException("Caller does not have carrier privileges on any UICC");
+            }
         }
 
         final long identity = Binder.clearCallingIdentity();
@@ -6517,7 +6625,7 @@
 
     /**
      * Get whether reboot is required or not after making changes to modem configurations.
-     * Return value defaults to false
+     * Return value defaults to true
      */
     @Override
     public boolean isRebootRequiredForModemConfigChange() {
@@ -6530,4 +6638,30 @@
         }
     }
 
+    private void updateModemStateMetrics() {
+        TelephonyMetrics metrics = TelephonyMetrics.getInstance();
+        // TODO: check the state for each modem if the api is ready.
+        metrics.updateEnabledModemBitmap((1 << TelephonyManager.from(mApp).getPhoneCount()) - 1);
+    }
+
+    @Override
+    public int[] getSlotsMapping() {
+        enforceReadPrivilegedPermission("getSlotsMapping");
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            int phoneCount = TelephonyManager.getDefault().getPhoneCount();
+            // All logical slots should have a mapping to a physical slot.
+            int[] logicalSlotsMapping = new int[phoneCount];
+            UiccSlotInfo[] slotInfos = getUiccSlotsInfo();
+            for (int i = 0; i < slotInfos.length; i++) {
+                if (SubscriptionManager.isValidPhoneId(slotInfos[i].getLogicalSlotIdx())) {
+                    logicalSlotsMapping[slotInfos[i].getLogicalSlotIdx()] = i;
+                }
+            }
+            return logicalSlotsMapping;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
 }
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index c1bd1b6..6f1f0a6 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -19,7 +19,6 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.ProgressDialog;
-import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -1284,6 +1283,34 @@
     }
 
     /**
+     * Register ICC status for all phones.
+     */
+    static final void registerIccStatus(Handler handler, int event, int phoneId) {
+        Phone[] phones = PhoneFactory.getPhones();
+        IccCard sim = phones[phoneId].getIccCard();
+        if (sim != null) {
+            if (VDBG) {
+                Log.v(LOG_TAG, "register for ICC status, phone " + phones[phoneId].getPhoneId());
+            }
+            sim.registerForNetworkLocked(handler, event, phones[phoneId]);
+        }
+    }
+
+    /**
+     * Unregister ICC status for a specific phone.
+     */
+    static final void unregisterIccStatus(Handler handler, int phoneId) {
+        Phone[] phones = PhoneFactory.getPhones();
+        IccCard sim = phones[phoneId].getIccCard();
+        if (sim != null) {
+            if (VDBG) {
+                Log.v(LOG_TAG, "unregister for ICC status, phone " + phones[phoneId].getPhoneId());
+            }
+            sim.unregisterForNetworkLocked(handler);
+        }
+    }
+
+    /**
      * Set the radio power on/off state for all phones.
      *
      * @param enabled true means on, false means off.
diff --git a/src/com/android/phone/ShortcutViewUtils.java b/src/com/android/phone/ShortcutViewUtils.java
index 28ee24f..595ea86 100644
--- a/src/com/android/phone/ShortcutViewUtils.java
+++ b/src/com/android/phone/ShortcutViewUtils.java
@@ -96,12 +96,12 @@
             return mPromotedEmergencyNumbers;
         }
 
-        public boolean isSufficientForEmergencyCall() {
+        public boolean isSufficientForEmergencyCall(@NonNull Context context) {
             // Checking mCountryIso because the emergency number list is not reliable to be
             // suggested to users if the device didn't camp to any network. In this case, users
             // can still try to dial emergency numbers with dial pad.
             return mCanPlaceEmergencyCall && mPromotedEmergencyNumbers != null
-                    && !TextUtils.isEmpty(mCountryIso);
+                    && isSupportedCountry(context, mCountryIso);
         }
 
         public boolean hasPromotedEmergencyNumber(String number) {
@@ -166,7 +166,7 @@
         if (defaultHandle != null) {
             PhoneInfo phone = loadPhoneInfo(defaultHandle, telephonyManager, telecomManager,
                     promotedLists);
-            if (phone.isSufficientForEmergencyCall()) {
+            if (phone.isSufficientForEmergencyCall(context)) {
                 return phone;
             }
             Log.w(LOG_TAG, "Default PhoneAccount is insufficient for emergency call: "
@@ -181,7 +181,7 @@
             for (PhoneAccountHandle handle : allHandles) {
                 PhoneInfo phone = loadPhoneInfo(handle, telephonyManager, telecomManager,
                         promotedLists);
-                if (phone.isSufficientForEmergencyCall()) {
+                if (phone.isSufficientForEmergencyCall(context)) {
                     return phone;
                 } else {
                     if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
@@ -196,6 +196,21 @@
         return null;
     }
 
+    private static boolean isSupportedCountry(@NonNull Context context, String countryIso) {
+        if (TextUtils.isEmpty(countryIso)) {
+            return false;
+        }
+
+        String[] countrysToEnableShortcutView = context.getResources().getStringArray(
+                R.array.config_countries_to_enable_shortcut_view);
+        for (String supportedCountry : countrysToEnableShortcutView) {
+            if (countryIso.equalsIgnoreCase(supportedCountry)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private static PhoneInfo loadPhoneInfo(@NonNull PhoneAccountHandle handle,
             @NonNull TelephonyManager telephonyManager, @NonNull TelecomManager telecomManager,
             Map<Integer, List<EmergencyNumber>> promotedLists) {
diff --git a/src/com/android/phone/ecc/CountryEccInfo.java b/src/com/android/phone/ecc/CountryEccInfo.java
deleted file mode 100644
index 969901d..0000000
--- a/src/com/android/phone/ecc/CountryEccInfo.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.phone.ecc;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.Collection;
-
-/**
- * ECC info of a country.
- */
-public class CountryEccInfo {
-    private final String mFallbackEcc;
-    private final EccInfo[] mEccInfoList;
-
-    public CountryEccInfo(String eccFallback, @NonNull Collection<EccInfo> eccInfoList) {
-        mFallbackEcc = eccFallback;
-        mEccInfoList = eccInfoList.toArray(new EccInfo[eccInfoList.size()]);
-    }
-
-    /**
-     * @return fallback ECC, null if not available.
-     */
-    public @Nullable String getFallbackEcc() {
-        return mFallbackEcc;
-    }
-
-    public @NonNull EccInfo[] getEccInfoList() {
-        return mEccInfoList.clone();
-    }
-}
diff --git a/src/com/android/phone/ecc/EccInfo.java b/src/com/android/phone/ecc/EccInfo.java
deleted file mode 100644
index fb41370..0000000
--- a/src/com/android/phone/ecc/EccInfo.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.phone.ecc;
-
-import androidx.annotation.NonNull;
-
-import java.util.Collection;
-
-/**
- * Emergency call code info.
- */
-public class EccInfo {
-    /**
-     * ECC Types.
-     */
-    public enum Type {
-        POLICE,
-        AMBULANCE,
-        FIRE,
-    }
-
-    private final String mNumber;
-    private final Type[] mTypes;
-
-    public EccInfo(@NonNull String number, @NonNull Type type) {
-        mNumber = number;
-        mTypes = new Type[]{ type };
-    }
-
-    public EccInfo(@NonNull String number, @NonNull Collection<Type> types) {
-        mNumber = number;
-        mTypes = types.toArray(new Type[types.size()]);
-    }
-
-    /**
-     * @return ECC number.
-     */
-    public @NonNull String getNumber() {
-        return mNumber;
-    }
-
-    /**
-     * Check whether the ECC number has any matches to the target type.
-     *
-     * @param target The target type to check.
-     * @return true if the target matches.
-     */
-    public boolean containsType(@NonNull Type target) {
-        for (Type type : mTypes) {
-            if (target.equals(type)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Get the types of the ECC number.
-     *
-     * @return Copied types array.
-     */
-    public Type[] getTypes() {
-        return mTypes.clone();
-    }
-
-    /**
-     * Get how many types the ECC number is.
-     *
-     * @return Count of types.
-     */
-    public int getTypesCount() {
-        return mTypes.length;
-    }
-}
diff --git a/src/com/android/phone/ecc/IsoToEccProtobufRepository.java b/src/com/android/phone/ecc/IsoToEccProtobufRepository.java
deleted file mode 100644
index 7d9b4f0..0000000
--- a/src/com/android/phone/ecc/IsoToEccProtobufRepository.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.phone.ecc;
-
-import android.content.Context;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.zip.GZIPInputStream;
-
-/**
- * Provides a mapping table from country ISO to ECC info. The data is stored in Protocol Buffers
- * binary format, compressed with GZIP.
- */
-public class IsoToEccProtobufRepository implements IsoToEccRepository {
-    private static final String LOG_TAG = "EccRepository";
-
-    private static IsoToEccProtobufRepository sInstance;
-
-    /**
-     * Returns the singleton instance of IsoToEccProtobufRepository
-     */
-    public static synchronized IsoToEccProtobufRepository getInstance() {
-        if (sInstance == null) {
-            sInstance = new IsoToEccProtobufRepository();
-        }
-        return sInstance;
-    }
-
-    private final Map<String, CountryEccInfo> mEccTable = new HashMap<>();
-
-    private IsoToEccProtobufRepository() {
-    }
-
-    @Override
-    @Nullable
-    public CountryEccInfo getCountryEccInfo(@NonNull Context context, String iso)
-            throws IOException {
-        if (TextUtils.isEmpty(iso)) {
-            return null;
-        }
-
-        synchronized (mEccTable) {
-            return mEccTable.get(iso.toUpperCase());
-        }
-    }
-
-    /**
-     * Loads the mapping table.
-     */
-    public void loadMappingTable(@NonNull Context context) {
-        ProtobufEccData.AllInfo allEccData = null;
-
-        long startTime = SystemClock.uptimeMillis();
-        try {
-            allEccData = parseEccData(new BufferedInputStream(
-                    context.getAssets().open("eccdata")));
-        } catch (IOException e) {
-            Log.e(LOG_TAG, "Failed to retrieve ECC: ", e);
-        }
-        long endTime = SystemClock.uptimeMillis();
-
-        if (allEccData == null) {
-            return;
-        }
-
-        if (Log.isLoggable(LOG_TAG, Log.DEBUG)) {
-            Log.d(LOG_TAG, "Loading time = " + (endTime - startTime) + "ms"
-                    + ", Country Count = " + allEccData.getCountriesCount()
-                    + ", initialized = " + allEccData.isInitialized());
-        }
-
-        // Converts to run-time data from Protobuf data.
-        synchronized (mEccTable) {
-            mEccTable.clear();
-            for (ProtobufEccData.CountryInfo countryData : allEccData.getCountriesList()) {
-                if (countryData.hasIsoCode()) {
-                    CountryEccInfo countryInfo = loadCountryEccInfo(countryData);
-                    if (countryInfo != null) {
-                        mEccTable.put(countryData.getIsoCode().toUpperCase(), countryInfo);
-                    }
-                }
-            }
-        }
-    }
-
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    Map<String, CountryEccInfo> getEccTable() {
-        return mEccTable;
-    }
-
-    private ProtobufEccData.AllInfo parseEccData(InputStream input) throws IOException {
-        return ProtobufEccData.AllInfo.parseFrom(new GZIPInputStream(input));
-    }
-
-    private EccInfo loadEccInfo(String isoCode, ProtobufEccData.EccInfo eccData) {
-        String phoneNumber = eccData.getPhoneNumber().trim();
-        if (phoneNumber.isEmpty()) {
-            Log.i(LOG_TAG, "Discard ecc " + phoneNumber
-                    + " for " + isoCode + " due to empty phone number");
-            return null;
-        }
-
-        ArraySet<EccInfo.Type> eccTypes = new ArraySet<>(eccData.getTypesCount());
-        for (ProtobufEccData.EccInfo.Type typeData : eccData.getTypesList()) {
-            switch (typeData) {
-                case POLICE:
-                    eccTypes.add(EccInfo.Type.POLICE);
-                    break;
-                case AMBULANCE:
-                    eccTypes.add(EccInfo.Type.AMBULANCE);
-                    break;
-                case FIRE:
-                    eccTypes.add(EccInfo.Type.FIRE);
-                    break;
-                default:
-                    // Ignores unknown types.
-            }
-        }
-
-        if (eccTypes.isEmpty()) {
-            Log.i(LOG_TAG, "Discard ecc " + phoneNumber
-                    + " for " + isoCode + " due to no valid type");
-            return null;
-        }
-        return new EccInfo(phoneNumber, eccTypes);
-    }
-
-    private CountryEccInfo loadCountryEccInfo(ProtobufEccData.CountryInfo countryData) {
-        ArrayMap<String, EccInfo> eccInfoMap = new ArrayMap<>(countryData.getEccsCount());
-        for (ProtobufEccData.EccInfo eccData : countryData.getEccsList()) {
-            EccInfo eccInfo = loadEccInfo(countryData.getIsoCode(), eccData);
-            String key = eccInfo.getNumber().trim();
-            EccInfo existentEccInfo = eccInfoMap.get(key);
-            if (existentEccInfo == null) {
-                eccInfoMap.put(key, eccInfo);
-            } else {
-                // Merges types of duplicated ECC info objects.
-                ArraySet<EccInfo.Type> eccTypes = new ArraySet<>(
-                        eccInfo.getTypesCount() + existentEccInfo.getTypesCount());
-                for (EccInfo.Type type : eccInfo.getTypes()) {
-                    eccTypes.add(type);
-                }
-                for (EccInfo.Type type : existentEccInfo.getTypes()) {
-                    eccTypes.add(type);
-                }
-                eccInfoMap.put(key, new EccInfo(eccInfo.getNumber(), eccTypes));
-            }
-        }
-
-        if (eccInfoMap.isEmpty() && !countryData.hasEccFallback()) {
-            Log.i(LOG_TAG, "Discard empty data for " + countryData.getIsoCode());
-            return null;
-        }
-        return new CountryEccInfo(countryData.getEccFallback(), eccInfoMap.values());
-    }
-}
diff --git a/src/com/android/phone/ecc/IsoToEccRepository.java b/src/com/android/phone/ecc/IsoToEccRepository.java
deleted file mode 100644
index 6d95af4..0000000
--- a/src/com/android/phone/ecc/IsoToEccRepository.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.phone.ecc;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.io.IOException;
-
-/**
- * Data source for country ISO to ECC info list mapping.
- */
-public interface IsoToEccRepository {
-    /**
-     * Get available emergency numbers for given country ISO. Because the possible of IO wait
-     * (depends on the implementation), this method should not be called in the main thread.
-     *
-     * @param context The context used to access resources.
-     * @param iso For which ECC info list is returned.
-     * @return The ECC info of given ISO. Null if no match.
-     * @throws IOException if an error occurs while initialize the repository or retrieving
-     * the {@link CountryEccInfo}.
-     */
-    @Nullable CountryEccInfo getCountryEccInfo(@NonNull Context context, @Nullable String iso)
-            throws IOException;
-}
diff --git a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
index 670f98d..ca45b31 100644
--- a/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
+++ b/src/com/android/phone/settings/PhoneAccountSettingsFragment.java
@@ -44,7 +44,7 @@
             "phone_accounts_accounts_list_category_key";
 
     private static final String DEFAULT_OUTGOING_ACCOUNT_KEY = "default_outgoing_account";
-    private static final String ALL_CALLING_ACCOUNTS_KEY = "phone_account_all_calling_accounts";
+    private static final String ALL_CALLING_ACCOUNTS_KEY = "phone_accounts_all_calling_accounts";
 
     private static final String SIP_SETTINGS_CATEGORY_PREF_KEY =
             "phone_accounts_sip_settings_category_key";
@@ -71,11 +71,21 @@
     private PreferenceCategory mAccountList;
 
     private AccountSelectionPreference mDefaultOutgoingAccount;
+    private Preference mAllCallingAccounts;
 
     private ListPreference mUseSipCalling;
     private SwitchPreference mSipReceiveCallsPreference;
     private SipPreferences mSipPreferences;
 
+    private final SubscriptionManager.OnSubscriptionsChangedListener
+            mOnSubscriptionsChangeListener =
+            new SubscriptionManager.OnSubscriptionsChangedListener() {
+        @Override
+        public void onSubscriptionsChanged() {
+            updateAccounts();
+        }
+    };
+
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -122,34 +132,11 @@
          */
         mAccountList = (PreferenceCategory) getPreferenceScreen().findPreference(
                 ACCOUNTS_LIST_CATEGORY_KEY);
-        List<PhoneAccountHandle> allNonSimAccounts =
-                getCallingAccounts(false /* includeSims */, true /* includeDisabled */);
-        // Check to see if we should show the entire section at all.
-        if (shouldShowConnectionServiceList(allNonSimAccounts)) {
-            List<PhoneAccountHandle> enabledAccounts =
-                    getCallingAccounts(true /* includeSims */, false /* includeDisabled */);
-            // Initialize the account list with the set of enabled & SIM accounts.
-            initAccountList(enabledAccounts);
+        mDefaultOutgoingAccount = (AccountSelectionPreference)
+                getPreferenceScreen().findPreference(DEFAULT_OUTGOING_ACCOUNT_KEY);
+        mAllCallingAccounts = getPreferenceScreen().findPreference(ALL_CALLING_ACCOUNTS_KEY);
 
-            mDefaultOutgoingAccount = (AccountSelectionPreference)
-                    getPreferenceScreen().findPreference(DEFAULT_OUTGOING_ACCOUNT_KEY);
-            mDefaultOutgoingAccount.setListener(this);
-
-            // Only show the 'Make Calls With..." option if there are multiple accounts.
-            if (enabledAccounts.size() > 1) {
-                updateDefaultOutgoingAccountsModel();
-            } else {
-                mAccountList.removePreference(mDefaultOutgoingAccount);
-            }
-
-            Preference allAccounts = getPreferenceScreen().findPreference(ALL_CALLING_ACCOUNTS_KEY);
-            // If there are no third party (nonSim) accounts, then don't show enable/disable dialog.
-            if (allNonSimAccounts.isEmpty() && allAccounts != null) {
-                mAccountList.removePreference(allAccounts);
-            }
-        } else {
-            getPreferenceScreen().removePreference(mAccountList);
-        }
+        updateAccounts();
 
         if (isPrimaryUser() && SipUtil.isVoipSupported(getActivity())) {
             mSipPreferences = new SipPreferences(getActivity());
@@ -183,6 +170,16 @@
             getPreferenceScreen().removePreference(
                     getPreferenceScreen().findPreference(SIP_SETTINGS_CATEGORY_PREF_KEY));
         }
+
+        SubscriptionManager.from(getActivity()).addOnSubscriptionsChangedListener(
+                mOnSubscriptionsChangeListener);
+    }
+
+    @Override
+    public void onPause() {
+        SubscriptionManager.from(getActivity()).removeOnSubscriptionsChangedListener(
+                mOnSubscriptionsChangeListener);
+        super.onPause();
     }
 
     /**
@@ -391,6 +388,40 @@
         return mTelephonyManager.isMultiSimEnabled() || allNonSimAccounts.size() > 0;
     }
 
+    private void updateAccounts() {
+        if (mAccountList != null) {
+            mAccountList.removeAll();
+            List<PhoneAccountHandle> allNonSimAccounts =
+                    getCallingAccounts(false /* includeSims */, true /* includeDisabled */);
+            // Check to see if we should show the entire section at all.
+            if (shouldShowConnectionServiceList(allNonSimAccounts)) {
+                List<PhoneAccountHandle> enabledAccounts =
+                        getCallingAccounts(true /* includeSims */, false /* includeDisabled */);
+                // Initialize the account list with the set of enabled & SIM accounts.
+                initAccountList(enabledAccounts);
+
+                mDefaultOutgoingAccount.setListener(this);
+                // Only show the 'Make Calls With..." option if there are multiple accounts.
+                if (enabledAccounts.size() > 1) {
+                    mAccountList.addPreference(mDefaultOutgoingAccount);
+                    updateDefaultOutgoingAccountsModel();
+                } else {
+                    mAccountList.removePreference(mDefaultOutgoingAccount);
+                }
+
+                // If there are no third party (nonSim) accounts,
+                // then don't show enable/disable dialog.
+                if (!allNonSimAccounts.isEmpty()) {
+                    mAccountList.addPreference(mAllCallingAccounts);
+                } else {
+                    mAccountList.removePreference(mAllCallingAccounts);
+                }
+            } else {
+                getPreferenceScreen().removePreference(mAccountList);
+            }
+        }
+    }
+
     private List<PhoneAccountHandle> getCallingAccounts(
             boolean includeSims, boolean includeDisabledAccounts) {
         PhoneAccountHandle emergencyAccountHandle = getEmergencyPhoneAccount();
diff --git a/src/com/android/services/telephony/ConferenceParticipantConnection.java b/src/com/android/services/telephony/ConferenceParticipantConnection.java
index 82baa92..1f330f9 100644
--- a/src/com/android/services/telephony/ConferenceParticipantConnection.java
+++ b/src/com/android/services/telephony/ConferenceParticipantConnection.java
@@ -33,11 +33,6 @@
  * Represents a participant in a conference call.
  */
 public class ConferenceParticipantConnection extends Connection {
-    /**
-     * RFC5767 states that a SIP URI with an unknown number should use an address of
-     * {@code anonymous@anonymous.invalid}.  E.g. the host name is anonymous.invalid.
-     */
-    private static final String ANONYMOUS_INVALID_HOST = "anonymous.invalid";
 
     /**
      * The user entity URI For the conference participant.
@@ -65,7 +60,7 @@
 
         mParentConnection = parentConnection;
 
-        int presentation = getParticipantPresentation(participant);
+        int presentation = participant.getParticipantPresentation();
         Uri address;
         if (presentation != PhoneConstants.PRESENTATION_ALLOWED) {
             address = null;
@@ -161,53 +156,7 @@
         setConnectionCapabilities(capabilities);
     }
 
-    /**
-     * Determines the number presentation for a conference participant.  Per RFC5767, if the host
-     * name contains {@code anonymous.invalid} we can assume that there is no valid caller ID
-     * information for the caller, otherwise we'll assume that the URI can be shown.
-     *
-     * @param participant The conference participant.
-     * @return The number presentation.
-     */
-    private int getParticipantPresentation(ConferenceParticipant participant) {
-        Uri address = participant.getHandle();
-        if (address == null) {
-            return PhoneConstants.PRESENTATION_RESTRICTED;
-        }
 
-        String number = address.getSchemeSpecificPart();
-        // If no number, bail early and set restricted presentation.
-        if (TextUtils.isEmpty(number)) {
-            return PhoneConstants.PRESENTATION_RESTRICTED;
-        }
-        // Per RFC3261, the host name portion can also potentially include extra information:
-        // E.g. sip:anonymous1@anonymous.invalid;legid=1
-        // In this case, hostName will be anonymous.invalid and there is an extra parameter for
-        // legid=1.
-        // Parameters are optional, and the address (e.g. test@test.com) will always be the first
-        // part, with any parameters coming afterwards.
-        String hostParts[] = number.split("[;]");
-        String addressPart = hostParts[0];
-
-        // Get the number portion from the address part.
-        // This will typically be formatted similar to: 6505551212@test.com
-        String numberParts[] = addressPart.split("[@]");
-
-        // If we can't parse the host name out of the URI, then there is probably other data
-        // present, and is likely a valid SIP URI.
-        if (numberParts.length != 2) {
-            return PhoneConstants.PRESENTATION_ALLOWED;
-        }
-        String hostName = numberParts[1];
-
-        // If the hostname portion of the SIP URI is the invalid host string, presentation is
-        // restricted.
-        if (hostName.equals(ANONYMOUS_INVALID_HOST)) {
-            return PhoneConstants.PRESENTATION_RESTRICTED;
-        }
-
-        return PhoneConstants.PRESENTATION_ALLOWED;
-    }
 
     /**
      * Attempts to build a tel: style URI from a conference participant.
@@ -311,6 +260,10 @@
         sb.append(Log.pii(mParentConnection.getAddress()));
         sb.append(" state:");
         sb.append(Connection.stateToString(getState()));
+        sb.append(" connectTime:");
+        sb.append(getConnectTimeMillis());
+        sb.append(" connectElapsedTime:");
+        sb.append(getConnectElapsedTimeMillis());
         sb.append("]");
 
         return sb.toString();
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index d5af25b..c992c74 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -33,7 +33,6 @@
 import android.telecom.VideoProfile;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
-import android.util.FeatureFlagUtils;
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -47,7 +46,6 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -542,6 +540,8 @@
     @Override
     public void onConnectionAdded(android.telecom.Connection connection) {
         // No-op
+        Log.d(this, "connection added: " + connection
+                + ", time: " + connection.getConnectTimeMillis());
     }
 
     @Override
@@ -884,7 +884,7 @@
             // Remove the participant from Telecom.  It'll get picked up in a future CEP update
             // again anyways.
             entry.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED,
-                    "EMULATING_SINGLE_CALL"));
+                    DisconnectCause.REASON_EMULATING_SINGLE_CALL));
             entry.removeConnectionListener(mParticipantListener);
             mTelephonyConnectionService.removeConnection(entry);
             removeConnection(entry);
@@ -919,8 +919,13 @@
         ConferenceParticipantConnection connection = new ConferenceParticipantConnection(
                 parent.getOriginalConnection(), participant);
         connection.addConnectionListener(mParticipantListener);
-        connection.setConnectTimeMillis(parent.getConnectTimeMillis());
-
+        if (participant.getConnectTime() == 0) {
+            connection.setConnectTimeMillis(parent.getConnectTimeMillis());
+            connection.setConnectionStartElapsedRealTime(parent.getConnectElapsedTimeMillis());
+        } else {
+            connection.setConnectTimeMillis(participant.getConnectTime());
+            connection.setConnectionStartElapsedRealTime(participant.getConnectElapsedTime());
+        }
         Log.i(this, "createConferenceParticipantConnection: participant=%s, connection=%s",
                 participant, connection);
 
@@ -1083,6 +1088,7 @@
                 c.updateState();
                 // Copy the connect time from the conferenceHost
                 c.setConnectTimeMillis(mConferenceHost.getConnectTimeMillis());
+                c.setConnectionStartElapsedRealTime(mConferenceHost.getConnectElapsedTimeMillis());
                 mTelephonyConnectionService.addExistingConnection(phoneAccountHandle, c);
                 mTelephonyConnectionService.addConnectionToConferenceController(c);
             } // CDMA case not applicable for SRVCC
diff --git a/src/com/android/services/telephony/RadioOnHelper.java b/src/com/android/services/telephony/RadioOnHelper.java
index cd08289..288c72c 100644
--- a/src/com/android/services/telephony/RadioOnHelper.java
+++ b/src/com/android/services/telephony/RadioOnHelper.java
@@ -93,13 +93,12 @@
      * get an onServiceStateChanged() callback when the radio successfully comes up.
      */
     private void powerOnRadio() {
-        Log.d(this, "powerOnRadio().");
 
         // If airplane mode is on, we turn it off the same way that the Settings activity turns it
         // off.
         if (Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.AIRPLANE_MODE_ON, 0) > 0) {
-            Log.d(this, "==> Turning off airplane mode.");
+            Log.d(this, "==> Turning off airplane mode for emergency call.");
 
             // Change the system setting
             Settings.Global.putInt(mContext.getContentResolver(),
diff --git a/src/com/android/services/telephony/RadioOnStateListener.java b/src/com/android/services/telephony/RadioOnStateListener.java
index 91a7d77..729f6a9 100644
--- a/src/com/android/services/telephony/RadioOnStateListener.java
+++ b/src/com/android/services/telephony/RadioOnStateListener.java
@@ -46,16 +46,16 @@
     }
 
     // Number of times to retry the call, and time between retry attempts.
+    // not final for testing
     private static int MAX_NUM_RETRIES = 5;
+    // not final for testing
     private static long TIME_BETWEEN_RETRIES_MILLIS = 5000;  // msec
 
     // Handler message codes; see handleMessage()
-    @VisibleForTesting
-    public static final int MSG_START_SEQUENCE = 1;
+    private static final int MSG_START_SEQUENCE = 1;
     @VisibleForTesting
     public static final int MSG_SERVICE_STATE_CHANGED = 2;
-    @VisibleForTesting
-    public static final int MSG_RETRY_TIMEOUT = 3;
+    private static final int MSG_RETRY_TIMEOUT = 3;
 
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
         @Override
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index bfd8e27..f925dd6 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -156,6 +156,10 @@
                 Log.w(this, "registerMmTelCapabilityCallback: registration failed, no ImsService"
                         + " available. Exception: " + e.getMessage());
                 return;
+            } catch (IllegalArgumentException e) {
+                Log.w(this, "registerMmTelCapabilityCallback: registration failed, invalid"
+                        + " subscription, Exception" + e.getMessage());
+                return;
             }
         }
 
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index e0db44e..6d7c1f0 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -40,8 +40,8 @@
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.emergency.EmergencyNumber;
 import android.text.TextUtils;
-import android.util.FeatureFlagUtils;
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -68,6 +68,7 @@
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Queue;
 import java.util.regex.Pattern;
 
@@ -208,26 +209,47 @@
     };
 
     // TelephonyManager Proxy interface for testing
+    @VisibleForTesting
     public interface TelephonyManagerProxy {
         int getPhoneCount();
         boolean hasIccCard(int slotId);
+        boolean isCurrentEmergencyNumber(String number);
+        Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList();
     }
 
-    private TelephonyManagerProxy mTelephonyManagerProxy = new TelephonyManagerProxy() {
-        private final TelephonyManager sTelephonyManager = TelephonyManager.getDefault();
+    private TelephonyManagerProxy mTelephonyManagerProxy;
+
+    private class TelephonyManagerProxyImpl implements TelephonyManagerProxy {
+        private final TelephonyManager mTelephonyManager;
+
+
+        TelephonyManagerProxyImpl(Context context) {
+            mTelephonyManager = new TelephonyManager(context);
+        }
 
         @Override
         public int getPhoneCount() {
-            return sTelephonyManager.getPhoneCount();
+            return mTelephonyManager.getPhoneCount();
         }
 
         @Override
         public boolean hasIccCard(int slotId) {
-            return sTelephonyManager.hasIccCard(slotId);
+            return mTelephonyManager.hasIccCard(slotId);
         }
-    };
+
+        @Override
+        public boolean isCurrentEmergencyNumber(String number) {
+            return mTelephonyManager.isCurrentEmergencyNumber(number);
+        }
+
+        @Override
+        public Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList() {
+            return mTelephonyManager.getCurrentEmergencyNumberList();
+        }
+    }
 
     //PhoneFactory proxy interface for testing
+    @VisibleForTesting
     public interface PhoneFactoryProxy {
         Phone getPhone(int index);
         Phone getDefaultPhone();
@@ -286,6 +308,7 @@
     public void onCreate() {
         super.onCreate();
         Log.initLogging(this);
+        setTelephonyManagerProxy(new TelephonyManagerProxyImpl(getApplicationContext()));
         mExpectedComponentName = new ComponentName(this, this.getClass());
         mEmergencyTonePlayer = new EmergencyTonePlayer(this);
         TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this);
@@ -387,9 +410,13 @@
             }
         }
 
+        final boolean isEmergencyNumber = mTelephonyManagerProxy.isCurrentEmergencyNumber(number);
+        // Find out if this is a test emergency number
+        final boolean isTestEmergencyNumber = isEmergencyNumberTestNumber(number);
+
         // Convert into emergency number if necessary
         // This is required in some regions (e.g. Taiwan).
-        if (!PhoneNumberUtils.isLocalEmergencyNumber(this, number)) {
+        if (isEmergencyNumber) {
             final Phone phone = getPhoneForAccount(request.getAccountHandle(), false,
                     handle.getSchemeSpecificPart());
             // We only do the conversion if the phone is not in service. The un-converted
@@ -408,9 +435,6 @@
         }
         final String numberToDial = number;
 
-        final boolean isEmergencyNumber =
-                PhoneNumberUtils.isLocalEmergencyNumber(this, numberToDial);
-
 
         final boolean isAirplaneModeOn = Settings.Global.getInt(getContentResolver(),
                 Settings.Global.AIRPLANE_MODE_ON, 0) > 0;
@@ -437,7 +461,12 @@
 
                 @Override
                 public boolean isOkToCall(Phone phone, int serviceState) {
-                    if (isEmergencyNumber) {
+                    // HAL 1.4 introduced a new variant of dial for emergency calls, which includes
+                    // an isTesting parameter. For HAL 1.4+, do not wait for IN_SERVICE, this will
+                    // be handled at the RIL/vendor level by emergencyDial(...).
+                    boolean waitForInServiceToDialEmergency = isTestEmergencyNumber
+                            && phone.getHalVersion().less(RIL.RADIO_HAL_VERSION_1_4);
+                    if (isEmergencyNumber && !waitForInServiceToDialEmergency) {
                         // We currently only look to make sure that the radio is on before dialing.
                         // We should be able to make emergency calls at any time after the radio has
                         // been powered on and isn't in the UNAVAILABLE state, even if it is
@@ -445,9 +474,10 @@
                         return (phone.getState() == PhoneConstants.State.OFFHOOK)
                             || phone.getServiceState().getState() != ServiceState.STATE_POWER_OFF;
                     } else {
-                        // It is not an emergency number, so wait until we are in service and ready
-                        // to make calls. This can happen when we power down the radio on bluetooth
-                        // to save power on watches.
+                        // Wait until we are in service and ready to make calls. This can happen
+                        // when we power down the radio on bluetooth to save power on watches or if
+                        // it is a test emergency number and we have to wait for the device to move
+                        // IN_SERVICE before the call can take place over normal routing.
                         return (phone.getState() == PhoneConstants.State.OFFHOOK)
                             || serviceState == ServiceState.STATE_IN_SERVICE;
                     }
@@ -487,6 +517,24 @@
         }
     }
 
+    private boolean isEmergencyNumberTestNumber(String number) {
+        Map<Integer, List<EmergencyNumber>> list =
+                mTelephonyManagerProxy.getCurrentEmergencyNumberList();
+        // Do not worry about which subscription the test emergency call is on yet, only detect that
+        // it is an emergency.
+        for (Integer sub : list.keySet()) {
+            for (EmergencyNumber eNumber : list.get(sub)) {
+                if (number.equals(eNumber.getNumber())
+                        && eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST)) {
+                    Log.i(this, "isEmergencyNumberTestNumber: " + number + " has been detected as "
+                            + "a test emergency number.,");
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Whether the cellular radio is power off because the device is on Bluetooth.
      */
diff --git a/testapps/TelephonyManagerTestApp/res/layout/calling_method.xml b/testapps/TelephonyManagerTestApp/res/layout/calling_method.xml
index 5145a63..6dd8bc2 100644
--- a/testapps/TelephonyManagerTestApp/res/layout/calling_method.xml
+++ b/testapps/TelephonyManagerTestApp/res/layout/calling_method.xml
@@ -73,7 +73,6 @@
         android:layout_height="50dip">
     </Button>
 
-
     <ScrollView
         android:id="@+id/return_value_wrapper"
         android:layout_width="fill_parent"
diff --git a/testapps/TelephonyRegistryTestApp/AndroidManifest.xml b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
index 1cfd3ba..550c9f0 100644
--- a/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
+++ b/testapps/TelephonyRegistryTestApp/AndroidManifest.xml
@@ -16,6 +16,9 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.phone.testapps.telephonyregistry">
+    <uses-sdk android:minSdkVersion="25"
+          android:targetSdkVersion="25"/>
+
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
diff --git a/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java b/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java
index 74cafcd..96f8bf7 100644
--- a/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java
+++ b/testapps/TelephonyRegistryTestApp/src/com/android/phone/testapps/telephonyregistry/TelephonyRegistryTestApp.java
@@ -24,6 +24,7 @@
 import android.telephony.CellInfo;
 import android.telephony.CellLocation;
 import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.util.SparseArray;
 import android.widget.Button;
@@ -76,6 +77,11 @@
             notify("onSrvccStateChanged", srvccState);
         }
 
+        @Override
+        public void onServiceStateChanged(ServiceState state) {
+            notify("onServiceStateChanged", state);
+        }
+
         private void notify(String method, Object data) {
             Notification.Builder builder = new Notification.Builder(TelephonyRegistryTestApp.this,
                     NOTIFICATION_CHANNEL);
diff --git a/tests/src/com/android/phone/LocationAccessPolicyTest.java b/tests/src/com/android/phone/LocationAccessPolicyTest.java
new file mode 100644
index 0000000..9938bf2
--- /dev/null
+++ b/tests/src/com/android/phone/LocationAccessPolicyTest.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.os.Build;
+import android.os.UserHandle;
+import android.telephony.LocationAccessPolicy;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public class LocationAccessPolicyTest {
+    private static class Scenario {
+        static class Builder {
+            private int mAppSdkLevel;
+            private boolean mAppHasFineManifest = false;
+            private boolean mAppHasCoarseManifest = false;
+            private int mFineAppOp = AppOpsManager.MODE_IGNORED;
+            private int mCoarseAppOp = AppOpsManager.MODE_IGNORED;
+            private boolean mIsDynamicLocationEnabled;
+            private LocationAccessPolicy.LocationPermissionQuery mQuery;
+            private LocationAccessPolicy.LocationPermissionResult mExpectedResult;
+            private String mName;
+
+            public Builder setAppSdkLevel(int appSdkLevel) {
+                mAppSdkLevel = appSdkLevel;
+                return this;
+            }
+
+            public Builder setAppHasFineManifest(boolean appHasFineManifest) {
+                mAppHasFineManifest = appHasFineManifest;
+                return this;
+            }
+
+            public Builder setAppHasCoarseManifest(
+                    boolean appHasCoarseManifest) {
+                mAppHasCoarseManifest = appHasCoarseManifest;
+                return this;
+            }
+
+            public Builder setFineAppOp(int fineAppOp) {
+                mFineAppOp = fineAppOp;
+                return this;
+            }
+
+            public Builder setCoarseAppOp(int coarseAppOp) {
+                mCoarseAppOp = coarseAppOp;
+                return this;
+            }
+
+            public Builder setIsDynamicLocationEnabled(
+                    boolean isDynamicLocationEnabled) {
+                mIsDynamicLocationEnabled = isDynamicLocationEnabled;
+                return this;
+            }
+
+            public Builder setQuery(
+                    LocationAccessPolicy.LocationPermissionQuery query) {
+                mQuery = query;
+                return this;
+            }
+
+            public Builder setExpectedResult(
+                    LocationAccessPolicy.LocationPermissionResult expectedResult) {
+                mExpectedResult = expectedResult;
+                return this;
+            }
+
+            public Builder setName(String name) {
+                mName = name;
+                return this;
+            }
+
+            public Scenario build() {
+                return new Scenario(mAppSdkLevel, mAppHasFineManifest, mAppHasCoarseManifest,
+                        mFineAppOp, mCoarseAppOp, mIsDynamicLocationEnabled, mQuery,
+                        mExpectedResult, mName);
+            }
+        }
+        int appSdkLevel;
+        boolean appHasFineManifest;
+        boolean appHasCoarseManifest;
+        int fineAppOp;
+        int coarseAppOp;
+        boolean isDynamicLocationEnabled;
+        LocationAccessPolicy.LocationPermissionQuery query;
+        LocationAccessPolicy.LocationPermissionResult expectedResult;
+        String name;
+
+        private Scenario(int appSdkLevel, boolean appHasFineManifest, boolean appHasCoarseManifest,
+                int fineAppOp, int coarseAppOp,
+                boolean isDynamicLocationEnabled,
+                LocationAccessPolicy.LocationPermissionQuery query,
+                LocationAccessPolicy.LocationPermissionResult expectedResult,
+                String name) {
+            this.appSdkLevel = appSdkLevel;
+            this.appHasFineManifest = appHasFineManifest;
+            this.appHasCoarseManifest = appHasFineManifest || appHasCoarseManifest;
+            this.fineAppOp = fineAppOp;
+            this.coarseAppOp = coarseAppOp == AppOpsManager.MODE_ALLOWED ? coarseAppOp : fineAppOp;
+            this.isDynamicLocationEnabled = isDynamicLocationEnabled;
+            this.query = query;
+            this.expectedResult = expectedResult;
+            this.name = name;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+    }
+
+    @Mock Context mContext;
+    @Mock AppOpsManager mAppOpsManager;
+    @Mock LocationManager mLocationManager;
+    @Mock PackageManager mPackageManager;
+    Scenario mScenario;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mockContextSystemService(AppOpsManager.class, mAppOpsManager);
+        mockContextSystemService(LocationManager.class, mLocationManager);
+        mockContextSystemService(PackageManager.class, mPackageManager);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+    }
+
+    private <T> void mockContextSystemService(Class<T> clazz , T obj) {
+        when(mContext.getSystemServiceName(eq(clazz))).thenReturn(clazz.getSimpleName());
+        when(mContext.getSystemService(clazz.getSimpleName())).thenReturn(obj);
+    }
+
+    public LocationAccessPolicyTest(Scenario scenario) {
+        mScenario = scenario;
+    }
+
+
+    @Test
+    public void test() {
+        setupScenario(mScenario);
+        assertEquals(mScenario.expectedResult,
+                LocationAccessPolicy.checkLocationPermission(mContext, mScenario.query));
+    }
+
+    private void setupScenario(Scenario s) {
+        when(mContext.checkPermission(eq(Manifest.permission.ACCESS_FINE_LOCATION),
+                anyInt(), anyInt())).thenReturn(s.appHasFineManifest
+                ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED);
+
+        when(mContext.checkPermission(eq(Manifest.permission.ACCESS_COARSE_LOCATION),
+                anyInt(), anyInt())).thenReturn(s.appHasCoarseManifest
+                ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED);
+
+        when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OP_FINE_LOCATION),
+                anyInt(), anyString()))
+                .thenReturn(s.fineAppOp);
+        when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OP_COARSE_LOCATION),
+                anyInt(), anyString()))
+                .thenReturn(s.coarseAppOp);
+
+        if (s.isDynamicLocationEnabled) {
+            when(mLocationManager.isLocationEnabledForUser(any(UserHandle.class))).thenReturn(true);
+            when(mContext.checkPermission(eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL),
+                    anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+        } else {
+            when(mLocationManager.isLocationEnabledForUser(any(UserHandle.class)))
+                    .thenReturn(false);
+            when(mContext.checkPermission(eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL),
+                    anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+        }
+
+        ApplicationInfo fakeAppInfo = new ApplicationInfo();
+        fakeAppInfo.targetSdkVersion = s.appSdkLevel;
+
+        try {
+            when(mPackageManager.getApplicationInfo(anyString(), anyInt()))
+                    .thenReturn(fakeAppInfo);
+        } catch (Exception e) {
+            // this is a formality
+        }
+    }
+
+    private static LocationAccessPolicy.LocationPermissionQuery.Builder getDefaultQueryBuilder() {
+        return new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                .setMethod("test")
+                .setCallingPackage("com.android.test")
+                .setCallingPid(10001)
+                .setCallingUid(10001);
+    }
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Collection<Scenario> getScenarios() {
+        List<Scenario> scenarios = new ArrayList<>();
+        scenarios.add(new Scenario.Builder()
+                .setName("System location is off")
+                .setAppHasFineManifest(true)
+                .setFineAppOp(AppOpsManager.MODE_ALLOWED)
+                .setAppSdkLevel(Build.VERSION_CODES.P)
+                .setIsDynamicLocationEnabled(false)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.N)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.DENIED_SOFT)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App on latest SDK level has all proper permissions for fine")
+                .setAppHasFineManifest(true)
+                .setFineAppOp(AppOpsManager.MODE_ALLOWED)
+                .setAppSdkLevel(Build.VERSION_CODES.P)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.N)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App on older SDK level missing permissions for fine but has coarse")
+                .setAppHasCoarseManifest(true)
+                .setCoarseAppOp(AppOpsManager.MODE_ALLOWED)
+                .setAppSdkLevel(Build.VERSION_CODES.JELLY_BEAN)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.M)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.JELLY_BEAN).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App on latest SDK level missing fine app ops permission")
+                .setAppHasFineManifest(true)
+                .setFineAppOp(AppOpsManager.MODE_ERRORED)
+                .setAppSdkLevel(Build.VERSION_CODES.P)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.N)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.DENIED_HARD)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App has coarse permission but fine permission isn't being enforced yet")
+                .setAppHasCoarseManifest(true)
+                .setCoarseAppOp(AppOpsManager.MODE_ALLOWED)
+                .setAppSdkLevel(LocationAccessPolicy.MAX_SDK_FOR_ANY_ENFORCEMENT + 1)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(
+                                LocationAccessPolicy.MAX_SDK_FOR_ANY_ENFORCEMENT + 1)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App on latest SDK level has coarse but missing fine when fine is req.")
+                .setAppHasCoarseManifest(true)
+                .setCoarseAppOp(AppOpsManager.MODE_ALLOWED)
+                .setAppSdkLevel(Build.VERSION_CODES.P)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.P)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.N).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.DENIED_HARD)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App on latest SDK level has MODE_IGNORED for app ops on fine")
+                .setAppHasCoarseManifest(true)
+                .setCoarseAppOp(AppOpsManager.MODE_ALLOWED)
+                .setFineAppOp(AppOpsManager.MODE_IGNORED)
+                .setAppSdkLevel(Build.VERSION_CODES.P)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.P)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.O).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.DENIED_HARD)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App has no permissions but it's sdk level grandfathers it in")
+                .setAppSdkLevel(Build.VERSION_CODES.N)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForFine(Build.VERSION_CODES.Q)
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.O).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
+                .build());
+
+        scenarios.add(new Scenario.Builder()
+                .setName("App on latest SDK level has proper permissions for coarse")
+                .setAppHasCoarseManifest(true)
+                .setCoarseAppOp(AppOpsManager.MODE_ALLOWED)
+                .setAppSdkLevel(Build.VERSION_CODES.P)
+                .setIsDynamicLocationEnabled(true)
+                .setQuery(getDefaultQueryBuilder()
+                        .setMinSdkVersionForCoarse(Build.VERSION_CODES.P).build())
+                .setExpectedResult(LocationAccessPolicy.LocationPermissionResult.ALLOWED)
+                .build());
+        return scenarios;
+    }
+}
diff --git a/tests/src/com/android/services/telephony/RadioOnStateListenerTest.java b/tests/src/com/android/services/telephony/RadioOnStateListenerTest.java
index fb214cc..d9de2e8 100644
--- a/tests/src/com/android/services/telephony/RadioOnStateListenerTest.java
+++ b/tests/src/com/android/services/telephony/RadioOnStateListenerTest.java
@@ -16,17 +16,25 @@
 
 package com.android.services.telephony;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.os.AsyncResult;
 import android.os.Handler;
-import android.telephony.ServiceState;
 import android.support.test.runner.AndroidJUnit4;
-import android.support.test.filters.FlakyTest;
+import android.telephony.ServiceState;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.TelephonyTestBase;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.ServiceStateTracker;
 
 import org.junit.After;
 import org.junit.Before;
@@ -34,16 +42,6 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.when;
-
 /**
  * Tests the RadioOnStateListener, which listens to one Phone and waits until its service
  * state changes to accepting emergency calls or in service. If it can not find a tower to camp onto
@@ -52,21 +50,26 @@
 @RunWith(AndroidJUnit4.class)
 public class RadioOnStateListenerTest extends TelephonyTestBase {
 
-    private static final long TIMEOUT_MS = 100;
+    private static final long TIMEOUT_MS = 1000;
 
     @Mock Phone mMockPhone;
     @Mock RadioOnStateListener.Callback mCallback;
     RadioOnStateListener mListener;
 
+    @Override
     @Before
     public void setUp() throws Exception {
         super.setUp();
         mListener = new RadioOnStateListener();
     }
 
+    @Override
     @After
     public void tearDown() throws Exception {
+        // Wait for the queue to clear...
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS /*ms timeout*/);
         mListener.getHandler().removeCallbacksAndMessages(null);
+        mListener = null;
         super.tearDown();
     }
 
@@ -86,8 +89,9 @@
     }
 
     /**
-     * {@link RadioOnStateListener.Callback#isOkToCall(int)} returns true, so we are expecting
-     * {@link RadioOnStateListener.Callback#onComplete(boolean)} to return true.
+     *  {@link RadioOnStateListener.Callback#isOkToCall(Phone, int)} returns true, so we are
+     *  expecting {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} to
+     *  return true.
      */
     @Test
     @SmallTest
@@ -107,8 +111,9 @@
     }
 
     /**
-     * We never receive a {@link RadioOnStateListener.Callback#onComplete(boolean)} because
-     * {@link RadioOnStateListener.Callback#isOkToCall(int)} returns false.
+     * We never receive a
+     * {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} because
+     * {@link RadioOnStateListener.Callback#isOkToCall(Phone, int)} returns false.
      */
     @Test
     @SmallTest
@@ -129,27 +134,27 @@
     }
 
     /**
-     * Tests {@link RadioOnStateListener.Callback#isOkToCall(int)} returning false and hitting the
-     * max number of retries. This should result in
-     * {@link RadioOnStateListener.Callback#onComplete(boolean)} returning false.
+     * Tests {@link RadioOnStateListener.Callback#isOkToCall(Phone, int)} returning false and
+     * hitting the max number of retries. This should result in
+     * {@link RadioOnStateListener.Callback#onComplete(RadioOnStateListener, boolean)} returning
+     * false.
      */
     @Test
-    @FlakyTest
+    @SmallTest
     public void testTimeout_RetryFailure() {
         ServiceState state = new ServiceState();
         state.setState(ServiceState.STATE_POWER_OFF);
         when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
         when(mMockPhone.getServiceState()).thenReturn(state);
         when(mCallback.isOkToCall(eq(mMockPhone), anyInt())).thenReturn(false);
-        mListener.setTimeBetweenRetriesMillis(50);
+        mListener.setTimeBetweenRetriesMillis(0/*ms*/);
         mListener.setMaxNumRetries(2);
 
         // Wait for the timer to expire and check state manually in onRetryTimeout
         mListener.waitForRadioOn(mMockPhone, mCallback);
-        waitForHandlerActionDelayed(mListener.getHandler(), TIMEOUT_MS, 500);
+        waitForHandlerActionDelayed(mListener.getHandler(), TIMEOUT_MS, TIMEOUT_MS /*delay*/);
 
         verify(mCallback).onComplete(eq(mListener), eq(false));
         verify(mMockPhone, times(2)).setRadioPower(eq(true));
     }
-
 }