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.util.Log; 25 26 import androidx.annotation.VisibleForTesting; 27 import androidx.lifecycle.Lifecycle; 28 import androidx.lifecycle.LifecycleObserver; 29 import androidx.lifecycle.OnLifecycleEvent; 30 import androidx.preference.Preference; 31 import androidx.preference.PreferenceCategory; 32 33 import com.android.settingslib.connectivity.ConnectivitySubsystemsRecoveryManager; 34 import com.android.settingslib.utils.HandlerInjector; 35 36 import java.lang.ref.WeakReference; 37 import java.util.ArrayList; 38 import java.util.List; 39 40 /** 41 * Helper class to restart connectivity for all requested subsystems. 42 */ 43 public class InternetResetHelper implements LifecycleObserver { 44 45 protected static final String TAG = "InternetResetHelper"; 46 public static final long RESTART_TIMEOUT_MS = 15_000; // 15 seconds 47 48 protected final Context mContext; 49 protected Preference mResettingPreference; 50 protected NetworkMobileProviderController mMobileNetworkController; 51 protected Preference mWifiTogglePreferences; 52 protected List<PreferenceCategory> mWifiNetworkPreferences = 53 new ArrayList<PreferenceCategory>(); 54 55 protected final WifiManager mWifiManager; 56 protected final IntentFilter mWifiStateFilter; 57 protected final BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() { 58 @Override 59 public void onReceive(Context context, Intent intent) { 60 updateWifiStateChange(); 61 } 62 }; 63 64 protected RecoveryWorker mRecoveryWorker; 65 protected boolean mIsWifiReady = true; 66 protected HandlerInjector mHandlerInjector; 67 protected final Runnable mTimeoutRunnable = () -> { 68 Log.w(TAG, "Resume preferences due to connectivity subsystems recovery timed out."); 69 mRecoveryWorker.clearRecovering(); 70 mIsWifiReady = true; 71 resumePreferences(); 72 }; 73 InternetResetHelper(Context context, Lifecycle lifecycle, NetworkMobileProviderController mobileNetworkController, Preference wifiTogglePreferences, PreferenceCategory connectedWifiEntryPreferenceCategory, PreferenceCategory firstWifiEntryPreferenceCategory, PreferenceCategory wifiEntryPreferenceCategory, Preference resettingPreference)74 public InternetResetHelper(Context context, Lifecycle lifecycle, 75 NetworkMobileProviderController mobileNetworkController, 76 Preference wifiTogglePreferences, 77 PreferenceCategory connectedWifiEntryPreferenceCategory, 78 PreferenceCategory firstWifiEntryPreferenceCategory, 79 PreferenceCategory wifiEntryPreferenceCategory, 80 Preference resettingPreference) { 81 mContext = context; 82 mMobileNetworkController = mobileNetworkController; 83 mWifiTogglePreferences = wifiTogglePreferences; 84 mWifiNetworkPreferences.add(connectedWifiEntryPreferenceCategory); 85 mWifiNetworkPreferences.add(firstWifiEntryPreferenceCategory); 86 mWifiNetworkPreferences.add(wifiEntryPreferenceCategory); 87 mResettingPreference = resettingPreference; 88 89 mHandlerInjector = new HandlerInjector(context.getMainThreadHandler()); 90 mWifiManager = mContext.getSystemService(WifiManager.class); 91 mWifiStateFilter = new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION); 92 mRecoveryWorker = RecoveryWorker.getInstance(mContext, this); 93 94 if (lifecycle != null) { 95 lifecycle.addObserver(this); 96 } 97 } 98 99 /** @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) */ 100 @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) onResume()101 public void onResume() { 102 mContext.registerReceiver(mWifiStateReceiver, mWifiStateFilter, 103 Context.RECEIVER_EXPORTED_UNAUDITED); 104 } 105 106 /** @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) */ 107 @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) onPause()108 public void onPause() { 109 mContext.unregisterReceiver(mWifiStateReceiver); 110 } 111 112 /** @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) */ 113 @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) onDestroy()114 public void onDestroy() { 115 mHandlerInjector.removeCallbacks(mTimeoutRunnable); 116 } 117 118 @VisibleForTesting updateWifiStateChange()119 protected void updateWifiStateChange() { 120 if (!mIsWifiReady && mWifiManager.isWifiEnabled()) { 121 Log.d(TAG, "The Wi-Fi subsystem is done for recovery."); 122 mIsWifiReady = true; 123 resumePreferences(); 124 } 125 } 126 suspendPreferences()127 protected void suspendPreferences() { 128 Log.d(TAG, "Suspend the subsystem preferences"); 129 if (mMobileNetworkController != null) { 130 mMobileNetworkController.hidePreference(true /* hide */, true /* immediately */); 131 } 132 if (mWifiTogglePreferences != null) { 133 mWifiTogglePreferences.setVisible(false); 134 } 135 for (PreferenceCategory pref : mWifiNetworkPreferences) { 136 pref.removeAll(); 137 pref.setVisible(false); 138 } 139 if (mResettingPreference != null) { 140 mResettingPreference.setVisible(true); 141 } 142 } 143 resumePreferences()144 protected void resumePreferences() { 145 boolean isRecoveryReady = !mRecoveryWorker.isRecovering(); 146 if (isRecoveryReady && mMobileNetworkController != null) { 147 Log.d(TAG, "Resume the Mobile Network controller"); 148 mMobileNetworkController.hidePreference(false /* hide */, true /* immediately */); 149 } 150 if (mIsWifiReady && mWifiTogglePreferences != null) { 151 Log.d(TAG, "Resume the Wi-Fi preferences"); 152 mWifiTogglePreferences.setVisible(true); 153 for (PreferenceCategory pref : mWifiNetworkPreferences) { 154 pref.setVisible(true); 155 } 156 } 157 if (isRecoveryReady && mIsWifiReady) { 158 mHandlerInjector.removeCallbacks(mTimeoutRunnable); 159 if (mResettingPreference != null) { 160 Log.d(TAG, "Resume the Resetting preference"); 161 mResettingPreference.setVisible(false); 162 } 163 } 164 } 165 showResettingAndSendTimeoutChecks()166 protected void showResettingAndSendTimeoutChecks() { 167 suspendPreferences(); 168 mHandlerInjector.postDelayed(mTimeoutRunnable, RESTART_TIMEOUT_MS); 169 } 170 171 /** Restart connectivity for all requested subsystems. */ restart()172 public void restart() { 173 if (!mRecoveryWorker.isRecoveryAvailable()) { 174 Log.e(TAG, "The connectivity subsystem is not available to restart."); 175 return; 176 } 177 showResettingAndSendTimeoutChecks(); 178 mIsWifiReady = !mWifiManager.isWifiEnabled(); 179 mRecoveryWorker.triggerRestart(); 180 } 181 182 /** Check if the connectivity subsystem is under recovering. */ checkRecovering()183 public void checkRecovering() { 184 if (!mRecoveryWorker.isRecovering()) return; 185 mIsWifiReady = false; 186 showResettingAndSendTimeoutChecks(); 187 } 188 189 /** 190 * This is a singleton class for ConnectivitySubsystemsRecoveryManager worker. 191 */ 192 @VisibleForTesting 193 public static class RecoveryWorker implements 194 ConnectivitySubsystemsRecoveryManager.RecoveryStatusCallback { 195 private static final String TAG = "RecoveryWorker"; 196 private static RecoveryWorker sInstance; 197 private static WeakReference<InternetResetHelper> sCallback; 198 private static ConnectivitySubsystemsRecoveryManager sRecoveryManager; 199 private static boolean sIsRecovering; 200 201 /** 202 * Create a singleton class for ConnectivitySubsystemsRecoveryManager. 203 * 204 * @param context The context to use for the content resolver. 205 * @param callback The callback of {@link InternetResetHelper} object. 206 * @return an instance of {@link RecoveryWorker} object. 207 */ getInstance(Context context, InternetResetHelper callback)208 public static RecoveryWorker getInstance(Context context, InternetResetHelper callback) { 209 sCallback = new WeakReference<>(callback); 210 if (sInstance != null) return sInstance; 211 212 sInstance = new RecoveryWorker(); 213 Context appContext = context.getApplicationContext(); 214 sRecoveryManager = new ConnectivitySubsystemsRecoveryManager(appContext, 215 appContext.getMainThreadHandler()); 216 return sInstance; 217 } 218 219 /** Returns true, If the subsystem service is recovering. */ isRecovering()220 public boolean isRecovering() { 221 return sIsRecovering; 222 } 223 224 /** Clear the recovering flag. */ clearRecovering()225 public void clearRecovering() { 226 sIsRecovering = false; 227 } 228 229 /** Returns true, If the subsystem service is recovery available. */ isRecoveryAvailable()230 public boolean isRecoveryAvailable() { 231 return sRecoveryManager.isRecoveryAvailable(); 232 } 233 234 /** Trigger connectivity recovery for all requested technologies. */ triggerRestart()235 public boolean triggerRestart() { 236 if (!isRecoveryAvailable()) { 237 Log.e(TAG, "The connectivity subsystem is not available to restart."); 238 return false; 239 } 240 sIsRecovering = true; 241 sRecoveryManager.triggerSubsystemRestart(null /* reason */, sInstance); 242 Log.d(TAG, "The connectivity subsystem is restarting for recovery."); 243 return true; 244 } 245 246 @Override onSubsystemRestartOperationBegin()247 public void onSubsystemRestartOperationBegin() { 248 Log.d(TAG, "The connectivity subsystem is starting for recovery."); 249 sIsRecovering = true; 250 } 251 252 @Override onSubsystemRestartOperationEnd()253 public void onSubsystemRestartOperationEnd() { 254 Log.d(TAG, "The connectivity subsystem is done for recovery."); 255 sIsRecovering = false; 256 InternetResetHelper callback = sCallback.get(); 257 if (callback == null) return; 258 callback.resumePreferences(); 259 } 260 } 261 } 262