| /* |
| * 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; |
| |
| } |
| } |