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