1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.wifi; 18 19 import static android.os.Environment.getDataMiscCeDirectory; 20 import static android.os.Environment.getDataMiscDirectory; 21 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.SystemApi; 26 import android.content.Context; 27 import android.os.Parcel; 28 import android.os.Parcelable; 29 import android.os.UserHandle; 30 import android.provider.Settings; 31 import android.util.AtomicFile; 32 import android.util.SparseArray; 33 34 import java.io.File; 35 import java.io.FileNotFoundException; 36 import java.io.InputStream; 37 import java.lang.annotation.Retention; 38 import java.lang.annotation.RetentionPolicy; 39 import java.util.Objects; 40 41 /** 42 * Class used to provide one time hooks for existing OEM devices to migrate their config store 43 * data and other settings to the wifi apex. 44 * @hide 45 */ 46 @SystemApi 47 public final class WifiMigration { 48 /** 49 * Directory to read the wifi config store files from under. 50 */ 51 private static final String LEGACY_WIFI_STORE_DIRECTORY_NAME = "wifi"; 52 /** 53 * Config store file for general shared store file. 54 * AOSP Path on Android 10: /data/misc/wifi/WifiConfigStore.xml 55 */ 56 public static final int STORE_FILE_SHARED_GENERAL = 0; 57 /** 58 * Config store file for softap shared store file. 59 * AOSP Path on Android 10: /data/misc/wifi/softap.conf 60 */ 61 public static final int STORE_FILE_SHARED_SOFTAP = 1; 62 /** 63 * Config store file for general user store file. 64 * AOSP Path on Android 10: /data/misc_ce/<userId>/wifi/WifiConfigStore.xml 65 */ 66 public static final int STORE_FILE_USER_GENERAL = 2; 67 /** 68 * Config store file for network suggestions user store file. 69 * AOSP Path on Android 10: /data/misc_ce/<userId>/wifi/WifiConfigStoreNetworkSuggestions.xml 70 */ 71 public static final int STORE_FILE_USER_NETWORK_SUGGESTIONS = 3; 72 73 /** @hide */ 74 @IntDef(prefix = { "STORE_FILE_SHARED_" }, value = { 75 STORE_FILE_SHARED_GENERAL, 76 STORE_FILE_SHARED_SOFTAP, 77 }) 78 @Retention(RetentionPolicy.SOURCE) 79 public @interface SharedStoreFileId { } 80 81 /** @hide */ 82 @IntDef(prefix = { "STORE_FILE_USER_" }, value = { 83 STORE_FILE_USER_GENERAL, 84 STORE_FILE_USER_NETWORK_SUGGESTIONS 85 }) 86 @Retention(RetentionPolicy.SOURCE) 87 public @interface UserStoreFileId { } 88 89 /** 90 * Mapping of Store file Id to Store file names. 91 * 92 * NOTE: This is the default path for the files on AOSP devices. If the OEM has modified 93 * the path or renamed the files, please edit this appropriately. 94 */ 95 private static final SparseArray<String> STORE_ID_TO_FILE_NAME = 96 new SparseArray<String>() {{ 97 put(STORE_FILE_SHARED_GENERAL, "WifiConfigStore.xml"); 98 put(STORE_FILE_SHARED_SOFTAP, "WifiConfigStoreSoftAp.xml"); 99 put(STORE_FILE_USER_GENERAL, "WifiConfigStore.xml"); 100 put(STORE_FILE_USER_NETWORK_SUGGESTIONS, "WifiConfigStoreNetworkSuggestions.xml"); 101 }}; 102 103 /** 104 * Pre-apex wifi shared folder. 105 */ getLegacyWifiSharedDirectory()106 private static File getLegacyWifiSharedDirectory() { 107 return new File(getDataMiscDirectory(), LEGACY_WIFI_STORE_DIRECTORY_NAME); 108 } 109 110 /** 111 * Pre-apex wifi user folder. 112 */ getLegacyWifiUserDirectory(int userId)113 private static File getLegacyWifiUserDirectory(int userId) { 114 return new File(getDataMiscCeDirectory(userId), LEGACY_WIFI_STORE_DIRECTORY_NAME); 115 } 116 117 /** 118 * Legacy files were stored as AtomicFile. So, always use AtomicFile to operate on it to ensure 119 * data integrity. 120 */ getSharedAtomicFile(@haredStoreFileId int storeFileId)121 private static AtomicFile getSharedAtomicFile(@SharedStoreFileId int storeFileId) { 122 return new AtomicFile(new File( 123 getLegacyWifiSharedDirectory(), 124 STORE_ID_TO_FILE_NAME.get(storeFileId))); 125 } 126 127 /** 128 * Legacy files were stored as AtomicFile. So, always use AtomicFile to operate on it to ensure 129 * data integrity. 130 */ getUserAtomicFile(@serStoreFileId int storeFileId, int userId)131 private static AtomicFile getUserAtomicFile(@UserStoreFileId int storeFileId, int userId) { 132 return new AtomicFile(new File( 133 getLegacyWifiUserDirectory(userId), 134 STORE_ID_TO_FILE_NAME.get(storeFileId))); 135 } 136 WifiMigration()137 private WifiMigration() { } 138 139 /** 140 * Load data from legacy shared wifi config store file. 141 * <p> 142 * Expected AOSP format is available in the sample files under {@code /frameworks/base/wifi/ 143 * java/android/net/wifi/migration_samples}. 144 * </p> 145 * <p> 146 * Note: 147 * <li>OEMs need to change the implementation of 148 * {@link #convertAndRetrieveSharedConfigStoreFile(int)} only if their existing config store 149 * format or file locations differs from the vanilla AOSP implementation.</li> 150 * <li>The wifi apex will invoke 151 * {@link #convertAndRetrieveSharedConfigStoreFile(int)} 152 * method on every bootup, it is the responsibility of the OEM implementation to ensure that 153 * they perform the necessary in place conversion of their config store file to conform to the 154 * AOSP format. The OEM should ensure that the method should only return the 155 * {@link InputStream} stream for the data to be migrated only on the first bootup.</li> 156 * <li>Once the migration is done, the apex will invoke 157 * {@link #removeSharedConfigStoreFile(int)} to delete the store file.</li> 158 * <li>The only relevant invocation of {@link #convertAndRetrieveSharedConfigStoreFile(int)} 159 * occurs when a previously released device upgrades to the wifi apex from an OEM 160 * implementation of the wifi stack. 161 * <li>Ensure that the legacy file paths are accessible to the wifi module (sepolicy rules, file 162 * permissions, etc). Since the wifi service continues to run inside system_server process, this 163 * method will be called from the same context (so ideally the file should still be accessible). 164 * </li> 165 * 166 * @param storeFileId Identifier for the config store file. One of 167 * {@link #STORE_FILE_SHARED_GENERAL} or {@link #STORE_FILE_SHARED_GENERAL} 168 * @return Instance of {@link InputStream} for migrating data, null if no migration is 169 * necessary. 170 * @throws IllegalArgumentException on invalid storeFileId. 171 */ 172 @Nullable convertAndRetrieveSharedConfigStoreFile( @haredStoreFileId int storeFileId)173 public static InputStream convertAndRetrieveSharedConfigStoreFile( 174 @SharedStoreFileId int storeFileId) { 175 if (storeFileId != STORE_FILE_SHARED_GENERAL && storeFileId != STORE_FILE_SHARED_SOFTAP) { 176 throw new IllegalArgumentException("Invalid shared store file id"); 177 } 178 try { 179 // OEMs should do conversions necessary here before returning the stream. 180 return getSharedAtomicFile(storeFileId).openRead(); 181 } catch (FileNotFoundException e) { 182 // Special handling for softap.conf. 183 // Note: OEM devices upgrading from Q -> R will only have the softap.conf file. 184 // Test devices running previous R builds however may have already migrated to the 185 // XML format. So, check for that above before falling back to check for legacy file. 186 if (storeFileId == STORE_FILE_SHARED_SOFTAP) { 187 return SoftApConfToXmlMigrationUtil.convert(); 188 } 189 return null; 190 } 191 } 192 193 /** 194 * Remove the legacy shared wifi config store file. 195 * 196 * @param storeFileId Identifier for the config store file. One of 197 * {@link #STORE_FILE_SHARED_GENERAL} or {@link #STORE_FILE_SHARED_GENERAL} 198 * @throws IllegalArgumentException on invalid storeFileId. 199 */ removeSharedConfigStoreFile(@haredStoreFileId int storeFileId)200 public static void removeSharedConfigStoreFile(@SharedStoreFileId int storeFileId) { 201 if (storeFileId != STORE_FILE_SHARED_GENERAL && storeFileId != STORE_FILE_SHARED_SOFTAP) { 202 throw new IllegalArgumentException("Invalid shared store file id"); 203 } 204 AtomicFile file = getSharedAtomicFile(storeFileId); 205 if (file.exists()) { 206 file.delete(); 207 return; 208 } 209 // Special handling for softap.conf. 210 // Note: OEM devices upgrading from Q -> R will only have the softap.conf file. 211 // Test devices running previous R builds however may have already migrated to the 212 // XML format. So, check for that above before falling back to check for legacy file. 213 if (storeFileId == STORE_FILE_SHARED_SOFTAP) { 214 SoftApConfToXmlMigrationUtil.remove(); 215 } 216 } 217 218 /** 219 * Load data from legacy user wifi config store file. 220 * <p> 221 * Expected AOSP format is available in the sample files under {@code /frameworks/base/wifi/ 222 * java/android/net/wifi/migration_samples}. 223 * </p> 224 * <p> 225 * Note: 226 * <li>OEMs need to change the implementation of 227 * {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)} only if their existing config 228 * store format or file locations differs from the vanilla AOSP implementation.</li> 229 * <li>The wifi apex will invoke 230 * {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)} 231 * method on every bootup, it is the responsibility of the OEM implementation to ensure that 232 * they perform the necessary in place conversion of their config store file to conform to the 233 * AOSP format. The OEM should ensure that the method should only return the 234 * {@link InputStream} stream for the data to be migrated only on the first bootup.</li> 235 * <li>Once the migration is done, the apex will invoke 236 * {@link #removeUserConfigStoreFile(int, UserHandle)} to delete the store file.</li> 237 * <li>The only relevant invocation of 238 * {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)} occurs when a previously 239 * released device upgrades to the wifi apex from an OEM implementation of the wifi 240 * stack. 241 * </li> 242 * <li>Ensure that the legacy file paths are accessible to the wifi module (sepolicy rules, file 243 * permissions, etc). Since the wifi service continues to run inside system_server process, this 244 * method will be called from the same context (so ideally the file should still be accessible). 245 * </li> 246 * 247 * @param storeFileId Identifier for the config store file. One of 248 * {@link #STORE_FILE_USER_GENERAL} or {@link #STORE_FILE_USER_NETWORK_SUGGESTIONS} 249 * @param userHandle User handle. 250 * @return Instance of {@link InputStream} for migrating data, null if no migration is 251 * necessary. 252 * @throws IllegalArgumentException on invalid storeFileId or userHandle. 253 */ 254 @Nullable convertAndRetrieveUserConfigStoreFile( @serStoreFileId int storeFileId, @NonNull UserHandle userHandle)255 public static InputStream convertAndRetrieveUserConfigStoreFile( 256 @UserStoreFileId int storeFileId, @NonNull UserHandle userHandle) { 257 if (storeFileId != STORE_FILE_USER_GENERAL 258 && storeFileId != STORE_FILE_USER_NETWORK_SUGGESTIONS) { 259 throw new IllegalArgumentException("Invalid user store file id"); 260 } 261 Objects.requireNonNull(userHandle); 262 try { 263 // OEMs should do conversions necessary here before returning the stream. 264 return getUserAtomicFile(storeFileId, userHandle.getIdentifier()).openRead(); 265 } catch (FileNotFoundException e) { 266 return null; 267 } 268 } 269 270 /** 271 * Remove the legacy user wifi config store file. 272 * 273 * @param storeFileId Identifier for the config store file. One of 274 * {@link #STORE_FILE_USER_GENERAL} or {@link #STORE_FILE_USER_NETWORK_SUGGESTIONS} 275 * @param userHandle User handle. 276 * @throws IllegalArgumentException on invalid storeFileId or userHandle. 277 */ removeUserConfigStoreFile( @serStoreFileId int storeFileId, @NonNull UserHandle userHandle)278 public static void removeUserConfigStoreFile( 279 @UserStoreFileId int storeFileId, @NonNull UserHandle userHandle) { 280 if (storeFileId != STORE_FILE_USER_GENERAL 281 && storeFileId != STORE_FILE_USER_NETWORK_SUGGESTIONS) { 282 throw new IllegalArgumentException("Invalid user store file id"); 283 } 284 Objects.requireNonNull(userHandle); 285 AtomicFile file = getUserAtomicFile(storeFileId, userHandle.getIdentifier()); 286 if (file.exists()) { 287 file.delete(); 288 } 289 } 290 291 /** 292 * Container for all the wifi settings data to migrate. 293 */ 294 public static final class SettingsMigrationData implements Parcelable { 295 private final boolean mScanAlwaysAvailable; 296 private final boolean mP2pFactoryResetPending; 297 private final String mP2pDeviceName; 298 private final boolean mSoftApTimeoutEnabled; 299 private final boolean mWakeupEnabled; 300 private final boolean mScanThrottleEnabled; 301 private final boolean mVerboseLoggingEnabled; 302 SettingsMigrationData(boolean scanAlwaysAvailable, boolean p2pFactoryResetPending, @Nullable String p2pDeviceName, boolean softApTimeoutEnabled, boolean wakeupEnabled, boolean scanThrottleEnabled, boolean verboseLoggingEnabled)303 private SettingsMigrationData(boolean scanAlwaysAvailable, boolean p2pFactoryResetPending, 304 @Nullable String p2pDeviceName, boolean softApTimeoutEnabled, boolean wakeupEnabled, 305 boolean scanThrottleEnabled, boolean verboseLoggingEnabled) { 306 mScanAlwaysAvailable = scanAlwaysAvailable; 307 mP2pFactoryResetPending = p2pFactoryResetPending; 308 mP2pDeviceName = p2pDeviceName; 309 mSoftApTimeoutEnabled = softApTimeoutEnabled; 310 mWakeupEnabled = wakeupEnabled; 311 mScanThrottleEnabled = scanThrottleEnabled; 312 mVerboseLoggingEnabled = verboseLoggingEnabled; 313 } 314 315 public static final @NonNull Parcelable.Creator<SettingsMigrationData> CREATOR = 316 new Parcelable.Creator<SettingsMigrationData>() { 317 @Override 318 public SettingsMigrationData createFromParcel(Parcel in) { 319 boolean scanAlwaysAvailable = in.readBoolean(); 320 boolean p2pFactoryResetPending = in.readBoolean(); 321 String p2pDeviceName = in.readString(); 322 boolean softApTimeoutEnabled = in.readBoolean(); 323 boolean wakeupEnabled = in.readBoolean(); 324 boolean scanThrottleEnabled = in.readBoolean(); 325 boolean verboseLoggingEnabled = in.readBoolean(); 326 return new SettingsMigrationData( 327 scanAlwaysAvailable, p2pFactoryResetPending, 328 p2pDeviceName, softApTimeoutEnabled, wakeupEnabled, 329 scanThrottleEnabled, verboseLoggingEnabled); 330 } 331 332 @Override 333 public SettingsMigrationData[] newArray(int size) { 334 return new SettingsMigrationData[size]; 335 } 336 }; 337 338 @Override describeContents()339 public int describeContents() { 340 return 0; 341 } 342 343 @Override writeToParcel(@onNull Parcel dest, int flags)344 public void writeToParcel(@NonNull Parcel dest, int flags) { 345 dest.writeBoolean(mScanAlwaysAvailable); 346 dest.writeBoolean(mP2pFactoryResetPending); 347 dest.writeString(mP2pDeviceName); 348 dest.writeBoolean(mSoftApTimeoutEnabled); 349 dest.writeBoolean(mWakeupEnabled); 350 dest.writeBoolean(mScanThrottleEnabled); 351 dest.writeBoolean(mVerboseLoggingEnabled); 352 } 353 354 /** 355 * @return True if scans are allowed even when wifi is toggled off, false otherwise. 356 */ isScanAlwaysAvailable()357 public boolean isScanAlwaysAvailable() { 358 return mScanAlwaysAvailable; 359 } 360 361 /** 362 * @return indicate whether factory reset request is pending. 363 */ isP2pFactoryResetPending()364 public boolean isP2pFactoryResetPending() { 365 return mP2pFactoryResetPending; 366 } 367 368 /** 369 * @return the Wi-Fi peer-to-peer device name 370 */ getP2pDeviceName()371 public @Nullable String getP2pDeviceName() { 372 return mP2pDeviceName; 373 } 374 375 /** 376 * @return Whether soft AP will shut down after a timeout period when no devices are 377 * connected. 378 */ isSoftApTimeoutEnabled()379 public boolean isSoftApTimeoutEnabled() { 380 return mSoftApTimeoutEnabled; 381 } 382 383 /** 384 * @return whether Wi-Fi Wakeup feature is enabled. 385 */ isWakeUpEnabled()386 public boolean isWakeUpEnabled() { 387 return mWakeupEnabled; 388 } 389 390 /** 391 * @return Whether wifi scan throttle is enabled or not. 392 */ isScanThrottleEnabled()393 public boolean isScanThrottleEnabled() { 394 return mScanThrottleEnabled; 395 } 396 397 /** 398 * @return Whether to enable verbose logging in Wi-Fi. 399 */ isVerboseLoggingEnabled()400 public boolean isVerboseLoggingEnabled() { 401 return mVerboseLoggingEnabled; 402 } 403 404 /** 405 * Builder to create instance of {@link SettingsMigrationData}. 406 */ 407 public static final class Builder { 408 private boolean mScanAlwaysAvailable; 409 private boolean mP2pFactoryResetPending; 410 private String mP2pDeviceName; 411 private boolean mSoftApTimeoutEnabled; 412 private boolean mWakeupEnabled; 413 private boolean mScanThrottleEnabled; 414 private boolean mVerboseLoggingEnabled; 415 Builder()416 public Builder() { 417 } 418 419 /** 420 * Setting to allow scans even when wifi is toggled off. 421 * 422 * @param available true if available, false otherwise. 423 * @return Instance of {@link Builder} to enable chaining of the builder method. 424 */ setScanAlwaysAvailable(boolean available)425 public @NonNull Builder setScanAlwaysAvailable(boolean available) { 426 mScanAlwaysAvailable = available; 427 return this; 428 } 429 430 /** 431 * Indicate whether factory reset request is pending. 432 * 433 * @param pending true if pending, false otherwise. 434 * @return Instance of {@link Builder} to enable chaining of the builder method. 435 */ setP2pFactoryResetPending(boolean pending)436 public @NonNull Builder setP2pFactoryResetPending(boolean pending) { 437 mP2pFactoryResetPending = pending; 438 return this; 439 } 440 441 /** 442 * The Wi-Fi peer-to-peer device name 443 * 444 * @param name Name if set, null otherwise. 445 * @return Instance of {@link Builder} to enable chaining of the builder method. 446 */ setP2pDeviceName(@ullable String name)447 public @NonNull Builder setP2pDeviceName(@Nullable String name) { 448 mP2pDeviceName = name; 449 return this; 450 } 451 452 /** 453 * Whether soft AP will shut down after a timeout period when no devices are connected. 454 * 455 * @param enabled true if enabled, false otherwise. 456 * @return Instance of {@link Builder} to enable chaining of the builder method. 457 */ setSoftApTimeoutEnabled(boolean enabled)458 public @NonNull Builder setSoftApTimeoutEnabled(boolean enabled) { 459 mSoftApTimeoutEnabled = enabled; 460 return this; 461 } 462 463 /** 464 * Value to specify if Wi-Fi Wakeup feature is enabled. 465 * 466 * @param enabled true if enabled, false otherwise. 467 * @return Instance of {@link Builder} to enable chaining of the builder method. 468 */ setWakeUpEnabled(boolean enabled)469 public @NonNull Builder setWakeUpEnabled(boolean enabled) { 470 mWakeupEnabled = enabled; 471 return this; 472 } 473 474 /** 475 * Whether wifi scan throttle is enabled or not. 476 * 477 * @param enabled true if enabled, false otherwise. 478 * @return Instance of {@link Builder} to enable chaining of the builder method. 479 */ setScanThrottleEnabled(boolean enabled)480 public @NonNull Builder setScanThrottleEnabled(boolean enabled) { 481 mScanThrottleEnabled = enabled; 482 return this; 483 } 484 485 /** 486 * Setting to enable verbose logging in Wi-Fi. 487 * 488 * @param enabled true if enabled, false otherwise. 489 * @return Instance of {@link Builder} to enable chaining of the builder method. 490 */ setVerboseLoggingEnabled(boolean enabled)491 public @NonNull Builder setVerboseLoggingEnabled(boolean enabled) { 492 mVerboseLoggingEnabled = enabled; 493 return this; 494 } 495 496 /** 497 * Build an instance of {@link SettingsMigrationData}. 498 * 499 * @return Instance of {@link SettingsMigrationData}. 500 */ build()501 public @NonNull SettingsMigrationData build() { 502 return new SettingsMigrationData(mScanAlwaysAvailable, mP2pFactoryResetPending, 503 mP2pDeviceName, mSoftApTimeoutEnabled, mWakeupEnabled, mScanThrottleEnabled, 504 mVerboseLoggingEnabled); 505 } 506 } 507 } 508 509 /** 510 * Load data from Settings.Global values. 511 * 512 * <p> 513 * Note: 514 * <li> This is method is invoked once on the first bootup. OEM can safely delete these settings 515 * once the migration is complete. The first & only relevant invocation of 516 * {@link #loadFromSettings(Context)} ()} occurs when a previously released 517 * device upgrades to the wifi apex from an OEM implementation of the wifi stack. 518 * </li> 519 * 520 * @param context Context to use for loading the settings provider. 521 * @return Instance of {@link SettingsMigrationData} for migrating data. 522 */ 523 @NonNull loadFromSettings(@onNull Context context)524 public static SettingsMigrationData loadFromSettings(@NonNull Context context) { 525 if (Settings.Global.getInt( 526 context.getContentResolver(), Settings.Global.WIFI_MIGRATION_COMPLETED, 0) == 1) { 527 // migration already complete, ignore. 528 return null; 529 } 530 SettingsMigrationData data = new SettingsMigrationData.Builder() 531 .setScanAlwaysAvailable( 532 Settings.Global.getInt(context.getContentResolver(), 533 Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) 534 .setP2pFactoryResetPending( 535 Settings.Global.getInt(context.getContentResolver(), 536 Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET, 0) == 1) 537 .setP2pDeviceName( 538 Settings.Global.getString(context.getContentResolver(), 539 Settings.Global.WIFI_P2P_DEVICE_NAME)) 540 .setSoftApTimeoutEnabled( 541 Settings.Global.getInt(context.getContentResolver(), 542 Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1) == 1) 543 .setWakeUpEnabled( 544 Settings.Global.getInt(context.getContentResolver(), 545 Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1) 546 .setScanThrottleEnabled( 547 Settings.Global.getInt(context.getContentResolver(), 548 Settings.Global.WIFI_SCAN_THROTTLE_ENABLED, 1) == 1) 549 .setVerboseLoggingEnabled( 550 Settings.Global.getInt(context.getContentResolver(), 551 Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0) == 1) 552 .build(); 553 Settings.Global.putInt( 554 context.getContentResolver(), Settings.Global.WIFI_MIGRATION_COMPLETED, 1); 555 return data; 556 557 } 558 } 559