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