1 /* 2 * Copyright (C) 2013 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.server.wifi; 18 19 import android.app.ActivityManager; 20 import android.app.Notification; 21 import android.app.PendingIntent; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.graphics.drawable.Icon; 26 import android.net.Uri; 27 import android.net.wifi.WifiContext; 28 import android.provider.Settings; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; 32 import com.android.wifi.resources.R; 33 34 import java.io.FileDescriptor; 35 import java.io.PrintWriter; 36 37 /* Tracks persisted settings for Wi-Fi and airplane mode interaction */ 38 public class WifiSettingsStore { 39 /* Values used to track the current state of Wi-Fi 40 * Key: Settings.Global.WIFI_ON 41 * Values: 42 * WIFI_DISABLED 43 * WIFI_ENABLED 44 * WIFI_ENABLED_APM_OVERRIDE 45 * WIFI_DISABLED_APM_ON 46 */ 47 @VisibleForTesting 48 public static final int WIFI_DISABLED = 0; 49 @VisibleForTesting 50 public static final int WIFI_ENABLED = 1; 51 /* Wifi enabled while in airplane mode */ 52 @VisibleForTesting 53 public static final int WIFI_ENABLED_APM_OVERRIDE = 2; 54 /* Wifi disabled due to airplane mode on */ 55 @VisibleForTesting 56 public static final int WIFI_DISABLED_APM_ON = 3; 57 58 /* Values used to track the current state of airplane mode 59 * Key: Settings.Global.AIRPLANE_MODE_ON 60 * Values: 61 * APM_DISABLED 62 * APM_ENABLED 63 */ 64 @VisibleForTesting 65 public static final int APM_DISABLED = 0; 66 @VisibleForTesting 67 public static final int APM_ENABLED = 1; 68 69 /* Values used to track whether Wi-Fi should remain on in airplane mode 70 * Key: Settings.Secure WIFI_APM_STATE 71 * Values: 72 * WIFI_TURNS_OFF_IN_APM 73 * WIFI_REMAINS_ON_IN_APM 74 */ 75 @VisibleForTesting 76 public static final String WIFI_APM_STATE = "wifi_apm_state"; 77 @VisibleForTesting 78 public static final int WIFI_TURNS_OFF_IN_APM = 0; 79 @VisibleForTesting 80 public static final int WIFI_REMAINS_ON_IN_APM = 1; 81 82 /* Values used to track whether Bluetooth should remain on in airplane mode 83 * Key: Settings.Secure BLUETOOTH_APM_STATE 84 * Values: 85 * BT_TURNS_OFF_IN_APM 86 * BT_REMAINS_ON_IN_APM 87 */ 88 @VisibleForTesting 89 public static final String BLUETOOTH_APM_STATE = "bluetooth_apm_state"; 90 @VisibleForTesting 91 public static final int BT_TURNS_OFF_IN_APM = 0; 92 @VisibleForTesting 93 public static final int BT_REMAINS_ON_IN_APM = 1; 94 95 /* Values used to track whether a notification has been shown 96 * Keys: 97 * Settings.Secure APM_WIFI_NOTIFICATION 98 * Settings.Secure APM_WIFI_ENABLED_NOTIFICATION 99 * Values: 100 * NOTIFICATION_NOT_SHOWN 101 * NOTIFICATION_SHOWN 102 */ 103 /* Track whether Wi-Fi remains on in airplane mode notification was shown */ 104 @VisibleForTesting 105 public static final String APM_WIFI_NOTIFICATION = "apm_wifi_notification"; 106 /* Track whether Wi-Fi enabled in airplane mode notification was shown */ 107 @VisibleForTesting 108 public static final String APM_WIFI_ENABLED_NOTIFICATION = "apm_wifi_enabled_notification"; 109 @VisibleForTesting 110 public static final int NOTIFICATION_NOT_SHOWN = 0; 111 @VisibleForTesting 112 public static final int NOTIFICATION_SHOWN = 1; 113 114 /** 115 * @hide constant copied from {@link Settings.Global} 116 * TODO(b/274636414): Migrate to official API in Android V. 117 */ 118 static final String SETTINGS_SATELLITE_MODE_RADIOS = "satellite_mode_radios"; 119 /** 120 * @hide constant copied from {@link Settings.Global} 121 * TODO(b/274636414): Migrate to official API in Android V. 122 */ 123 static final String SETTINGS_SATELLITE_MODE_ENABLED = "satellite_mode_enabled"; 124 125 /* Persisted state that tracks the wifi & airplane interaction from settings */ 126 private int mPersistWifiState = WIFI_DISABLED; 127 128 /* Tracks current airplane mode state */ 129 private boolean mAirplaneModeOn = false; 130 131 /* Tracks the wifi state before entering airplane mode*/ 132 private boolean mIsWifiOnBeforeEnteringApm = false; 133 134 /* Tracks the wifi state after entering airplane mode*/ 135 private boolean mIsWifiOnAfterEnteringApm = false; 136 137 /* Tracks whether user toggled wifi in airplane mode */ 138 private boolean mUserToggledWifiDuringApm = false; 139 140 /* Tracks whether user toggled wifi within one minute of entering airplane mode */ 141 private boolean mUserToggledWifiAfterEnteringApmWithinMinute = false; 142 143 /* Tracks when airplane mode has been enabled in milliseconds since boot */ 144 private long mApmEnabledTimeSinceBootMillis = 0; 145 146 private final String mApmEnhancementHelpLink; 147 private final WifiContext mContext; 148 private final WifiSettingsConfigStore mSettingsConfigStore; 149 private final WifiThreadRunner mWifiThreadRunner; 150 private final FrameworkFacade mFrameworkFacade; 151 private final WifiNotificationManager mNotificationManager; 152 private final DeviceConfigFacade mDeviceConfigFacade; 153 private final WifiMetrics mWifiMetrics; 154 private final Clock mClock; 155 private boolean mSatelliteModeOn; 156 WifiSettingsStore(WifiContext context, WifiSettingsConfigStore sharedPreferences, WifiThreadRunner wifiThread, FrameworkFacade frameworkFacade, WifiNotificationManager notificationManager, DeviceConfigFacade deviceConfigFacade, WifiMetrics wifiMetrics, Clock clock)157 WifiSettingsStore(WifiContext context, WifiSettingsConfigStore sharedPreferences, 158 WifiThreadRunner wifiThread, FrameworkFacade frameworkFacade, 159 WifiNotificationManager notificationManager, DeviceConfigFacade deviceConfigFacade, 160 WifiMetrics wifiMetrics, Clock clock) { 161 mContext = context; 162 mSettingsConfigStore = sharedPreferences; 163 mWifiThreadRunner = wifiThread; 164 mFrameworkFacade = frameworkFacade; 165 mNotificationManager = notificationManager; 166 mDeviceConfigFacade = deviceConfigFacade; 167 mWifiMetrics = wifiMetrics; 168 mClock = clock; 169 mAirplaneModeOn = getPersistedAirplaneModeOn(); 170 mPersistWifiState = getPersistedWifiState(); 171 mApmEnhancementHelpLink = mContext.getString(R.string.config_wifiApmEnhancementHelpLink); 172 mSatelliteModeOn = getPersistedSatelliteModeOn(); 173 } 174 getUserSecureIntegerSetting(String name, int def)175 private int getUserSecureIntegerSetting(String name, int def) { 176 Context userContext = mFrameworkFacade.getUserContext(mContext); 177 return mFrameworkFacade.getSecureIntegerSetting(userContext, name, def); 178 } 179 setUserSecureIntegerSetting(String name, int value)180 private void setUserSecureIntegerSetting(String name, int value) { 181 Context userContext = mFrameworkFacade.getUserContext(mContext); 182 mFrameworkFacade.setSecureIntegerSetting(userContext, name, value); 183 } 184 isWifiToggleEnabled()185 public synchronized boolean isWifiToggleEnabled() { 186 return mPersistWifiState == WIFI_ENABLED 187 || mPersistWifiState == WIFI_ENABLED_APM_OVERRIDE; 188 } 189 190 /** 191 * Returns true if airplane mode is currently on. 192 * @return {@code true} if airplane mode is on. 193 */ isAirplaneModeOn()194 public synchronized boolean isAirplaneModeOn() { 195 return mAirplaneModeOn; 196 } 197 isScanAlwaysAvailableToggleEnabled()198 public synchronized boolean isScanAlwaysAvailableToggleEnabled() { 199 return getPersistedScanAlwaysAvailable(); 200 } 201 isScanAlwaysAvailable()202 public synchronized boolean isScanAlwaysAvailable() { 203 return !mAirplaneModeOn && getPersistedScanAlwaysAvailable(); 204 } 205 isWifiScoringEnabled()206 public synchronized boolean isWifiScoringEnabled() { 207 return getPersistedWifiScoringEnabled(); 208 } 209 isWifiPasspointEnabled()210 public synchronized boolean isWifiPasspointEnabled() { 211 return getPersistedWifiPasspointEnabled(); 212 } 213 getWifiMultiInternetMode()214 public synchronized int getWifiMultiInternetMode() { 215 return getPersistedWifiMultiInternetMode(); 216 } 217 setPersistWifiState(int state)218 public void setPersistWifiState(int state) { 219 mPersistWifiState = state; 220 } 221 showNotification(int titleId, int messageId)222 private void showNotification(int titleId, int messageId) { 223 String settingsPackage = mFrameworkFacade.getSettingsPackageName(mContext); 224 if (settingsPackage == null) return; 225 226 Intent openLinkIntent = new Intent(Intent.ACTION_VIEW) 227 .setData(Uri.parse(mApmEnhancementHelpLink)) 228 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 229 PendingIntent tapPendingIntent = mFrameworkFacade.getActivity(mContext, 0, openLinkIntent, 230 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); 231 232 String title = mContext.getResources().getString(titleId); 233 String message = mContext.getResources().getString(messageId); 234 Notification.Builder builder = mFrameworkFacade.makeNotificationBuilder(mContext, 235 WifiService.NOTIFICATION_APM_ALERTS) 236 .setAutoCancel(true) 237 .setLocalOnly(true) 238 .setContentTitle(title) 239 .setContentText(message) 240 .setContentIntent(tapPendingIntent) 241 .setVisibility(Notification.VISIBILITY_PUBLIC) 242 .setStyle(new Notification.BigTextStyle().bigText(message)) 243 .setSmallIcon(Icon.createWithResource(mContext.getWifiOverlayApkPkgName(), 244 R.drawable.ic_wifi_settings)); 245 mNotificationManager.notify(SystemMessage.NOTE_WIFI_APM_NOTIFICATION, builder.build()); 246 } 247 handleWifiToggled(boolean wifiEnabled)248 public synchronized boolean handleWifiToggled(boolean wifiEnabled) { 249 // Can Wi-Fi be toggled in airplane mode ? 250 if (mAirplaneModeOn && !isAirplaneToggleable()) { 251 return false; 252 } 253 if (wifiEnabled) { 254 if (mAirplaneModeOn) { 255 persistWifiState(WIFI_ENABLED_APM_OVERRIDE); 256 if (mDeviceConfigFacade.isApmEnhancementEnabled()) { 257 setUserSecureIntegerSetting(WIFI_APM_STATE, WIFI_REMAINS_ON_IN_APM); 258 if (getUserSecureIntegerSetting(APM_WIFI_ENABLED_NOTIFICATION, 259 NOTIFICATION_NOT_SHOWN) == NOTIFICATION_NOT_SHOWN) { 260 mWifiThreadRunner.post( 261 () -> showNotification(R.string.wifi_enabled_apm_first_time_title, 262 R.string.wifi_enabled_apm_first_time_message)); 263 setUserSecureIntegerSetting( 264 APM_WIFI_ENABLED_NOTIFICATION, NOTIFICATION_SHOWN); 265 } 266 } 267 } else { 268 persistWifiState(WIFI_ENABLED); 269 } 270 } else { 271 // When wifi state is disabled, we do not care 272 // if airplane mode is on or not. The scenario of 273 // wifi being disabled due to airplane mode being turned on 274 // is handled handleAirplaneModeToggled() 275 persistWifiState(WIFI_DISABLED); 276 if (mDeviceConfigFacade.isApmEnhancementEnabled() && mAirplaneModeOn) { 277 setUserSecureIntegerSetting(WIFI_APM_STATE, WIFI_TURNS_OFF_IN_APM); 278 } 279 } 280 if (mAirplaneModeOn) { 281 if (!mUserToggledWifiDuringApm) { 282 mUserToggledWifiAfterEnteringApmWithinMinute = 283 mClock.getElapsedSinceBootMillis() - mApmEnabledTimeSinceBootMillis 284 < 60_000; 285 } 286 mUserToggledWifiDuringApm = true; 287 } 288 return true; 289 } 290 291 synchronized boolean updateAirplaneModeTracker() { 292 // Is Wi-Fi sensitive to airplane mode changes ? 293 if (!isAirplaneSensitive()) { 294 return false; 295 } 296 297 mAirplaneModeOn = getPersistedAirplaneModeOn(); 298 return true; 299 } 300 301 synchronized void handleAirplaneModeToggled() { 302 if (mAirplaneModeOn) { 303 mApmEnabledTimeSinceBootMillis = mClock.getElapsedSinceBootMillis(); 304 mIsWifiOnBeforeEnteringApm = mPersistWifiState == WIFI_ENABLED; 305 if (mPersistWifiState == WIFI_ENABLED) { 306 if (mDeviceConfigFacade.isApmEnhancementEnabled() 307 && getUserSecureIntegerSetting(WIFI_APM_STATE, WIFI_TURNS_OFF_IN_APM) 308 == WIFI_REMAINS_ON_IN_APM) { 309 persistWifiState(WIFI_ENABLED_APM_OVERRIDE); 310 if (getUserSecureIntegerSetting(APM_WIFI_NOTIFICATION, NOTIFICATION_NOT_SHOWN) 311 == NOTIFICATION_NOT_SHOWN 312 && !isBluetoothEnabledOnApm()) { 313 mWifiThreadRunner.post( 314 () -> showNotification(R.string.apm_enabled_first_time_title, 315 R.string.apm_enabled_first_time_message)); 316 setUserSecureIntegerSetting(APM_WIFI_NOTIFICATION, NOTIFICATION_SHOWN); 317 } 318 } else { 319 // Wifi disabled due to airplane on 320 persistWifiState(WIFI_DISABLED_APM_ON); 321 } 322 } 323 mIsWifiOnAfterEnteringApm = mPersistWifiState == WIFI_ENABLED_APM_OVERRIDE; 324 } else { mWifiMetrics.reportAirplaneModeSession(mIsWifiOnBeforeEnteringApm, mIsWifiOnAfterEnteringApm, mPersistWifiState == WIFI_ENABLED_APM_OVERRIDE, getUserSecureIntegerSetting(APM_WIFI_ENABLED_NOTIFICATION, NOTIFICATION_NOT_SHOWN) == NOTIFICATION_SHOWN, mUserToggledWifiDuringApm, mUserToggledWifiAfterEnteringApmWithinMinute)325 mWifiMetrics.reportAirplaneModeSession(mIsWifiOnBeforeEnteringApm, 326 mIsWifiOnAfterEnteringApm, 327 mPersistWifiState == WIFI_ENABLED_APM_OVERRIDE, 328 getUserSecureIntegerSetting(APM_WIFI_ENABLED_NOTIFICATION, 329 NOTIFICATION_NOT_SHOWN) == NOTIFICATION_SHOWN, 330 mUserToggledWifiDuringApm, mUserToggledWifiAfterEnteringApmWithinMinute); 331 mUserToggledWifiDuringApm = false; 332 mUserToggledWifiAfterEnteringApmWithinMinute = false; 333 334 /* On airplane mode disable, restore wifi state if necessary */ 335 if (mPersistWifiState == WIFI_ENABLED_APM_OVERRIDE 336 || mPersistWifiState == WIFI_DISABLED_APM_ON) { 337 persistWifiState(WIFI_ENABLED); 338 } 339 } 340 } 341 342 synchronized void handleWifiScanAlwaysAvailableToggled(boolean isAvailable) { 343 persistScanAlwaysAvailableState(isAvailable); 344 } 345 346 synchronized boolean handleWifiScoringEnabled(boolean enabled) { 347 persistWifiScoringEnabledState(enabled); 348 return true; 349 } 350 351 /** 352 * Handle the Wifi Passpoint enable/disable status change. 353 */ 354 public synchronized void handleWifiPasspointEnabled(boolean enabled) { 355 persistWifiPasspointEnabledState(enabled); 356 } 357 358 /** 359 * Handle the Wifi Multi Internet state change. 360 */ 361 public synchronized void handleWifiMultiInternetMode(int mode) { 362 persistWifiMultiInternetMode(mode); 363 } 364 365 /** 366 * Indicate whether Wi-Fi should remain on when airplane mode is enabled 367 */ 368 public boolean shouldWifiRemainEnabledWhenApmEnabled() { 369 return mDeviceConfigFacade.isApmEnhancementEnabled() 370 && isWifiToggleEnabled() 371 && (getUserSecureIntegerSetting(WIFI_APM_STATE, 372 WIFI_TURNS_OFF_IN_APM) == WIFI_REMAINS_ON_IN_APM); 373 } 374 375 private boolean isBluetoothEnabledOnApm() { 376 return mFrameworkFacade.getIntegerSetting(mContext.getContentResolver(), 377 Settings.Global.BLUETOOTH_ON, 0) != 0 378 && getUserSecureIntegerSetting(BLUETOOTH_APM_STATE, BT_TURNS_OFF_IN_APM) 379 == BT_REMAINS_ON_IN_APM; 380 } 381 382 synchronized void updateSatelliteModeTracker() { 383 mSatelliteModeOn = getPersistedSatelliteModeOn(); 384 } 385 386 public boolean isSatelliteModeOn() { 387 return mSatelliteModeOn; 388 } 389 390 void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 391 pw.println("WifiState " + getPersistedWifiState()); 392 pw.println("AirplaneModeOn " + getPersistedAirplaneModeOn()); 393 pw.println("ScanAlwaysAvailable " + getPersistedScanAlwaysAvailable()); 394 pw.println("WifiScoringState " + getPersistedWifiScoringEnabled()); 395 pw.println("WifiPasspointState " + getPersistedWifiPasspointEnabled()); 396 pw.println("WifiMultiInternetMode " + getPersistedWifiMultiInternetMode()); 397 pw.println("WifiStateApm " + (getUserSecureIntegerSetting(WIFI_APM_STATE, 398 WIFI_TURNS_OFF_IN_APM) == WIFI_REMAINS_ON_IN_APM)); 399 pw.println("WifiStateBt " + isBluetoothEnabledOnApm()); 400 pw.println("WifiStateUser " + ActivityManager.getCurrentUser()); 401 pw.println("AirplaneModeEnhancementEnabled " 402 + mDeviceConfigFacade.isApmEnhancementEnabled()); 403 if (mAirplaneModeOn) { 404 pw.println("WifiOnBeforeEnteringApm " + mIsWifiOnBeforeEnteringApm); 405 pw.println("WifiOnAfterEnteringApm " + mIsWifiOnAfterEnteringApm); 406 pw.println("UserToggledWifiDuringApm " + mUserToggledWifiDuringApm); 407 pw.println("UserToggledWifiAfterEnteringApmWithinMinute " 408 + mUserToggledWifiAfterEnteringApmWithinMinute); 409 } 410 pw.println("SatelliteModeOn " + mSatelliteModeOn); 411 } 412 413 private void persistWifiState(int state) { 414 final ContentResolver cr = mContext.getContentResolver(); 415 mPersistWifiState = state; 416 mFrameworkFacade.setIntegerSetting(cr, Settings.Global.WIFI_ON, state); 417 } 418 419 private void persistScanAlwaysAvailableState(boolean isAvailable) { 420 mSettingsConfigStore.put( 421 WifiSettingsConfigStore.WIFI_SCAN_ALWAYS_AVAILABLE, isAvailable); 422 } 423 424 private void persistWifiScoringEnabledState(boolean enabled) { 425 mSettingsConfigStore.put( 426 WifiSettingsConfigStore.WIFI_SCORING_ENABLED, enabled); 427 } 428 429 private void persistWifiPasspointEnabledState(boolean enabled) { 430 mSettingsConfigStore.put( 431 WifiSettingsConfigStore.WIFI_PASSPOINT_ENABLED, enabled); 432 } 433 434 private void persistWifiMultiInternetMode(int mode) { 435 mSettingsConfigStore.put( 436 WifiSettingsConfigStore.WIFI_MULTI_INTERNET_MODE, mode); 437 } 438 439 /* Does Wi-Fi need to be disabled when airplane mode is on ? */ 440 private boolean isAirplaneSensitive() { 441 String airplaneModeRadios = mFrameworkFacade.getStringSetting(mContext, 442 Settings.Global.AIRPLANE_MODE_RADIOS); 443 return airplaneModeRadios == null 444 || airplaneModeRadios.contains(Settings.Global.RADIO_WIFI); 445 } 446 447 /* Is Wi-Fi allowed to be re-enabled while airplane mode is on ? */ 448 private boolean isAirplaneToggleable() { 449 String toggleableRadios = mFrameworkFacade.getStringSetting(mContext, 450 Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS); 451 return toggleableRadios != null 452 && toggleableRadios.contains(Settings.Global.RADIO_WIFI); 453 } 454 455 private int getPersistedWifiState() { 456 final ContentResolver cr = mContext.getContentResolver(); 457 try { 458 return mFrameworkFacade.getIntegerSetting(cr, Settings.Global.WIFI_ON); 459 } catch (Settings.SettingNotFoundException e) { 460 mFrameworkFacade.setIntegerSetting(cr, Settings.Global.WIFI_ON, WIFI_DISABLED); 461 return WIFI_DISABLED; 462 } 463 } 464 465 private boolean getPersistedAirplaneModeOn() { 466 return mFrameworkFacade.getIntegerSetting(mContext.getContentResolver(), 467 Settings.Global.AIRPLANE_MODE_ON, APM_DISABLED) == APM_ENABLED; 468 } 469 470 private boolean getPersistedScanAlwaysAvailable() { 471 return mSettingsConfigStore.get( 472 WifiSettingsConfigStore.WIFI_SCAN_ALWAYS_AVAILABLE); 473 } 474 475 private boolean getPersistedWifiScoringEnabled() { 476 return mSettingsConfigStore.get( 477 WifiSettingsConfigStore.WIFI_SCORING_ENABLED); 478 } 479 480 private boolean getPersistedWifiPasspointEnabled() { 481 return mSettingsConfigStore.get( 482 WifiSettingsConfigStore.WIFI_PASSPOINT_ENABLED); 483 } 484 485 private int getPersistedWifiMultiInternetMode() { 486 return mSettingsConfigStore.get( 487 WifiSettingsConfigStore.WIFI_MULTI_INTERNET_MODE); 488 } 489 490 private boolean getPersistedIsSatelliteModeSensitive() { 491 String satelliteRadios = mFrameworkFacade.getStringSetting(mContext, 492 SETTINGS_SATELLITE_MODE_RADIOS); 493 return satelliteRadios != null 494 && satelliteRadios.contains(Settings.Global.RADIO_WIFI); 495 } 496 497 /** Returns true if satellite mode is turned on. */ 498 private boolean getPersistedSatelliteModeOn() { 499 if (!getPersistedIsSatelliteModeSensitive()) return false; 500 return mFrameworkFacade.getIntegerSetting( 501 mContext, SETTINGS_SATELLITE_MODE_ENABLED, 0) == 1; 502 } 503 } 504