summaryrefslogtreecommitdiff
path: root/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
blob: c61f8a9c3b534bedd9135894928ea43be4d78a4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settingslib.connectivity;

import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.SubsystemRestartTrackingCallback;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.provider.Settings;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.util.Log;

/**
 * An interface class to manage connectivity subsystem recovery/restart operations.
 */
public class ConnectivitySubsystemsRecoveryManager {
    private static final String TAG = "ConnectivitySubsystemsRecoveryManager";

    private final Context mContext;
    private final Handler mHandler;
    private RecoveryAvailableListener mRecoveryAvailableListener = null;

    private static final long RESTART_TIMEOUT_MS = 15_000; // 15 seconds

    private WifiManager mWifiManager = null;
    private TelephonyManager mTelephonyManager = null;
    private final BroadcastReceiver mApmMonitor = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            RecoveryAvailableListener listener = mRecoveryAvailableListener;
            if (listener != null) {
                listener.onRecoveryAvailableChangeListener(isRecoveryAvailable());
            }
        }
    };
    private boolean mApmMonitorRegistered = false;
    private boolean mWifiRestartInProgress = false;
    private boolean mTelephonyRestartInProgress = false;
    private RecoveryStatusCallback mCurrentRecoveryCallback = null;
    private final SubsystemRestartTrackingCallback mWifiSubsystemRestartTrackingCallback =
            new SubsystemRestartTrackingCallback() {
        @Override
        public void onSubsystemRestarting() {
            // going to do nothing on this - already assuming that subsystem is restarting
        }

        @Override
        public void onSubsystemRestarted() {
            mWifiRestartInProgress = false;
            stopTrackingWifiRestart();
            checkIfAllSubsystemsRestartsAreDone();
        }
    };
    private final MobileTelephonyCallback mTelephonyCallback = new MobileTelephonyCallback();

    private class MobileTelephonyCallback extends TelephonyCallback implements
            TelephonyCallback.RadioPowerStateListener {
        @Override
        public void onRadioPowerStateChanged(int state) {
            if (!mTelephonyRestartInProgress || mCurrentRecoveryCallback == null) {
                stopTrackingTelephonyRestart();
            }

            if (state == TelephonyManager.RADIO_POWER_ON) {
                mTelephonyRestartInProgress = false;
                stopTrackingTelephonyRestart();
                checkIfAllSubsystemsRestartsAreDone();
            }
        }
    }

    public ConnectivitySubsystemsRecoveryManager(@NonNull Context context,
            @NonNull Handler handler) {
        mContext = context;
        mHandler = new Handler(handler.getLooper());

        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) {
            mWifiManager = mContext.getSystemService(WifiManager.class);
            if (mWifiManager == null) {
                Log.e(TAG, "WifiManager not available!?");
            }
        }

        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
            mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
            if (mTelephonyManager == null) {
                Log.e(TAG, "TelephonyManager not available!?");
            }
        }
    }

    /**
     * A listener which indicates to the caller whether a recovery operation is available across
     * the specified technologies.
     *
     * Set using {@link #setRecoveryAvailableListener(RecoveryAvailableListener)}, cleared
     * using {@link #clearRecoveryAvailableListener()}.
     */
    public interface RecoveryAvailableListener {
        /**
         * Called whenever the recovery availability status changes.
         *
         * @param isAvailable True if recovery is available across ANY of the requested
         *                    technologies, false if recovery is not available across ALL of the
         *                    requested technologies.
         */
        void onRecoveryAvailableChangeListener(boolean isAvailable);
    }

    /**
     * Set a {@link RecoveryAvailableListener} to listen to changes in the recovery availability
     * operation for the specified technology(ies).
     *
     * @param listener Listener to be triggered
     */
    public void setRecoveryAvailableListener(@NonNull RecoveryAvailableListener listener) {
        mHandler.post(() -> {
            mRecoveryAvailableListener = listener;
            startTrackingRecoveryAvailability();
        });
    }

    /**
     * Clear a listener set with
     * {@link #setRecoveryAvailableListener(RecoveryAvailableListener)}.
     */
    public void clearRecoveryAvailableListener() {
        mHandler.post(() -> {
            mRecoveryAvailableListener = null;
            stopTrackingRecoveryAvailability();
        });
    }

    private boolean isApmEnabled() {
        return Settings.Global.getInt(mContext.getContentResolver(),
                Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
    }

    private boolean isWifiEnabled() {
        // TODO: this doesn't consider the scan-only mode. I.e. WiFi is "disabled" while location
        // mode is enabled. Probably need to reset WiFi in that state as well. Though this may
        // appear strange to the user in that they've actually disabled WiFi.
        return mWifiManager != null && (mWifiManager.isWifiEnabled()
                || mWifiManager.isWifiApEnabled());
    }

    /**
     * Provide an indication as to whether subsystem recovery is "available" - i.e. will be
     * executed if triggered via {@link #triggerSubsystemRestart(String, RecoveryStatusCallback)}.
     *
     * @return true if a subsystem recovery is available, false otherwise.
     */
    public boolean isRecoveryAvailable() {
        if (!isApmEnabled()) return true;

        // even if APM is enabled we may still have recovery potential if WiFi is enabled
        return isWifiEnabled();
    }

    private void startTrackingRecoveryAvailability() {
        if (mApmMonitorRegistered) return;

        mContext.registerReceiver(mApmMonitor,
                new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED), null, mHandler);
        mApmMonitorRegistered = true;
    }

    private void stopTrackingRecoveryAvailability() {
        if (!mApmMonitorRegistered) return;

        mContext.unregisterReceiver(mApmMonitor);
        mApmMonitorRegistered = false;
    }

    private void startTrackingWifiRestart() {
        mWifiManager.registerSubsystemRestartTrackingCallback(new HandlerExecutor(mHandler),
                mWifiSubsystemRestartTrackingCallback);
    }

    private void stopTrackingWifiRestart() {
        mWifiManager.unregisterSubsystemRestartTrackingCallback(
                mWifiSubsystemRestartTrackingCallback);
    }

    private void startTrackingTelephonyRestart() {
        mTelephonyManager.registerTelephonyCallback(new HandlerExecutor(mHandler),
                mTelephonyCallback);
    }

    private void stopTrackingTelephonyRestart() {
        mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback);
    }

    private void checkIfAllSubsystemsRestartsAreDone() {
        if (!mWifiRestartInProgress && !mTelephonyRestartInProgress
                && mCurrentRecoveryCallback != null) {
            mCurrentRecoveryCallback.onSubsystemRestartOperationEnd();
            mCurrentRecoveryCallback = null;
        }
    }

    /**
     * Callbacks used with
     * {@link #triggerSubsystemRestart(String, RecoveryStatusCallback)} to get
     * information about when recovery starts and is completed.
     */
    public interface RecoveryStatusCallback {
        /**
         * Callback for a subsystem restart triggered via
         * {@link #triggerSubsystemRestart(String, RecoveryStatusCallback)} - indicates
         * that operation has started.
         */
        void onSubsystemRestartOperationBegin();

        /**
         * Callback for a subsystem restart triggered via
         * {@link #triggerSubsystemRestart(String, RecoveryStatusCallback)} - indicates
         * that operation has ended. Note that subsystems may still take some time to come up to
         * full functionality.
         */
        void onSubsystemRestartOperationEnd();
    }

    /**
     * Trigger connectivity recovery for all requested technologies.
     *
     * @param reason   An optional reason code to pass through to the technology-specific
     *                 API. May be used to trigger a bug report.
     * @param callback Callbacks triggered when recovery status changes.
     */
    public void triggerSubsystemRestart(String reason, @NonNull RecoveryStatusCallback callback) {
        // TODO: b/183530649 : clean-up or make use of the `reason` argument
        mHandler.post(() -> {
            boolean someSubsystemRestarted = false;

            if (mWifiRestartInProgress) {
                Log.e(TAG, "Wifi restart still in progress");
                return;
            }

            if (mTelephonyRestartInProgress) {
                Log.e(TAG, "Telephony restart still in progress");
                return;
            }

            if (isWifiEnabled()) {
                mWifiManager.restartWifiSubsystem();
                mWifiRestartInProgress = true;
                someSubsystemRestarted = true;
                startTrackingWifiRestart();
            }

            if (mTelephonyManager != null && !isApmEnabled()) {
                if (mTelephonyManager.rebootRadio()) {
                    mTelephonyRestartInProgress = true;
                    someSubsystemRestarted = true;
                    startTrackingTelephonyRestart();
                }
            }

            if (someSubsystemRestarted) {
                mCurrentRecoveryCallback = callback;
                callback.onSubsystemRestartOperationBegin();

                mHandler.postDelayed(() -> {
                    stopTrackingWifiRestart();
                    stopTrackingTelephonyRestart();
                    mWifiRestartInProgress = false;
                    mTelephonyRestartInProgress = false;
                    if (mCurrentRecoveryCallback != null) {
                        mCurrentRecoveryCallback.onSubsystemRestartOperationEnd();
                        mCurrentRecoveryCallback = null;
                    }
                }, RESTART_TIMEOUT_MS);
            }
        });
    }
}