1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.phone.settings; 18 19 import android.app.Activity; 20 import android.content.ComponentName; 21 import android.content.Intent; 22 import android.os.Bundle; 23 import android.os.RemoteException; 24 import android.telephony.SubscriptionManager; 25 import android.util.Log; 26 27 import com.android.internal.telephony.IIntegerConsumer; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 /** 33 * Trampolines a request to Settings to get the SMS subscription associated with an SmsManager 34 * operation. 35 * 36 * Since a Service can not start an Activity with 37 * {@link Activity#startActivityForResult(Intent, int)} and get a response (only Activities can 38 * handle the results), we have to "Trampoline" this operation by creating an empty Activity whose 39 * only job is to call startActivityForResult with the correct Intent and handle the result. 40 */ 41 // TODO: SmsManager should be constructed with an activity context so it can start as part of its 42 // task and fall back to PickSmsSubscriptionActivity being called in PhoneInterfaceManager if not 43 // called from an activity context. 44 public class PickSmsSubscriptionActivity extends Activity { 45 46 private static final String LOG_TAG = "PickSmsSubActivity"; 47 48 // Defined in Settings SimDialogActivity 49 private static final String RESULT_SUB_ID = "result_sub_id"; 50 public static final String DIALOG_TYPE_KEY = "dialog_type"; 51 public static final int SMS_PICK_FOR_MESSAGE = 4; 52 53 private static final ComponentName SETTINGS_SUB_PICK_ACTIVITY = new ComponentName( 54 "com.android.settings", "com.android.settings.sim.SimDialogActivity"); 55 56 private static final List<IIntegerConsumer> sSmsPickPendingList = new ArrayList<>(); 57 58 private static final int REQUEST_GET_SMS_SUB_ID = 1; 59 60 /** 61 * Adds a consumer to the list of pending results that will be accepted once the activity 62 * completes. 63 */ addPendingResult(IIntegerConsumer consumer)64 public static void addPendingResult(IIntegerConsumer consumer) { 65 synchronized (sSmsPickPendingList) { 66 sSmsPickPendingList.add(consumer); 67 } 68 Log.i(LOG_TAG, "queue pending result, token: " + consumer); 69 } 70 sendResultAndClear(int resultId)71 private static void sendResultAndClear(int resultId) { 72 // If the calling process died, just ignore callback. 73 synchronized (sSmsPickPendingList) { 74 for (IIntegerConsumer c : sSmsPickPendingList) { 75 try { 76 c.accept(resultId); 77 Log.i(LOG_TAG, "Result received, token: " + c + ", result: " + resultId); 78 } catch (RemoteException e) { 79 // The calling process died, skip this one. 80 } 81 } 82 sSmsPickPendingList.clear(); 83 } 84 } 85 86 // Keep track if this activity has been stopped (i.e. user navigated away, power screen off,...) 87 // if so, treat it as the user navigating away and end the task if it is restarted without an 88 // onCreate/onNewIntent. 89 private boolean mPreviouslyStopped = false; 90 91 @Override onCreate(Bundle savedInstanceState)92 protected void onCreate(Bundle savedInstanceState) { 93 super.onCreate(savedInstanceState); 94 mPreviouslyStopped = false; 95 } 96 97 @Override onNewIntent(Intent intent)98 protected void onNewIntent(Intent intent) { 99 mPreviouslyStopped = false; 100 } 101 102 @Override onResume()103 protected void onResume() { 104 super.onResume(); 105 // This is cause a little jank with the recents display, but there is no other way to handle 106 // the case where activity has stopped and we want to dismiss the dialog. We use the 107 // tag "excludeFromRecents", but in the cases where it is still shown, kill it in onResume. 108 if (mPreviouslyStopped) { 109 finishAndRemoveTask(); 110 } else { 111 launchSmsPicker(new Intent(getIntent())); 112 } 113 } 114 115 @Override onStop()116 protected void onStop() { 117 super.onStop(); 118 // User navigated away from dialog, send invalid sub id result. 119 mPreviouslyStopped = true; 120 sendResultAndClear(SubscriptionManager.INVALID_SUBSCRIPTION_ID); 121 // triggers cancelled result for onActivityResult 122 finishActivity(REQUEST_GET_SMS_SUB_ID); 123 } 124 125 @Override onActivityResult(int requestCode, int resultCode, Intent data)126 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 127 if (requestCode == REQUEST_GET_SMS_SUB_ID) { 128 int result = data == null ? SubscriptionManager.INVALID_SUBSCRIPTION_ID : 129 data.getIntExtra(RESULT_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); 130 if (resultCode == Activity.RESULT_OK) { 131 sendResultAndClear(result); 132 } else { 133 sendResultAndClear(SubscriptionManager.INVALID_SUBSCRIPTION_ID); 134 } 135 } 136 // This will be handled in onResume - we do not want to call this all the time here because 137 // we need to be able to restart if stopped and a new intent comes in via onNewIntent. 138 if (!mPreviouslyStopped) { 139 finishAndRemoveTask(); 140 } 141 } 142 launchSmsPicker(Intent trampolineIntent)143 private void launchSmsPicker(Intent trampolineIntent) { 144 trampolineIntent.setComponent(SETTINGS_SUB_PICK_ACTIVITY); 145 // Remove this flag if it exists, we want the settings activity to be part of this task. 146 trampolineIntent.removeFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 147 startActivityForResult(trampolineIntent, REQUEST_GET_SMS_SUB_ID); 148 } 149 } 150