• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.internal.telephony.emergency;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.os.UserHandle;
22 import android.provider.Settings;
23 import android.telephony.TelephonyManager;
24 
25 import com.android.internal.telephony.IIntegerConsumer;
26 import com.android.internal.telephony.Phone;
27 import com.android.internal.telephony.PhoneFactory;
28 import com.android.internal.telephony.satellite.SatelliteController;
29 import com.android.telephony.Rlog;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 
34 /**
35  * Helper class that implements special behavior related to emergency calls or making phone calls
36  * when the radio is in the POWER_OFF STATE. Specifically, this class handles the case of the user
37  * trying to dial an emergency number while the radio is off (i.e. the device is in airplane mode)
38  * or a normal number while the radio is off (because of the device is on Bluetooth), by turning the
39  * radio back on, waiting for it to come up, and then retrying the call.
40  */
41 public class RadioOnHelper implements RadioOnStateListener.Callback {
42 
43     private static final String TAG = "RadioOnStateListener";
44 
45     private final Context mContext;
46     private RadioOnStateListener.Callback mCallback;
47     private List<RadioOnStateListener> mListeners;
48     private List<RadioOnStateListener> mInProgressListeners;
49     private boolean mIsRadioReady;
50 
RadioOnHelper(Context context)51     public RadioOnHelper(Context context) {
52         mContext = context;
53         mInProgressListeners = new ArrayList<>(2);
54     }
55 
setupListeners()56     private void setupListeners() {
57         if (mListeners == null) {
58             mListeners = new ArrayList<>(2);
59         }
60         int activeModems = TelephonyManager.from(mContext).getActiveModemCount();
61         // Add new listeners if active modem count increased.
62         while (mListeners.size() < activeModems) {
63             mListeners.add(new RadioOnStateListener());
64         }
65         // Clean up listeners if active modem count decreased.
66         while (mListeners.size() > activeModems) {
67             mListeners.get(mListeners.size() - 1).cleanup();
68             mListeners.remove(mListeners.size() - 1);
69         }
70     }
71 
72     /**
73      * Starts the "turn on radio" sequence. This is the (single) external API of the RadioOnHelper
74      * class.
75      *
76      * This method kicks off the following sequence:
77      * - Power on the radio for each Phone and disable the satellite modem
78      * - Listen for events telling us the radio has come up or the satellite modem is disabled.
79      * - Retry if we've gone a significant amount of time without any response.
80      * - Finally, clean up any leftover state.
81      *
82      * This method is safe to call from any thread, since it simply posts a message to the
83      * RadioOnHelper's handler (thus ensuring that the rest of the sequence is entirely serialized,
84      * and runs on the main looper.)
85      */
triggerRadioOnAndListen(RadioOnStateListener.Callback callback, boolean forEmergencyCall, Phone phoneForEmergencyCall, boolean isTestEmergencyNumber, int emergencyTimeoutIntervalMillis)86     public void triggerRadioOnAndListen(RadioOnStateListener.Callback callback,
87             boolean forEmergencyCall, Phone phoneForEmergencyCall, boolean isTestEmergencyNumber,
88             int emergencyTimeoutIntervalMillis) {
89         setupListeners();
90         mCallback = callback;
91         mInProgressListeners.clear();
92         mIsRadioReady = false;
93         for (int i = 0; i < TelephonyManager.from(mContext).getActiveModemCount(); i++) {
94             Phone phone = PhoneFactory.getPhone(i);
95             if (phone == null) {
96                 continue;
97             }
98 
99             int timeoutCallbackInterval = (phone == phoneForEmergencyCall)
100                     ? emergencyTimeoutIntervalMillis : 0;
101             mInProgressListeners.add(mListeners.get(i));
102             mListeners.get(i).waitForRadioOn(phone, this, forEmergencyCall, forEmergencyCall
103                     && phone == phoneForEmergencyCall, timeoutCallbackInterval);
104         }
105         powerOnRadio(forEmergencyCall, phoneForEmergencyCall, isTestEmergencyNumber);
106         if (SatelliteController.getInstance().isSatelliteEnabledOrBeingEnabled()) {
107             powerOffSatellite();
108         }
109     }
110 
111     /**
112      * Attempt to power on the radio (i.e. take the device out of airplane mode). We'll eventually
113      * get an onServiceStateChanged() callback when the radio successfully comes up.
114      */
powerOnRadio(boolean forEmergencyCall, Phone phoneForEmergencyCall, boolean isTestEmergencyNumber)115     private void powerOnRadio(boolean forEmergencyCall, Phone phoneForEmergencyCall,
116             boolean isTestEmergencyNumber) {
117 
118         // Always try to turn on the radio here independent of APM setting - if we got here in the
119         // first place, the radio is off independent of APM setting.
120         for (Phone phone : PhoneFactory.getPhones()) {
121             Rlog.d(TAG, "powerOnRadio, enabling Radio");
122             if (isTestEmergencyNumber) {
123                 phone.setRadioPowerOnForTestEmergencyCall(phone == phoneForEmergencyCall);
124             } else {
125                 phone.setRadioPower(true, forEmergencyCall, phone == phoneForEmergencyCall,
126                         false);
127             }
128         }
129 
130         // If airplane mode is on, we turn it off the same way that the Settings activity turns it
131         // off to keep the setting in sync.
132         if (Settings.Global.getInt(mContext.getContentResolver(),
133                 Settings.Global.AIRPLANE_MODE_ON, 0) > 0) {
134             Rlog.d(TAG, "==> Turning off airplane mode for emergency call.");
135 
136             // Change the system setting
137             Settings.Global.putInt(mContext.getContentResolver(),
138                     Settings.Global.AIRPLANE_MODE_ON, 0);
139 
140             // Post the broadcast intend for change in airplane mode TODO: We really should not be
141             // in charge of sending this broadcast. If changing the setting is sufficient to trigger
142             // all of the rest of the logic, then that should also trigger the broadcast intent.
143             Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
144             intent.putExtra("state", false);
145             mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
146         }
147     }
148 
149     /**
150      * Attempt to power off the satellite modem. We'll eventually get an
151      * onSatelliteModemStateChanged() callback when the satellite modem is successfully disabled.
152      */
powerOffSatellite()153     private void powerOffSatellite() {
154         SatelliteController satelliteController = SatelliteController.getInstance();
155         satelliteController.requestSatelliteEnabled(
156                 false /* enableSatellite */, false /* enableDemoMode */, false /* isEmergency */,
157                 new IIntegerConsumer.Stub() {
158                     @Override
159                     public void accept(int result) {
160 
161                     }
162                 });
163     }
164 
165     /**
166      * This method is called from multiple Listeners on the Main Looper. Synchronization is not
167      * necessary.
168      */
169     @Override
onComplete(RadioOnStateListener listener, boolean isRadioReady)170     public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
171         mIsRadioReady |= isRadioReady;
172         mInProgressListeners.remove(listener);
173         if (mCallback != null && mInProgressListeners.isEmpty()) {
174             mCallback.onComplete(null, mIsRadioReady);
175         }
176     }
177 
178     @Override
isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable)179     public boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable) {
180         return (mCallback == null)
181                 ? false : mCallback.isOkToCall(phone, serviceState, imsVoiceCapable);
182     }
183 
184     @Override
onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable)185     public boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable) {
186         return (mCallback == null)
187                 ? false : mCallback.onTimeout(phone, serviceState, imsVoiceCapable);
188     }
189 }
190