Merge "Add plumbing for the 911 redial"
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index dfbec46..e2f4fee 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -190,16 +190,25 @@
         }
     }
 
+    private void listenPhoneState(boolean listen) {
+        TelephonyManager telephonyManager =
+                (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
+        telephonyManager.listen(mPhoneStateListener, listen
+                ? PhoneStateListener.LISTEN_CALL_STATE : PhoneStateListener.LISTEN_NONE);
+    }
+
     private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
         @Override
         public void onCallStateChanged(int state, String incomingNumber) {
             if (DBG) log("PhoneStateListener onCallStateChanged: state is " + state);
+            // Use TelecomManager#getCallStete instead of 'state' parameter because it needs
+            // to check the current state of all phone calls.
+            boolean isCallStateIdle =
+                    mTelecomManager.getCallState() == TelephonyManager.CALL_STATE_IDLE;
             if (mEnableVideoCalling != null) {
-                // Use TelephonyManager#getCallStete instead of 'state' parameter because it needs
-                // to check the current state of all phone calls.
-                boolean isCallStateIdle =
-                        mTelecomManager.getCallState() == TelephonyManager.CALL_STATE_IDLE;
                 mEnableVideoCalling.setEnabled(isCallStateIdle);
+            }
+            if (mButtonWifiCalling != null) {
                 mButtonWifiCalling.setEnabled(isCallStateIdle);
             }
         }
@@ -208,10 +217,7 @@
     @Override
     protected void onPause() {
         super.onPause();
-        TelephonyManager telephonyManager =
-                (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
-        telephonyManager.createForSubscriptionId(mPhone.getSubId())
-                .listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+        listenPhoneState(false);
     }
 
     @Override
@@ -219,6 +225,7 @@
         super.onResume();
 
         updateImsManager(mPhone);
+        listenPhoneState(true);
         PreferenceScreen preferenceScreen = getPreferenceScreen();
         if (preferenceScreen != null) {
             preferenceScreen.removeAll();
@@ -228,7 +235,6 @@
 
         TelephonyManager telephonyManager = getSystemService(TelephonyManager.class)
                 .createForSubscriptionId(mPhone.getSubId());
-        telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
 
         PreferenceScreen prefSet = getPreferenceScreen();
         mVoicemailSettingsScreen =
diff --git a/src/com/android/phone/CellInfoUtil.java b/src/com/android/phone/CellInfoUtil.java
index c0409d8..2c5f2a8 100644
--- a/src/com/android/phone/CellInfoUtil.java
+++ b/src/com/android/phone/CellInfoUtil.java
@@ -34,6 +34,8 @@
 
 import com.android.internal.telephony.OperatorInfo;
 
+import java.util.List;
+
 /**
  * Add static Utility functions to get information from the CellInfo object.
  * TODO: Modify {@link CellInfo} for simplify those functions
@@ -166,4 +168,10 @@
         }
         return oi;
     }
+
+    /** Checks whether the network operator is forbidden. */
+    public static boolean isForbidden(CellInfo cellInfo, List<String> forbiddenPlmns) {
+        String plmn = CellInfoUtil.getOperatorInfoFromCellInfo(cellInfo).getOperatorNumeric();
+        return forbiddenPlmns != null && forbiddenPlmns.contains(plmn);
+    }
 }
diff --git a/src/com/android/phone/EmergencyDialer.java b/src/com/android/phone/EmergencyDialer.java
index 31e1958..157cf1d 100644
--- a/src/com/android/phone/EmergencyDialer.java
+++ b/src/com/android/phone/EmergencyDialer.java
@@ -183,14 +183,9 @@
         super.onCreate(icicle);
 
         // Allow this activity to be displayed in front of the keyguard / lockscreen.
-        WindowManager.LayoutParams lp = getWindow().getAttributes();
-        lp.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
-
-        // When no proximity sensor is available, use a shorter timeout.
-        // TODO: Do we enable this for non proximity devices any more?
-        // lp.userActivityTimeout = USER_ACTIVITY_TIMEOUT_WHEN_NO_PROX_SENSOR;
-
-        getWindow().setAttributes(lp);
+        setShowWhenLocked(true);
+        // Allow turning screen on
+        setTurnScreenOn(true);
 
         mColorExtractor = new ColorExtractor(this);
         GradientColors lockScreenColors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
diff --git a/src/com/android/phone/ImsUtil.java b/src/com/android/phone/ImsUtil.java
index 4d8ff80..d83d869 100644
--- a/src/com/android/phone/ImsUtil.java
+++ b/src/com/android/phone/ImsUtil.java
@@ -53,11 +53,20 @@
      * @return {@code true} if WFC is supported by the platform and has been enabled by the user.
      */
     public static boolean isWfcEnabled(Context context) {
-        ImsManager imsManager = getDefaultImsManagerInstance(context);
+        return isWfcEnabled(context, SubscriptionManager.getDefaultVoicePhoneId());
+    }
+
+    /**
+     * @return {@code true} if WFC is supported per Slot and has been enabled by the user.
+     */
+    public static boolean isWfcEnabled(Context context, int phoneId) {
+        ImsManager imsManager = ImsManager.getInstance(context, phoneId);
         boolean isEnabledByPlatform = imsManager.isWfcEnabledByPlatform();
         boolean isEnabledByUser = imsManager.isWfcEnabledByUser();
-        if (DBG) Log.d(LOG_TAG, "isWfcEnabled :: isEnabledByPlatform=" + isEnabledByPlatform);
-        if (DBG) Log.d(LOG_TAG, "isWfcEnabled :: isEnabledByUser=" + isEnabledByUser);
+        if (DBG) Log.d(LOG_TAG, "isWfcEnabled :: isEnabledByPlatform=" + isEnabledByPlatform
+                + " phoneId=" + phoneId);
+        if (DBG) Log.d(LOG_TAG, "isWfcEnabled :: isEnabledByUser=" + isEnabledByUser
+                + " phoneId=" + phoneId);
         return isEnabledByPlatform && isEnabledByUser;
     }
 
@@ -66,10 +75,20 @@
      * enabled, this will return {@code false}.
      */
     public static boolean isWfcModeWifiOnly(Context context) {
-        boolean isWifiOnlyMode = getDefaultImsManagerInstance(context).getWfcMode()
-                == ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY;
-        if (DBG) Log.d(LOG_TAG, "isWfcModeWifiOnly :: isWifiOnlyMode" + isWifiOnlyMode);
-        return isWfcEnabled(context) && isWifiOnlyMode;
+        return isWfcModeWifiOnly(context, SubscriptionManager.getDefaultVoicePhoneId());
+    }
+
+    /**
+     * @return {@code true} if the Slot is configured to use "Wi-Fi only" mode. If WFC is not
+     * enabled, this will return {@code false}.
+     */
+    public static boolean isWfcModeWifiOnly(Context context, int phoneId) {
+        ImsManager imsManager = ImsManager.getInstance(context, phoneId);
+        boolean isWifiOnlyMode =
+                imsManager.getWfcMode() == ImsConfig.WfcModeFeatureValueConstants.WIFI_ONLY;
+        if (DBG) Log.d(LOG_TAG, "isWfcModeWifiOnly :: isWifiOnlyMode" + isWifiOnlyMode
+                + " phoneId=" + phoneId);
+        return isWfcEnabled(context, phoneId) && isWifiOnlyMode;
     }
 
     /**
@@ -81,9 +100,21 @@
      * @return {@code true} if use of WFC should be promoted, {@code false} otherwise.
      */
     public static boolean shouldPromoteWfc(Context context) {
+        return shouldPromoteWfc(context, SubscriptionManager.getDefaultVoicePhoneId());
+    }
+
+    /**
+     * When a call cannot be placed, determines if the use of WFC should be promoted, per the
+     * carrier config of the slot.  Use of WFC is promoted to the user if the device is
+     * connected to a WIFI network, WFC is disabled but provisioned, and the carrier config
+     * indicates that the features should be promoted.
+     *
+     * @return {@code true} if use of WFC should be promoted, {@code false} otherwise.
+     */
+    public static boolean shouldPromoteWfc(Context context, int phoneId) {
         CarrierConfigManager cfgManager = (CarrierConfigManager) context
                 .getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (cfgManager == null || !cfgManager.getConfig()
+        if (cfgManager == null || cfgManager.getConfigForSubId(getSubId(phoneId))
                 .getBoolean(CarrierConfigManager.KEY_CARRIER_PROMOTE_WFC_ON_CALL_FAIL_BOOL)) {
             return false;
         }
@@ -97,7 +128,8 @@
         if (cm != null) {
             NetworkInfo ni = cm.getActiveNetworkInfo();
             if (ni != null && ni.isConnected()) {
-                return ni.getType() == ConnectivityManager.TYPE_WIFI && !isWfcEnabled(context);
+                return ni.getType() == ConnectivityManager.TYPE_WIFI && !isWfcEnabled(context,
+                        phoneId);
             }
         }
         return false;
@@ -106,4 +138,13 @@
     private static ImsManager getDefaultImsManagerInstance(Context context) {
         return ImsManager.getInstance(context, SubscriptionManager.getDefaultVoicePhoneId());
     }
+
+    private static int getSubId(int phoneId) {
+        final int[] subIds = SubscriptionManager.getSubId(phoneId);
+        int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        if (subIds != null && subIds.length >= 1) {
+            subId = subIds[0];
+        }
+        return subId;
+    }
 }
diff --git a/src/com/android/phone/NetworkOperatorPreference.java b/src/com/android/phone/NetworkOperatorPreference.java
index f29c038..85adf16 100644
--- a/src/com/android/phone/NetworkOperatorPreference.java
+++ b/src/com/android/phone/NetworkOperatorPreference.java
@@ -30,25 +30,30 @@
 
 import com.android.settingslib.graph.SignalDrawable;
 
+import java.util.List;
+
 /**
  * A Preference represents a network operator in the NetworkSelectSetting fragment.
  */
 public class NetworkOperatorPreference extends Preference {
 
     private static final String TAG = "NetworkOperatorPref";
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
     // number of signal strength level
     public static final int NUMBER_OF_LEVELS = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
     private CellInfo mCellInfo;
+    private List<String> mForbiddenPlmns;
     private int mLevel = -1;
 
     // The following constants are used to draw signal icon.
     private static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
     private static final int NO_CELL_DATA_CONNECTED_ICON = 0;
 
-    public NetworkOperatorPreference(CellInfo cellinfo, Context context) {
+    public NetworkOperatorPreference(
+            CellInfo cellinfo, Context context, List<String> forbiddenPlmns) {
         super(context);
         mCellInfo = cellinfo;
+        mForbiddenPlmns = forbiddenPlmns;
         refresh();
     }
 
@@ -61,7 +66,11 @@
      */
     public void refresh() {
         if (DBG) Log.d(TAG, "refresh the network: " + CellInfoUtil.getNetworkTitle(mCellInfo));
-        setTitle(CellInfoUtil.getNetworkTitle(mCellInfo));
+        String networkTitle = CellInfoUtil.getNetworkTitle(mCellInfo);
+        if (CellInfoUtil.isForbidden(mCellInfo, mForbiddenPlmns)) {
+            networkTitle += " " + getContext().getResources().getString(R.string.forbidden_network);
+        }
+        setTitle(networkTitle);
         int level = CellInfoUtil.getLevel(mCellInfo);
         if (DBG) Log.d(TAG, "refresh level: " + String.valueOf(level));
         if (mLevel != level) {
diff --git a/src/com/android/phone/NetworkSelectListPreference.java b/src/com/android/phone/NetworkSelectListPreference.java
index 2a55839..3cb4155 100644
--- a/src/com/android/phone/NetworkSelectListPreference.java
+++ b/src/com/android/phone/NetworkSelectListPreference.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.os.AsyncResult;
+import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Parcel;
@@ -47,6 +48,7 @@
 import com.android.internal.telephony.PhoneFactory;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 
@@ -76,6 +78,7 @@
 
     private int mSubId;
     private NetworkOperators mNetworkOperators;
+    private List<String> mForbiddenPlmns;
 
     private ProgressDialog mProgressDialog;
     public NetworkSelectListPreference(Context context, AttributeSet attrs) {
@@ -89,10 +92,21 @@
 
     @Override
     protected void onClick() {
-        // Start the one-time network scan via {@link Phone#getAvailableNetworks()}.
-        // {@link NetworkQueryService will return a {@link onResults()} callback first with a list
-        // of CellInfo, and then will return a {@link onComplete} indicating the scan completed.
-        loadNetworksList();
+        showProgressDialog(DIALOG_NETWORK_LIST_LOAD);
+        TelephonyManager telephonyManager = (TelephonyManager)
+                getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        new AsyncTask<Void, Void, List<String>>() {
+            @Override
+            protected List<String> doInBackground(Void... voids) {
+                return Arrays.asList(telephonyManager.getForbiddenPlmns());
+            }
+
+            @Override
+            protected void onPostExecute(List<String> result) {
+                mForbiddenPlmns = result;
+                loadNetworksList();
+            }
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
     private final Handler mHandler = new Handler() {
@@ -155,7 +169,7 @@
 
         /** Returns the scan results to the user, this callback will be called only one time. */
         public void onResults(List<CellInfo> results) {
-            if (DBG) logd("get scan results.");
+            if (DBG) logd("get scan results: " + results.toString());
             Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RESULTS, results);
             msg.sendToTarget();
         }
@@ -283,9 +297,6 @@
 
     private void loadNetworksList() {
         if (DBG) logd("load networks list...");
-
-        showProgressDialog(DIALOG_NETWORK_LIST_LOAD);
-
         try {
             if (mNetworkQueryService != null) {
                 mNetworkQueryService.startNetworkQuery(mCallback, mPhoneId, false);
@@ -324,10 +335,12 @@
             for (CellInfo cellInfo: mCellInfoList) {
                 // Display each operator name only once.
                 String networkTitle = getNetworkTitle(cellInfo);
-                if (!networkEntriesList.contains(networkTitle)) {
-                    networkEntriesList.add(networkTitle);
-                    networkEntryValuesList.add(getOperatorNumeric(cellInfo));
+                if (CellInfoUtil.isForbidden(cellInfo, mForbiddenPlmns)) {
+                    networkTitle += " "
+                            + getContext().getResources().getString(R.string.forbidden_network);
                 }
+                networkEntriesList.add(networkTitle);
+                networkEntryValuesList.add(getOperatorNumeric(cellInfo));
             }
             setEntries(networkEntriesList.toArray(new CharSequence[networkEntriesList.size()]));
             setEntryValues(networkEntryValuesList.toArray(
diff --git a/src/com/android/phone/NetworkSelectSetting.java b/src/com/android/phone/NetworkSelectSetting.java
index efa8684..96e4a26 100644
--- a/src/com/android/phone/NetworkSelectSetting.java
+++ b/src/com/android/phone/NetworkSelectSetting.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.AsyncResult;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -50,6 +51,7 @@
 import com.android.internal.telephony.PhoneFactory;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -60,7 +62,7 @@
 public class NetworkSelectSetting extends PreferenceFragment {
 
     private static final String TAG = "NetworkSelectSetting";
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
 
     private static final int EVENT_NETWORK_SELECTION_DONE = 1;
     private static final int EVENT_NETWORK_SCAN_RESULTS = 2;
@@ -84,6 +86,7 @@
     private NetworkOperatorPreference mSelectedNetworkOperatorPreference;
     private TelephonyManager mTelephonyManager;
     private NetworkOperators mNetworkOperators;
+    private List<String> mForbiddenPlmns;
 
     private final Runnable mUpdateNetworkOperatorsRunnable = () -> {
         updateNetworkOperatorsPreferenceCategory();
@@ -103,7 +106,7 @@
 
     @Override
     public void onCreate(Bundle icicle) {
-        logd("onCreate");
+        if (DBG) logd("onCreate");
         super.onCreate(icicle);
 
         mPhoneId = getArguments().getInt(NetworkSelectSettingActivity.KEY_PHONE_ID);
@@ -123,7 +126,7 @@
 
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
-        logd("onViewCreated");
+        if (DBG) logd("onViewCreated");
         super.onViewCreated(view, savedInstanceState);
 
         if (getListView() != null) {
@@ -154,9 +157,18 @@
     public void onStart() {
         if (DBG) logd("onStart");
         super.onStart();
+        new AsyncTask<Void, Void, List<String>>() {
+            @Override
+            protected List<String> doInBackground(Void... voids) {
+                return Arrays.asList(mTelephonyManager.getForbiddenPlmns());
+            }
 
-        // Bind the NetworkQueryService
-        bindNetworkQueryService();
+            @Override
+            protected void onPostExecute(List<String> result) {
+                mForbiddenPlmns = result;
+                bindNetworkQueryService();
+            }
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
     /**
@@ -382,8 +394,8 @@
         configConnectedNetworkOperatorsPreferenceCategory();
         for (int index = 0; index < mCellInfoList.size(); index++) {
             if (!mCellInfoList.get(index).isRegistered()) {
-                NetworkOperatorPreference pref =
-                        new NetworkOperatorPreference(mCellInfoList.get(index), getContext());
+                NetworkOperatorPreference pref = new NetworkOperatorPreference(
+                        mCellInfoList.get(index), getContext(), mForbiddenPlmns);
                 pref.setKey(CellInfoUtil.getNetworkTitle(mCellInfoList.get(index)));
                 pref.setOrder(index);
                 mNetworkOperatorsPreferences.addPreference(pref);
@@ -422,7 +434,7 @@
             if (cellInfo != null) {
                 if (DBG) logd("Currently registered cell: " + cellInfo.toString());
                 NetworkOperatorPreference pref =
-                        new NetworkOperatorPreference(cellInfo, getContext());
+                        new NetworkOperatorPreference(cellInfo, getContext(), mForbiddenPlmns);
                 pref.setTitle(mTelephonyManager.getNetworkOperatorName());
                 pref.setSummary(R.string.network_connected);
                 // Update the signal strength icon, since the default signalStrength value would be
@@ -503,7 +515,7 @@
         // Remove the current ConnectedNetworkOperatorsPreference
         removeConnectedNetworkOperatorPreference();
         final NetworkOperatorPreference pref =
-                new NetworkOperatorPreference(cellInfo, getContext());
+                new NetworkOperatorPreference(cellInfo, getContext(), mForbiddenPlmns);
         pref.setSummary(R.string.network_connected);
         mConnectedNetworkOperatorsPreference.addPreference(pref);
         PreferenceScreen preferenceScreen = getPreferenceScreen();
@@ -543,18 +555,18 @@
         if (DBG) logd("before aggregate: " + cellInfoList.toString());
         Map<String, CellInfo> map = new HashMap<>();
         for (CellInfo cellInfo: cellInfoList) {
-            String networkTitle = CellInfoUtil.getNetworkTitle(cellInfo);
-            if (cellInfo.isRegistered() || !map.containsKey(networkTitle)) {
-                map.put(networkTitle, cellInfo);
+            String plmn = CellInfoUtil.getOperatorInfoFromCellInfo(cellInfo).getOperatorNumeric();
+            if (cellInfo.isRegistered() || !map.containsKey(plmn)) {
+                map.put(plmn, cellInfo);
             } else {
-                if (map.get(networkTitle).isRegistered()
-                        || CellInfoUtil.getLevel(map.get(networkTitle))
+                if (map.get(plmn).isRegistered()
+                        || CellInfoUtil.getLevel(map.get(plmn))
                         > CellInfoUtil.getLevel(cellInfo)) {
                     // Skip if the stored cellInfo is registered or has higher signal strength level
                     continue;
                 }
                 // Otherwise replace it with the new CellInfo
-                map.put(networkTitle, cellInfo);
+                map.put(plmn, cellInfo);
             }
         }
         return new ArrayList<>(map.values());
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 0026311..b6894c5 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -1701,7 +1701,7 @@
             return null;
         }
 
-        WorkSource workSource = getWorkSource(null, Binder.getCallingUid());
+        WorkSource workSource = getWorkSource(Binder.getCallingUid());
         phone.getCellLocation(workSource).fillInNotifierBundle(data);
         return data;
     }
@@ -1785,7 +1785,7 @@
 
         ArrayList<NeighboringCellInfo> cells = null;
 
-        WorkSource workSource = getWorkSource(null, Binder.getCallingUid());
+        WorkSource workSource = getWorkSource(Binder.getCallingUid());
         try {
             cells = (ArrayList<NeighboringCellInfo>) sendRequest(
                     CMD_HANDLE_NEIGHBORING_CELL, workSource,
@@ -1807,7 +1807,7 @@
         }
 
         if (DBG_LOC) log("getAllCellInfo: is active user");
-        WorkSource workSource = getWorkSource(null, Binder.getCallingUid());
+        WorkSource workSource = getWorkSource(Binder.getCallingUid());
         List<CellInfo> cellInfos = new ArrayList<CellInfo>();
         for (Phone phone : PhoneFactory.getPhones()) {
             final List<CellInfo> info = phone.getAllCellInfo(workSource);
@@ -1819,7 +1819,7 @@
     @Override
     public void setCellInfoListRate(int rateInMillis) {
         enforceModifyPermission();
-        WorkSource workSource = getWorkSource(null, Binder.getCallingUid());
+        WorkSource workSource = getWorkSource(Binder.getCallingUid());
         mPhone.setCellInfoListRate(rateInMillis, workSource);
     }
 
@@ -4112,14 +4112,9 @@
         return null;
     }
 
-    private WorkSource getWorkSource(WorkSource workSource, int uid) {
-        if (workSource != null) {
-            return workSource;
-        }
-
+    private WorkSource getWorkSource(int uid) {
         String packageName = mPhone.getContext().getPackageManager().getNameForUid(uid);
-        workSource = new WorkSource(uid, packageName);
-        return workSource;
+        return new WorkSource(uid, packageName);
     }
 
     /**
diff --git a/src/com/android/services/telephony/CdmaConnection.java b/src/com/android/services/telephony/CdmaConnection.java
index ffa9dbc..4c869a9 100644
--- a/src/com/android/services/telephony/CdmaConnection.java
+++ b/src/com/android/services/telephony/CdmaConnection.java
@@ -224,7 +224,8 @@
             } catch (CallStateException e) {
                 Log.e(this, e, "Failed to hangup call waiting call");
             }
-            setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause));
+            setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause,
+                    null, getPhone().getPhoneId()));
         }
     }
 
diff --git a/src/com/android/services/telephony/DisconnectCauseUtil.java b/src/com/android/services/telephony/DisconnectCauseUtil.java
index 361e5dc..d6d63d3 100644
--- a/src/com/android/services/telephony/DisconnectCauseUtil.java
+++ b/src/com/android/services/telephony/DisconnectCauseUtil.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.media.ToneGenerator;
 import android.telecom.DisconnectCause;
+import android.telephony.SubscriptionManager;
 
 import com.android.internal.telephony.CallFailCause;
 import com.android.phone.ImsUtil;
@@ -63,13 +64,44 @@
     */
     public static DisconnectCause toTelecomDisconnectCause(
             int telephonyDisconnectCause, int telephonyPerciseDisconnectCause, String reason) {
+        return toTelecomDisconnectCause(telephonyDisconnectCause, telephonyPerciseDisconnectCause,
+                reason, SubscriptionManager.getDefaultVoicePhoneId());
+    }
+
+    /**
+     * Converts from a disconnect code in {@link android.telephony.DisconnectCause} into a more
+     * generic {@link android.telecom.DisconnectCause}.object, possibly populated with a localized
+     * message and tone for Slot.
+     *
+     * @param telephonyDisconnectCause The code for the reason for the disconnect.
+     * @param phoneId To support localized message based on phoneId
+     */
+    public static DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause,
+            String reason, int phoneId) {
+        return toTelecomDisconnectCause(telephonyDisconnectCause, CallFailCause.NOT_VALID,
+                reason, phoneId);
+    }
+
+   /**
+    * Converts from a disconnect code in {@link android.telephony.DisconnectCause} into a more
+    * generic {@link android.telecom.DisconnectCause}.object, possibly populated with a localized
+    * message and tone for Slot.
+    *
+    * @param telephonyDisconnectCause The code for the reason for the disconnect.
+    * @param telephonyPerciseDisconnectCause The code for the percise reason for the disconnect.
+    * @param reason Description of the reason for the disconnect, not intended for the user to see..
+    * @param phoneId To support localized message based on phoneId
+    */
+    public static DisconnectCause toTelecomDisconnectCause(
+            int telephonyDisconnectCause, int telephonyPerciseDisconnectCause, String reason,
+            int phoneId) {
         Context context = PhoneGlobals.getInstance();
         return new DisconnectCause(
                 toTelecomDisconnectCauseCode(telephonyDisconnectCause),
                 toTelecomDisconnectCauseLabel(context, telephonyDisconnectCause,
                         telephonyPerciseDisconnectCause),
-                toTelecomDisconnectCauseDescription(context, telephonyDisconnectCause),
-                toTelecomDisconnectReason(context,telephonyDisconnectCause, reason),
+                toTelecomDisconnectCauseDescription(context, telephonyDisconnectCause, phoneId),
+                toTelecomDisconnectReason(context,telephonyDisconnectCause, reason, phoneId),
                 toTelecomDisconnectCauseTone(telephonyDisconnectCause));
     }
 
@@ -483,7 +515,7 @@
      * Returns a description of the disconnect cause to be shown to the user.
      */
     private static CharSequence toTelecomDisconnectCauseDescription(
-            Context context, int telephonyDisconnectCause) {
+            Context context, int telephonyDisconnectCause, int phoneId) {
         if (context == null ) {
             return "";
         }
@@ -560,11 +592,11 @@
                 // TODO: Offer the option to turn the radio on, and automatically retry the call
                 // once network registration is complete.
 
-                if (ImsUtil.shouldPromoteWfc(context)) {
+                if (ImsUtil.shouldPromoteWfc(context, phoneId)) {
                     resourceId = R.string.incall_error_promote_wfc;
-                } else if (ImsUtil.isWfcModeWifiOnly(context)) {
+                } else if (ImsUtil.isWfcModeWifiOnly(context, phoneId)) {
                     resourceId = R.string.incall_error_wfc_only_no_wireless_network;
-                } else if (ImsUtil.isWfcEnabled(context)) {
+                } else if (ImsUtil.isWfcEnabled(context, phoneId)) {
                     resourceId = R.string.incall_error_power_off_wfc;
                 } else {
                     resourceId = R.string.incall_error_power_off;
@@ -592,11 +624,11 @@
 
             case android.telephony.DisconnectCause.OUT_OF_SERVICE:
                 // No network connection.
-                if (ImsUtil.shouldPromoteWfc(context)) {
+                if (ImsUtil.shouldPromoteWfc(context, phoneId)) {
                     resourceId = R.string.incall_error_promote_wfc;
-                } else if (ImsUtil.isWfcModeWifiOnly(context)) {
+                } else if (ImsUtil.isWfcModeWifiOnly(context, phoneId)) {
                     resourceId = R.string.incall_error_wfc_only_no_wireless_network;
-                } else if (ImsUtil.isWfcEnabled(context)) {
+                } else if (ImsUtil.isWfcEnabled(context, phoneId)) {
                     resourceId = R.string.incall_error_out_of_service_wfc;
                 } else {
                     resourceId = R.string.incall_error_out_of_service;
@@ -670,7 +702,7 @@
      * @return The disconnect reason.
      */
     private static String toTelecomDisconnectReason(Context context, int telephonyDisconnectCause,
-            String reason) {
+            String reason, int phoneId) {
 
         if (context == null) {
             return "";
@@ -682,7 +714,7 @@
                 // intentional fall-through
             case android.telephony.DisconnectCause.OUT_OF_SERVICE:
                 // No network connection.
-                if (ImsUtil.shouldPromoteWfc(context)) {
+                if (ImsUtil.shouldPromoteWfc(context, phoneId)) {
                     return android.telecom.DisconnectCause.REASON_WIFI_ON_BUT_WFC_OFF;
                 }
                 break;
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 830027c..ba55cf4 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -935,8 +935,14 @@
                 if (mConferenceHost == null) {
                     disconnectCause = new DisconnectCause(DisconnectCause.CANCELED);
                 } else {
-                    disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause(
-                            mConferenceHost.getOriginalConnection().getDisconnectCause());
+                    if (mConferenceHost.getPhone() != null) {
+                        disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause(
+                                mConferenceHost.getOriginalConnection().getDisconnectCause(),
+                                null, mConferenceHost.getPhone().getPhoneId());
+                    } else {
+                        disconnectCause = DisconnectCauseUtil.toTelecomDisconnectCause(
+                                mConferenceHost.getOriginalConnection().getDisconnectCause());
+                    }
                 }
                 setDisconnected(disconnectCause);
                 disconnectConferenceParticipants();
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 81e0257..ed5ef55 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -1195,7 +1195,7 @@
                 wasVideoCall = call.wasVideoCall();
             }
 
-            isVowifiEnabled = ImsUtil.isWfcEnabled(phone.getContext());
+            isVowifiEnabled = ImsUtil.isWfcEnabled(phone.getContext(), phone.getPhoneId());
         }
 
         if (isCurrentVideoCall) {
@@ -1572,7 +1572,8 @@
                         setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
                                 mOriginalConnection.getDisconnectCause(),
                                 preciseDisconnectCause,
-                                mOriginalConnection.getVendorDisconnectCause()));
+                                mOriginalConnection.getVendorDisconnectCause(),
+                                getPhone().getPhoneId()));
                         close();
                     }
                     break;
@@ -2080,7 +2081,7 @@
         boolean isVoWifiEnabled = false;
         if (isIms) {
             ImsPhone imsPhone = (ImsPhone) phone;
-            isVoWifiEnabled = ImsUtil.isWfcEnabled(phone.getContext());
+            isVoWifiEnabled = ImsUtil.isWfcEnabled(phone.getContext(), phone.getPhoneId());
         }
         PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils
                 .makePstnPhoneAccountHandle(phone.getDefaultPhone())
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index c7b2096..d937d96 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -290,7 +290,8 @@
                 return Connection.createFailedConnection(
                         DisconnectCauseUtil.toTelecomDisconnectCause(
                                 android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING,
-                                "Voicemail scheme provided but no voicemail number set."));
+                                "Voicemail scheme provided but no voicemail number set.",
+                                phone.getPhoneId()));
             }
 
             // Convert voicemail: to tel:
@@ -331,7 +332,8 @@
                             DisconnectCauseUtil.toTelecomDisconnectCause(
                                     android.telephony.DisconnectCause
                                             .CDMA_ALREADY_ACTIVATED,
-                                    "Tried to dial *228"));
+                                    "Tried to dial *228",
+                                    phone.getPhoneId()));
                 }
             }
         }
@@ -481,7 +483,8 @@
                 originalConnection.setDisconnected(
                         DisconnectCauseUtil.toTelecomDisconnectCause(
                                 android.telephony.DisconnectCause.OUTGOING_CANCELED,
-                                "Reconnecting outgoing Emergency Call."));
+                                "Reconnecting outgoing Emergency Call.",
+                                phone.getPhoneId()));
                 originalConnection.destroy();
             } else {
                 placeOutgoingConnection((TelephonyConnection) originalConnection, phone, request);
@@ -579,8 +582,8 @@
                 return Connection.createFailedConnection(
                         DisconnectCauseUtil.toTelecomDisconnectCause(
                                 android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY,
-                                "Cannot make non-emergency call in ECM mode."
-                        ));
+                                "Cannot make non-emergency call in ECM mode.",
+                                phone.getPhoneId()));
             }
         }
 
@@ -597,7 +600,8 @@
                         return Connection.createFailedConnection(
                                 DisconnectCauseUtil.toTelecomDisconnectCause(
                                         android.telephony.DisconnectCause.OUT_OF_SERVICE,
-                                        "ServiceState.STATE_OUT_OF_SERVICE"));
+                                        "ServiceState.STATE_OUT_OF_SERVICE",
+                                        phone.getPhoneId()));
                     }
                 case ServiceState.STATE_POWER_OFF:
                     // Don't disconnect if radio is power off because the device is on Bluetooth.
@@ -607,13 +611,15 @@
                     return Connection.createFailedConnection(
                             DisconnectCauseUtil.toTelecomDisconnectCause(
                                     android.telephony.DisconnectCause.POWER_OFF,
-                                    "ServiceState.STATE_POWER_OFF"));
+                                    "ServiceState.STATE_POWER_OFF",
+                                    phone.getPhoneId()));
                 default:
                     Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state);
                     return Connection.createFailedConnection(
                             DisconnectCauseUtil.toTelecomDisconnectCause(
                                     android.telephony.DisconnectCause.OUTGOING_FAILURE,
-                                    "Unknown service state " + state));
+                                    "Unknown service state " + state,
+                                    phone.getPhoneId()));
             }
         }
 
@@ -621,7 +627,8 @@
         if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled(context) &&
                 !isEmergencyNumber) {
             return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause(
-                    android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED));
+                    android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED,
+                    null, phone.getPhoneId()));
         }
 
         // Check for additional limits on CDMA phones.
@@ -635,7 +642,8 @@
             return Connection.createFailedConnection(
                     DisconnectCauseUtil.toTelecomDisconnectCause(
                             android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING,
-                            "Call forwarding while roaming"));
+                            "Call forwarding while roaming",
+                            phone.getPhoneId()));
         }
 
 
@@ -646,7 +654,8 @@
             return Connection.createFailedConnection(
                     DisconnectCauseUtil.toTelecomDisconnectCause(
                             android.telephony.DisconnectCause.OUTGOING_FAILURE,
-                            "Invalid phone type"));
+                            "Invalid phone type",
+                            phone.getPhoneId()));
         }
         connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED);
         connection.setInitializing();
@@ -685,7 +694,8 @@
             return Connection.createFailedConnection(
                     DisconnectCauseUtil.toTelecomDisconnectCause(
                             android.telephony.DisconnectCause.INCOMING_MISSED,
-                            "Found no ringing call"));
+                            "Found no ringing call",
+                            phone.getPhoneId()));
         }
 
         com.android.internal.telephony.Connection originalConnection =
@@ -1036,7 +1046,7 @@
                 cause = android.telephony.DisconnectCause.POWER_OFF;
             }
             connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
-                    cause, e.getMessage()));
+                    cause, e.getMessage(), phone.getPhoneId()));
             connection.clearOriginalConnection();
             connection.destroy();
             return;
@@ -1060,7 +1070,7 @@
             }
             Log.d(this, "placeOutgoingConnection, phone.dial returned null");
             connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
-                    telephonyDisconnectCause, "Connection is null"));
+                    telephonyDisconnectCause, "Connection is null", phone.getPhoneId()));
             connection.clearOriginalConnection();
             connection.destroy();
         } else {
diff --git a/testapps/TelephonyManagerTestApp/Android.mk b/testapps/TelephonyManagerTestApp/Android.mk
new file mode 100644
index 0000000..290b261
--- /dev/null
+++ b/testapps/TelephonyManagerTestApp/Android.mk
@@ -0,0 +1,19 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+src_dirs := src
+res_dirs := res
+
+LOCAL_SRC_FILES := $(call all-java-files-under, $(src_dirs))
+LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
+
+LOCAL_JAVACFLAGS := -parameters
+
+LOCAL_PACKAGE_NAME := TelephonyManagerTestApp
+LOCAL_PRIVATE_PLATFORM_APIS := true
+
+LOCAL_CERTIFICATE := platform
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/testapps/TelephonyManagerTestApp/AndroidManifest.xml b/testapps/TelephonyManagerTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..044d0b2
--- /dev/null
+++ b/testapps/TelephonyManagerTestApp/AndroidManifest.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.phone.testapps.telephonymanagertestapp">
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
+    <uses-permission android:name="android.permission.CALL_PRIVILEGED"/>
+    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+
+    android.Manifest.permission.ACCESS_FINE_LOCATION
+    <application android:label="TelephonyManagerTestApp">
+        <activity
+            android:name=".TelephonyManagerTestApp"
+            android:label="TelephonyManagerTestApp">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <action android:name="android.intent.action.SEARCH" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data
+                android:name="android.app.searchable"
+                android:resource="@xml/searchable">
+            </meta-data>
+        </activity>
+
+        <activity
+            android:name=".CallingMethodActivity"
+            android:label="CallingMethodActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
diff --git a/testapps/TelephonyManagerTestApp/res/layout/abstract_method_view.xml b/testapps/TelephonyManagerTestApp/res/layout/abstract_method_view.xml
new file mode 100644
index 0000000..fe5e0e4
--- /dev/null
+++ b/testapps/TelephonyManagerTestApp/res/layout/abstract_method_view.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+    <TextView
+        android:id="@+id/tags"
+        android:layout_width="fill_parent"
+        android:layout_height="20dip"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:ellipsize="marquee"
+        android:maxLines="1"
+        android:textSize="12sp" />
+
+    <TextView
+        android:id="@+id/method_name"
+        android:layout_width="fill_parent"
+        android:layout_height="30dip"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:ellipsize="marquee"
+        android:maxLines="2"
+        android:textSize="20sp" />
+
+    <TextView
+        android:id="@+id/parameters"
+        android:layout_width="fill_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:ellipsize="end"
+        android:maxLines="1"
+        android:textSize="12sp" />
+
+</LinearLayout>
diff --git a/testapps/TelephonyManagerTestApp/res/layout/activity_main.xml b/testapps/TelephonyManagerTestApp/res/layout/activity_main.xml
new file mode 100644
index 0000000..af655db
--- /dev/null
+++ b/testapps/TelephonyManagerTestApp/res/layout/activity_main.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <ListView
+        android:id="@android:id/list"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent">
+    </ListView>
+</LinearLayout>
diff --git a/testapps/TelephonyManagerTestApp/res/layout/calling_method.xml b/testapps/TelephonyManagerTestApp/res/layout/calling_method.xml
new file mode 100644
index 0000000..668b708
--- /dev/null
+++ b/testapps/TelephonyManagerTestApp/res/layout/calling_method.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                  android:orientation="horizontal"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content">
+        <TextView
+            android:id="@+id/sub_id"
+            android:text="subId: "
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="1"
+            android:textSize="20sp" />
+
+        <EditText
+            android:id="@+id/sub_id_value"
+            android:text="-1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:maxLines="1"
+            android:textSize="20sp" />
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/tags"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:ellipsize="marquee"
+        android:maxLines="1"
+        android:textSize="15sp" />
+
+    <TextView
+        android:id="@+id/method_name"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:ellipsize="marquee"
+        android:textSize="30sp" />
+
+    <ListView
+        android:id="@android:id/list"
+        android:layout_height="wrap_content"
+        android:layout_width="fill_parent">
+    </ListView>
+
+    <Button
+        android:id="@+id/go_button"
+        android:title="Go"
+        android:text="Go!"
+        android:layout_width="80dip"
+        android:layout_height="50dip">
+    </Button>
+
+    <TextView
+        android:id="@+id/return_value"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:ellipsize="marquee"
+        android:textSize="15sp" />
+</LinearLayout>
diff --git a/testapps/TelephonyManagerTestApp/res/layout/parameter_field.xml b/testapps/TelephonyManagerTestApp/res/layout/parameter_field.xml
new file mode 100644
index 0000000..6bb40fd
--- /dev/null
+++ b/testapps/TelephonyManagerTestApp/res/layout/parameter_field.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="horizontal"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+    <TextView
+        android:id="@+id/field_name"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:maxLines="1"
+        android:textSize="15sp" />
+
+    <!--android:ellipsize="marquee"-->
+    <!--android:layout_alignParentBottom="true"-->
+    <!--android:layout_alignParentRight="true"-->
+
+    <EditText
+        android:id="@+id/field_value"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:maxLines="1"
+        android:textSize="15sp" />
+</LinearLayout>
diff --git a/testapps/TelephonyManagerTestApp/res/menu/search_input.xml b/testapps/TelephonyManagerTestApp/res/menu/search_input.xml
new file mode 100644
index 0000000..261a049
--- /dev/null
+++ b/testapps/TelephonyManagerTestApp/res/menu/search_input.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/search_input"
+        android:title="Search"
+        android:actionViewClass="android.widget.SearchView"
+        android:showAsAction="collapseActionView|ifRoom"
+        android:imeOptions="actionSearch">
+    </item>
+</menu>
diff --git a/testapps/TelephonyManagerTestApp/res/xml/searchable.xml b/testapps/TelephonyManagerTestApp/res/xml/searchable.xml
new file mode 100644
index 0000000..05cf491
--- /dev/null
+++ b/testapps/TelephonyManagerTestApp/res/xml/searchable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<searchable xmlns:android="http://schemas.android.com/apk/res/android"
+    android:label=".TelephonyManagerTestApp"
+    android:hint="Search...">
+</searchable>
\ No newline at end of file
diff --git a/testapps/TelephonyManagerTestApp/src/com/android/phone/testapps/telephonymanagertestapp/CallingMethodActivity.java b/testapps/TelephonyManagerTestApp/src/com/android/phone/testapps/telephonymanagertestapp/CallingMethodActivity.java
new file mode 100644
index 0000000..aa9dbc0
--- /dev/null
+++ b/testapps/TelephonyManagerTestApp/src/com/android/phone/testapps/telephonymanagertestapp/CallingMethodActivity.java
@@ -0,0 +1,152 @@
+/*
+ * 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.testapps.telephonymanagertestapp;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+/**
+ * Activity to call a specific method of TelephonyManager.
+ */
+public class CallingMethodActivity extends ListActivity {
+    private Class[] mParameterTypes;
+    private Object[] mParameterValues;
+    private Button mGoButton;
+    private Method mMethod;
+    private TextView mReturnValue;
+    private EditText mSubIdField;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.calling_method);
+
+        if (TelephonyManagerTestApp.sCurrentMethod == null) {
+            finish();
+            return;
+        }
+
+        mMethod = TelephonyManagerTestApp.sCurrentMethod;
+
+        mGoButton = findViewById(R.id.go_button);
+        mReturnValue = findViewById(R.id.return_value);
+        mSubIdField = findViewById(R.id.sub_id_value);
+        setListAdapter(new ParameterListAdapter());
+
+        mParameterTypes = mMethod.getParameterTypes();
+        mParameterValues = new Object[mParameterTypes.length];
+
+        String tags = Modifier.toString(mMethod.getModifiers()) + ' '
+                + TelephonyManagerTestApp.getShortTypeName(mMethod.getReturnType().toString());
+        ((TextView) findViewById(R.id.tags)).setText(tags);
+        ((TextView) findViewById(R.id.method_name)).setText(mMethod.getName());
+
+        mGoButton.setOnClickListener((View v) -> executeCallMethod());
+        mReturnValue.setText("Return value: ");
+    }
+
+    private void executeCallMethod() {
+        try {
+            int subId = Integer.parseInt(mSubIdField.getText().toString());
+
+            for (int i = 0; i < mParameterTypes.length; i++) {
+                String text = ((EditText) getListAdapter().getItem(i)).getText().toString();
+                if (mParameterTypes[i] == int.class) {
+                    mParameterValues[i] = Integer.parseInt(text);
+                } else if (mParameterTypes[i] == boolean.class) {
+                    mParameterValues[i] = Boolean.parseBoolean(text);
+                } else if (mParameterTypes[i] == Long.class) {
+                    mParameterValues[i] = Long.parseLong(text);
+                }
+            }
+            Log.d(TelephonyManagerTestApp.TAG, "Invoking method " + mMethod.getName());
+
+            mMethod.setAccessible(true);
+            if (!mMethod.getReturnType().equals(Void.TYPE)) {
+                Object result = mMethod.invoke(new TelephonyManager(this, subId), mParameterValues);
+                if (result instanceof String) {
+                    if (((String) result).isEmpty()) {
+                        result = "empty string";
+                    }
+                }
+                Log.d(TelephonyManagerTestApp.TAG, "result is " + result);
+                mReturnValue.setText("Return value: " + result);
+            } else {
+                mMethod.invoke(new TelephonyManager(this, subId), mParameterValues);
+                mReturnValue.setText("Return value: successfully returned");
+            }
+
+        } catch (Exception exception) {
+            Log.d(TelephonyManagerTestApp.TAG, "NoSuchMethodException " + exception);
+            mReturnValue.setText("NoSuchMethodException " + exception);
+        }
+    }
+
+    private class ParameterListAdapter extends BaseAdapter {
+        ArrayList<EditText> mEditTexts = new ArrayList<>();
+        @Override
+        public int getCount() {
+            return mParameterTypes == null ? 0 : mParameterTypes.length;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup container) {
+            if (mParameterTypes == null || mParameterTypes.length <= position) {
+                return null;
+            }
+
+            if (convertView == null) {
+                convertView = getLayoutInflater().inflate(
+                        R.layout.parameter_field, container, false);
+            }
+
+            Class aClass = mParameterTypes[position];
+
+            ((TextView) convertView.findViewById(R.id.field_name)).setText(
+                    TelephonyManagerTestApp.getShortTypeName(aClass.toString()) + ": ");
+            mEditTexts.add(convertView.findViewById(R.id.field_value));
+
+            return convertView;
+        }
+
+        @Override
+        public Object getItem(int position) {
+            if (mEditTexts == null || mEditTexts.size() <= position) {
+                return null;
+            }
+
+            return mEditTexts.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+    }
+}
diff --git a/testapps/TelephonyManagerTestApp/src/com/android/phone/testapps/telephonymanagertestapp/TelephonyManagerTestApp.java b/testapps/TelephonyManagerTestApp/src/com/android/phone/testapps/telephonymanagertestapp/TelephonyManagerTestApp.java
new file mode 100644
index 0000000..45c76a7
--- /dev/null
+++ b/testapps/TelephonyManagerTestApp/src/com/android/phone/testapps/telephonymanagertestapp/TelephonyManagerTestApp.java
@@ -0,0 +1,198 @@
+/*
+ * 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.testapps.telephonymanagertestapp;
+
+import android.app.ListActivity;
+import android.app.SearchManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telephony.TelephonyManager;
+import android.view.Menu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.TextView;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Parameter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Main activity.
+ * Activity to choose which method to call.
+ */
+public class TelephonyManagerTestApp extends ListActivity implements
+        SearchView.OnQueryTextListener {
+    public static String TAG = "TMTestApp";
+
+    private List<Method> mMethods = new ArrayList<>();
+    private List<Method> mFilteredMethods = new ArrayList<>();
+    static Method sCurrentMethod;
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Initialize search view
+        getMenuInflater().inflate(R.menu.search_input, menu);
+        SearchView searchView = (SearchView) menu.findItem(R.id.search_input).getActionView();
+        SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
+        searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
+        searchView.setOnQueryTextListener(this);
+        searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+            @Override
+            public void onViewDetachedFromWindow(View arg0) {
+                mFilteredMethods.clear();
+                mFilteredMethods.addAll(mMethods);
+                ((ListViewAdapter) mAdapter).notifyDataSetChanged();
+            }
+
+            @Override
+            public void onViewAttachedToWindow(View arg0) {
+            }
+        });
+        return true;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        try {
+            Class c = TelephonyManager.class;
+            mMethods = Arrays.asList(c.getDeclaredMethods());
+            mFilteredMethods.addAll(mMethods);
+            mAdapter = new ListViewAdapter();
+            setListAdapter(mAdapter);
+        } catch (Throwable e) {
+            System.err.println(e);
+            finish();
+        }
+    }
+
+    private class ListViewAdapter extends BaseAdapter {
+        @Override
+        public int getCount() {
+            return mFilteredMethods.size();
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup container) {
+            if (mFilteredMethods.size() <= position) {
+                return null;
+            }
+
+            if (convertView == null) {
+                convertView = getLayoutInflater().inflate(
+                        R.layout.abstract_method_view, container, false);
+            }
+
+            Method method = mFilteredMethods.get(position);
+            String tags = Modifier.toString(method.getModifiers()) + ' '
+                    + getShortTypeName(method.getReturnType().toString());
+            String parameters = getParameters(method.getParameterTypes(), method.getParameters());
+            String methodName = (parameters == null) ? (method.getName() + "()") : method.getName();
+
+            ((TextView) convertView.findViewById(R.id.tags)).setText(tags);
+            ((TextView) convertView.findViewById(R.id.method_name)).setText(methodName);
+            ((TextView) convertView.findViewById(R.id.parameters)).setText(parameters);
+            return convertView;
+        }
+
+        @Override
+        public Object getItem(int position) {
+            if (mFilteredMethods.size() <= position) {
+                return null;
+            }
+
+            return mFilteredMethods.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+    }
+
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        sCurrentMethod = mFilteredMethods.get(position);
+        Intent intent = new Intent(this, CallingMethodActivity.class);
+
+        startActivity(intent);
+    }
+
+    @Override
+    public boolean onQueryTextSubmit(String query) {
+        filterMethods(query);
+        return true;
+    }
+
+    @Override
+    public boolean onQueryTextChange(String newText) {
+        return false;
+    }
+
+    private void filterMethods(String text) {
+        mFilteredMethods.clear();
+
+        if (text == null || text.isEmpty()) {
+            mFilteredMethods.addAll(mMethods);
+        } else {
+            for (Method method : mMethods) {
+                if (method.getName().contains(text)) {
+                    mFilteredMethods.add(method);
+                }
+            }
+        }
+
+        ((ListViewAdapter) mAdapter).notifyDataSetChanged();
+
+    }
+
+    private String getParameters(Class<?>[] types, Parameter[] parameters) {
+        if (types == null || types.length == 0) {
+            return null;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        sb.append('(');
+        for (int j = 0; j < types.length; j++) {
+            String typeName = getShortTypeName(types[j].getTypeName());
+            sb.append(typeName);
+            if (j < (types.length - 1)) {
+                sb.append(", ");
+            }
+        }
+        sb.append(')');
+
+        return sb.toString();
+    }
+
+    static String getShortTypeName(String typeName) {
+        if (typeName == null) {
+            return null;
+        }
+
+        String[] parts = typeName.split("[. ]");
+        return parts[parts.length - 1];
+    }
+}