1 /* 2 * Copyright (C) 2016 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.services.telephony; 18 19 import android.os.AsyncResult; 20 import android.os.Handler; 21 import android.os.Looper; 22 import android.os.Message; 23 import android.telephony.ServiceState; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.internal.os.SomeArgs; 27 import com.android.internal.telephony.CommandsInterface; 28 import com.android.internal.telephony.Phone; 29 import com.android.internal.telephony.PhoneConstants; 30 import com.android.internal.telephony.SubscriptionController; 31 32 /** 33 * Helper class that listens to a Phone's radio state and sends a callback when the radio state of 34 * that Phone is either "in service" or "emergency calls only." 35 */ 36 public class EmergencyCallStateListener { 37 38 /** 39 * Receives the result of the EmergencyCallStateListener's attempt to turn on the radio. 40 */ 41 interface Callback { onComplete(EmergencyCallStateListener listener, boolean isRadioReady)42 void onComplete(EmergencyCallStateListener listener, boolean isRadioReady); 43 } 44 45 // Number of times to retry the call, and time between retry attempts. 46 private static int MAX_NUM_RETRIES = 5; 47 private static long TIME_BETWEEN_RETRIES_MILLIS = 5000; // msec 48 49 // Handler message codes; see handleMessage() 50 @VisibleForTesting 51 public static final int MSG_START_SEQUENCE = 1; 52 @VisibleForTesting 53 public static final int MSG_SERVICE_STATE_CHANGED = 2; 54 @VisibleForTesting 55 public static final int MSG_RETRY_TIMEOUT = 3; 56 57 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 58 @Override 59 public void handleMessage(Message msg) { 60 switch (msg.what) { 61 case MSG_START_SEQUENCE: 62 SomeArgs args = (SomeArgs) msg.obj; 63 try { 64 Phone phone = (Phone) args.arg1; 65 EmergencyCallStateListener.Callback callback = 66 (EmergencyCallStateListener.Callback) args.arg2; 67 startSequenceInternal(phone, callback); 68 } finally { 69 args.recycle(); 70 } 71 break; 72 case MSG_SERVICE_STATE_CHANGED: 73 onServiceStateChanged((ServiceState) ((AsyncResult) msg.obj).result); 74 break; 75 case MSG_RETRY_TIMEOUT: 76 onRetryTimeout(); 77 break; 78 default: 79 Log.wtf(this, "handleMessage: unexpected message: %d.", msg.what); 80 break; 81 } 82 } 83 }; 84 85 86 private Callback mCallback; // The callback to notify upon completion. 87 private Phone mPhone; // The phone that will attempt to place the call. 88 private int mNumRetriesSoFar; 89 90 /** 91 * Starts the "wait for radio" sequence. This is the (single) external API of the 92 * EmergencyCallStateListener class. 93 * 94 * This method kicks off the following sequence: 95 * - Listen for the service state change event telling us the radio has come up. 96 * - Retry if we've gone {@link #TIME_BETWEEN_RETRIES_MILLIS} without any response from the 97 * radio. 98 * - Finally, clean up any leftover state. 99 * 100 * This method is safe to call from any thread, since it simply posts a message to the 101 * EmergencyCallStateListener's handler (thus ensuring that the rest of the sequence is entirely 102 * serialized, and runs only on the handler thread.) 103 */ waitForRadioOn(Phone phone, Callback callback)104 public void waitForRadioOn(Phone phone, Callback callback) { 105 Log.d(this, "waitForRadioOn: Phone " + phone.getPhoneId()); 106 107 if (mPhone != null) { 108 // If there already is an ongoing request, ignore the new one! 109 return; 110 } 111 112 SomeArgs args = SomeArgs.obtain(); 113 args.arg1 = phone; 114 args.arg2 = callback; 115 mHandler.obtainMessage(MSG_START_SEQUENCE, args).sendToTarget(); 116 } 117 118 /** 119 * Actual implementation of waitForRadioOn(), guaranteed to run on the handler thread. 120 * 121 * @see #waitForRadioOn 122 */ startSequenceInternal(Phone phone, Callback callback)123 private void startSequenceInternal(Phone phone, Callback callback) { 124 Log.d(this, "startSequenceInternal: Phone " + phone.getPhoneId()); 125 126 // First of all, clean up any state left over from a prior emergency call sequence. This 127 // ensures that we'll behave sanely if another startTurnOnRadioSequence() comes in while 128 // we're already in the middle of the sequence. 129 cleanup(); 130 131 mPhone = phone; 132 mCallback = callback; 133 134 registerForServiceStateChanged(); 135 // Next step: when the SERVICE_STATE_CHANGED event comes in, we'll retry the call; see 136 // onServiceStateChanged(). But also, just in case, start a timer to make sure we'll retry 137 // the call even if the SERVICE_STATE_CHANGED event never comes in for some reason. 138 startRetryTimer(); 139 } 140 141 /** 142 * Handles the SERVICE_STATE_CHANGED event. Normally this event tells us that the radio has 143 * finally come up. In that case, it's now safe to actually place the emergency call. 144 */ onServiceStateChanged(ServiceState state)145 private void onServiceStateChanged(ServiceState state) { 146 Log.d(this, "onServiceStateChanged(), new state = %s, Phone = %s", state, 147 mPhone.getPhoneId()); 148 149 // Possible service states: 150 // - STATE_IN_SERVICE // Normal operation 151 // - STATE_OUT_OF_SERVICE // Still searching for an operator to register to, 152 // // or no radio signal 153 // - STATE_EMERGENCY_ONLY // Phone is locked; only emergency numbers are allowed 154 // - STATE_POWER_OFF // Radio is explicitly powered off (airplane mode) 155 156 if (isOkToCall(state.getState())) { 157 // Woo hoo! It's OK to actually place the call. 158 Log.d(this, "onServiceStateChanged: ok to call!"); 159 160 onComplete(true); 161 cleanup(); 162 } else { 163 // The service state changed, but we're still not ready to call yet. 164 Log.d(this, "onServiceStateChanged: not ready to call yet, keep waiting."); 165 } 166 } 167 168 /** 169 * We currently only look to make sure that the radio is on before dialing. We should be able to 170 * make emergency calls at any time after the radio has been powered on and isn't in the 171 * UNAVAILABLE state, even if it is reporting the OUT_OF_SERVICE state. 172 */ isOkToCall(int serviceState)173 private boolean isOkToCall(int serviceState) { 174 return (mPhone.getState() == PhoneConstants.State.OFFHOOK) || 175 mPhone.getServiceStateTracker().isRadioOn(); 176 } 177 178 /** 179 * Handles the retry timer expiring. 180 */ onRetryTimeout()181 private void onRetryTimeout() { 182 int serviceState = mPhone.getServiceState().getState(); 183 Log.d(this, "onRetryTimeout(): phone state = %s, service state = %d, retries = %d.", 184 mPhone.getState(), serviceState, mNumRetriesSoFar); 185 186 // - If we're actually in a call, we've succeeded. 187 // - Otherwise, if the radio is now on, that means we successfully got out of airplane mode 188 // but somehow didn't get the service state change event. In that case, try to place the 189 // call. 190 // - If the radio is still powered off, try powering it on again. 191 192 if (isOkToCall(serviceState)) { 193 Log.d(this, "onRetryTimeout: Radio is on. Cleaning up."); 194 195 // Woo hoo -- we successfully got out of airplane mode. 196 onComplete(true); 197 cleanup(); 198 } else { 199 // Uh oh; we've waited the full TIME_BETWEEN_RETRIES_MILLIS and the radio is still not 200 // powered-on. Try again. 201 202 mNumRetriesSoFar++; 203 Log.d(this, "mNumRetriesSoFar is now " + mNumRetriesSoFar); 204 205 if (mNumRetriesSoFar > MAX_NUM_RETRIES) { 206 Log.w(this, "Hit MAX_NUM_RETRIES; giving up."); 207 cleanup(); 208 } else { 209 Log.d(this, "Trying (again) to turn on the radio."); 210 mPhone.setRadioPower(true); 211 startRetryTimer(); 212 } 213 } 214 } 215 216 /** 217 * Clean up when done with the whole sequence: either after successfully turning on the radio, 218 * or after bailing out because of too many failures. 219 * 220 * The exact cleanup steps are: 221 * - Notify callback if we still hadn't sent it a response. 222 * - Double-check that we're not still registered for any telephony events 223 * - Clean up any extraneous handler messages (like retry timeouts) still in the queue 224 * 225 * Basically this method guarantees that there will be no more activity from the 226 * EmergencyCallStateListener until someone kicks off the whole sequence again with another call 227 * to {@link #waitForRadioOn} 228 * 229 * TODO: Do the work for the comment below: 230 * Note we don't call this method simply after a successful call to placeCall(), since it's 231 * still possible the call will disconnect very quickly with an OUT_OF_SERVICE error. 232 */ cleanup()233 private void cleanup() { 234 Log.d(this, "cleanup()"); 235 236 // This will send a failure call back if callback has yet to be invoked. If the callback 237 // was already invoked, it's a no-op. 238 onComplete(false); 239 240 unregisterForServiceStateChanged(); 241 cancelRetryTimer(); 242 243 // Used for unregisterForServiceStateChanged() so we null it out here instead. 244 mPhone = null; 245 mNumRetriesSoFar = 0; 246 } 247 startRetryTimer()248 private void startRetryTimer() { 249 cancelRetryTimer(); 250 mHandler.sendEmptyMessageDelayed(MSG_RETRY_TIMEOUT, TIME_BETWEEN_RETRIES_MILLIS); 251 } 252 cancelRetryTimer()253 private void cancelRetryTimer() { 254 mHandler.removeMessages(MSG_RETRY_TIMEOUT); 255 } 256 registerForServiceStateChanged()257 private void registerForServiceStateChanged() { 258 // Unregister first, just to make sure we never register ourselves twice. (We need this 259 // because Phone.registerForServiceStateChanged() does not prevent multiple registration of 260 // the same handler.) 261 unregisterForServiceStateChanged(); 262 mPhone.registerForServiceStateChanged(mHandler, MSG_SERVICE_STATE_CHANGED, null); 263 } 264 unregisterForServiceStateChanged()265 private void unregisterForServiceStateChanged() { 266 // This method is safe to call even if we haven't set mPhone yet. 267 if (mPhone != null) { 268 mPhone.unregisterForServiceStateChanged(mHandler); // Safe even if unnecessary 269 } 270 mHandler.removeMessages(MSG_SERVICE_STATE_CHANGED); // Clean up any pending messages too 271 } 272 onComplete(boolean isRadioReady)273 private void onComplete(boolean isRadioReady) { 274 if (mCallback != null) { 275 Callback tempCallback = mCallback; 276 mCallback = null; 277 tempCallback.onComplete(this, isRadioReady); 278 } 279 } 280 281 @VisibleForTesting getHandler()282 public Handler getHandler() { 283 return mHandler; 284 } 285 286 @VisibleForTesting setMaxNumRetries(int retries)287 public void setMaxNumRetries(int retries) { 288 MAX_NUM_RETRIES = retries; 289 } 290 291 @VisibleForTesting setTimeBetweenRetriesMillis(long timeMs)292 public void setTimeBetweenRetriesMillis(long timeMs) { 293 TIME_BETWEEN_RETRIES_MILLIS = timeMs; 294 } 295 296 @Override equals(Object o)297 public boolean equals(Object o) { 298 if (this == o) return true; 299 if (o == null || !getClass().equals(o.getClass())) return false; 300 301 EmergencyCallStateListener that = (EmergencyCallStateListener) o; 302 303 if (mNumRetriesSoFar != that.mNumRetriesSoFar) { 304 return false; 305 } 306 if (mCallback != null ? !mCallback.equals(that.mCallback) : that.mCallback != null) { 307 return false; 308 } 309 return mPhone != null ? mPhone.equals(that.mPhone) : that.mPhone == null; 310 311 } 312 } 313