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.view.WindowManager; 26 import android.util.Log; 27 28 import com.android.internal.telephony.IIntegerConsumer; 29 30 import java.util.ArrayList; 31 import java.util.List; 32 33 /** 34 * Trampolines a request to Settings to get the SMS subscription associated with an SmsManager 35 * operation. 36 * 37 * Since a Service can not start an Activity with 38 * {@link Activity#startActivityForResult(Intent, int)} and get a response (only Activities can 39 * handle the results), we have to "Trampoline" this operation by creating an empty Activity whose 40 * only job is to call startActivityForResult with the correct Intent and handle the result. 41 */ 42 // TODO: SmsManager should be constructed with an activity context so it can start as part of its 43 // task and fall back to PickSmsSubscriptionActivity being called in PhoneInterfaceManager if not 44 // called from an activity context. 45 public class PickSmsSubscriptionActivity extends Activity { 46 47 private static final String LOG_TAG = "PickSmsSubActivity"; 48 49 // Defined in Settings SimDialogActivity 50 private static final String RESULT_SUB_ID = "result_sub_id"; 51 public static final String DIALOG_TYPE_KEY = "dialog_type"; 52 public static final int SMS_PICK_FOR_MESSAGE = 4; 53 54 private static final ComponentName SETTINGS_SUB_PICK_ACTIVITY = new ComponentName( 55 "com.android.settings", "com.android.settings.sim.SimDialogActivity"); 56 57 private static final List<IIntegerConsumer> sSmsPickPendingList = new ArrayList<>(); 58 59 private static final int REQUEST_GET_SMS_SUB_ID = 1; 60 61 /** 62 * Adds a consumer to the list of pending results that will be accepted once the activity 63 * completes. 64 */ addPendingResult(IIntegerConsumer consumer)65 public static void addPendingResult(IIntegerConsumer consumer) { 66 synchronized (sSmsPickPendingList) { 67 sSmsPickPendingList.add(consumer); 68 } 69 Log.i(LOG_TAG, "queue pending result, token: " + consumer); 70 } 71 sendResultAndClear(int resultId)72 private static void sendResultAndClear(int resultId) { 73 // If the calling process died, just ignore callback. 74 synchronized (sSmsPickPendingList) { 75 for (IIntegerConsumer c : sSmsPickPendingList) { 76 try { 77 c.accept(resultId); 78 Log.i(LOG_TAG, "Result received, token: " + c + ", result: " + resultId); 79 } catch (RemoteException e) { 80 // The calling process died, skip this one. 81 } 82 } 83 sSmsPickPendingList.clear(); 84 } 85 } 86 87 // Keep track if this activity has been stopped (i.e. user navigated away, power screen off,...) 88 // if so, treat it as the user navigating away and end the task if it is restarted without an 89 // onCreate/onNewIntent. 90 private boolean mPreviouslyStopped = false; 91 92 @Override onCreate(Bundle savedInstanceState)93 protected void onCreate(Bundle savedInstanceState) { 94 super.onCreate(savedInstanceState); 95 getWindow().addSystemFlags( 96 android.view.WindowManager.LayoutParams 97 .SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 98 mPreviouslyStopped = false; 99 } 100 101 @Override onNewIntent(Intent intent)102 protected void onNewIntent(Intent intent) { 103 mPreviouslyStopped = false; 104 } 105 106 @Override onResume()107 protected void onResume() { 108 super.onResume(); 109 // This is cause a little jank with the recents display, but there is no other way to handle 110 // the case where activity has stopped and we want to dismiss the dialog. We use the 111 // tag "excludeFromRecents", but in the cases where it is still shown, kill it in onResume. 112 if (mPreviouslyStopped) { 113 finishAndRemoveTask(); 114 } else { 115 launchSmsPicker(new Intent(getIntent())); 116 } 117 } 118 119 @Override onStop()120 protected void onStop() { 121 super.onStop(); 122 // User navigated away from dialog, send invalid sub id result. 123 mPreviouslyStopped = true; 124 sendResultAndClear(SubscriptionManager.INVALID_SUBSCRIPTION_ID); 125 // triggers cancelled result for onActivityResult 126 finishActivity(REQUEST_GET_SMS_SUB_ID); 127 } 128 129 @Override onActivityResult(int requestCode, int resultCode, Intent data)130 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 131 if (requestCode == REQUEST_GET_SMS_SUB_ID) { 132 int result = data == null ? SubscriptionManager.INVALID_SUBSCRIPTION_ID : 133 data.getIntExtra(RESULT_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); 134 if (resultCode == Activity.RESULT_OK) { 135 sendResultAndClear(result); 136 } else { 137 sendResultAndClear(SubscriptionManager.INVALID_SUBSCRIPTION_ID); 138 } 139 } 140 // This will be handled in onResume - we do not want to call this all the time here because 141 // we need to be able to restart if stopped and a new intent comes in via onNewIntent. 142 if (!mPreviouslyStopped) { 143 finishAndRemoveTask(); 144 } 145 } 146 launchSmsPicker(Intent trampolineIntent)147 private void launchSmsPicker(Intent trampolineIntent) { 148 trampolineIntent.setComponent(SETTINGS_SUB_PICK_ACTIVITY); 149 // Remove this flag if it exists, we want the settings activity to be part of this task. 150 trampolineIntent.removeFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 151 startActivityForResult(trampolineIntent, REQUEST_GET_SMS_SUB_ID); 152 } 153 } 154