[SettingsLib] Wrapper for the "fix connectivity" Settings action

Provide a shim API for WiFi and Telephony subsystem restart operations.

Bug: 175084231
Test: TBD - pending UI test integration
Change-Id: Ie016937e0c6768716958b3a647522e7e7a3f6df5
This commit is contained in:
Etan Cohen
2020-12-07 21:57:20 -08:00
parent 565151c564
commit a84603182b

View File

@@ -0,0 +1,310 @@
/*
* 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.os.Handler;
import android.os.HandlerExecutor;
import android.provider.Settings;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
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 BroadcastReceiver mWifiMonitor = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!mWifiRestartInProgress || mCurrentRecoveryCallback == null) {
stopTrackingWifiRestart();
}
// TODO: harden this code to avoid race condition. What if WiFi toggled just before
// recovery triggered. Either use new broadcasts from framework or detect more state
// changes.
boolean recoveryDone = false;
if (TextUtils.equals(intent.getAction(), WifiManager.WIFI_STATE_CHANGED_ACTION)) {
if (intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED) {
recoveryDone = true;
}
} else if (TextUtils.equals(intent.getAction(),
WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
if (intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE,
WifiManager.WIFI_AP_STATE_FAILED) == WifiManager.WIFI_AP_STATE_ENABLED) {
recoveryDone = true;
}
}
if (recoveryDone) {
mWifiRestartInProgress = false;
stopTrackingWifiRestart();
checkIfAllSubsystemsRestartsAreDone();
}
}
};
private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
@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() {
IntentFilter filter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
mContext.registerReceiver(mWifiMonitor, filter, null, mHandler);
}
private void stopTrackingWifiRestart() {
mContext.unregisterReceiver(mWifiMonitor);
}
private void startTrackingTelephonyRestart() {
mTelephonyManager.registerPhoneStateListener(new HandlerExecutor(mHandler),
mPhoneStateListener);
}
private void stopTrackingTelephonyRestart() {
mTelephonyManager.unregisterPhoneStateListener(mPhoneStateListener);
}
private void checkIfAllSubsystemsRestartsAreDone() {
if (!mWifiRestartInProgress && !mTelephonyRestartInProgress) {
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) {
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(reason);
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;
mCurrentRecoveryCallback.onSubsystemRestartOperationEnd();
mCurrentRecoveryCallback = null;
}, RESTART_TIMEOUT_MS);
}
});
}
}