| /* |
| * 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); |
| } |
| }); |
| } |
| } |
| |