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