/* * Copyright (C) 2021 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.settings.network; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.wifi.WifiManager; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.text.TextUtils; import android.util.Log; import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.OnLifecycleEvent; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import com.android.settingslib.connectivity.ConnectivitySubsystemsRecoveryManager; import java.util.ArrayList; import java.util.List; /** * Helper class to restart connectivity for all requested subsystems. */ public class InternetResetHelper implements LifecycleObserver, ConnectivitySubsystemsRecoveryManager.RecoveryStatusCallback { protected static final String TAG = "InternetResetHelper"; public static final long RESTART_TIMEOUT_MS = 15_000; // 15 seconds protected final Context mContext; protected Preference mResettingPreference; protected NetworkMobileProviderController mMobileNetworkController; protected Preference mWifiTogglePreferences; protected List mWifiNetworkPreferences = new ArrayList(); protected final WifiManager mWifiManager; protected final IntentFilter mWifiStateFilter; protected final BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() { @Override @WorkerThread public void onReceive(Context context, Intent intent) { if (intent != null && TextUtils.equals(intent.getAction(), WifiManager.NETWORK_STATE_CHANGED_ACTION)) { updateWifiStateChange(); } } }; protected ConnectivitySubsystemsRecoveryManager mConnectivitySubsystemsRecoveryManager; protected HandlerThread mWorkerThread; protected boolean mIsRecoveryReady; protected boolean mIsWifiReady; protected HandlerInjector mHandlerInjector; protected final Runnable mResumeRunnable = () -> { resumePreferences(); }; protected final Runnable mTimeoutRunnable = () -> { mIsRecoveryReady = true; mIsWifiReady = true; resumePreferences(); }; public InternetResetHelper(Context context, Lifecycle lifecycle) { mContext = context; mHandlerInjector = new HandlerInjector(context); mWifiManager = mContext.getSystemService(WifiManager.class); mWifiStateFilter = new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION); mWorkerThread = new HandlerThread(TAG + "{" + Integer.toHexString(System.identityHashCode(this)) + "}", Process.THREAD_PRIORITY_BACKGROUND); mWorkerThread.start(); mConnectivitySubsystemsRecoveryManager = new ConnectivitySubsystemsRecoveryManager( mContext, mWorkerThread.getThreadHandler()); if (lifecycle != null) { lifecycle.addObserver(this); } } /** @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) */ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void onResume() { mContext.registerReceiver(mWifiStateReceiver, mWifiStateFilter); } /** @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) */ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) public void onPause() { mContext.unregisterReceiver(mWifiStateReceiver); } /** @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) */ @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) public void onDestroy() { mHandlerInjector.removeCallbacks(mResumeRunnable); mHandlerInjector.removeCallbacks(mTimeoutRunnable); mWorkerThread.quit(); } @Override @WorkerThread public void onSubsystemRestartOperationBegin() { Log.d(TAG, "The connectivity subsystem is starting for recovery."); } @Override @WorkerThread public void onSubsystemRestartOperationEnd() { Log.d(TAG, "The connectivity subsystem is done for recovery."); if (!mIsRecoveryReady) { mIsRecoveryReady = true; mHandlerInjector.postDelayed(mResumeRunnable, 0 /* delayMillis */); } } @VisibleForTesting @WorkerThread protected void updateWifiStateChange() { if (!mIsWifiReady && mWifiManager.isWifiEnabled()) { Log.d(TAG, "The Wi-Fi subsystem is done for recovery."); mIsWifiReady = true; mHandlerInjector.postDelayed(mResumeRunnable, 0 /* delayMillis */); } } /** * Sets the resetting preference. */ @UiThread public void setResettingPreference(Preference preference) { mResettingPreference = preference; } /** * Sets the mobile network controller. */ @UiThread public void setMobileNetworkController(NetworkMobileProviderController controller) { mMobileNetworkController = controller; } /** * Sets the Wi-Fi toggle preference. */ @UiThread public void setWifiTogglePreference(Preference preference) { mWifiTogglePreferences = preference; } /** * Adds the Wi-Fi network preference. */ @UiThread public void addWifiNetworkPreference(PreferenceCategory preference) { if (preference != null) { mWifiNetworkPreferences.add(preference); } } @UiThread protected void suspendPreferences() { Log.d(TAG, "Suspend the subsystem preferences"); if (mMobileNetworkController != null) { mMobileNetworkController.hidePreference(true /* hide */, true /* immediately */); } if (mWifiTogglePreferences != null) { mWifiTogglePreferences.setVisible(false); } for (PreferenceCategory pref : mWifiNetworkPreferences) { pref.removeAll(); pref.setVisible(false); } if (mResettingPreference != null) { mResettingPreference.setVisible(true); } } @UiThread protected void resumePreferences() { if (mIsRecoveryReady && mMobileNetworkController != null) { Log.d(TAG, "Resume the Mobile Network controller"); mMobileNetworkController.hidePreference(false /* hide */, false /* immediately */); } if (mIsWifiReady && mWifiTogglePreferences != null) { Log.d(TAG, "Resume the Wi-Fi preferences"); mWifiTogglePreferences.setVisible(true); for (PreferenceCategory pref : mWifiNetworkPreferences) { pref.setVisible(true); } } if (mIsRecoveryReady && mIsWifiReady) { mHandlerInjector.removeCallbacks(mTimeoutRunnable); if (mResettingPreference != null) { Log.d(TAG, "Resume the Resetting preference"); mResettingPreference.setVisible(false); } } } /** * Restart connectivity for all requested subsystems. */ @UiThread public void restart() { if (!mConnectivitySubsystemsRecoveryManager.isRecoveryAvailable()) { Log.e(TAG, "The connectivity subsystem is not available to restart."); return; } Log.d(TAG, "The connectivity subsystem is restarting for recovery."); suspendPreferences(); mIsRecoveryReady = false; mIsWifiReady = !mWifiManager.isWifiEnabled(); mHandlerInjector.postDelayed(mTimeoutRunnable, RESTART_TIMEOUT_MS); mConnectivitySubsystemsRecoveryManager.triggerSubsystemRestart(null /* reason */, this); } /** * Wrapper for testing compatibility. */ @VisibleForTesting static class HandlerInjector { protected final Handler mHandler; HandlerInjector(Context context) { mHandler = context.getMainThreadHandler(); } public void postDelayed(Runnable runnable, long delayMillis) { mHandler.postDelayed(runnable, delayMillis); } public void removeCallbacks(Runnable runnable) { mHandler.removeCallbacks(runnable); } } }