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);
+ }
+}