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