blob: 4bb99a0b67b0766b587adb1138f53665ca79239e [file] [log] [blame]
/*
* 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.annotation.StringDef;
import android.content.Context;
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.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.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.Map;
/**
* Store data for storing wifi settings. These are key (string) / value pairs that are stored in
* WifiConfigStore.xml file in a separate section.
* TODO(b/149738301): Rework this class.
*/
public class WifiSettingsConfigStore {
private static final String TAG = "WifiSettingsConfigStore";
/******** Wifi shared pref keys ***************/
@StringDef({
WIFI_P2P_DEVICE_NAME,
WIFI_P2P_PENDING_FACTORY_RESET,
WIFI_SCAN_ALWAYS_AVAILABLE,
WIFI_SCAN_THROTTLE_ENABLED,
WIFI_VERBOSE_LOGGING_ENABLED
})
@Retention(RetentionPolicy.SOURCE)
public @interface WifiSettingsKey {}
/**
* The Wi-Fi peer-to-peer device name
*/
public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
/**
* Indicate whether factory reset request is pending.
*/
public static final String WIFI_P2P_PENDING_FACTORY_RESET = "wifi_p2p_pending_factory_reset";
/**
* Allow scans to be enabled even wifi is turned off.
*/
public static final String WIFI_SCAN_ALWAYS_AVAILABLE = "wifi_scan_always_enabled";
/**
* Whether wifi scan throttle is enabled or not.
*/
public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled";
/**
* Setting to enable verbose logging in Wi-Fi; disabled by default, and setting to 1
* will enable it. In the future, additional values may be supported.
*/
public static final String WIFI_VERBOSE_LOGGING_ENABLED = "wifi_verbose_logging_enabled";
/******** Wifi shared pref keys ***************/
private final Context mContext;
private final Handler mHandler;
private final WifiConfigManager mWifiConfigManager;
private final Object mLock = new Object();
@GuardedBy("mLock")
private final Map<String, Object> mSettings = new HashMap<>();
@GuardedBy("mLock")
private final Map<String, Map<OnSettingsChangedListener, Handler>> mListeners =
new HashMap<>();
private boolean mHasNewDataToSerialize = false;
/**
* Interface for a settings change listener.
*/
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 @WifiSettingsKey String key, @NonNull Object newValue);
}
public WifiSettingsConfigStore(@NonNull Context context, @NonNull Handler handler,
@NonNull WifiConfigManager wifiConfigManager,
@NonNull WifiConfigStore wifiConfigStore) {
mContext = context;
mHandler = handler;
mWifiConfigManager = wifiConfigManager;
// Register our data store.
wifiConfigStore.registerStoreData(new StoreData());
}
private void invokeAllListeners() {
synchronized (mLock) {
for (String key : mSettings.keySet()) {
invokeListeners(key);
}
}
}
private void invokeListeners(@NonNull @WifiSettingsKey String key) {
synchronized (mLock) {
Object newValue = mSettings.get(key);
Map<OnSettingsChangedListener, Handler> listeners = mListeners.get(key);
if (listeners == null || listeners.isEmpty()) return;
for (Map.Entry<OnSettingsChangedListener, Handler> 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(true);
invokeAllListeners();
});
}
/**
* Trigger config store writes and invoke listeners in the main wifi service looper's handler.
*/
private void triggerSaveToStoreAndInvokeListeners(@NonNull @WifiSettingsKey String key) {
mHandler.post(() -> {
mHasNewDataToSerialize = true;
mWifiConfigManager.saveToStore(true);
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.
WifiMigration.SettingsMigrationData dataToMigrate =
WifiMigration.loadFromSettings(mContext);
if (dataToMigrate == null) {
Log.e(TAG, "Not settings data to migrate");
return;
}
Log.i(TAG, "Migrating data out of settings to shared preferences");
mSettings.put(WIFI_P2P_DEVICE_NAME, dataToMigrate.getP2pDeviceName());
mSettings.put(WIFI_P2P_PENDING_FACTORY_RESET, dataToMigrate.isP2pFactoryResetPending());
mSettings.put(WIFI_SCAN_THROTTLE_ENABLED, dataToMigrate.isScanThrottleEnabled());
mSettings.put(WIFI_VERBOSE_LOGGING_ENABLED, dataToMigrate.isVerboseLoggingEnabled());
triggerSaveToStoreAndInvokeAllListeners();
}
/**
* Store an int value to the stored settings.
*
* @param key One of the settings keys.
* @param value Value to be stored.
*/
public void putInt(@NonNull @WifiSettingsKey String key, int value) {
synchronized (mLock) {
mSettings.put(key, value);
}
triggerSaveToStoreAndInvokeListeners(key);
}
/**
* Store a boolean value to the stored settings.
*
* @param key One of the settings keys.
* @param value Value to be stored.
*/
public void putBoolean(@NonNull @WifiSettingsKey String key, boolean value) {
synchronized (mLock) {
mSettings.put(key, value);
}
triggerSaveToStoreAndInvokeListeners(key);
}
/**
* Store a String value to the stored settings.
*
* @param key One of the settings keys.
* @param value Value to be stored.
*/
public void putString(@NonNull @WifiSettingsKey String key, @NonNull String value) {
synchronized (mLock) {
mSettings.put(key, value);
}
triggerSaveToStoreAndInvokeListeners(key);
}
/**
* Retrieve an int value from the stored settings.
*
* @param key One of the settings keys.
* @param defValue Default value to be returned if the key does not exist.
* @return value stored in settings, defValue if the key does not exist.
*/
public int getInt(@NonNull @WifiSettingsKey String key, int defValue) {
synchronized (mLock) {
return (int) mSettings.getOrDefault(key, defValue);
}
}
/**
* Retrieve a boolean value from the stored settings.
*
* @param key One of the settings keys.
* @param defValue Default value to be returned if the key does not exist.
* @return value stored in settings, defValue if the key does not exist.
*/
public boolean getBoolean(@NonNull @WifiSettingsKey String key, boolean defValue) {
synchronized (mLock) {
return (boolean) mSettings.getOrDefault(key, defValue);
}
}
/**
* Retrieve a string value from the stored settings.
*
* @param key One of the settings keys.
* @param defValue Default value to be returned if the key does not exist.
* @return value stored in settings, defValue if the key does not exist.
*/
public @Nullable String getString(@NonNull @WifiSettingsKey String key,
@Nullable String defValue) {
synchronized (mLock) {
return (String) mSettings.getOrDefault(key, defValue);
}
}
/**
* 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 @WifiSettingsKey String key,
@NonNull OnSettingsChangedListener listener, @NonNull Handler handler) {
synchronized (mLock) {
mListeners.computeIfAbsent(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 @WifiSettingsKey String key,
@NonNull OnSettingsChangedListener listener) {
synchronized (mLock) {
Map<OnSettingsChangedListener, Handler> listeners = mListeners.get(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);
}
}
}
/**
* Store data for persisting the settings data to config store.
*/
private class StoreData implements WifiConfigStore.StoreData {
private static final String XML_TAG_SECTION_HEADER = "Settings";
private 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,
@NonNull WifiConfigStoreMigrationDataHolder storeMigrationDataHolder)
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<String, Object> 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;
}
}
if (values != null) {
synchronized (mLock) {
mSettings.putAll(values);
// Invoke all the registered listeners.
invokeAllListeners();
}
}
}
@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;
}
}
}