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