/* * 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.server.wifi; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.net.wifi.WifiManager; import android.net.wifi.WifiMigration; import android.os.Handler; import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wifi.util.SettingsMigrationDataHolder; import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil; import com.android.server.wifi.util.XmlUtil; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; /** * Store data for storing wifi settings. These are key (string) / value pairs that are stored in * WifiConfigStore.xml file in a separate section. */ public class WifiSettingsConfigStore { private static final String TAG = "WifiSettingsConfigStore"; // List of all allowed keys. private static final ArrayList sKeys = new ArrayList<>(); /******** Wifi shared pref keys ***************/ /** * Indicate whether factory reset request is pending. */ public static final Key WIFI_P2P_PENDING_FACTORY_RESET = new Key<>("wifi_p2p_pending_factory_reset", false); /** * Allow scans to be enabled even wifi is turned off. */ public static final Key WIFI_SCAN_ALWAYS_AVAILABLE = new Key<>("wifi_scan_always_enabled", false); /** * Whether wifi scan throttle is enabled or not. */ public static final Key WIFI_SCAN_THROTTLE_ENABLED = new Key<>("wifi_scan_throttle_enabled", true); /** * Setting to enable verbose logging in Wi-Fi. */ public static final Key WIFI_VERBOSE_LOGGING_ENABLED = new Key<>("wifi_verbose_logging_enabled", false); /** * Setting to enable verbose logging in Wi-Fi Aware. */ public static final Key WIFI_AWARE_VERBOSE_LOGGING_ENABLED = new Key<>("wifi_aware_verbose_logging_enabled", false); /** * The Wi-Fi peer-to-peer device name */ public static final Key WIFI_P2P_DEVICE_NAME = new Key<>("wifi_p2p_device_name", null); /** * The Wi-Fi peer-to-peer device mac address */ public static final Key WIFI_P2P_DEVICE_ADDRESS = new Key<>("wifi_p2p_device_address", null); /** * Whether Wifi scoring is enabled or not. */ public static final Key WIFI_SCORING_ENABLED = new Key<>("wifi_scoring_enabled", true); /** * Whether Wifi Passpoint is enabled or not. */ public static final Key WIFI_PASSPOINT_ENABLED = new Key<>("wifi_passpoint_enabled", true); /** * Whether Wifi Multi Internet is enabled for multi ap, dbs or disabled. */ public static final Key WIFI_MULTI_INTERNET_MODE = new Key("wifi_multi_internet_mode", WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED); /** * Store the STA factory MAC address retrieved from the driver on the first bootup. */ public static final Key WIFI_STA_FACTORY_MAC_ADDRESS = new Key<>("wifi_sta_factory_mac_address", null); /** * Store the Secondary STA factory MAC address retrieved from the driver on the first bootup. */ public static final Key SECONDARY_WIFI_STA_FACTORY_MAC_ADDRESS = new Key<>("secondary_wifi_sta_factory_mac_address", null); /** * Store the default country code updated via {@link WifiManager#setDefaultCountryCode(String)} */ public static final Key WIFI_DEFAULT_COUNTRY_CODE = new Key<>("wifi_default_country_code", WifiCountryCode.getOemDefaultCountryCode()); /** * Store the supported features retrieved from WiFi HAL and Supplicant HAL */ public static final Key WIFI_NATIVE_SUPPORTED_FEATURES = new Key<>("wifi_native_supported_features", 0L); /** * Store the supported features retrieved from WiFi HAL and Supplicant HAL */ public static final Key WIFI_NATIVE_SUPPORTED_STA_BANDS = new Key<>("wifi_native_supported_sta_bands", 0); /** * Store the static chip info retrieved from WiFi HAL */ public static final Key WIFI_STATIC_CHIP_INFO = new Key<>("wifi_static_chip_info", ""); /** * Store the last country code used by Soft AP. */ public static final Key WIFI_SOFT_AP_COUNTRY_CODE = new Key<>("wifi_last_country_code", ""); /** * Store the available channel frequencies in a JSON array for Soft AP for the last country * code used. */ public static final Key WIFI_AVAILABLE_SOFT_AP_FREQS_MHZ = new Key<>("wifi_available_soft_ap_freqs_mhz", "[]"); /** * Whether to show a dialog when third party apps attempt to enable wifi. */ public static final Key SHOW_DIALOG_WHEN_THIRD_PARTY_APPS_ENABLE_WIFI = new Key<>("show_dialog_when_third_party_apps_enable_wifi", false); /** * Whether the * {@link WifiManager#setThirdPartyAppEnablingWifiConfirmationDialogEnabled(boolean)} API was * called to set the value of {@link #SHOW_DIALOG_WHEN_THIRD_PARTY_APPS_ENABLE_WIFI}. */ public static final Key SHOW_DIALOG_WHEN_THIRD_PARTY_APPS_ENABLE_WIFI_SET_BY_API = new Key<>("show_dialog_when_third_party_apps_enable_wifi_set_by_api", false); /** * AIDL version implemented by the Supplicant service. */ public static final Key SUPPLICANT_HAL_AIDL_SERVICE_VERSION = new Key<>("supplicant_hal_aidl_service_version", -1); /** * Whether the WEP network is allowed or not. */ public static final Key WIFI_WEP_ALLOWED = new Key<>("wep_allowed", true); /** * Store wiphy capability for 11be support. */ public static final Key WIFI_WIPHY_11BE_SUPPORTED = new Key<>("wifi_wiphy_11be_supported", true); /** * Whether the D2D is allowed or not when infra sta is disabled. */ public static final Key D2D_ALLOWED_WHEN_INFRA_STA_DISABLED = new Key<>("d2d_allowed_when_infra_sta_disabled", false); // List of all keys which require to backup and restore. private static final List sBackupRestoreKeys = List.of( WIFI_WEP_ALLOWED, D2D_ALLOWED_WHEN_INFRA_STA_DISABLED); /******** Wifi shared pref keys ***************/ private final Context mContext; private final Handler mHandler; private final SettingsMigrationDataHolder mSettingsMigrationDataHolder; private final WifiConfigManager mWifiConfigManager; private final Object mLock = new Object(); @GuardedBy("mLock") private final Map mSettings = new HashMap<>(); @GuardedBy("mLock") private final Map> mListeners = new HashMap<>(); private WifiMigration.SettingsMigrationData mCachedMigrationData = null; private boolean mHasNewDataToSerialize = false; /** * Interface for a settings change listener. * @param Type of the value. */ public interface OnSettingsChangedListener { /** * Invoked when a particular key settings changes. * * @param key Key that was changed. * @param newValue New value that was assigned to the key. */ void onSettingsChanged(@NonNull Key key, @Nullable T newValue); } public WifiSettingsConfigStore(@NonNull Context context, @NonNull Handler handler, @NonNull SettingsMigrationDataHolder settingsMigrationDataHolder, @NonNull WifiConfigManager wifiConfigManager, @NonNull WifiConfigStore wifiConfigStore) { mContext = context; mHandler = handler; mSettingsMigrationDataHolder = settingsMigrationDataHolder; mWifiConfigManager = wifiConfigManager; // Register our data store. wifiConfigStore.registerStoreData(new StoreData()); } public ArrayList getAllKeys() { return sKeys; } public List getAllBackupRestoreKeys() { return sBackupRestoreKeys; } private void invokeAllListeners() { synchronized (mLock) { for (Key key : sKeys) { invokeListeners(key); } } } private void invokeListeners(@NonNull Key key) { synchronized (mLock) { if (!mSettings.containsKey(key.key)) return; Object newValue = mSettings.get(key.key); Map listeners = mListeners.get(key.key); if (listeners == null || listeners.isEmpty()) return; for (Map.Entry listener : listeners.entrySet()) { // Trigger the callback in the appropriate handler. listener.getValue().post(() -> listener.getKey().onSettingsChanged(key, newValue)); } } } /** * Trigger config store writes and invoke listeners in the main wifi service looper's handler. */ private void triggerSaveToStoreAndInvokeAllListeners() { mHandler.post(() -> { mHasNewDataToSerialize = true; mWifiConfigManager.saveToStore(); invokeAllListeners(); }); } /** * Trigger config store writes and invoke listeners in the main wifi service looper's handler. */ private void triggerSaveToStoreAndInvokeListeners(@NonNull Key key) { mHandler.post(() -> { mHasNewDataToSerialize = true; mWifiConfigManager.saveToStore(); invokeListeners(key); }); } /** * Performs a one time migration from Settings.Global values to settings store. Only * performed one time if the settings store is empty. */ private void migrateFromSettingsIfNeeded() { if (!mSettings.isEmpty()) return; // already migrated. mCachedMigrationData = mSettingsMigrationDataHolder.retrieveData(); if (mCachedMigrationData == null) { Log.e(TAG, "No settings data to migrate"); return; } Log.i(TAG, "Migrating data out of settings to shared preferences"); mSettings.put(WIFI_P2P_DEVICE_NAME.key, mCachedMigrationData.getP2pDeviceName()); mSettings.put(WIFI_P2P_PENDING_FACTORY_RESET.key, mCachedMigrationData.isP2pFactoryResetPending()); mSettings.put(WIFI_SCAN_ALWAYS_AVAILABLE.key, mCachedMigrationData.isScanAlwaysAvailable()); mSettings.put(WIFI_SCAN_THROTTLE_ENABLED.key, mCachedMigrationData.isScanThrottleEnabled()); mSettings.put(WIFI_VERBOSE_LOGGING_ENABLED.key, mCachedMigrationData.isVerboseLoggingEnabled()); triggerSaveToStoreAndInvokeAllListeners(); } /** * Store a value to the stored settings. * * @param key One of the settings keys. * @param value Value to be stored. */ public void put(@NonNull Key key, @Nullable T value) { synchronized (mLock) { mSettings.put(key.key, value); } triggerSaveToStoreAndInvokeListeners(key); } /** * Retrieve a value from the stored settings. * * @param key One of the settings keys. * @return value stored in settings, defValue if the key does not exist. */ public @Nullable T get(@NonNull Key key) { synchronized (mLock) { return (T) mSettings.getOrDefault(key.key, key.defaultValue); } } /** * Register for settings change listener. * * @param key One of the settings keys. * @param listener Listener to be registered. * @param handler Handler to post the listener */ public void registerChangeListener(@NonNull Key key, @NonNull OnSettingsChangedListener listener, @NonNull Handler handler) { synchronized (mLock) { mListeners.computeIfAbsent( key.key, ignore -> new HashMap<>()).put(listener, handler); } } /** * Unregister for settings change listener. * * @param key One of the settings keys. * @param listener Listener to be unregistered. */ public void unregisterChangeListener(@NonNull Key key, @NonNull OnSettingsChangedListener listener) { synchronized (mLock) { Map listeners = mListeners.get(key.key); if (listeners == null || listeners.isEmpty()) { Log.e(TAG, "No listeners for " + key); return; } if (listeners.remove(listener) == null) { Log.e(TAG, "Unknown listener for " + key); } } } /** * Dump output for debugging. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(); pw.println("Dump of " + TAG); pw.println("Settings:"); for (Map.Entry entry : mSettings.entrySet()) { pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue()); } if (mCachedMigrationData == null) return; pw.println("Migration data:"); pw.print(WIFI_P2P_DEVICE_NAME.key); pw.print("="); pw.println(mCachedMigrationData.getP2pDeviceName()); pw.print(WIFI_P2P_PENDING_FACTORY_RESET.key); pw.print("="); pw.println(mCachedMigrationData.isP2pFactoryResetPending()); pw.print(WIFI_SCAN_ALWAYS_AVAILABLE.key); pw.print("="); pw.println(mCachedMigrationData.isScanAlwaysAvailable()); pw.print(WIFI_SCAN_THROTTLE_ENABLED.key); pw.print("="); pw.println(mCachedMigrationData.isScanThrottleEnabled()); pw.print(WIFI_VERBOSE_LOGGING_ENABLED.key); pw.print("="); pw.println(mCachedMigrationData.isVerboseLoggingEnabled()); pw.println(); } /** * Base class to store string key and its default value. * @param Type of the value. */ public static class Key { public final String key; public final T defaultValue; private Key(@NonNull String key, T defaultValue) { this.key = key; this.defaultValue = defaultValue; sKeys.add(this); } @VisibleForTesting public String getKey() { return key; } @Override public String toString() { return "[Key " + key + ", DefaultValue: " + defaultValue + "]"; } @Override public boolean equals(Object o) { if (o == this) { return true; } // null instanceof [type]" also returns false if (!(o instanceof Key)) { return false; } Key anotherKey = (Key) o; return Objects.equals(key, anotherKey.key) && Objects.equals(defaultValue, anotherKey.defaultValue); } @Override public int hashCode() { return Objects.hash(key, defaultValue); } } /** * Store data for persisting the settings data to config store. */ public class StoreData implements WifiConfigStore.StoreData { public static final String XML_TAG_SECTION_HEADER = "Settings"; public static final String XML_TAG_VALUES = "Values"; @Override public void serializeData(XmlSerializer out, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { synchronized (mLock) { XmlUtil.writeNextValue(out, XML_TAG_VALUES, mSettings); } } @Override public void deserializeData(XmlPullParser in, int outerTagDepth, @WifiConfigStore.Version int version, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { if (in == null) { // Empty read triggers the migration since it indicates that there is no settings // data stored in the settings store. migrateFromSettingsIfNeeded(); return; } Map values = deserializeSettingsData(in, outerTagDepth); if (values != null) { synchronized (mLock) { mSettings.putAll(values); // Invoke all the registered listeners. invokeAllListeners(); } } } /** * Parse out the wifi settings from the input xml stream. */ public static Map deserializeSettingsData( XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException { Map values = null; while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) { String[] valueName = new String[1]; Object value = XmlUtil.readCurrentValue(in, valueName); if (TextUtils.isEmpty(valueName[0])) { throw new XmlPullParserException("Missing value name"); } switch (valueName[0]) { case XML_TAG_VALUES: values = (Map) value; break; default: Log.w(TAG, "Ignoring unknown tag under " + XML_TAG_SECTION_HEADER + ": " + valueName[0]); break; } } return values; } @Override public void resetData() { synchronized (mLock) { mSettings.clear(); } } @Override public boolean hasNewDataToSerialize() { return mHasNewDataToSerialize; } @Override public String getName() { return XML_TAG_SECTION_HEADER; } @Override public @WifiConfigStore.StoreFileId int getStoreFileId() { // Shared general store. return WifiConfigStore.STORE_FILE_SHARED_GENERAL; } } }