• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
60     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
61         @Override
62         public void handleMessage(Message msg) {
63             switch (msg.what) {
64                 case MSG_START_SEQUENCE:
65                     SomeArgs args = (SomeArgs) msg.obj;
66                     try {
67                         Phone phone = (Phone) args.arg1;
68                         RadioOnStateListener.Callback callback =
69                                 (RadioOnStateListener.Callback) args.arg2;
70                         startSequenceInternal(phone, callback);
71                     } finally {
72                         args.recycle();
73                     }
74                     break;
75                 case MSG_SERVICE_STATE_CHANGED:
76                     onServiceStateChanged((ServiceState) ((AsyncResult) msg.obj).result);
77                     break;
78                 case MSG_RETRY_TIMEOUT:
79                     onRetryTimeout();
80                     break;
81                 default:
82                     Log.wtf(this, "handleMessage: unexpected message: %d.", msg.what);
83                     break;
84             }
85         }
86     };
87 
88 
89     private Callback mCallback;  // The callback to notify upon completion.
90     private Phone mPhone;  // The phone that will attempt to place the call.
91     private int mNumRetriesSoFar;
92 
93     /**
94      * Starts the "wait for radio" sequence. This is the (single) external API of the
95      * RadioOnStateListener class.
96      *
97      * This method kicks off the following sequence:
98      * - Listen for the service state change event telling us the radio has come up.
99      * - Retry if we've gone {@link #TIME_BETWEEN_RETRIES_MILLIS} without any response from the
100      *   radio.
101      * - Finally, clean up any leftover state.
102      *
103      * This method is safe to call from any thread, since it simply posts a message to the
104      * RadioOnStateListener's handler (thus ensuring that the rest of the sequence is entirely
105      * serialized, and runs only on the handler thread.)
106      */
waitForRadioOn(Phone phone, Callback callback)107     public void waitForRadioOn(Phone phone, Callback callback) {
108         Log.d(this, "waitForRadioOn: Phone " + phone.getPhoneId());
109 
110         if (mPhone != null) {
111             // If there already is an ongoing request, ignore the new one!
112             return;
113         }
114 
115         SomeArgs args = SomeArgs.obtain();
116         args.arg1 = phone;
117         args.arg2 = callback;
118         mHandler.obtainMessage(MSG_START_SEQUENCE, args).sendToTarget();
119     }
120 
121     /**
122      * Actual implementation of waitForRadioOn(), guaranteed to run on the handler thread.
123      *
124      * @see #waitForRadioOn
125      */
startSequenceInternal(Phone phone, Callback callback)126     private void startSequenceInternal(Phone phone, Callback callback) {
127         Log.d(this, "startSequenceInternal: Phone " + phone.getPhoneId());
128 
129         // First of all, clean up any state left over from a prior RadioOn call sequence. This
130         // ensures that we'll behave sanely if another startTurnOnRadioSequence() comes in while
131         // we're already in the middle of the sequence.
132         cleanup();
133 
134         mPhone = phone;
135         mCallback = callback;
136 
137         registerForServiceStateChanged();
138         // Next step: when the SERVICE_STATE_CHANGED event comes in, we'll retry the call; see
139         // onServiceStateChanged(). But also, just in case, start a timer to make sure we'll retry
140         // the call even if the SERVICE_STATE_CHANGED event never comes in for some reason.
141         startRetryTimer();
142     }
143 
144     /**
145      * Handles the SERVICE_STATE_CHANGED event. This event tells us that the radio state has changed
146      * and is probably coming up. We can now check to see if the conditions are met to place the
147      * call with {@link Callback#isOkToCall}
148      */
onServiceStateChanged(ServiceState state)149     private void onServiceStateChanged(ServiceState state) {
150         Log.d(this, "onServiceStateChanged(), new state = %s, Phone = %s", state,
151                 mPhone.getPhoneId());
152 
153         // Possible service states:
154         // - STATE_IN_SERVICE        // Normal operation
155         // - STATE_OUT_OF_SERVICE    // Still searching for an operator to register to,
156         //                           // or no radio signal
157         // - STATE_EMERGENCY_ONLY    // Only emergency numbers are allowed; currently not used
158         // - STATE_POWER_OFF         // Radio is explicitly powered off (airplane mode)
159 
160         if (isOkToCall(state.getState())) {
161             // Woo hoo!  It's OK to actually place the call.
162             Log.d(this, "onServiceStateChanged: ok to call!");
163 
164             onComplete(true);
165             cleanup();
166         } else {
167             // The service state changed, but we're still not ready to call yet.
168             Log.d(this, "onServiceStateChanged: not ready to call yet, keep waiting.");
169         }
170     }
171 
172     /**
173      * Callback to see if it is okay to call yet, given the current conditions.
174      */
isOkToCall(int serviceState)175     private boolean isOkToCall(int serviceState) {
176         return (mCallback == null) ? false : mCallback.isOkToCall(mPhone, serviceState);
177     }
178 
179     /**
180      * Handles the retry timer expiring.
181      */
onRetryTimeout()182     private void onRetryTimeout() {
183         int serviceState = mPhone.getServiceState().getState();
184         Log.d(this, "onRetryTimeout():  phone state = %s, service state = %d, retries = %d.",
185                 mPhone.getState(), serviceState, mNumRetriesSoFar);
186 
187         // - If we're actually in a call, we've succeeded.
188         // - Otherwise, if the radio is now on, that means we successfully got out of airplane mode
189         //   but somehow didn't get the service state change event.  In that case, try to place the
190         //   call.
191         // - If the radio is still powered off, try powering it on again.
192 
193         if (isOkToCall(serviceState)) {
194             Log.d(this, "onRetryTimeout: Radio is on. Cleaning up.");
195 
196             // Woo hoo -- we successfully got out of airplane mode.
197             onComplete(true);
198             cleanup();
199         } else {
200             // Uh oh; we've waited the full TIME_BETWEEN_RETRIES_MILLIS and the radio is still not
201             // powered-on.  Try again.
202 
203             mNumRetriesSoFar++;
204             Log.d(this, "mNumRetriesSoFar is now " + mNumRetriesSoFar);
205 
206             if (mNumRetriesSoFar > MAX_NUM_RETRIES) {
207                 Log.w(this, "Hit MAX_NUM_RETRIES; giving up.");
208                 cleanup();
209             } else {
210                 Log.d(this, "Trying (again) to turn on the radio.");
211                 mPhone.setRadioPower(true);
212                 startRetryTimer();
213             }
214         }
215     }
216 
217     /**
218      * Clean up when done with the whole sequence: either after successfully turning on the radio,
219      * or after bailing out because of too many failures.
220      *
221      * The exact cleanup steps are:
222      * - Notify callback if we still hadn't sent it a response.
223      * - Double-check that we're not still registered for any telephony events
224      * - Clean up any extraneous handler messages (like retry timeouts) still in the queue
225      *
226      * Basically this method guarantees that there will be no more activity from the
227      * RadioOnStateListener until someone kicks off the whole sequence again with another call
228      * to {@link #waitForRadioOn}
229      *
230      * TODO: Do the work for the comment below:
231      * Note we don't call this method simply after a successful call to placeCall(), since it's
232      * still possible the call will disconnect very quickly with an OUT_OF_SERVICE error.
233      */
cleanup()234     private void cleanup() {
235         Log.d(this, "cleanup()");
236 
237         // This will send a failure call back if callback has yet to be invoked.  If the callback
238         // was already invoked, it's a no-op.
239         onComplete(false);
240 
241         unregisterForServiceStateChanged();
242         cancelRetryTimer();
243 
244         // Used for unregisterForServiceStateChanged() so we null it out here instead.
245         mPhone = null;
246         mNumRetriesSoFar = 0;
247     }
248 
startRetryTimer()249     private void startRetryTimer() {
250         cancelRetryTimer();
251         mHandler.sendEmptyMessageDelayed(MSG_RETRY_TIMEOUT, TIME_BETWEEN_RETRIES_MILLIS);
252     }
253 
cancelRetryTimer()254     private void cancelRetryTimer() {
255         mHandler.removeMessages(MSG_RETRY_TIMEOUT);
256     }
257 
registerForServiceStateChanged()258     private void registerForServiceStateChanged() {
259         // Unregister first, just to make sure we never register ourselves twice.  (We need this
260         // because Phone.registerForServiceStateChanged() does not prevent multiple registration of
261         // the same handler.)
262         unregisterForServiceStateChanged();
263         mPhone.registerForServiceStateChanged(mHandler, MSG_SERVICE_STATE_CHANGED, null);
264     }
265 
unregisterForServiceStateChanged()266     private void unregisterForServiceStateChanged() {
267         // This method is safe to call even if we haven't set mPhone yet.
268         if (mPhone != null) {
269             mPhone.unregisterForServiceStateChanged(mHandler);  // Safe even if unnecessary
270         }
271         mHandler.removeMessages(MSG_SERVICE_STATE_CHANGED);  // Clean up any pending messages too
272     }
273 
onComplete(boolean isRadioReady)274     private void onComplete(boolean isRadioReady) {
275         if (mCallback != null) {
276             Callback tempCallback = mCallback;
277             mCallback = null;
278             tempCallback.onComplete(this, isRadioReady);
279         }
280     }
281 
282     @VisibleForTesting
getHandler()283     public Handler getHandler() {
284         return mHandler;
285     }
286 
287     @VisibleForTesting
setMaxNumRetries(int retries)288     public void setMaxNumRetries(int retries) {
289         MAX_NUM_RETRIES = retries;
290     }
291 
292     @VisibleForTesting
setTimeBetweenRetriesMillis(long timeMs)293     public void setTimeBetweenRetriesMillis(long timeMs) {
294         TIME_BETWEEN_RETRIES_MILLIS = timeMs;
295     }
296 
297     @Override
equals(Object o)298     public boolean equals(Object o) {
299         if (this == o) return true;
300         if (o == null || !getClass().equals(o.getClass())) return false;
301 
302         RadioOnStateListener that = (RadioOnStateListener) o;
303 
304         if (mNumRetriesSoFar != that.mNumRetriesSoFar) {
305             return false;
306         }
307         if (mCallback != null ? !mCallback.equals(that.mCallback) : that.mCallback != null) {
308             return false;
309         }
310         return mPhone != null ? mPhone.equals(that.mPhone) : that.mPhone == null;
311 
312     }
313 }
314