1 /*
2  * Copyright (C) 2021 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.settings.network;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.net.wifi.WifiManager;
24 import android.os.Handler;
25 import android.os.HandlerThread;
26 import android.os.Process;
27 import android.text.TextUtils;
28 import android.util.Log;
29 
30 import androidx.annotation.UiThread;
31 import androidx.annotation.VisibleForTesting;
32 import androidx.annotation.WorkerThread;
33 import androidx.lifecycle.Lifecycle;
34 import androidx.lifecycle.LifecycleObserver;
35 import androidx.lifecycle.OnLifecycleEvent;
36 import androidx.preference.Preference;
37 import androidx.preference.PreferenceCategory;
38 
39 import com.android.settingslib.connectivity.ConnectivitySubsystemsRecoveryManager;
40 
41 import java.util.ArrayList;
42 import java.util.List;
43 
44 /**
45  * Helper class to restart connectivity for all requested subsystems.
46  */
47 public class InternetResetHelper implements LifecycleObserver,
48         ConnectivitySubsystemsRecoveryManager.RecoveryStatusCallback {
49 
50     protected static final String TAG = "InternetResetHelper";
51     public static final long RESTART_TIMEOUT_MS = 15_000; // 15 seconds
52 
53     protected final Context mContext;
54     protected Preference mResettingPreference;
55     protected NetworkMobileProviderController mMobileNetworkController;
56     protected Preference mWifiTogglePreferences;
57     protected List<PreferenceCategory> mWifiNetworkPreferences =
58             new ArrayList<PreferenceCategory>();
59 
60     protected final WifiManager mWifiManager;
61     protected final IntentFilter mWifiStateFilter;
62     protected final BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() {
63         @Override
64         @WorkerThread
65         public void onReceive(Context context, Intent intent) {
66             if (intent != null && TextUtils.equals(intent.getAction(),
67                     WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
68                 updateWifiStateChange();
69             }
70         }
71     };
72 
73     protected ConnectivitySubsystemsRecoveryManager mConnectivitySubsystemsRecoveryManager;
74     protected HandlerThread mWorkerThread;
75     protected boolean mIsRecoveryReady;
76     protected boolean mIsWifiReady;
77     protected HandlerInjector mHandlerInjector;
78     protected final Runnable mResumeRunnable = () -> {
79         resumePreferences();
80     };
81     protected final Runnable mTimeoutRunnable = () -> {
82         mIsRecoveryReady = true;
83         mIsWifiReady = true;
84         resumePreferences();
85     };
86 
InternetResetHelper(Context context, Lifecycle lifecycle)87     public InternetResetHelper(Context context, Lifecycle lifecycle) {
88         mContext = context;
89         mHandlerInjector = new HandlerInjector(context);
90         mWifiManager = mContext.getSystemService(WifiManager.class);
91         mWifiStateFilter = new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION);
92 
93         mWorkerThread = new HandlerThread(TAG
94                 + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
95                 Process.THREAD_PRIORITY_BACKGROUND);
96         mWorkerThread.start();
97         mConnectivitySubsystemsRecoveryManager = new ConnectivitySubsystemsRecoveryManager(
98                 mContext, mWorkerThread.getThreadHandler());
99 
100         if (lifecycle != null) {
101             lifecycle.addObserver(this);
102         }
103     }
104 
105     /** @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) */
106     @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
onResume()107     public void onResume() {
108         mContext.registerReceiver(mWifiStateReceiver, mWifiStateFilter);
109     }
110 
111     /** @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) */
112     @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
onPause()113     public void onPause() {
114         mContext.unregisterReceiver(mWifiStateReceiver);
115     }
116 
117     /** @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) */
118     @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
onDestroy()119     public void onDestroy() {
120         mHandlerInjector.removeCallbacks(mResumeRunnable);
121         mHandlerInjector.removeCallbacks(mTimeoutRunnable);
122         mWorkerThread.quit();
123     }
124 
125     @Override
126     @WorkerThread
onSubsystemRestartOperationBegin()127     public void onSubsystemRestartOperationBegin() {
128         Log.d(TAG, "The connectivity subsystem is starting for recovery.");
129     }
130 
131     @Override
132     @WorkerThread
onSubsystemRestartOperationEnd()133     public void onSubsystemRestartOperationEnd() {
134         Log.d(TAG, "The connectivity subsystem is done for recovery.");
135         if (!mIsRecoveryReady) {
136             mIsRecoveryReady = true;
137             mHandlerInjector.postDelayed(mResumeRunnable, 0 /* delayMillis */);
138         }
139     }
140 
141     @VisibleForTesting
142     @WorkerThread
updateWifiStateChange()143     protected void updateWifiStateChange() {
144         if (!mIsWifiReady && mWifiManager.isWifiEnabled()) {
145             Log.d(TAG, "The Wi-Fi subsystem is done for recovery.");
146             mIsWifiReady = true;
147             mHandlerInjector.postDelayed(mResumeRunnable, 0 /* delayMillis */);
148         }
149     }
150 
151     /**
152      * Sets the resetting preference.
153      */
154     @UiThread
setResettingPreference(Preference preference)155     public void setResettingPreference(Preference preference) {
156         mResettingPreference = preference;
157     }
158 
159     /**
160      * Sets the mobile network controller.
161      */
162     @UiThread
setMobileNetworkController(NetworkMobileProviderController controller)163     public void setMobileNetworkController(NetworkMobileProviderController controller) {
164         mMobileNetworkController = controller;
165     }
166 
167     /**
168      * Sets the Wi-Fi toggle preference.
169      */
170     @UiThread
setWifiTogglePreference(Preference preference)171     public void setWifiTogglePreference(Preference preference) {
172         mWifiTogglePreferences = preference;
173     }
174 
175     /**
176      * Adds the Wi-Fi network preference.
177      */
178     @UiThread
addWifiNetworkPreference(PreferenceCategory preference)179     public void addWifiNetworkPreference(PreferenceCategory preference) {
180         if (preference != null) {
181             mWifiNetworkPreferences.add(preference);
182         }
183     }
184 
185     @UiThread
suspendPreferences()186     protected void suspendPreferences() {
187         Log.d(TAG, "Suspend the subsystem preferences");
188         if (mMobileNetworkController != null) {
189             mMobileNetworkController.hidePreference(true /* hide */, true /* immediately */);
190         }
191         if (mWifiTogglePreferences != null) {
192             mWifiTogglePreferences.setVisible(false);
193         }
194         for (PreferenceCategory pref : mWifiNetworkPreferences) {
195             pref.removeAll();
196             pref.setVisible(false);
197         }
198         if (mResettingPreference != null) {
199             mResettingPreference.setVisible(true);
200         }
201     }
202 
203     @UiThread
resumePreferences()204     protected void resumePreferences() {
205         if (mIsRecoveryReady && mMobileNetworkController != null) {
206             Log.d(TAG, "Resume the Mobile Network controller");
207             mMobileNetworkController.hidePreference(false /* hide */, false /* immediately */);
208         }
209         if (mIsWifiReady && mWifiTogglePreferences != null) {
210             Log.d(TAG, "Resume the Wi-Fi preferences");
211             mWifiTogglePreferences.setVisible(true);
212             for (PreferenceCategory pref : mWifiNetworkPreferences) {
213                 pref.setVisible(true);
214             }
215         }
216         if (mIsRecoveryReady && mIsWifiReady) {
217             mHandlerInjector.removeCallbacks(mTimeoutRunnable);
218             if (mResettingPreference != null) {
219                 Log.d(TAG, "Resume the Resetting preference");
220                 mResettingPreference.setVisible(false);
221             }
222         }
223     }
224 
225     /**
226      * Restart connectivity for all requested subsystems.
227      */
228     @UiThread
restart()229     public void restart() {
230         if (!mConnectivitySubsystemsRecoveryManager.isRecoveryAvailable()) {
231             Log.e(TAG, "The connectivity subsystem is not available to restart.");
232             return;
233         }
234 
235         Log.d(TAG, "The connectivity subsystem is restarting for recovery.");
236         suspendPreferences();
237         mIsRecoveryReady = false;
238         mIsWifiReady = !mWifiManager.isWifiEnabled();
239         mHandlerInjector.postDelayed(mTimeoutRunnable, RESTART_TIMEOUT_MS);
240         mConnectivitySubsystemsRecoveryManager.triggerSubsystemRestart(null /* reason */, this);
241     }
242 
243     /**
244      * Wrapper for testing compatibility.
245      */
246     @VisibleForTesting
247     static class HandlerInjector {
248         protected final Handler mHandler;
249 
HandlerInjector(Context context)250         HandlerInjector(Context context) {
251             mHandler = context.getMainThreadHandler();
252         }
253 
postDelayed(Runnable runnable, long delayMillis)254         public void postDelayed(Runnable runnable, long delayMillis) {
255             mHandler.postDelayed(runnable, delayMillis);
256         }
257 
removeCallbacks(Runnable runnable)258         public void removeCallbacks(Runnable runnable) {
259             mHandler.removeCallbacks(runnable);
260         }
261     }
262 }
263