1/*
2 * Copyright (C) 2014 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
17package com.android.services.telephony;
18
19import android.content.Context;
20import android.content.Intent;
21import android.os.UserHandle;
22import android.provider.Settings;
23import android.telephony.TelephonyManager;
24
25import com.android.internal.telephony.Phone;
26import com.android.internal.telephony.PhoneFactory;
27
28import java.util.ArrayList;
29import java.util.HashSet;
30import java.util.List;
31
32/**
33 * Helper class that implements special behavior related to emergency calls or making phone calls
34 * when the radio is in the POWER_OFF STATE. Specifically, this class handles the case of the user
35 * trying to dial an emergency number while the radio is off (i.e. the device is in airplane mode)
36 * or a normal number while the radio is off (because of the device is on Bluetooth), by turning the
37 * radio back on, waiting for it to come up, and then retrying the call.
38 */
39public class RadioOnHelper implements RadioOnStateListener.Callback {
40
41    private final Context mContext;
42    private RadioOnStateListener.Callback mCallback;
43    private List<RadioOnStateListener> mListeners;
44    private List<RadioOnStateListener> mInProgressListeners;
45    private boolean mIsRadioOnCallingEnabled;
46
47    public RadioOnHelper(Context context) {
48        mContext = context;
49        mInProgressListeners = new ArrayList<>(2);
50    }
51
52    private void setupListeners() {
53        if (mListeners != null) {
54            return;
55        }
56        mListeners = new ArrayList<>(2);
57        for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
58            mListeners.add(new RadioOnStateListener());
59        }
60    }
61    /**
62     * Starts the "turn on radio" sequence. This is the (single) external API of the
63     * RadioOnHelper class.
64     *
65     * This method kicks off the following sequence:
66     * - Power on the radio for each Phone
67     * - Listen for radio events telling us the radio has come up.
68     * - Retry if we've gone a significant amount of time without any response from the radio.
69     * - Finally, clean up any leftover state.
70     *
71     * This method is safe to call from any thread, since it simply posts a message to the
72     * RadioOnHelper's handler (thus ensuring that the rest of the sequence is entirely
73     * serialized, and runs on the main looper.)
74     */
75    public void triggerRadioOnAndListen(RadioOnStateListener.Callback callback) {
76        setupListeners();
77        mCallback = callback;
78        mInProgressListeners.clear();
79        mIsRadioOnCallingEnabled = false;
80        for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
81            Phone phone = PhoneFactory.getPhone(i);
82            if (phone == null) {
83                continue;
84            }
85
86            mInProgressListeners.add(mListeners.get(i));
87            mListeners.get(i).waitForRadioOn(phone, this);
88        }
89
90        powerOnRadio();
91    }
92    /**
93     * Attempt to power on the radio (i.e. take the device out of airplane mode). We'll eventually
94     * get an onServiceStateChanged() callback when the radio successfully comes up.
95     */
96    private void powerOnRadio() {
97        Log.d(this, "powerOnRadio().");
98
99        // If airplane mode is on, we turn it off the same way that the Settings activity turns it
100        // off.
101        if (Settings.Global.getInt(mContext.getContentResolver(),
102                Settings.Global.AIRPLANE_MODE_ON, 0) > 0) {
103            Log.d(this, "==> Turning off airplane mode.");
104
105            // Change the system setting
106            Settings.Global.putInt(mContext.getContentResolver(),
107                    Settings.Global.AIRPLANE_MODE_ON, 0);
108
109            // Post the broadcast intend for change in airplane mode
110            // TODO: We really should not be in charge of sending this broadcast.
111            // If changing the setting is sufficient to trigger all of the rest of the logic,
112            // then that should also trigger the broadcast intent.
113            Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
114            intent.putExtra("state", false);
115            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
116        }
117    }
118
119    /**
120     * This method is called from multiple Listeners on the Main Looper.
121     * Synchronization is not necessary.
122     */
123    @Override
124    public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
125        mIsRadioOnCallingEnabled |= isRadioReady;
126        mInProgressListeners.remove(listener);
127        if (mCallback != null && mInProgressListeners.isEmpty()) {
128            mCallback.onComplete(null, mIsRadioOnCallingEnabled);
129        }
130    }
131
132    @Override
133    public boolean isOkToCall(Phone phone, int serviceState) {
134        return (mCallback == null) ? false : mCallback.isOkToCall(phone, serviceState);
135    }
136}
137