1 /* 2 * Copyright (C) 2020 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.settingslib.connectivity; 18 19 import android.annotation.NonNull; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.content.pm.PackageManager; 25 import android.net.wifi.WifiManager; 26 import android.net.wifi.WifiManager.SubsystemRestartTrackingCallback; 27 import android.os.Handler; 28 import android.os.HandlerExecutor; 29 import android.provider.Settings; 30 import android.telephony.TelephonyCallback; 31 import android.telephony.TelephonyManager; 32 import android.util.Log; 33 34 /** 35 * An interface class to manage connectivity subsystem recovery/restart operations. 36 */ 37 public class ConnectivitySubsystemsRecoveryManager { 38 private static final String TAG = "ConnectivitySubsystemsRecoveryManager"; 39 40 private final Context mContext; 41 private final Handler mHandler; 42 private RecoveryAvailableListener mRecoveryAvailableListener = null; 43 44 private static final long RESTART_TIMEOUT_MS = 15_000; // 15 seconds 45 46 private WifiManager mWifiManager = null; 47 private TelephonyManager mTelephonyManager = null; 48 private final BroadcastReceiver mApmMonitor = new BroadcastReceiver() { 49 @Override 50 public void onReceive(Context context, Intent intent) { 51 RecoveryAvailableListener listener = mRecoveryAvailableListener; 52 if (listener != null) { 53 listener.onRecoveryAvailableChangeListener(isRecoveryAvailable()); 54 } 55 } 56 }; 57 private boolean mApmMonitorRegistered = false; 58 private boolean mWifiRestartInProgress = false; 59 private boolean mTelephonyRestartInProgress = false; 60 private RecoveryStatusCallback mCurrentRecoveryCallback = null; 61 private final SubsystemRestartTrackingCallback mWifiSubsystemRestartTrackingCallback = 62 new SubsystemRestartTrackingCallback() { 63 @Override 64 public void onSubsystemRestarting() { 65 // going to do nothing on this - already assuming that subsystem is restarting 66 } 67 68 @Override 69 public void onSubsystemRestarted() { 70 mWifiRestartInProgress = false; 71 stopTrackingWifiRestart(); 72 checkIfAllSubsystemsRestartsAreDone(); 73 } 74 }; 75 private final MobileTelephonyCallback mTelephonyCallback = new MobileTelephonyCallback(); 76 77 private class MobileTelephonyCallback extends TelephonyCallback implements 78 TelephonyCallback.RadioPowerStateListener { 79 @Override onRadioPowerStateChanged(int state)80 public void onRadioPowerStateChanged(int state) { 81 if (!mTelephonyRestartInProgress || mCurrentRecoveryCallback == null) { 82 stopTrackingTelephonyRestart(); 83 } 84 85 if (state == TelephonyManager.RADIO_POWER_ON) { 86 mTelephonyRestartInProgress = false; 87 stopTrackingTelephonyRestart(); 88 checkIfAllSubsystemsRestartsAreDone(); 89 } 90 } 91 } 92 ConnectivitySubsystemsRecoveryManager(@onNull Context context, @NonNull Handler handler)93 public ConnectivitySubsystemsRecoveryManager(@NonNull Context context, 94 @NonNull Handler handler) { 95 mContext = context; 96 mHandler = new Handler(handler.getLooper()); 97 98 if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) { 99 mWifiManager = mContext.getSystemService(WifiManager.class); 100 if (mWifiManager == null) { 101 Log.e(TAG, "WifiManager not available!?"); 102 } 103 } 104 105 if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { 106 mTelephonyManager = mContext.getSystemService(TelephonyManager.class); 107 if (mTelephonyManager == null) { 108 Log.e(TAG, "TelephonyManager not available!?"); 109 } 110 } 111 } 112 113 /** 114 * A listener which indicates to the caller whether a recovery operation is available across 115 * the specified technologies. 116 * 117 * Set using {@link #setRecoveryAvailableListener(RecoveryAvailableListener)}, cleared 118 * using {@link #clearRecoveryAvailableListener()}. 119 */ 120 public interface RecoveryAvailableListener { 121 /** 122 * Called whenever the recovery availability status changes. 123 * 124 * @param isAvailable True if recovery is available across ANY of the requested 125 * technologies, false if recovery is not available across ALL of the 126 * requested technologies. 127 */ onRecoveryAvailableChangeListener(boolean isAvailable)128 void onRecoveryAvailableChangeListener(boolean isAvailable); 129 } 130 131 /** 132 * Set a {@link RecoveryAvailableListener} to listen to changes in the recovery availability 133 * operation for the specified technology(ies). 134 * 135 * @param listener Listener to be triggered 136 */ setRecoveryAvailableListener(@onNull RecoveryAvailableListener listener)137 public void setRecoveryAvailableListener(@NonNull RecoveryAvailableListener listener) { 138 mHandler.post(() -> { 139 mRecoveryAvailableListener = listener; 140 startTrackingRecoveryAvailability(); 141 }); 142 } 143 144 /** 145 * Clear a listener set with 146 * {@link #setRecoveryAvailableListener(RecoveryAvailableListener)}. 147 */ clearRecoveryAvailableListener()148 public void clearRecoveryAvailableListener() { 149 mHandler.post(() -> { 150 mRecoveryAvailableListener = null; 151 stopTrackingRecoveryAvailability(); 152 }); 153 } 154 isApmEnabled()155 private boolean isApmEnabled() { 156 return Settings.Global.getInt(mContext.getContentResolver(), 157 Settings.Global.AIRPLANE_MODE_ON, 0) == 1; 158 } 159 isWifiEnabled()160 private boolean isWifiEnabled() { 161 // TODO: this doesn't consider the scan-only mode. I.e. WiFi is "disabled" while location 162 // mode is enabled. Probably need to reset WiFi in that state as well. Though this may 163 // appear strange to the user in that they've actually disabled WiFi. 164 return mWifiManager != null && (mWifiManager.isWifiEnabled() 165 || mWifiManager.isWifiApEnabled()); 166 } 167 168 /** 169 * Provide an indication as to whether subsystem recovery is "available" - i.e. will be 170 * executed if triggered via {@link #triggerSubsystemRestart(String, RecoveryStatusCallback)}. 171 * 172 * @return true if a subsystem recovery is available, false otherwise. 173 */ isRecoveryAvailable()174 public boolean isRecoveryAvailable() { 175 if (!isApmEnabled()) return true; 176 177 // even if APM is enabled we may still have recovery potential if WiFi is enabled 178 return isWifiEnabled(); 179 } 180 startTrackingRecoveryAvailability()181 private void startTrackingRecoveryAvailability() { 182 if (mApmMonitorRegistered) return; 183 184 mContext.registerReceiver(mApmMonitor, 185 new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED), null, mHandler); 186 mApmMonitorRegistered = true; 187 } 188 stopTrackingRecoveryAvailability()189 private void stopTrackingRecoveryAvailability() { 190 if (!mApmMonitorRegistered) return; 191 192 mContext.unregisterReceiver(mApmMonitor); 193 mApmMonitorRegistered = false; 194 } 195 startTrackingWifiRestart()196 private void startTrackingWifiRestart() { 197 mWifiManager.registerSubsystemRestartTrackingCallback(new HandlerExecutor(mHandler), 198 mWifiSubsystemRestartTrackingCallback); 199 } 200 stopTrackingWifiRestart()201 private void stopTrackingWifiRestart() { 202 mWifiManager.unregisterSubsystemRestartTrackingCallback( 203 mWifiSubsystemRestartTrackingCallback); 204 } 205 startTrackingTelephonyRestart()206 private void startTrackingTelephonyRestart() { 207 mTelephonyManager.registerTelephonyCallback(new HandlerExecutor(mHandler), 208 mTelephonyCallback); 209 } 210 stopTrackingTelephonyRestart()211 private void stopTrackingTelephonyRestart() { 212 mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback); 213 } 214 checkIfAllSubsystemsRestartsAreDone()215 private void checkIfAllSubsystemsRestartsAreDone() { 216 if (!mWifiRestartInProgress && !mTelephonyRestartInProgress 217 && mCurrentRecoveryCallback != null) { 218 mCurrentRecoveryCallback.onSubsystemRestartOperationEnd(); 219 mCurrentRecoveryCallback = null; 220 } 221 } 222 223 /** 224 * Callbacks used with 225 * {@link #triggerSubsystemRestart(String, RecoveryStatusCallback)} to get 226 * information about when recovery starts and is completed. 227 */ 228 public interface RecoveryStatusCallback { 229 /** 230 * Callback for a subsystem restart triggered via 231 * {@link #triggerSubsystemRestart(String, RecoveryStatusCallback)} - indicates 232 * that operation has started. 233 */ onSubsystemRestartOperationBegin()234 void onSubsystemRestartOperationBegin(); 235 236 /** 237 * Callback for a subsystem restart triggered via 238 * {@link #triggerSubsystemRestart(String, RecoveryStatusCallback)} - indicates 239 * that operation has ended. Note that subsystems may still take some time to come up to 240 * full functionality. 241 */ onSubsystemRestartOperationEnd()242 void onSubsystemRestartOperationEnd(); 243 } 244 245 /** 246 * Trigger connectivity recovery for all requested technologies. 247 * 248 * @param reason An optional reason code to pass through to the technology-specific 249 * API. May be used to trigger a bug report. 250 * @param callback Callbacks triggered when recovery status changes. 251 */ triggerSubsystemRestart(String reason, @NonNull RecoveryStatusCallback callback)252 public void triggerSubsystemRestart(String reason, @NonNull RecoveryStatusCallback callback) { 253 // TODO: b/183530649 : clean-up or make use of the `reason` argument 254 mHandler.post(() -> { 255 boolean someSubsystemRestarted = false; 256 257 if (mWifiRestartInProgress) { 258 Log.e(TAG, "Wifi restart still in progress"); 259 return; 260 } 261 262 if (mTelephonyRestartInProgress) { 263 Log.e(TAG, "Telephony restart still in progress"); 264 return; 265 } 266 267 if (isWifiEnabled()) { 268 mWifiManager.restartWifiSubsystem(); 269 mWifiRestartInProgress = true; 270 someSubsystemRestarted = true; 271 startTrackingWifiRestart(); 272 } 273 274 if (mTelephonyManager != null && !isApmEnabled()) { 275 if (mTelephonyManager.rebootRadio()) { 276 mTelephonyRestartInProgress = true; 277 someSubsystemRestarted = true; 278 startTrackingTelephonyRestart(); 279 } 280 } 281 282 if (someSubsystemRestarted) { 283 mCurrentRecoveryCallback = callback; 284 callback.onSubsystemRestartOperationBegin(); 285 286 mHandler.postDelayed(() -> { 287 stopTrackingWifiRestart(); 288 stopTrackingTelephonyRestart(); 289 mWifiRestartInProgress = false; 290 mTelephonyRestartInProgress = false; 291 if (mCurrentRecoveryCallback != null) { 292 mCurrentRecoveryCallback.onSubsystemRestartOperationEnd(); 293 mCurrentRecoveryCallback = null; 294 } 295 }, RESTART_TIMEOUT_MS); 296 } 297 }); 298 } 299 } 300 301