blob: c61f8a9c3b534bedd9135894928ea43be4d78a4d [file] [log] [blame]
/*
* 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.net.wifi.WifiManager.SubsystemRestartTrackingCallback;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.provider.Settings;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
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 SubsystemRestartTrackingCallback mWifiSubsystemRestartTrackingCallback =
new SubsystemRestartTrackingCallback() {
@Override
public void onSubsystemRestarting() {
// going to do nothing on this - already assuming that subsystem is restarting
}
@Override
public void onSubsystemRestarted() {
mWifiRestartInProgress = false;
stopTrackingWifiRestart();
checkIfAllSubsystemsRestartsAreDone();
}
};
private final MobileTelephonyCallback mTelephonyCallback = new MobileTelephonyCallback();
private class MobileTelephonyCallback extends TelephonyCallback implements
TelephonyCallback.RadioPowerStateListener {
@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() {
mWifiManager.registerSubsystemRestartTrackingCallback(new HandlerExecutor(mHandler),
mWifiSubsystemRestartTrackingCallback);
}
private void stopTrackingWifiRestart() {
mWifiManager.unregisterSubsystemRestartTrackingCallback(
mWifiSubsystemRestartTrackingCallback);
}
private void startTrackingTelephonyRestart() {
mTelephonyManager.registerTelephonyCallback(new HandlerExecutor(mHandler),
mTelephonyCallback);
}
private void stopTrackingTelephonyRestart() {
mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback);
}
private void checkIfAllSubsystemsRestartsAreDone() {
if (!mWifiRestartInProgress && !mTelephonyRestartInProgress
&& mCurrentRecoveryCallback != null) {
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) {
// TODO: b/183530649 : clean-up or make use of the `reason` argument
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();
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;
if (mCurrentRecoveryCallback != null) {
mCurrentRecoveryCallback.onSubsystemRestartOperationEnd();
mCurrentRecoveryCallback = null;
}
}, RESTART_TIMEOUT_MS);
}
});
}
}