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