/* * Copyright (C) 2013 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.server.wifi; import android.app.ActivityManager; import android.app.Notification; import android.app.PendingIntent; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Icon; import android.net.Uri; import android.net.wifi.WifiContext; import android.provider.Settings; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.wifi.resources.R; import java.io.FileDescriptor; import java.io.PrintWriter; /* Tracks persisted settings for Wi-Fi and airplane mode interaction */ public class WifiSettingsStore { /* Values used to track the current state of Wi-Fi * Key: Settings.Global.WIFI_ON * Values: * WIFI_DISABLED * WIFI_ENABLED * WIFI_ENABLED_APM_OVERRIDE * WIFI_DISABLED_APM_ON */ private static final String TAG = "WifiSettingsStore"; @VisibleForTesting public static final int WIFI_DISABLED = 0; @VisibleForTesting public static final int WIFI_ENABLED = 1; /* Wifi enabled while in airplane mode */ @VisibleForTesting public static final int WIFI_ENABLED_APM_OVERRIDE = 2; /* Wifi disabled due to airplane mode on */ @VisibleForTesting public static final int WIFI_DISABLED_APM_ON = 3; /* Values used to track the current state of airplane mode * Key: Settings.Global.AIRPLANE_MODE_ON * Values: * APM_DISABLED * APM_ENABLED */ @VisibleForTesting public static final int APM_DISABLED = 0; @VisibleForTesting public static final int APM_ENABLED = 1; /* Values used to track whether Wi-Fi should remain on in airplane mode * Key: Settings.Secure WIFI_APM_STATE * Values: * WIFI_TURNS_OFF_IN_APM * WIFI_REMAINS_ON_IN_APM */ @VisibleForTesting public static final String WIFI_APM_STATE = "wifi_apm_state"; @VisibleForTesting public static final int WIFI_TURNS_OFF_IN_APM = 0; @VisibleForTesting public static final int WIFI_REMAINS_ON_IN_APM = 1; /* Values used to track whether Bluetooth should remain on in airplane mode * Key: Settings.Secure BLUETOOTH_APM_STATE * Values: * BT_TURNS_OFF_IN_APM * BT_REMAINS_ON_IN_APM */ @VisibleForTesting public static final String BLUETOOTH_APM_STATE = "bluetooth_apm_state"; @VisibleForTesting public static final int BT_TURNS_OFF_IN_APM = 0; @VisibleForTesting public static final int BT_REMAINS_ON_IN_APM = 1; /* Values used to track whether a notification has been shown * Keys: * Settings.Secure APM_WIFI_NOTIFICATION * Settings.Secure APM_WIFI_ENABLED_NOTIFICATION * Values: * NOTIFICATION_NOT_SHOWN * NOTIFICATION_SHOWN */ /* Track whether Wi-Fi remains on in airplane mode notification was shown */ @VisibleForTesting public static final String APM_WIFI_NOTIFICATION = "apm_wifi_notification"; /* Track whether Wi-Fi enabled in airplane mode notification was shown */ @VisibleForTesting public static final String APM_WIFI_ENABLED_NOTIFICATION = "apm_wifi_enabled_notification"; @VisibleForTesting public static final int NOTIFICATION_NOT_SHOWN = 0; @VisibleForTesting public static final int NOTIFICATION_SHOWN = 1; /** * @hide constant copied from {@link Settings.Global} * TODO(b/274636414): Migrate to official API in Android V. */ static final String SETTINGS_SATELLITE_MODE_RADIOS = "satellite_mode_radios"; /** * @hide constant copied from {@link Settings.Global} * TODO(b/274636414): Migrate to official API in Android V. */ static final String SETTINGS_SATELLITE_MODE_ENABLED = "satellite_mode_enabled"; /* Persisted state that tracks the wifi & airplane interaction from settings */ private int mPersistWifiState = WIFI_DISABLED; /* Tracks current airplane mode state */ private boolean mAirplaneModeOn = false; /* Tracks the wifi state before entering airplane mode*/ private boolean mIsWifiOnBeforeEnteringApm = false; /* Tracks the wifi state after entering airplane mode*/ private boolean mIsWifiOnAfterEnteringApm = false; /* Tracks whether user toggled wifi in airplane mode */ private boolean mUserToggledWifiDuringApm = false; /* Tracks whether user toggled wifi within one minute of entering airplane mode */ private boolean mUserToggledWifiAfterEnteringApmWithinMinute = false; /* Tracks when airplane mode has been enabled in milliseconds since boot */ private long mApmEnabledTimeSinceBootMillis = 0; private final String mApmEnhancementHelpLink; private final WifiContext mContext; private final WifiSettingsConfigStore mSettingsConfigStore; private final WifiThreadRunner mWifiThreadRunner; private final FrameworkFacade mFrameworkFacade; private final WifiNotificationManager mNotificationManager; private final DeviceConfigFacade mDeviceConfigFacade; private final WifiMetrics mWifiMetrics; private final Clock mClock; private boolean mSatelliteModeOn; WifiSettingsStore(WifiContext context, WifiSettingsConfigStore sharedPreferences, WifiThreadRunner wifiThread, FrameworkFacade frameworkFacade, WifiNotificationManager notificationManager, DeviceConfigFacade deviceConfigFacade, WifiMetrics wifiMetrics, Clock clock) { mContext = context; mSettingsConfigStore = sharedPreferences; mWifiThreadRunner = wifiThread; mFrameworkFacade = frameworkFacade; mNotificationManager = notificationManager; mDeviceConfigFacade = deviceConfigFacade; mWifiMetrics = wifiMetrics; mClock = clock; mAirplaneModeOn = getPersistedAirplaneModeOn(); mPersistWifiState = getPersistedWifiState(); mApmEnhancementHelpLink = mContext.getString(R.string.config_wifiApmEnhancementHelpLink); mSatelliteModeOn = getPersistedSatelliteModeOn(); } private int getUserSecureIntegerSetting(String name, int def) { Context userContext = mFrameworkFacade.getUserContext(mContext); return mFrameworkFacade.getSecureIntegerSetting(userContext, name, def); } private void setUserSecureIntegerSetting(String name, int value) { Context userContext = mFrameworkFacade.getUserContext(mContext); mFrameworkFacade.setSecureIntegerSetting(userContext, name, value); } public synchronized boolean isWifiToggleEnabled() { return mPersistWifiState == WIFI_ENABLED || mPersistWifiState == WIFI_ENABLED_APM_OVERRIDE; } /** * Returns true if airplane mode is currently on. * @return {@code true} if airplane mode is on. */ public synchronized boolean isAirplaneModeOn() { return mAirplaneModeOn; } public synchronized boolean isScanAlwaysAvailableToggleEnabled() { return getPersistedScanAlwaysAvailable(); } public synchronized boolean isScanAlwaysAvailable() { return !mAirplaneModeOn && getPersistedScanAlwaysAvailable(); } public synchronized boolean isWifiScoringEnabled() { return getPersistedWifiScoringEnabled(); } public synchronized boolean isWifiPasspointEnabled() { return getPersistedWifiPasspointEnabled(); } public synchronized boolean isWifiScanThrottleEnabled() { return getPersistedWifiScanThrottleEnabled(); } public synchronized int getWifiMultiInternetMode() { return getPersistedWifiMultiInternetMode(); } public void setPersistWifiState(int state) { mPersistWifiState = state; } private void showNotification(int titleId, int messageId) { String settingsPackage = mFrameworkFacade.getSettingsPackageName(mContext); if (settingsPackage == null) return; Intent openLinkIntent = new Intent(Intent.ACTION_VIEW) .setData(Uri.parse(mApmEnhancementHelpLink)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent tapPendingIntent = mFrameworkFacade.getActivity(mContext, 0, openLinkIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); String title = mContext.getResources().getString(titleId); String message = mContext.getResources().getString(messageId); Notification.Builder builder = mFrameworkFacade.makeNotificationBuilder(mContext, WifiService.NOTIFICATION_APM_ALERTS) .setAutoCancel(true) .setLocalOnly(true) .setContentTitle(title) .setContentText(message) .setContentIntent(tapPendingIntent) .setVisibility(Notification.VISIBILITY_PUBLIC) .setStyle(new Notification.BigTextStyle().bigText(message)) .setSmallIcon(Icon.createWithResource(mContext.getWifiOverlayApkPkgName(), R.drawable.ic_wifi_settings)); mNotificationManager.notify(SystemMessage.NOTE_WIFI_APM_NOTIFICATION, builder.build()); } public synchronized boolean handleWifiToggled(boolean wifiEnabled) { // Can Wi-Fi be toggled in airplane mode ? if (mAirplaneModeOn && !isAirplaneToggleable()) { return false; } if (wifiEnabled) { if (mAirplaneModeOn) { persistWifiState(WIFI_ENABLED_APM_OVERRIDE); if (mDeviceConfigFacade.isApmEnhancementEnabled()) { setUserSecureIntegerSetting(WIFI_APM_STATE, WIFI_REMAINS_ON_IN_APM); if (getUserSecureIntegerSetting(APM_WIFI_ENABLED_NOTIFICATION, NOTIFICATION_NOT_SHOWN) == NOTIFICATION_NOT_SHOWN) { mWifiThreadRunner.post( () -> showNotification(R.string.wifi_enabled_apm_first_time_title, R.string.wifi_enabled_apm_first_time_message), TAG + "#handleWifiToggled"); setUserSecureIntegerSetting( APM_WIFI_ENABLED_NOTIFICATION, NOTIFICATION_SHOWN); } } } else { persistWifiState(WIFI_ENABLED); } } else { // When wifi state is disabled, we do not care // if airplane mode is on or not. The scenario of // wifi being disabled due to airplane mode being turned on // is handled handleAirplaneModeToggled() persistWifiState(WIFI_DISABLED); if (mDeviceConfigFacade.isApmEnhancementEnabled() && mAirplaneModeOn) { setUserSecureIntegerSetting(WIFI_APM_STATE, WIFI_TURNS_OFF_IN_APM); } } if (mAirplaneModeOn) { if (!mUserToggledWifiDuringApm) { mUserToggledWifiAfterEnteringApmWithinMinute = mClock.getElapsedSinceBootMillis() - mApmEnabledTimeSinceBootMillis < 60_000; } mUserToggledWifiDuringApm = true; } return true; } synchronized boolean updateAirplaneModeTracker() { // Is Wi-Fi sensitive to airplane mode changes ? if (!isAirplaneSensitive()) { return false; } mAirplaneModeOn = getPersistedAirplaneModeOn(); return true; } synchronized void handleAirplaneModeToggled() { if (mAirplaneModeOn) { mApmEnabledTimeSinceBootMillis = mClock.getElapsedSinceBootMillis(); mIsWifiOnBeforeEnteringApm = mPersistWifiState == WIFI_ENABLED; if (mPersistWifiState == WIFI_ENABLED) { if (mDeviceConfigFacade.isApmEnhancementEnabled() && getUserSecureIntegerSetting(WIFI_APM_STATE, WIFI_TURNS_OFF_IN_APM) == WIFI_REMAINS_ON_IN_APM) { persistWifiState(WIFI_ENABLED_APM_OVERRIDE); if (getUserSecureIntegerSetting(APM_WIFI_NOTIFICATION, NOTIFICATION_NOT_SHOWN) == NOTIFICATION_NOT_SHOWN && !isBluetoothEnabledOnApm()) { mWifiThreadRunner.post( () -> showNotification(R.string.apm_enabled_first_time_title, R.string.apm_enabled_first_time_message), TAG + "#handleAirplaneModeToggled"); setUserSecureIntegerSetting(APM_WIFI_NOTIFICATION, NOTIFICATION_SHOWN); } } else { // Wifi disabled due to airplane on persistWifiState(WIFI_DISABLED_APM_ON); } } mIsWifiOnAfterEnteringApm = mPersistWifiState == WIFI_ENABLED_APM_OVERRIDE; } else { mWifiMetrics.reportAirplaneModeSession(mIsWifiOnBeforeEnteringApm, mIsWifiOnAfterEnteringApm, mPersistWifiState == WIFI_ENABLED_APM_OVERRIDE, getUserSecureIntegerSetting(APM_WIFI_ENABLED_NOTIFICATION, NOTIFICATION_NOT_SHOWN) == NOTIFICATION_SHOWN, mUserToggledWifiDuringApm, mUserToggledWifiAfterEnteringApmWithinMinute); mUserToggledWifiDuringApm = false; mUserToggledWifiAfterEnteringApmWithinMinute = false; /* On airplane mode disable, restore wifi state if necessary */ if (mPersistWifiState == WIFI_ENABLED_APM_OVERRIDE || mPersistWifiState == WIFI_DISABLED_APM_ON) { persistWifiState(WIFI_ENABLED); } } } synchronized void handleWifiScanAlwaysAvailableToggled(boolean isAvailable) { persistScanAlwaysAvailableState(isAvailable); } synchronized boolean handleWifiScoringEnabled(boolean enabled) { persistWifiScoringEnabledState(enabled); return true; } /** * Handle the Wifi Passpoint enable/disable status change. */ public synchronized void handleWifiPasspointEnabled(boolean enabled) { persistWifiPasspointEnabledState(enabled); } /** * Handle the Wifi Multi Internet state change. */ public synchronized void handleWifiMultiInternetMode(int mode) { persistWifiMultiInternetMode(mode); } /** * Indicate whether Wi-Fi should remain on when airplane mode is enabled */ public boolean shouldWifiRemainEnabledWhenApmEnabled() { return mDeviceConfigFacade.isApmEnhancementEnabled() && isWifiToggleEnabled() && (getUserSecureIntegerSetting(WIFI_APM_STATE, WIFI_TURNS_OFF_IN_APM) == WIFI_REMAINS_ON_IN_APM); } private boolean isBluetoothEnabledOnApm() { return mFrameworkFacade.getIntegerSetting(mContext.getContentResolver(), Settings.Global.BLUETOOTH_ON, 0) != 0 && getUserSecureIntegerSetting(BLUETOOTH_APM_STATE, BT_TURNS_OFF_IN_APM) == BT_REMAINS_ON_IN_APM; } synchronized void updateSatelliteModeTracker() { mSatelliteModeOn = getPersistedSatelliteModeOn(); } public boolean isSatelliteModeOn() { return mSatelliteModeOn; } void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("WifiState " + getPersistedWifiState()); pw.println("AirplaneModeOn " + getPersistedAirplaneModeOn()); pw.println("ScanAlwaysAvailable " + getPersistedScanAlwaysAvailable()); pw.println("WifiScoringState " + getPersistedWifiScoringEnabled()); pw.println("WifiPasspointState " + getPersistedWifiPasspointEnabled()); pw.println("WifiMultiInternetMode " + getPersistedWifiMultiInternetMode()); pw.println("WifiStateApm " + (getUserSecureIntegerSetting(WIFI_APM_STATE, WIFI_TURNS_OFF_IN_APM) == WIFI_REMAINS_ON_IN_APM)); pw.println("WifiStateBt " + isBluetoothEnabledOnApm()); pw.println("WifiStateUser " + ActivityManager.getCurrentUser()); pw.println("AirplaneModeEnhancementEnabled " + mDeviceConfigFacade.isApmEnhancementEnabled()); if (mAirplaneModeOn) { pw.println("WifiOnBeforeEnteringApm " + mIsWifiOnBeforeEnteringApm); pw.println("WifiOnAfterEnteringApm " + mIsWifiOnAfterEnteringApm); pw.println("UserToggledWifiDuringApm " + mUserToggledWifiDuringApm); pw.println("UserToggledWifiAfterEnteringApmWithinMinute " + mUserToggledWifiAfterEnteringApmWithinMinute); } pw.println("SatelliteModeOn " + mSatelliteModeOn); } private void persistWifiState(int state) { final ContentResolver cr = mContext.getContentResolver(); mPersistWifiState = state; mFrameworkFacade.setIntegerSetting(cr, Settings.Global.WIFI_ON, state); } private void persistScanAlwaysAvailableState(boolean isAvailable) { mSettingsConfigStore.put( WifiSettingsConfigStore.WIFI_SCAN_ALWAYS_AVAILABLE, isAvailable); } private void persistWifiScoringEnabledState(boolean enabled) { mSettingsConfigStore.put( WifiSettingsConfigStore.WIFI_SCORING_ENABLED, enabled); } private void persistWifiPasspointEnabledState(boolean enabled) { mSettingsConfigStore.put( WifiSettingsConfigStore.WIFI_PASSPOINT_ENABLED, enabled); } private void persistWifiMultiInternetMode(int mode) { mSettingsConfigStore.put( WifiSettingsConfigStore.WIFI_MULTI_INTERNET_MODE, mode); } /* Does Wi-Fi need to be disabled when airplane mode is on ? */ private boolean isAirplaneSensitive() { String airplaneModeRadios = mFrameworkFacade.getStringSetting(mContext, Settings.Global.AIRPLANE_MODE_RADIOS); return airplaneModeRadios == null || airplaneModeRadios.contains(Settings.Global.RADIO_WIFI); } /* Is Wi-Fi allowed to be re-enabled while airplane mode is on ? */ private boolean isAirplaneToggleable() { String toggleableRadios = mFrameworkFacade.getStringSetting(mContext, Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS); return toggleableRadios != null && toggleableRadios.contains(Settings.Global.RADIO_WIFI); } private int getPersistedWifiState() { final ContentResolver cr = mContext.getContentResolver(); try { return mFrameworkFacade.getIntegerSetting(cr, Settings.Global.WIFI_ON); } catch (Settings.SettingNotFoundException e) { mFrameworkFacade.setIntegerSetting(cr, Settings.Global.WIFI_ON, WIFI_DISABLED); return WIFI_DISABLED; } } private boolean getPersistedAirplaneModeOn() { return mFrameworkFacade.getIntegerSetting(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, APM_DISABLED) == APM_ENABLED; } private boolean getPersistedScanAlwaysAvailable() { return mSettingsConfigStore.get( WifiSettingsConfigStore.WIFI_SCAN_ALWAYS_AVAILABLE); } private boolean getPersistedWifiScoringEnabled() { return mSettingsConfigStore.get( WifiSettingsConfigStore.WIFI_SCORING_ENABLED); } private boolean getPersistedWifiPasspointEnabled() { return mSettingsConfigStore.get( WifiSettingsConfigStore.WIFI_PASSPOINT_ENABLED); } private boolean getPersistedWifiScanThrottleEnabled() { return mSettingsConfigStore.get( WifiSettingsConfigStore.WIFI_SCAN_THROTTLE_ENABLED); } private int getPersistedWifiMultiInternetMode() { return mSettingsConfigStore.get( WifiSettingsConfigStore.WIFI_MULTI_INTERNET_MODE); } private boolean getPersistedIsSatelliteModeSensitive() { String satelliteRadios = mFrameworkFacade.getStringSetting(mContext, SETTINGS_SATELLITE_MODE_RADIOS); return satelliteRadios != null && satelliteRadios.contains(Settings.Global.RADIO_WIFI); } /** Returns true if satellite mode is turned on. */ private boolean getPersistedSatelliteModeOn() { if (!getPersistedIsSatelliteModeSensitive()) return false; return mFrameworkFacade.getIntegerSetting( mContext, SETTINGS_SATELLITE_MODE_ENABLED, 0) == 1; } }