/* * 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//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//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 STORE_ID_TO_FILE_NAME = new SparseArray() {{ 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. *

* Expected AOSP format is available in the sample files under {@code * frameworks/base/wifi/non-updatable/migration_samples/}. *

*

* Note: *

  • 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.
  • *
  • 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.
  • *
  • Once the migration is done, the apex will invoke * {@link #removeSharedConfigStoreFile(int)} to delete the store file.
  • *
  • 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. *
  • 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). *
  • * * @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. *

    * Expected AOSP format is available in the sample files under {@code * frameworks/base/wifi/non-updatable/migration_samples/}. *

    *

    * Note: *

  • 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.
  • *
  • 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.
  • *
  • Once the migration is done, the apex will invoke * {@link #removeUserConfigStoreFile(int, UserHandle)} to delete the store file.
  • *
  • 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. *
  • *
  • 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). *
  • * * @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 CREATOR = new Parcelable.Creator() { @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. * *

    * Note: *

  • 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. *
  • * * @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; } }