Merge "Add test for Ussd Parser" into qt-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6326008..938d714 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -607,6 +607,14 @@
             android:name="com.android.internal.telephony.uicc.ShowInstallAppNotificationReceiver"
             android:exported="false"/>
 
+        <activity
+            android:name="com.android.phone.settings.PickSmsSubscriptionActivity"
+            android:exported="false"
+            android:excludeFromRecents="true"
+            android:launchMode="singleTop"
+            android:configChanges="orientation|screenSize|keyboardHidden"
+            android:theme="@style/Theme.Transparent"/>
+
         <service
             android:name="com.android.phone.vvm.RemoteVvmTaskManager"
             android:exported="false"/>
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index ffb7292..e6fa00a 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -398,7 +398,8 @@
             prefSet.removePreference(mEnableVideoCalling);
         }
 
-        final PhoneAccountHandle simCallManager = mTelecomManager.getSimCallManager();
+        final PhoneAccountHandle simCallManager = mTelecomManager.getSimCallManagerForSubscription(
+                mPhone.getSubId());
         if (simCallManager != null) {
             Intent intent = PhoneAccountSettingsFragment.buildPhoneAccountConfigureIntent(
                     this, simCallManager);
diff --git a/src/com/android/phone/MobileNetworkSettings.java b/src/com/android/phone/MobileNetworkSettings.java
index c123e66..e67758d 100644
--- a/src/com/android/phone/MobileNetworkSettings.java
+++ b/src/com/android/phone/MobileNetworkSettings.java
@@ -149,12 +149,17 @@
 
 
     /**
-     * Returns true if Wifi calling is enabled for at least one phone.
+     * Returns true if Wifi calling is enabled for at least one subscription.
      */
     public static boolean isWifiCallingEnabled(Context context) {
-        int phoneCount = TelephonyManager.from(context).getPhoneCount();
-        for (int i = 0; i < phoneCount; i++) {
-            if (isWifiCallingEnabled(context, i)) {
+        SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
+        if (subManager == null) {
+            Log.e(MobileNetworkFragment.LOG_TAG,
+                    "isWifiCallingEnabled: couldn't get system service.");
+            return false;
+        }
+        for (int subId : subManager.getActiveSubscriptionIdList()) {
+            if (isWifiCallingEnabled(context, subId)) {
                 return true;
             }
         }
@@ -162,11 +167,12 @@
     }
 
     /**
-     * Returns true if Wifi calling is enabled for the specific phone with id {@code phoneId}.
+     * Returns true if Wifi calling is enabled for the specific subscription with id {@code subId}.
      */
-    public static boolean isWifiCallingEnabled(Context context, int phoneId) {
+    public static boolean isWifiCallingEnabled(Context context, int subId) {
         final PhoneAccountHandle simCallManager =
-                TelecomManager.from(context).getSimCallManager();
+                TelecomManager.from(context).getSimCallManagerForSubscription(subId);
+        final int phoneId = SubscriptionManager.getSlotIndex(subId);
 
         boolean isWifiCallingEnabled;
         if (simCallManager != null) {
@@ -1890,14 +1896,14 @@
             }
 
             // Removes the preference if the wifi calling is disabled.
-            if (!isWifiCallingEnabled(getContext(), SubscriptionManager.getPhoneId(mSubId))) {
+            if (!isWifiCallingEnabled(getContext(), mSubId)) {
                 mCallingCategory.removePreference(mWiFiCallingPref);
                 return;
             }
 
             // See what Telecom thinks the SIM call manager is.
             final PhoneAccountHandle simCallManager =
-                    TelecomManager.from(getContext()).getSimCallManager();
+                    TelecomManager.from(getContext()).getSimCallManagerForSubscription(mSubId);
 
             // Check which SIM call manager is for the current sub ID.
             PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mSubId);
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 7d25661..2e16dfe 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -121,6 +121,7 @@
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.DefaultPhoneNotifier;
 import com.android.internal.telephony.HalVersion;
+import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.INumberVerificationCallback;
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.IccCard;
@@ -139,6 +140,7 @@
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.SmsApplication;
 import com.android.internal.telephony.SmsApplication.SmsApplicationData;
+import com.android.internal.telephony.SmsPermissions;
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.telephony.dataconnection.ApnSettingUtils;
@@ -156,6 +158,7 @@
 import com.android.internal.telephony.uicc.UiccSlot;
 import com.android.internal.telephony.util.VoicemailNotificationSettingsUtil;
 import com.android.internal.util.HexDump;
+import com.android.phone.settings.PickSmsSubscriptionActivity;
 import com.android.phone.vvm.PhoneAccountHandleConverter;
 import com.android.phone.vvm.RemoteVvmTaskManager;
 import com.android.phone.vvm.VisualVoicemailSettingsUtil;
@@ -2136,10 +2139,16 @@
             case DENIED_HARD:
                 throw new SecurityException("Not allowed to access cell info");
             case DENIED_SOFT:
+                try {
+                    cb.onCellInfo(new ArrayList<CellInfo>());
+                } catch (RemoteException re) {
+                    // Drop without consequences
+                }
                 return;
         }
 
-        final Phone phone = getPhone(subId);
+
+        final Phone phone = getPhoneFromSubId(subId);
         if (phone == null) throw new IllegalArgumentException("Invalid Subscription Id: " + subId);
 
         sendRequestAsync(CMD_REQUEST_CELL_INFO_UPDATE, cb, phone, workSource);
@@ -7060,4 +7069,21 @@
             Binder.restoreCallingIdentity(identity);
         }
     }
+
+    @Override
+    public void enqueueSmsPickResult(String callingPackage, IIntegerConsumer pendingSubIdResult) {
+        SmsPermissions permissions = new SmsPermissions(getDefaultPhone(), mApp,
+                (AppOpsManager) mApp.getSystemService(Context.APP_OPS_SERVICE));
+        if (!permissions.checkCallingCanSendSms(callingPackage, "Sending message")) {
+            throw new SecurityException("Requires SEND_SMS permission to perform this operation");
+        }
+        PickSmsSubscriptionActivity.addPendingResult(pendingSubIdResult);
+        Intent intent = new Intent();
+        intent.setClass(mApp, PickSmsSubscriptionActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        // Bring up choose default SMS subscription dialog right now
+        intent.putExtra(PickSmsSubscriptionActivity.DIALOG_TYPE_KEY,
+                PickSmsSubscriptionActivity.SMS_PICK_FOR_MESSAGE);
+        mApp.startActivity(intent);
+    }
 }
diff --git a/src/com/android/phone/settings/PickSmsSubscriptionActivity.java b/src/com/android/phone/settings/PickSmsSubscriptionActivity.java
new file mode 100644
index 0000000..cfbce28
--- /dev/null
+++ b/src/com/android/phone/settings/PickSmsSubscriptionActivity.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2019 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.settings;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+
+import com.android.internal.telephony.IIntegerConsumer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Trampolines a request to Settings to get the SMS subscription associated with an SmsManager
+ * operation.
+ *
+ * Since a Service can not start an Activity with
+ * {@link Activity#startActivityForResult(Intent, int)} and get a response (only Activities can
+ * handle the results), we have to "Trampoline" this operation by creating an empty Activity whose
+ * only job is to call startActivityForResult with the correct Intent and handle the result.
+ */
+// TODO: SmsManager should be constructed with an activity context so it can start as part of its
+// task and fall back to PickSmsSubscriptionActivity being called in PhoneInterfaceManager if not
+// called from an activity context.
+public class PickSmsSubscriptionActivity extends Activity {
+
+    private static final String LOG_TAG = "PickSmsSubActivity";
+
+    // Defined in Settings SimDialogActivity
+    private static final String RESULT_SUB_ID = "result_sub_id";
+    public static final String DIALOG_TYPE_KEY = "dialog_type";
+    public static final int SMS_PICK_FOR_MESSAGE = 4;
+
+    private static final ComponentName SETTINGS_SUB_PICK_ACTIVITY = new ComponentName(
+            "com.android.settings", "com.android.settings.sim.SimDialogActivity");
+
+    private static final List<IIntegerConsumer> sSmsPickPendingList = new ArrayList<>();
+
+    private static final int REQUEST_GET_SMS_SUB_ID = 1;
+
+    /**
+     * Adds a consumer to the list of pending results that will be accepted once the activity
+     * completes.
+     */
+    public static void addPendingResult(IIntegerConsumer consumer) {
+        synchronized (sSmsPickPendingList) {
+            sSmsPickPendingList.add(consumer);
+        }
+        Log.i(LOG_TAG, "queue pending result, token: " + consumer);
+    }
+
+    private static void sendResultAndClear(int resultId) {
+        // If the calling process died, just ignore callback.
+        synchronized (sSmsPickPendingList) {
+            for (IIntegerConsumer c : sSmsPickPendingList) {
+                try {
+                    c.accept(resultId);
+                    Log.i(LOG_TAG, "Result received, token: " + c + ", result: " + resultId);
+                } catch (RemoteException e) {
+                    // The calling process died, skip this one.
+                }
+            }
+            sSmsPickPendingList.clear();
+        }
+    }
+
+    // Keep track if this activity has been stopped (i.e. user navigated away, power screen off,...)
+    // if so, treat it as the user navigating away and end the task if it is restarted without an
+    // onCreate/onNewIntent.
+    private boolean mPreviouslyStopped = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mPreviouslyStopped = false;
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        mPreviouslyStopped = false;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        // This is cause a little jank with the recents display, but there is no other way to handle
+        // the case where activity has stopped and we want to dismiss the dialog. We use the
+        // tag "excludeFromRecents", but in the cases where it is still shown, kill it in onResume.
+        if (mPreviouslyStopped) {
+            finishAndRemoveTask();
+        } else {
+            launchSmsPicker(new Intent(getIntent()));
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        // User navigated away from dialog, send invalid sub id result.
+        mPreviouslyStopped = true;
+        sendResultAndClear(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        // triggers cancelled result for onActivityResult
+        finishActivity(REQUEST_GET_SMS_SUB_ID);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUEST_GET_SMS_SUB_ID) {
+            int result = data == null ? SubscriptionManager.INVALID_SUBSCRIPTION_ID :
+                    data.getIntExtra(RESULT_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            if (resultCode == Activity.RESULT_OK) {
+                sendResultAndClear(result);
+            } else {
+                sendResultAndClear(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+            }
+        }
+        // This will be handled in onResume - we do not want to call this all the time here because
+        // we need to be able to restart if stopped and a new intent comes in via onNewIntent.
+        if (!mPreviouslyStopped) {
+            finishAndRemoveTask();
+        }
+    }
+
+    private void launchSmsPicker(Intent trampolineIntent) {
+        trampolineIntent.setComponent(SETTINGS_SUB_PICK_ACTIVITY);
+        // Remove this flag if it exists, we want the settings activity to be part of this task.
+        trampolineIntent.removeFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivityForResult(trampolineIntent, REQUEST_GET_SMS_SUB_ID);
+    }
+}
diff --git a/testapps/SmsManagerTestApp/Android.mk b/testapps/SmsManagerTestApp/Android.mk
new file mode 100644
index 0000000..307366b
--- /dev/null
+++ b/testapps/SmsManagerTestApp/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+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_PACKAGE_NAME := SmsManagerTestApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/testapps/SmsManagerTestApp/AndroidManifest.xml b/testapps/SmsManagerTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..c5f4621
--- /dev/null
+++ b/testapps/SmsManagerTestApp/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?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
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.phone.testapps.smsmanagertestapp">
+    <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="29" />
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <application android:label="SmsManagerTestApp">
+        <activity
+            android:name=".SmsManagerTestApp"
+            android:label="SmsManagerTestApp">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <service android:name=".SmsManagerTestService" android:exported="false" />
+        <receiver android:name=".SendStatusReceiver"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.phone.testapps.smsmanagertestapp.message_sent_action" />
+                <data android:scheme="content" />
+            </intent-filter>
+        </receiver>
+
+    </application>
+</manifest>
+
diff --git a/testapps/SmsManagerTestApp/res/layout/activity_main.xml b/testapps/SmsManagerTestApp/res/layout/activity_main.xml
new file mode 100644
index 0000000..39fb6c6
--- /dev/null
+++ b/testapps/SmsManagerTestApp/res/layout/activity_main.xml
@@ -0,0 +1,67 @@
+<?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="wrap_content"
+    android:orientation="vertical"
+    android:paddingLeft="4dp">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" >
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" android:layout_weight="0"
+            android:text="Outgoing SMS Phone Number"/>
+
+        <EditText
+            android:id="@+id/phone_number_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:inputType="phone" android:text="5555551212"/>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height= "1dp"
+            android:paddingRight="4dp"
+            android:background="?android:attr/listDivider" />
+
+        <Button
+            android:id="@+id/send_text_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingRight="4dp"
+            android:text="@string/send_text_button"/>
+        <Button
+            android:id="@+id/send_text_button_service"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingRight="4dp"
+            android:text="@string/send_text_service_button"/>
+        <Button
+            android:id="@+id/get_sub_for_result_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingRight="4dp"
+            android:text="@string/get_sub_for_result_button"/>
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/testapps/SmsManagerTestApp/res/values/donottranslate_strings.xml b/testapps/SmsManagerTestApp/res/values/donottranslate_strings.xml
new file mode 100644
index 0000000..d6497a3
--- /dev/null
+++ b/testapps/SmsManagerTestApp/res/values/donottranslate_strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<resources>
+    <string name="send_text_button">Send Outgoing Text Now.</string>
+    <string name="send_text_service_button">Send Outgoing Text after 5 sec.</string>
+    <string name="get_sub_for_result_button">Ask user for sub id.</string>
+</resources>
\ No newline at end of file
diff --git a/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/SendStatusReceiver.java b/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/SendStatusReceiver.java
new file mode 100644
index 0000000..03709d1
--- /dev/null
+++ b/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/SendStatusReceiver.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 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.smsmanagertestapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.widget.Toast;
+
+/**
+ * Handles the PendingIntent result from SMS messages send to Telephony. Reports the results of
+ * those messages using Toasts.
+ */
+public class SendStatusReceiver extends BroadcastReceiver {
+
+    public static final String MESSAGE_SENT_ACTION =
+            "com.android.phone.testapps.smsmanagertestapp.message_sent_action";
+
+    // Defined by platform, but no constant provided. See docs for SmsManager.sendTextMessage.
+    private static final String EXTRA_ERROR_CODE = "errorCode";
+    private static final String EXTRA_NO_DEFAULT = "noDefault";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final int resultCode = getResultCode();
+        if (MESSAGE_SENT_ACTION.equals(intent.getAction())) {
+            int errorCode = intent.getIntExtra(EXTRA_ERROR_CODE, -1);
+            boolean userCancel = intent.getBooleanExtra(EXTRA_NO_DEFAULT, false);
+            if (userCancel) {
+                Toast.makeText(context, "SMS not sent, user cancelled.", Toast.LENGTH_LONG).show();
+            } else {
+                Toast.makeText(context, "SMS result=" + resultCode + ", error extra=" + errorCode,
+                        Toast.LENGTH_LONG).show();
+            }
+        }
+    }
+}
diff --git a/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/SmsManagerTestApp.java b/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/SmsManagerTestApp.java
new file mode 100644
index 0000000..75536f3
--- /dev/null
+++ b/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/SmsManagerTestApp.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2019 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.smsmanagertestapp;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.telephony.SmsManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Supports sending an SMS immediately and offloading the sending of the SMS to a background task.
+ */
+public class SmsManagerTestApp extends Activity {
+
+    private static final int REQUEST_PERMISSION_READ_STATE = 1;
+    private static final int REQUEST_GET_SMS_SUB_ID = 2;
+
+    private static final ComponentName SETTINGS_SUB_PICK_ACTIVITY = new ComponentName(
+            "com.android.settings", "com.android.settings.sim.SimDialogActivity");
+
+    /*
+     * Forwarded constants from SimDialogActivity.
+     */
+    private static final String DIALOG_TYPE_KEY = "dialog_type";
+    public static final String RESULT_SUB_ID = "result_sub_id";
+    private static final int SMS_PICK = 2;
+
+    private static int sMessageId = 0;
+    private boolean mIsReadPhoneStateGranted = false;
+
+    private EditText mPhoneNumber;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_main);
+
+        findViewById(R.id.send_text_button).setOnClickListener(this::sendOutgoingSms);
+        findViewById(R.id.send_text_button_service)
+                .setOnClickListener(this::sendOutgoingSmsService);
+        findViewById(R.id.get_sub_for_result_button).setOnClickListener(this::getSubIdForResult);
+        mPhoneNumber = (EditText) findViewById(R.id.phone_number_text);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE)
+                != PackageManager.PERMISSION_GRANTED
+                || checkSelfPermission(Manifest.permission.SEND_SMS)
+                        != PackageManager.PERMISSION_GRANTED) {
+            mIsReadPhoneStateGranted = false;
+            requestPermissions(new String[]{Manifest.permission.READ_PHONE_STATE,
+                    Manifest.permission.SEND_SMS}, REQUEST_PERMISSION_READ_STATE);
+        } else {
+            mIsReadPhoneStateGranted = true;
+        }
+        if (mIsReadPhoneStateGranted) {
+            mPhoneNumber.setText(getPhoneNumber(), TextView.BufferType.NORMAL);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        stopService(new Intent(this, SmsManagerTestService.class));
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions,
+            int[] grantResults) {
+        switch (requestCode) {
+            case REQUEST_PERMISSION_READ_STATE: {
+                if (grantResults.length > 0
+                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                    mIsReadPhoneStateGranted = true;
+                } else {
+                    // permission denied
+                    Toast.makeText(this, "read_phone_state denied.", Toast.LENGTH_SHORT).show();
+                }
+            }
+
+        }
+
+        if (mIsReadPhoneStateGranted) {
+            mPhoneNumber.setText(getPhoneNumber(), TextView.BufferType.NORMAL);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case (REQUEST_GET_SMS_SUB_ID) : {
+                int resultSubId;
+                if (resultCode == RESULT_OK) {
+                    resultSubId = data == null ? -1 : data.getIntExtra(RESULT_SUB_ID,
+                            SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+                    Toast.makeText(this, "User picked sub id = " + resultSubId,
+                            Toast.LENGTH_LONG).show();
+                } else {
+                    Toast.makeText(this, "User cancelled dialog.",
+                            Toast.LENGTH_SHORT).show();
+                }
+                break;
+            }
+        }
+    }
+
+
+    private void sendOutgoingSms(View view) {
+        String phoneNumber = mPhoneNumber.getText().toString();
+        if (TextUtils.isEmpty(phoneNumber)) {
+            Toast.makeText(this, "Couldn't get phone number from view! Ignoring request...",
+                    Toast.LENGTH_SHORT).show();
+            return;
+        }
+        if (mIsReadPhoneStateGranted) {
+            SmsManager m = SmsManager.getDefault();
+            m.sendTextMessage(phoneNumber, null, "Test",
+                    PendingIntent.getBroadcast(this, sMessageId, getSendStatusIntent(), 0),
+                    null);
+            sMessageId++;
+        }
+    }
+
+    private void sendOutgoingSmsService(View view) {
+        String phoneNumber = mPhoneNumber.getText().toString();
+        if (TextUtils.isEmpty(phoneNumber)) {
+            Toast.makeText(this, "Couldn't get phone number from view! Ignoring request...",
+                    Toast.LENGTH_SHORT).show();
+            return;
+        }
+        if (mIsReadPhoneStateGranted) {
+            Intent sendSmsIntent = new Intent(SmsManagerTestService.SEND_SMS);
+            sendSmsIntent.putExtra(SmsManagerTestService.EXTRA_SEND_TEXT, "Text");
+            sendSmsIntent.putExtra(SmsManagerTestService.EXTRA_SEND_NUMBER, phoneNumber);
+            sendSmsIntent.putExtra(SmsManagerTestService.EXTRA_SEND_INTENT,
+                    PendingIntent.getBroadcast(this, sMessageId, getSendStatusIntent(), 0));
+            sendSmsIntent.setComponent(new ComponentName(this, SmsManagerTestService.class));
+            startService(sendSmsIntent);
+            sMessageId++;
+        }
+    }
+    private void getSubIdForResult(View view) {
+        // ask the user for a default SMS SIM.
+        Intent intent = new Intent();
+        intent.setComponent(SETTINGS_SUB_PICK_ACTIVITY);
+        intent.putExtra(DIALOG_TYPE_KEY, SMS_PICK);
+        try {
+            startActivity(intent, null);
+        } catch (ActivityNotFoundException anfe) {
+            // If Settings is not installed, only log the error as we do not want to break
+            // legacy applications.
+            Toast.makeText(this, "Unable to launch Settings application.",
+                    Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    private Intent getSendStatusIntent() {
+        // Encode requestId in intent data
+        return new Intent(SendStatusReceiver.MESSAGE_SENT_ACTION, null, this,
+                SendStatusReceiver.class);
+    }
+
+    private String getPhoneNumber() {
+        String result = "6505551212";
+        int defaultSmsSub = SubscriptionManager.getDefaultSmsSubscriptionId();
+        if (mIsReadPhoneStateGranted) {
+            TelephonyManager tm = getSystemService(TelephonyManager.class);
+            if (tm != null) {
+                tm = tm.createForSubscriptionId(defaultSmsSub);
+                String line1Number = tm.getLine1Number();
+                if (!TextUtils.isEmpty(line1Number)) {
+                    return line1Number;
+                }
+            }
+        } else {
+            Toast.makeText(this, "Couldn't resolve line 1 due to permissions error.",
+                    Toast.LENGTH_LONG).show();
+        }
+        return result;
+    }
+}
diff --git a/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/SmsManagerTestService.java b/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/SmsManagerTestService.java
new file mode 100644
index 0000000..fcf4a67
--- /dev/null
+++ b/testapps/SmsManagerTestApp/src/com/android/phone/testapps/smsmanagertestapp/SmsManagerTestService.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 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.smsmanagertestapp;
+
+import android.app.IntentService;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.telephony.SmsManager;
+import android.util.Log;
+
+/**
+ * IntentService whose purpose is to handle outgoing SMS intents for this application and schedule
+ * them onto a AsyncTask to sleep for 5 seconds. This allows us to simulate SMS messages being sent
+ * from background services.
+ */
+public class SmsManagerTestService extends IntentService {
+
+    private static final String LOG_TAG = "smsmanagertestservice";
+
+    private static class SendSmsJob extends AsyncTask<Intent, Void, Void> {
+
+        @Override
+        protected Void doInBackground(Intent... intents) {
+            Intent intent = intents[0];
+            try {
+                Thread.sleep(5000);
+            } catch (InterruptedException e) {
+                // testing
+            }
+
+            String text = intent.getStringExtra(EXTRA_SEND_TEXT);
+            String phoneNumber = intent.getStringExtra(EXTRA_SEND_NUMBER);
+            PendingIntent sendIntent = intent.getParcelableExtra(EXTRA_SEND_INTENT);
+            sendSms(phoneNumber, text, sendIntent);
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Void aVoid) {
+            Log.i(LOG_TAG, "SMS sent");
+        }
+
+    }
+
+    public static final String SEND_SMS = "com.android.phone.testapps.smsmanagertestapp.send_sms";
+    public static final String EXTRA_SEND_TEXT = "text";
+    public static final String EXTRA_SEND_NUMBER = "number";
+    public static final String EXTRA_SEND_INTENT = "sendIntent";
+
+    public SmsManagerTestService() {
+        super("SmsManagerTestService");
+    }
+
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        switch (intent.getAction()) {
+            case SEND_SMS : {
+                new SendSmsJob().execute(intent);
+                break;
+            }
+        }
+    }
+
+    private static void sendSms(String phoneNumber, String text, PendingIntent sendIntent) {
+        SmsManager m = SmsManager.getDefault();
+        m.sendTextMessage(phoneNumber, null, text, sendIntent, null);
+    }
+}