blob: 5792d27a94f9948a7331d2daeb362edaff4ed83f [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 android.net.wifi;
import static android.os.Environment.getDataMiscCeDirectory;
import static android.os.Environment.getDataMiscDirectory;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.AtomicFile;
import android.util.SparseArray;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
* Class used to provide one time hooks for existing OEM devices to migrate their config store
* data and other settings to the wifi apex.
* @hide
*/
@SystemApi
public final class WifiMigration {
/**
* Directory to read the wifi config store files from under.
*/
private static final String LEGACY_WIFI_STORE_DIRECTORY_NAME = "wifi";
/**
* Config store file for general shared store file.
* AOSP Path on Android 10: /data/misc/wifi/WifiConfigStore.xml
*/
public static final int STORE_FILE_SHARED_GENERAL = 0;
/**
* Config store file for softap shared store file.
* AOSP Path on Android 10: /data/misc/wifi/softap.conf
*/
public static final int STORE_FILE_SHARED_SOFTAP = 1;
/**
* Config store file for general user store file.
* AOSP Path on Android 10: /data/misc_ce/<userId>/wifi/WifiConfigStore.xml
*/
public static final int STORE_FILE_USER_GENERAL = 2;
/**
* Config store file for network suggestions user store file.
* AOSP Path on Android 10: /data/misc_ce/<userId>/wifi/WifiConfigStoreNetworkSuggestions.xml
*/
public static final int STORE_FILE_USER_NETWORK_SUGGESTIONS = 3;
/** @hide */
@IntDef(prefix = { "STORE_FILE_SHARED_" }, value = {
STORE_FILE_SHARED_GENERAL,
STORE_FILE_SHARED_SOFTAP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SharedStoreFileId { }
/** @hide */
@IntDef(prefix = { "STORE_FILE_USER_" }, value = {
STORE_FILE_USER_GENERAL,
STORE_FILE_USER_NETWORK_SUGGESTIONS
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserStoreFileId { }
/**
* Mapping of Store file Id to Store file names.
*
* NOTE: This is the default path for the files on AOSP devices. If the OEM has modified
* the path or renamed the files, please edit this appropriately.
*/
private static final SparseArray<String> STORE_ID_TO_FILE_NAME =
new SparseArray<String>() {{
put(STORE_FILE_SHARED_GENERAL, "WifiConfigStore.xml");
put(STORE_FILE_SHARED_SOFTAP, "WifiConfigStoreSoftAp.xml");
put(STORE_FILE_USER_GENERAL, "WifiConfigStore.xml");
put(STORE_FILE_USER_NETWORK_SUGGESTIONS, "WifiConfigStoreNetworkSuggestions.xml");
}};
/**
* Pre-apex wifi shared folder.
*/
private static File getLegacyWifiSharedDirectory() {
return new File(getDataMiscDirectory(), LEGACY_WIFI_STORE_DIRECTORY_NAME);
}
/**
* Pre-apex wifi user folder.
*/
private static File getLegacyWifiUserDirectory(int userId) {
return new File(getDataMiscCeDirectory(userId), LEGACY_WIFI_STORE_DIRECTORY_NAME);
}
/**
* Legacy files were stored as AtomicFile. So, always use AtomicFile to operate on it to ensure
* data integrity.
*/
private static AtomicFile getSharedAtomicFile(@SharedStoreFileId int storeFileId) {
return new AtomicFile(new File(
getLegacyWifiSharedDirectory(),
STORE_ID_TO_FILE_NAME.get(storeFileId)));
}
/**
* Legacy files were stored as AtomicFile. So, always use AtomicFile to operate on it to ensure
* data integrity.
*/
private static AtomicFile getUserAtomicFile(@UserStoreFileId int storeFileId, int userId) {
return new AtomicFile(new File(
getLegacyWifiUserDirectory(userId),
STORE_ID_TO_FILE_NAME.get(storeFileId)));
}
private WifiMigration() { }
/**
* Load data from legacy shared wifi config store file.
* <p>
* Expected AOSP format is available in the sample files under {@code /frameworks/base/wifi/
* java/android/net/wifi/migration_samples}.
* </p>
* <p>
* Note:
* <li>OEMs need to change the implementation of
* {@link #convertAndRetrieveSharedConfigStoreFile(int)} only if their existing config store
* format or file locations differs from the vanilla AOSP implementation.</li>
* <li>The wifi apex will invoke
* {@link #convertAndRetrieveSharedConfigStoreFile(int)}
* method on every bootup, it is the responsibility of the OEM implementation to ensure that
* they perform the necessary in place conversion of their config store file to conform to the
* AOSP format. The OEM should ensure that the method should only return the
* {@link InputStream} stream for the data to be migrated only on the first bootup.</li>
* <li>Once the migration is done, the apex will invoke
* {@link #removeSharedConfigStoreFile(int)} to delete the store file.</li>
* <li>The only relevant invocation of {@link #convertAndRetrieveSharedConfigStoreFile(int)}
* occurs when a previously released device upgrades to the wifi apex from an OEM
* implementation of the wifi stack.
* <li>Ensure that the legacy file paths are accessible to the wifi module (sepolicy rules, file
* permissions, etc). Since the wifi service continues to run inside system_server process, this
* method will be called from the same context (so ideally the file should still be accessible).
* </li>
*
* @param storeFileId Identifier for the config store file. One of
* {@link #STORE_FILE_SHARED_GENERAL} or {@link #STORE_FILE_SHARED_GENERAL}
* @return Instance of {@link InputStream} for migrating data, null if no migration is
* necessary.
* @throws IllegalArgumentException on invalid storeFileId.
*/
@Nullable
public static InputStream convertAndRetrieveSharedConfigStoreFile(
@SharedStoreFileId int storeFileId) {
if (storeFileId != STORE_FILE_SHARED_GENERAL && storeFileId != STORE_FILE_SHARED_SOFTAP) {
throw new IllegalArgumentException("Invalid shared store file id");
}
try {
// OEMs should do conversions necessary here before returning the stream.
return getSharedAtomicFile(storeFileId).openRead();
} catch (FileNotFoundException e) {
// Special handling for softap.conf.
// Note: OEM devices upgrading from Q -> R will only have the softap.conf file.
// Test devices running previous R builds however may have already migrated to the
// XML format. So, check for that above before falling back to check for legacy file.
if (storeFileId == STORE_FILE_SHARED_SOFTAP) {
return SoftApConfToXmlMigrationUtil.convert();
}
return null;
}
}
/**
* Remove the legacy shared wifi config store file.
*
* @param storeFileId Identifier for the config store file. One of
* {@link #STORE_FILE_SHARED_GENERAL} or {@link #STORE_FILE_SHARED_GENERAL}
* @throws IllegalArgumentException on invalid storeFileId.
*/
public static void removeSharedConfigStoreFile(@SharedStoreFileId int storeFileId) {
if (storeFileId != STORE_FILE_SHARED_GENERAL && storeFileId != STORE_FILE_SHARED_SOFTAP) {
throw new IllegalArgumentException("Invalid shared store file id");
}
AtomicFile file = getSharedAtomicFile(storeFileId);
if (file.exists()) {
file.delete();
return;
}
// Special handling for softap.conf.
// Note: OEM devices upgrading from Q -> R will only have the softap.conf file.
// Test devices running previous R builds however may have already migrated to the
// XML format. So, check for that above before falling back to check for legacy file.
if (storeFileId == STORE_FILE_SHARED_SOFTAP) {
SoftApConfToXmlMigrationUtil.remove();
}
}
/**
* Load data from legacy user wifi config store file.
* <p>
* Expected AOSP format is available in the sample files under {@code /frameworks/base/wifi/
* java/android/net/wifi/migration_samples}.
* </p>
* <p>
* Note:
* <li>OEMs need to change the implementation of
* {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)} only if their existing config
* store format or file locations differs from the vanilla AOSP implementation.</li>
* <li>The wifi apex will invoke
* {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)}
* method on every bootup, it is the responsibility of the OEM implementation to ensure that
* they perform the necessary in place conversion of their config store file to conform to the
* AOSP format. The OEM should ensure that the method should only return the
* {@link InputStream} stream for the data to be migrated only on the first bootup.</li>
* <li>Once the migration is done, the apex will invoke
* {@link #removeUserConfigStoreFile(int, UserHandle)} to delete the store file.</li>
* <li>The only relevant invocation of
* {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)} occurs when a previously
* released device upgrades to the wifi apex from an OEM implementation of the wifi
* stack.
* </li>
* <li>Ensure that the legacy file paths are accessible to the wifi module (sepolicy rules, file
* permissions, etc). Since the wifi service continues to run inside system_server process, this
* method will be called from the same context (so ideally the file should still be accessible).
* </li>
*
* @param storeFileId Identifier for the config store file. One of
* {@link #STORE_FILE_USER_GENERAL} or {@link #STORE_FILE_USER_NETWORK_SUGGESTIONS}
* @param userHandle User handle.
* @return Instance of {@link InputStream} for migrating data, null if no migration is
* necessary.
* @throws IllegalArgumentException on invalid storeFileId or userHandle.
*/
@Nullable
public static InputStream convertAndRetrieveUserConfigStoreFile(
@UserStoreFileId int storeFileId, @NonNull UserHandle userHandle) {
if (storeFileId != STORE_FILE_USER_GENERAL
&& storeFileId != STORE_FILE_USER_NETWORK_SUGGESTIONS) {
throw new IllegalArgumentException("Invalid user store file id");
}
Objects.requireNonNull(userHandle);
try {
// OEMs should do conversions necessary here before returning the stream.
return getUserAtomicFile(storeFileId, userHandle.getIdentifier()).openRead();
} catch (FileNotFoundException e) {
return null;
}
}
/**
* Remove the legacy user wifi config store file.
*
* @param storeFileId Identifier for the config store file. One of
* {@link #STORE_FILE_USER_GENERAL} or {@link #STORE_FILE_USER_NETWORK_SUGGESTIONS}
* @param userHandle User handle.
* @throws IllegalArgumentException on invalid storeFileId or userHandle.
*/
public static void removeUserConfigStoreFile(
@UserStoreFileId int storeFileId, @NonNull UserHandle userHandle) {
if (storeFileId != STORE_FILE_USER_GENERAL
&& storeFileId != STORE_FILE_USER_NETWORK_SUGGESTIONS) {
throw new IllegalArgumentException("Invalid user store file id");
}
Objects.requireNonNull(userHandle);
AtomicFile file = getUserAtomicFile(storeFileId, userHandle.getIdentifier());
if (file.exists()) {
file.delete();
}
}
/**
* Container for all the wifi settings data to migrate.
*/
public static final class SettingsMigrationData implements Parcelable {
private final boolean mScanAlwaysAvailable;
private final boolean mP2pFactoryResetPending;
private final String mP2pDeviceName;
private final boolean mSoftApTimeoutEnabled;
private final boolean mWakeupEnabled;
private final boolean mScanThrottleEnabled;
private final boolean mVerboseLoggingEnabled;
private SettingsMigrationData(boolean scanAlwaysAvailable, boolean p2pFactoryResetPending,
@Nullable String p2pDeviceName, boolean softApTimeoutEnabled, boolean wakeupEnabled,
boolean scanThrottleEnabled, boolean verboseLoggingEnabled) {
mScanAlwaysAvailable = scanAlwaysAvailable;
mP2pFactoryResetPending = p2pFactoryResetPending;
mP2pDeviceName = p2pDeviceName;
mSoftApTimeoutEnabled = softApTimeoutEnabled;
mWakeupEnabled = wakeupEnabled;
mScanThrottleEnabled = scanThrottleEnabled;
mVerboseLoggingEnabled = verboseLoggingEnabled;
}
public static final @NonNull Parcelable.Creator<SettingsMigrationData> CREATOR =
new Parcelable.Creator<SettingsMigrationData>() {
@Override
public SettingsMigrationData createFromParcel(Parcel in) {
boolean scanAlwaysAvailable = in.readBoolean();
boolean p2pFactoryResetPending = in.readBoolean();
String p2pDeviceName = in.readString();
boolean softApTimeoutEnabled = in.readBoolean();
boolean wakeupEnabled = in.readBoolean();
boolean scanThrottleEnabled = in.readBoolean();
boolean verboseLoggingEnabled = in.readBoolean();
return new SettingsMigrationData(
scanAlwaysAvailable, p2pFactoryResetPending,
p2pDeviceName, softApTimeoutEnabled, wakeupEnabled,
scanThrottleEnabled, verboseLoggingEnabled);
}
@Override
public SettingsMigrationData[] newArray(int size) {
return new SettingsMigrationData[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeBoolean(mScanAlwaysAvailable);
dest.writeBoolean(mP2pFactoryResetPending);
dest.writeString(mP2pDeviceName);
dest.writeBoolean(mSoftApTimeoutEnabled);
dest.writeBoolean(mWakeupEnabled);
dest.writeBoolean(mScanThrottleEnabled);
dest.writeBoolean(mVerboseLoggingEnabled);
}
/**
* @return True if scans are allowed even when wifi is toggled off, false otherwise.
*/
public boolean isScanAlwaysAvailable() {
return mScanAlwaysAvailable;
}
/**
* @return indicate whether factory reset request is pending.
*/
public boolean isP2pFactoryResetPending() {
return mP2pFactoryResetPending;
}
/**
* @return the Wi-Fi peer-to-peer device name
*/
public @Nullable String getP2pDeviceName() {
return mP2pDeviceName;
}
/**
* @return Whether soft AP will shut down after a timeout period when no devices are
* connected.
*/
public boolean isSoftApTimeoutEnabled() {
return mSoftApTimeoutEnabled;
}
/**
* @return whether Wi-Fi Wakeup feature is enabled.
*/
public boolean isWakeUpEnabled() {
return mWakeupEnabled;
}
/**
* @return Whether wifi scan throttle is enabled or not.
*/
public boolean isScanThrottleEnabled() {
return mScanThrottleEnabled;
}
/**
* @return Whether to enable verbose logging in Wi-Fi.
*/
public boolean isVerboseLoggingEnabled() {
return mVerboseLoggingEnabled;
}
/**
* Builder to create instance of {@link SettingsMigrationData}.
*/
public static final class Builder {
private boolean mScanAlwaysAvailable;
private boolean mP2pFactoryResetPending;
private String mP2pDeviceName;
private boolean mSoftApTimeoutEnabled;
private boolean mWakeupEnabled;
private boolean mScanThrottleEnabled;
private boolean mVerboseLoggingEnabled;
public Builder() {
}
/**
* Setting to allow scans even when wifi is toggled off.
*
* @param available true if available, false otherwise.
* @return Instance of {@link Builder} to enable chaining of the builder method.
*/
public @NonNull Builder setScanAlwaysAvailable(boolean available) {
mScanAlwaysAvailable = available;
return this;
}
/**
* Indicate whether factory reset request is pending.
*
* @param pending true if pending, false otherwise.
* @return Instance of {@link Builder} to enable chaining of the builder method.
*/
public @NonNull Builder setP2pFactoryResetPending(boolean pending) {
mP2pFactoryResetPending = pending;
return this;
}
/**
* The Wi-Fi peer-to-peer device name
*
* @param name Name if set, null otherwise.
* @return Instance of {@link Builder} to enable chaining of the builder method.
*/
public @NonNull Builder setP2pDeviceName(@Nullable String name) {
mP2pDeviceName = name;
return this;
}
/**
* Whether soft AP will shut down after a timeout period when no devices are connected.
*
* @param enabled true if enabled, false otherwise.
* @return Instance of {@link Builder} to enable chaining of the builder method.
*/
public @NonNull Builder setSoftApTimeoutEnabled(boolean enabled) {
mSoftApTimeoutEnabled = enabled;
return this;
}
/**
* Value to specify if Wi-Fi Wakeup feature is enabled.
*
* @param enabled true if enabled, false otherwise.
* @return Instance of {@link Builder} to enable chaining of the builder method.
*/
public @NonNull Builder setWakeUpEnabled(boolean enabled) {
mWakeupEnabled = enabled;
return this;
}
/**
* Whether wifi scan throttle is enabled or not.
*
* @param enabled true if enabled, false otherwise.
* @return Instance of {@link Builder} to enable chaining of the builder method.
*/
public @NonNull Builder setScanThrottleEnabled(boolean enabled) {
mScanThrottleEnabled = enabled;
return this;
}
/**
* Setting to enable verbose logging in Wi-Fi.
*
* @param enabled true if enabled, false otherwise.
* @return Instance of {@link Builder} to enable chaining of the builder method.
*/
public @NonNull Builder setVerboseLoggingEnabled(boolean enabled) {
mVerboseLoggingEnabled = enabled;
return this;
}
/**
* Build an instance of {@link SettingsMigrationData}.
*
* @return Instance of {@link SettingsMigrationData}.
*/
public @NonNull SettingsMigrationData build() {
return new SettingsMigrationData(mScanAlwaysAvailable, mP2pFactoryResetPending,
mP2pDeviceName, mSoftApTimeoutEnabled, mWakeupEnabled, mScanThrottleEnabled,
mVerboseLoggingEnabled);
}
}
}
/**
* Load data from Settings.Global values.
*
* <p>
* Note:
* <li> This is method is invoked once on the first bootup. OEM can safely delete these settings
* once the migration is complete. The first & only relevant invocation of
* {@link #loadFromSettings(Context)} ()} occurs when a previously released
* device upgrades to the wifi apex from an OEM implementation of the wifi stack.
* </li>
*
* @param context Context to use for loading the settings provider.
* @return Instance of {@link SettingsMigrationData} for migrating data.
*/
@NonNull
public static SettingsMigrationData loadFromSettings(@NonNull Context context) {
if (Settings.Global.getInt(
context.getContentResolver(), Settings.Global.WIFI_MIGRATION_COMPLETED, 0) == 1) {
// migration already complete, ignore.
return null;
}
SettingsMigrationData data = new SettingsMigrationData.Builder()
.setScanAlwaysAvailable(
Settings.Global.getInt(context.getContentResolver(),
Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1)
.setP2pFactoryResetPending(
Settings.Global.getInt(context.getContentResolver(),
Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET, 0) == 1)
.setP2pDeviceName(
Settings.Global.getString(context.getContentResolver(),
Settings.Global.WIFI_P2P_DEVICE_NAME))
.setSoftApTimeoutEnabled(
Settings.Global.getInt(context.getContentResolver(),
Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1) == 1)
.setWakeUpEnabled(
Settings.Global.getInt(context.getContentResolver(),
Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1)
.setScanThrottleEnabled(
Settings.Global.getInt(context.getContentResolver(),
Settings.Global.WIFI_SCAN_THROTTLE_ENABLED, 1) == 1)
.setVerboseLoggingEnabled(
Settings.Global.getInt(context.getContentResolver(),
Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0) == 1)
.build();
Settings.Global.putInt(
context.getContentResolver(), Settings.Global.WIFI_MIGRATION_COMPLETED, 1);
return data;
}
}