• 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.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