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.os.AsyncResult; 20 import android.os.Handler; 21 import android.os.Looper; 22 import android.os.Message; 23 import android.telephony.ServiceState; 24 import android.telephony.satellite.ISatelliteModemStateCallback; 25 26 import com.android.internal.annotations.VisibleForTesting; 27 import com.android.internal.os.SomeArgs; 28 import com.android.internal.telephony.IIntegerConsumer; 29 import com.android.internal.telephony.Phone; 30 import com.android.internal.telephony.satellite.SatelliteController; 31 import com.android.telephony.Rlog; 32 33 import java.util.Locale; 34 35 /** 36 * Helper class that listens to a Phone's radio state and sends an onComplete callback when we 37 * return true for isOkToCall. 38 */ 39 public class RadioOnStateListener { 40 41 public interface Callback { 42 /** 43 * Receives the result of the RadioOnStateListener's attempt to turn on the radio 44 * and turn off the satellite modem. 45 */ onComplete(RadioOnStateListener listener, boolean isRadioReady)46 void onComplete(RadioOnStateListener listener, boolean isRadioReady); 47 48 /** 49 * Returns whether or not this phone is ok to call. 50 * If it is, onComplete will be called shortly after. 51 * 52 * @param phone The Phone associated. 53 * @param serviceState The service state of that phone. 54 * @param imsVoiceCapable The IMS voice capability of that phone. 55 * @return {@code true} if this phone is ok to call. Otherwise, {@code false}. 56 */ isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable)57 boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable); 58 59 /** 60 * Returns whether or not this phone is ok to call. 61 * This callback will be called when timeout happens. 62 * If this returns {@code true}, onComplete will be called shortly after. 63 * Otherwise, a new timer will be started again to keep waiting for next timeout. 64 * The timeout interval will be passed to {@link #waitForRadioOn()} when registering 65 * this callback. 66 * 67 * @param phone The Phone associated. 68 * @param serviceState The service state of that phone. 69 * @param imsVoiceCapable The IMS voice capability of that phone. 70 * @return {@code true} if this phone is ok to call. Otherwise, {@code false}. 71 */ onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable)72 boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable); 73 } 74 75 private static final String TAG = "RadioOnStateListener"; 76 77 // Number of times to retry the call, and time between retry attempts. 78 // not final for testing 79 private static int MAX_NUM_RETRIES = 5; 80 // not final for testing 81 private static long TIME_BETWEEN_RETRIES_MILLIS = 5000; // msec 82 83 // Handler message codes; see handleMessage() 84 private static final int MSG_START_SEQUENCE = 1; 85 @VisibleForTesting 86 public static final int MSG_SERVICE_STATE_CHANGED = 2; 87 private static final int MSG_RETRY_TIMEOUT = 3; 88 @VisibleForTesting 89 public static final int MSG_RADIO_ON = 4; 90 public static final int MSG_RADIO_OFF_OR_NOT_AVAILABLE = 5; 91 public static final int MSG_IMS_CAPABILITY_CHANGED = 6; 92 public static final int MSG_TIMEOUT_ONTIMEOUT_CALLBACK = 7; 93 @VisibleForTesting 94 public static final int MSG_SATELLITE_ENABLED_CHANGED = 8; 95 96 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 97 @Override 98 public void handleMessage(Message msg) { 99 switch (msg.what) { 100 case MSG_START_SEQUENCE: 101 SomeArgs args = (SomeArgs) msg.obj; 102 try { 103 Phone phone = (Phone) args.arg1; 104 RadioOnStateListener.Callback callback = 105 (RadioOnStateListener.Callback) args.arg2; 106 boolean forEmergencyCall = (boolean) args.arg3; 107 boolean isSelectedPhoneForEmergencyCall = (boolean) args.arg4; 108 int onTimeoutCallbackInterval = args.argi1; 109 startSequenceInternal(phone, callback, forEmergencyCall, 110 isSelectedPhoneForEmergencyCall, onTimeoutCallbackInterval); 111 } finally { 112 args.recycle(); 113 } 114 break; 115 case MSG_SERVICE_STATE_CHANGED: 116 onServiceStateChanged((ServiceState) ((AsyncResult) msg.obj).result); 117 break; 118 case MSG_RADIO_ON: 119 onRadioOn(); 120 break; 121 case MSG_RADIO_OFF_OR_NOT_AVAILABLE: 122 registerForRadioOn(); 123 break; 124 case MSG_RETRY_TIMEOUT: 125 onRetryTimeout(); 126 break; 127 case MSG_IMS_CAPABILITY_CHANGED: 128 onImsCapabilityChanged(); 129 break; 130 case MSG_TIMEOUT_ONTIMEOUT_CALLBACK: 131 onTimeoutCallbackTimeout(); 132 break; 133 case MSG_SATELLITE_ENABLED_CHANGED: 134 onSatelliteEnabledChanged(); 135 break; 136 default: 137 Rlog.w(TAG, String.format(Locale.getDefault(), 138 "handleMessage: unexpected message: %d.", msg.what)); 139 break; 140 } 141 } 142 }; 143 144 private final ISatelliteModemStateCallback mSatelliteCallback = 145 new ISatelliteModemStateCallback.Stub() { 146 @Override 147 public void onSatelliteModemStateChanged(int state) { 148 mHandler.obtainMessage(MSG_SATELLITE_ENABLED_CHANGED).sendToTarget(); 149 } 150 151 @Override 152 public void onEmergencyModeChanged(boolean isEmergency) { 153 Rlog.d(TAG, "onEmergencyModeChanged: ignored " + isEmergency); 154 } 155 156 @Override 157 public void onRegistrationFailure(int causeCode) { 158 Rlog.d(TAG, "onRegistrationFailure: causeCode " + causeCode); 159 } 160 161 @Override 162 public void onTerrestrialNetworkAvailableChanged(boolean isAvailable) { 163 Rlog.d(TAG, "onTerrestrialNetworkAvailableChanged: isAvailable " + isAvailable); 164 } 165 }; 166 167 private Callback mCallback; // The callback to notify upon completion. 168 private Phone mPhone; // The phone that will attempt to place the call. 169 // SatelliteController instance to check whether satellite has been disabled. 170 private SatelliteController mSatelliteController; 171 private boolean mForEmergencyCall; // Whether radio is being turned on for emergency call. 172 // Whether this phone is selected to place emergency call. Can be true only if 173 // mForEmergencyCall is true. 174 private boolean mSelectedPhoneForEmergencyCall; 175 private int mNumRetriesSoFar; 176 private int mOnTimeoutCallbackInterval; // the interval between onTimeout callbacks 177 178 /** 179 * Starts the "wait for radio" sequence. This is the (single) external API of the 180 * RadioOnStateListener class. 181 * 182 * This method kicks off the following sequence: 183 * - Listen for the service state change event telling us the radio has come up. 184 * - Listen for the satellite state changed event telling us the satellite service is disabled. 185 * - Retry if we've gone {@link #TIME_BETWEEN_RETRIES_MILLIS} without any response from the 186 * radio. 187 * - Finally, clean up any leftover state. 188 * 189 * This method is safe to call from any thread, since it simply posts a message to the 190 * RadioOnStateListener's handler (thus ensuring that the rest of the sequence is entirely 191 * serialized, and runs only on the handler thread.) 192 */ waitForRadioOn(Phone phone, Callback callback, boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall, int onTimeoutCallbackInterval)193 public void waitForRadioOn(Phone phone, Callback callback, 194 boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall, 195 int onTimeoutCallbackInterval) { 196 Rlog.d(TAG, "waitForRadioOn: Phone " + phone.getPhoneId()); 197 198 if (mPhone != null) { 199 // If there already is an ongoing request, ignore the new one! 200 return; 201 } 202 203 SomeArgs args = SomeArgs.obtain(); 204 args.arg1 = phone; 205 args.arg2 = callback; 206 args.arg3 = forEmergencyCall; 207 args.arg4 = isSelectedPhoneForEmergencyCall; 208 args.argi1 = onTimeoutCallbackInterval; 209 mHandler.obtainMessage(MSG_START_SEQUENCE, args).sendToTarget(); 210 } 211 212 /** 213 * Actual implementation of waitForRadioOn(), guaranteed to run on the handler thread. 214 * 215 * @see #waitForRadioOn 216 */ startSequenceInternal(Phone phone, Callback callback, boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall, int onTimeoutCallbackInterval)217 private void startSequenceInternal(Phone phone, Callback callback, 218 boolean forEmergencyCall, boolean isSelectedPhoneForEmergencyCall, 219 int onTimeoutCallbackInterval) { 220 Rlog.d(TAG, "startSequenceInternal: Phone " + phone.getPhoneId()); 221 mSatelliteController = SatelliteController.getInstance(); 222 223 // First of all, clean up any state left over from a prior RadioOn call sequence. This 224 // ensures that we'll behave sanely if another startTurnOnRadioSequence() comes in while 225 // we're already in the middle of the sequence. 226 cleanup(); 227 228 mPhone = phone; 229 mCallback = callback; 230 mForEmergencyCall = forEmergencyCall; 231 mSelectedPhoneForEmergencyCall = isSelectedPhoneForEmergencyCall; 232 mOnTimeoutCallbackInterval = onTimeoutCallbackInterval; 233 234 registerForServiceStateChanged(); 235 // Register for RADIO_OFF to handle cases where emergency call is dialed before 236 // we receive UNSOL_RESPONSE_RADIO_STATE_CHANGED with RADIO_OFF. 237 registerForRadioOff(); 238 if (mSatelliteController.isSatelliteEnabledOrBeingEnabled()) { 239 // Register for satellite modem state changed to notify when satellite is disabled. 240 registerForSatelliteEnabledChanged(); 241 } 242 // Next step: when the SERVICE_STATE_CHANGED or SATELLITE_ENABLED_CHANGED event comes in, 243 // we'll retry the call; see onServiceStateChanged() and onSatelliteEnabledChanged(). 244 // But also, just in case, start a timer to make sure we'll retry the call even if the 245 // SERVICE_STATE_CHANGED or SATELLITE_ENABLED_CHANGED events never come in for some reason. 246 startRetryTimer(); 247 registerForImsCapabilityChanged(); 248 startOnTimeoutCallbackTimer(); 249 } 250 onImsCapabilityChanged()251 private void onImsCapabilityChanged() { 252 if (mPhone == null) { 253 return; 254 } 255 256 boolean imsVoiceCapable = mPhone.isVoiceOverCellularImsEnabled(); 257 258 Rlog.d(TAG, String.format("onImsCapabilityChanged, capable = %s, Phone = %s", 259 imsVoiceCapable, mPhone.getPhoneId())); 260 261 if (isOkToCall(mPhone.getServiceState().getState(), imsVoiceCapable)) { 262 Rlog.d(TAG, "onImsCapabilityChanged: ok to call!"); 263 264 onComplete(true); 265 cleanup(); 266 } else { 267 // The IMS capability changed, but we're still not ready to call yet. 268 Rlog.d(TAG, "onImsCapabilityChanged: not ready to call yet, keep waiting."); 269 } 270 } 271 onTimeoutCallbackTimeout()272 private void onTimeoutCallbackTimeout() { 273 if (mPhone == null) { 274 return; 275 } 276 277 if (onTimeout(mPhone.getServiceState().getState(), 278 mPhone.isVoiceOverCellularImsEnabled())) { 279 Rlog.d(TAG, "onTimeout: ok to call!"); 280 281 onComplete(true); 282 cleanup(); 283 } else if (mNumRetriesSoFar > MAX_NUM_RETRIES) { 284 Rlog.w(TAG, "onTimeout: Hit MAX_NUM_RETRIES; giving up."); 285 cleanup(); 286 } else { 287 Rlog.d(TAG, "onTimeout: not ready to call yet, keep waiting."); 288 startOnTimeoutCallbackTimer(); 289 } 290 } 291 292 /** 293 * Handles the SERVICE_STATE_CHANGED event. This event tells us that the radio state has changed 294 * and is probably coming up. We can now check to see if the conditions are met to place the 295 * call with {@link Callback#isOkToCall} 296 */ onServiceStateChanged(ServiceState state)297 private void onServiceStateChanged(ServiceState state) { 298 if (mPhone == null) { 299 return; 300 } 301 Rlog.d(TAG, String.format("onServiceStateChanged(), new state = %s, Phone = %s", state, 302 mPhone.getPhoneId())); 303 304 // Possible service states: 305 // - STATE_IN_SERVICE // Normal operation 306 // - STATE_OUT_OF_SERVICE // Still searching for an operator to register to, 307 // // or no radio signal 308 // - STATE_EMERGENCY_ONLY // Only emergency numbers are allowed; currently not used 309 // - STATE_POWER_OFF // Radio is explicitly powered off (airplane mode) 310 311 if (isOkToCall(state.getState(), mPhone.isVoiceOverCellularImsEnabled())) { 312 // Woo hoo! It's OK to actually place the call. 313 Rlog.d(TAG, "onServiceStateChanged: ok to call!"); 314 315 onComplete(true); 316 cleanup(); 317 } else { 318 // The service state changed, but we're still not ready to call yet. 319 Rlog.d(TAG, "onServiceStateChanged: not ready to call yet, keep waiting."); 320 } 321 } 322 onRadioOn()323 private void onRadioOn() { 324 if (mPhone == null) { 325 return; 326 } 327 ServiceState state = mPhone.getServiceState(); 328 Rlog.d(TAG, String.format("onRadioOn, state = %s, Phone = %s", state, mPhone.getPhoneId())); 329 if (isOkToCall(state.getState(), mPhone.isVoiceOverCellularImsEnabled())) { 330 onComplete(true); 331 cleanup(); 332 } else { 333 Rlog.d(TAG, "onRadioOn: not ready to call yet, keep waiting."); 334 } 335 } 336 onSatelliteEnabledChanged()337 private void onSatelliteEnabledChanged() { 338 if (mPhone == null) { 339 return; 340 } 341 if (isOkToCall(mPhone.getServiceState().getState(), 342 mPhone.isVoiceOverCellularImsEnabled())) { 343 onComplete(true); 344 cleanup(); 345 } else { 346 Rlog.d(TAG, "onSatelliteEnabledChanged: not ready to call yet, keep waiting."); 347 } 348 } 349 350 /** 351 * Callback to see if it is okay to call yet, given the current conditions. 352 */ isOkToCall(int serviceState, boolean imsVoiceCapable)353 private boolean isOkToCall(int serviceState, boolean imsVoiceCapable) { 354 return (mCallback == null) 355 ? false : mCallback.isOkToCall(mPhone, serviceState, imsVoiceCapable); 356 } 357 358 /** 359 * Callback to see if it is okay to call yet, given the current conditions. 360 */ onTimeout(int serviceState, boolean imsVoiceCapable)361 private boolean onTimeout(int serviceState, boolean imsVoiceCapable) { 362 return (mCallback == null) 363 ? false : mCallback.onTimeout(mPhone, serviceState, imsVoiceCapable); 364 } 365 366 /** 367 * Handles the retry timer expiring. 368 */ onRetryTimeout()369 private void onRetryTimeout() { 370 if (mPhone == null) { 371 return; 372 } 373 int serviceState = mPhone.getServiceState().getState(); 374 Rlog.d(TAG, 375 String.format(Locale.getDefault(), 376 "onRetryTimeout(): phone state = %s, service state = %d, retries = %d.", 377 mPhone.getState(), serviceState, mNumRetriesSoFar)); 378 379 // - If we're actually in a call, we've succeeded. 380 // - Otherwise, if the radio is now on, that means we successfully got out of airplane mode 381 // but somehow didn't get the service state change event. In that case, try to place the 382 // call. 383 // - If the radio is still powered off, try powering it on again. 384 385 if (isOkToCall(serviceState, mPhone.isVoiceOverCellularImsEnabled())) { 386 Rlog.d(TAG, "onRetryTimeout: Radio is on. Cleaning up."); 387 388 // Woo hoo -- we successfully got out of airplane mode. 389 onComplete(true); 390 cleanup(); 391 } else { 392 // Uh oh; we've waited the full TIME_BETWEEN_RETRIES_MILLIS and the radio is still not 393 // powered-on. Try again. 394 395 mNumRetriesSoFar++; 396 Rlog.d(TAG, "mNumRetriesSoFar is now " + mNumRetriesSoFar); 397 398 if (mNumRetriesSoFar > MAX_NUM_RETRIES) { 399 if (mHandler.hasMessages(MSG_TIMEOUT_ONTIMEOUT_CALLBACK)) { 400 Rlog.w(TAG, "Hit MAX_NUM_RETRIES; waiting onTimeout callback"); 401 return; 402 } 403 Rlog.w(TAG, "Hit MAX_NUM_RETRIES; giving up."); 404 cleanup(); 405 } else { 406 Rlog.d(TAG, "Trying (again) to turn the radio on and satellite modem off."); 407 mPhone.setRadioPower(true, mForEmergencyCall, mSelectedPhoneForEmergencyCall, 408 false); 409 if (mSatelliteController.isSatelliteEnabledOrBeingEnabled()) { 410 mSatelliteController.requestSatelliteEnabled( 411 false /* enableSatellite */, false /* enableDemoMode */, 412 false /* isEmergency*/, 413 new IIntegerConsumer.Stub() { 414 @Override 415 public void accept(int result) { 416 mHandler.obtainMessage(MSG_SATELLITE_ENABLED_CHANGED) 417 .sendToTarget(); 418 } 419 }); 420 } 421 startRetryTimer(); 422 } 423 } 424 } 425 426 /** 427 * Clean up when done with the whole sequence: either after successfully turning on the radio, 428 * or after bailing out because of too many failures. 429 * 430 * The exact cleanup steps are: 431 * - Notify callback if we still hadn't sent it a response. 432 * - Double-check that we're not still registered for any telephony events 433 * - Clean up any extraneous handler messages (like retry timeouts) still in the queue 434 * 435 * Basically this method guarantees that there will be no more activity from the 436 * RadioOnStateListener until someone kicks off the whole sequence again with another call to 437 * {@link #waitForRadioOn} 438 * 439 * TODO: Do the work for the comment below: Note we don't call this method simply after a 440 * successful call to placeCall(), since it's still possible the call will disconnect very 441 * quickly with an OUT_OF_SERVICE error. 442 */ cleanup()443 public void cleanup() { 444 Rlog.d(TAG, "cleanup()"); 445 446 // This will send a failure call back if callback has yet to be invoked. If the callback was 447 // already invoked, it's a no-op. 448 onComplete(false); 449 450 unregisterForServiceStateChanged(); 451 unregisterForRadioOff(); 452 unregisterForRadioOn(); 453 unregisterForSatelliteEnabledChanged(); 454 cancelRetryTimer(); 455 unregisterForImsCapabilityChanged(); 456 457 // Used for unregisterForServiceStateChanged() so we null it out here instead. 458 mPhone = null; 459 mNumRetriesSoFar = 0; 460 mOnTimeoutCallbackInterval = 0; 461 } 462 startRetryTimer()463 private void startRetryTimer() { 464 cancelRetryTimer(); 465 mHandler.sendEmptyMessageDelayed(MSG_RETRY_TIMEOUT, TIME_BETWEEN_RETRIES_MILLIS); 466 } 467 cancelRetryTimer()468 private void cancelRetryTimer() { 469 mHandler.removeMessages(MSG_RETRY_TIMEOUT); 470 } 471 registerForServiceStateChanged()472 private void registerForServiceStateChanged() { 473 // Unregister first, just to make sure we never register ourselves twice. (We need this 474 // because Phone.registerForServiceStateChanged() does not prevent multiple registration of 475 // the same handler.) 476 unregisterForServiceStateChanged(); 477 mPhone.registerForServiceStateChanged(mHandler, MSG_SERVICE_STATE_CHANGED, null); 478 } 479 unregisterForServiceStateChanged()480 private void unregisterForServiceStateChanged() { 481 // This method is safe to call even if we haven't set mPhone yet. 482 if (mPhone != null) { 483 mPhone.unregisterForServiceStateChanged(mHandler); // Safe even if unnecessary 484 } 485 mHandler.removeMessages(MSG_SERVICE_STATE_CHANGED); // Clean up any pending messages too 486 } 487 registerForRadioOff()488 private void registerForRadioOff() { 489 mPhone.mCi.registerForOffOrNotAvailable(mHandler, MSG_RADIO_OFF_OR_NOT_AVAILABLE, null); 490 } 491 unregisterForRadioOff()492 private void unregisterForRadioOff() { 493 // This method is safe to call even if we haven't set mPhone yet. 494 if (mPhone != null) { 495 mPhone.mCi.unregisterForOffOrNotAvailable(mHandler); // Safe even if unnecessary 496 } 497 mHandler.removeMessages(MSG_RADIO_OFF_OR_NOT_AVAILABLE); // Clean up any pending messages 498 } 499 registerForRadioOn()500 private void registerForRadioOn() { 501 unregisterForRadioOff(); 502 mPhone.mCi.registerForOn(mHandler, MSG_RADIO_ON, null); 503 } 504 unregisterForRadioOn()505 private void unregisterForRadioOn() { 506 // This method is safe to call even if we haven't set mPhone yet. 507 if (mPhone != null) { 508 mPhone.mCi.unregisterForOn(mHandler); // Safe even if unnecessary 509 } 510 mHandler.removeMessages(MSG_RADIO_ON); // Clean up any pending messages too 511 } 512 registerForSatelliteEnabledChanged()513 private void registerForSatelliteEnabledChanged() { 514 mSatelliteController.registerForSatelliteModemStateChanged(mSatelliteCallback); 515 } 516 unregisterForSatelliteEnabledChanged()517 private void unregisterForSatelliteEnabledChanged() { 518 mSatelliteController.unregisterForModemStateChanged(mSatelliteCallback); 519 mHandler.removeMessages(MSG_SATELLITE_ENABLED_CHANGED); 520 } 521 registerForImsCapabilityChanged()522 private void registerForImsCapabilityChanged() { 523 unregisterForImsCapabilityChanged(); 524 mPhone.getServiceStateTracker() 525 .registerForImsCapabilityChanged(mHandler, MSG_IMS_CAPABILITY_CHANGED, null); 526 } 527 unregisterForImsCapabilityChanged()528 private void unregisterForImsCapabilityChanged() { 529 if (mPhone != null) { 530 mPhone.getServiceStateTracker() 531 .unregisterForImsCapabilityChanged(mHandler); 532 } 533 mHandler.removeMessages(MSG_IMS_CAPABILITY_CHANGED); 534 } 535 startOnTimeoutCallbackTimer()536 private void startOnTimeoutCallbackTimer() { 537 Rlog.d(TAG, "startOnTimeoutCallbackTimer: mOnTimeoutCallbackInterval=" 538 + mOnTimeoutCallbackInterval); 539 mHandler.removeMessages(MSG_TIMEOUT_ONTIMEOUT_CALLBACK); 540 if (mOnTimeoutCallbackInterval > 0) { 541 mHandler.sendEmptyMessageDelayed(MSG_TIMEOUT_ONTIMEOUT_CALLBACK, 542 mOnTimeoutCallbackInterval); 543 } 544 } 545 onComplete(boolean isRadioReady)546 private void onComplete(boolean isRadioReady) { 547 if (mCallback != null) { 548 Callback tempCallback = mCallback; 549 mCallback = null; 550 tempCallback.onComplete(this, isRadioReady); 551 } 552 } 553 554 @VisibleForTesting getHandler()555 public Handler getHandler() { 556 return mHandler; 557 } 558 559 @VisibleForTesting setMaxNumRetries(int retries)560 public void setMaxNumRetries(int retries) { 561 MAX_NUM_RETRIES = retries; 562 } 563 564 @VisibleForTesting setTimeBetweenRetriesMillis(long timeMs)565 public void setTimeBetweenRetriesMillis(long timeMs) { 566 TIME_BETWEEN_RETRIES_MILLIS = timeMs; 567 } 568 569 @Override equals(Object o)570 public boolean equals(Object o) { 571 if (this == o) 572 return true; 573 if (o == null || !getClass().equals(o.getClass())) 574 return false; 575 576 RadioOnStateListener that = (RadioOnStateListener) o; 577 578 if (mNumRetriesSoFar != that.mNumRetriesSoFar) { 579 return false; 580 } 581 if (mCallback != null ? !mCallback.equals(that.mCallback) : that.mCallback != null) { 582 return false; 583 } 584 return mPhone != null ? mPhone.equals(that.mPhone) : that.mPhone == null; 585 } 586 587 @Override hashCode()588 public int hashCode() { 589 int hash = 7; 590 hash = 31 * hash + mNumRetriesSoFar; 591 hash = 31 * hash + (mCallback == null ? 0 : mCallback.hashCode()); 592 hash = 31 * hash + (mPhone == null ? 0 : mPhone.hashCode()); 593 return hash; 594 } 595 } 596