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.FlaggedApi; 23 import android.annotation.IntDef; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.SystemApi; 27 import android.content.Context; 28 import android.net.wifi.flags.Flags; 29 import android.os.Binder; 30 import android.os.Parcel; 31 import android.os.Parcelable; 32 import android.os.Process; 33 import android.os.ServiceSpecificException; 34 import android.os.UserHandle; 35 import android.provider.Settings; 36 import android.security.legacykeystore.ILegacyKeystore; 37 import android.util.AtomicFile; 38 import android.util.Log; 39 import android.util.SparseArray; 40 41 import com.android.internal.os.BackgroundThread; 42 43 import java.io.File; 44 import java.io.FileNotFoundException; 45 import java.io.InputStream; 46 import java.lang.annotation.Retention; 47 import java.lang.annotation.RetentionPolicy; 48 import java.util.Arrays; 49 import java.util.HashSet; 50 import java.util.List; 51 import java.util.Objects; 52 import java.util.Set; 53 import java.util.concurrent.Executor; 54 import java.util.function.IntConsumer; 55 56 /** 57 * Class used to provide one time hooks for existing OEM devices to migrate their config store 58 * data and other settings to the wifi apex. 59 * @hide 60 */ 61 @SystemApi 62 public final class WifiMigration { 63 private static final String TAG = "WifiMigration"; 64 65 /** 66 * Directory to read the wifi config store files from under. 67 */ 68 private static final String LEGACY_WIFI_STORE_DIRECTORY_NAME = "wifi"; 69 /** 70 * Config store file for general shared store file. 71 * AOSP Path on Android 10: /data/misc/wifi/WifiConfigStore.xml 72 */ 73 public static final int STORE_FILE_SHARED_GENERAL = 0; 74 /** 75 * Config store file for softap shared store file. 76 * AOSP Path on Android 10: /data/misc/wifi/softap.conf 77 */ 78 public static final int STORE_FILE_SHARED_SOFTAP = 1; 79 /** 80 * Config store file for general user store file. 81 * AOSP Path on Android 10: /data/misc_ce/<userId>/wifi/WifiConfigStore.xml 82 */ 83 public static final int STORE_FILE_USER_GENERAL = 2; 84 /** 85 * Config store file for network suggestions user store file. 86 * AOSP Path on Android 10: /data/misc_ce/<userId>/wifi/WifiConfigStoreNetworkSuggestions.xml 87 */ 88 public static final int STORE_FILE_USER_NETWORK_SUGGESTIONS = 3; 89 90 /** @hide */ 91 @IntDef(prefix = { "STORE_FILE_SHARED_" }, value = { 92 STORE_FILE_SHARED_GENERAL, 93 STORE_FILE_SHARED_SOFTAP, 94 }) 95 @Retention(RetentionPolicy.SOURCE) 96 public @interface SharedStoreFileId { } 97 98 /** @hide */ 99 @IntDef(prefix = { "STORE_FILE_USER_" }, value = { 100 STORE_FILE_USER_GENERAL, 101 STORE_FILE_USER_NETWORK_SUGGESTIONS 102 }) 103 @Retention(RetentionPolicy.SOURCE) 104 public @interface UserStoreFileId { } 105 106 /** 107 * Keystore migration was completed successfully. 108 * @hide 109 */ 110 @FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION_READ_ONLY) 111 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) 112 public static final int KEYSTORE_MIGRATION_SUCCESS_MIGRATION_COMPLETE = 0; 113 114 /** 115 * Keystore migration was not needed. 116 * @hide 117 */ 118 @FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION_READ_ONLY) 119 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) 120 public static final int KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED = 1; 121 122 /** 123 * Keystore migration failed because an exception was encountered. 124 * @hide 125 */ 126 @FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION_READ_ONLY) 127 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) 128 public static final int KEYSTORE_MIGRATION_FAILURE_ENCOUNTERED_EXCEPTION = 2; 129 130 /** @hide */ 131 @IntDef(prefix = { "KEYSTORE_MIGRATION_" }, value = { 132 KEYSTORE_MIGRATION_SUCCESS_MIGRATION_COMPLETE, 133 KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED, 134 KEYSTORE_MIGRATION_FAILURE_ENCOUNTERED_EXCEPTION 135 }) 136 @Retention(RetentionPolicy.SOURCE) 137 public @interface KeystoreMigrationStatus { } 138 139 /** 140 * Mapping of Store file Id to Store file names. 141 * 142 * NOTE: This is the default path for the files on AOSP devices. If the OEM has modified 143 * the path or renamed the files, please edit this appropriately. 144 */ 145 private static final SparseArray<String> STORE_ID_TO_FILE_NAME = 146 new SparseArray<String>() {{ 147 put(STORE_FILE_SHARED_GENERAL, "WifiConfigStore.xml"); 148 put(STORE_FILE_SHARED_SOFTAP, "WifiConfigStoreSoftAp.xml"); 149 put(STORE_FILE_USER_GENERAL, "WifiConfigStore.xml"); 150 put(STORE_FILE_USER_NETWORK_SUGGESTIONS, "WifiConfigStoreNetworkSuggestions.xml"); 151 }}; 152 153 /** 154 * Pre-apex wifi shared folder. 155 */ getLegacyWifiSharedDirectory()156 private static File getLegacyWifiSharedDirectory() { 157 return new File(getDataMiscDirectory(), LEGACY_WIFI_STORE_DIRECTORY_NAME); 158 } 159 160 /** 161 * Pre-apex wifi user folder. 162 */ getLegacyWifiUserDirectory(int userId)163 private static File getLegacyWifiUserDirectory(int userId) { 164 return new File(getDataMiscCeDirectory(userId), LEGACY_WIFI_STORE_DIRECTORY_NAME); 165 } 166 167 /** 168 * Legacy files were stored as AtomicFile. So, always use AtomicFile to operate on it to ensure 169 * data integrity. 170 */ getSharedAtomicFile(@haredStoreFileId int storeFileId)171 private static AtomicFile getSharedAtomicFile(@SharedStoreFileId int storeFileId) { 172 return new AtomicFile(new File( 173 getLegacyWifiSharedDirectory(), 174 STORE_ID_TO_FILE_NAME.get(storeFileId))); 175 } 176 177 /** 178 * Legacy files were stored as AtomicFile. So, always use AtomicFile to operate on it to ensure 179 * data integrity. 180 */ getUserAtomicFile(@serStoreFileId int storeFileId, int userId)181 private static AtomicFile getUserAtomicFile(@UserStoreFileId int storeFileId, int userId) { 182 return new AtomicFile(new File( 183 getLegacyWifiUserDirectory(userId), 184 STORE_ID_TO_FILE_NAME.get(storeFileId))); 185 } 186 WifiMigration()187 private WifiMigration() { } 188 189 /** 190 * Load data from legacy shared wifi config store file. 191 * <p> 192 * Expected AOSP format is available in the sample files under {@code 193 * frameworks/base/wifi/non-updatable/migration_samples/}. 194 * </p> 195 * <p> 196 * Note: 197 * <li>OEMs need to change the implementation of 198 * {@link #convertAndRetrieveSharedConfigStoreFile(int)} only if their existing config store 199 * format or file locations differs from the vanilla AOSP implementation.</li> 200 * <li>The wifi apex will invoke 201 * {@link #convertAndRetrieveSharedConfigStoreFile(int)} 202 * method on every bootup, it is the responsibility of the OEM implementation to ensure that 203 * they perform the necessary in place conversion of their config store file to conform to the 204 * AOSP format. The OEM should ensure that the method should only return the 205 * {@link InputStream} stream for the data to be migrated only on the first bootup.</li> 206 * <li>Once the migration is done, the apex will invoke 207 * {@link #removeSharedConfigStoreFile(int)} to delete the store file.</li> 208 * <li>The only relevant invocation of {@link #convertAndRetrieveSharedConfigStoreFile(int)} 209 * occurs when a previously released device upgrades to the wifi apex from an OEM 210 * implementation of the wifi stack. 211 * <li>Ensure that the legacy file paths are accessible to the wifi module (sepolicy rules, file 212 * permissions, etc). Since the wifi service continues to run inside system_server process, this 213 * method will be called from the same context (so ideally the file should still be accessible). 214 * </li> 215 * 216 * @param storeFileId Identifier for the config store file. One of 217 * {@link #STORE_FILE_SHARED_GENERAL} or {@link #STORE_FILE_SHARED_GENERAL} 218 * @return Instance of {@link InputStream} for migrating data, null if no migration is 219 * necessary. 220 * @throws IllegalArgumentException on invalid storeFileId. 221 */ 222 @Nullable convertAndRetrieveSharedConfigStoreFile( @haredStoreFileId int storeFileId)223 public static InputStream convertAndRetrieveSharedConfigStoreFile( 224 @SharedStoreFileId int storeFileId) { 225 if (storeFileId != STORE_FILE_SHARED_GENERAL && storeFileId != STORE_FILE_SHARED_SOFTAP) { 226 throw new IllegalArgumentException("Invalid shared store file id"); 227 } 228 try { 229 // OEMs should do conversions necessary here before returning the stream. 230 return getSharedAtomicFile(storeFileId).openRead(); 231 } catch (FileNotFoundException e) { 232 // Special handling for softap.conf. 233 // Note: OEM devices upgrading from Q -> R will only have the softap.conf file. 234 // Test devices running previous R builds however may have already migrated to the 235 // XML format. So, check for that above before falling back to check for legacy file. 236 if (storeFileId == STORE_FILE_SHARED_SOFTAP) { 237 return SoftApConfToXmlMigrationUtil.convert(); 238 } 239 return null; 240 } 241 } 242 243 /** 244 * Remove the legacy shared wifi config store file. 245 * 246 * @param storeFileId Identifier for the config store file. One of 247 * {@link #STORE_FILE_SHARED_GENERAL} or {@link #STORE_FILE_SHARED_GENERAL} 248 * @throws IllegalArgumentException on invalid storeFileId. 249 */ removeSharedConfigStoreFile(@haredStoreFileId int storeFileId)250 public static void removeSharedConfigStoreFile(@SharedStoreFileId int storeFileId) { 251 if (storeFileId != STORE_FILE_SHARED_GENERAL && storeFileId != STORE_FILE_SHARED_SOFTAP) { 252 throw new IllegalArgumentException("Invalid shared store file id"); 253 } 254 AtomicFile file = getSharedAtomicFile(storeFileId); 255 if (file.exists()) { 256 file.delete(); 257 return; 258 } 259 // Special handling for softap.conf. 260 // Note: OEM devices upgrading from Q -> R will only have the softap.conf file. 261 // Test devices running previous R builds however may have already migrated to the 262 // XML format. So, check for that above before falling back to check for legacy file. 263 if (storeFileId == STORE_FILE_SHARED_SOFTAP) { 264 SoftApConfToXmlMigrationUtil.remove(); 265 } 266 } 267 268 /** 269 * Load data from legacy user wifi config store file. 270 * <p> 271 * Expected AOSP format is available in the sample files under {@code 272 * frameworks/base/wifi/non-updatable/migration_samples/}. 273 * </p> 274 * <p> 275 * Note: 276 * <li>OEMs need to change the implementation of 277 * {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)} only if their existing config 278 * store format or file locations differs from the vanilla AOSP implementation.</li> 279 * <li>The wifi apex will invoke 280 * {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)} 281 * method on every bootup, it is the responsibility of the OEM implementation to ensure that 282 * they perform the necessary in place conversion of their config store file to conform to the 283 * AOSP format. The OEM should ensure that the method should only return the 284 * {@link InputStream} stream for the data to be migrated only on the first bootup.</li> 285 * <li>Once the migration is done, the apex will invoke 286 * {@link #removeUserConfigStoreFile(int, UserHandle)} to delete the store file.</li> 287 * <li>The only relevant invocation of 288 * {@link #convertAndRetrieveUserConfigStoreFile(int, UserHandle)} occurs when a previously 289 * released device upgrades to the wifi apex from an OEM implementation of the wifi 290 * stack. 291 * </li> 292 * <li>Ensure that the legacy file paths are accessible to the wifi module (sepolicy rules, file 293 * permissions, etc). Since the wifi service continues to run inside system_server process, this 294 * method will be called from the same context (so ideally the file should still be accessible). 295 * </li> 296 * 297 * @param storeFileId Identifier for the config store file. One of 298 * {@link #STORE_FILE_USER_GENERAL} or {@link #STORE_FILE_USER_NETWORK_SUGGESTIONS} 299 * @param userHandle User handle. 300 * @return Instance of {@link InputStream} for migrating data, null if no migration is 301 * necessary. 302 * @throws IllegalArgumentException on invalid storeFileId or userHandle. 303 */ 304 @Nullable convertAndRetrieveUserConfigStoreFile( @serStoreFileId int storeFileId, @NonNull UserHandle userHandle)305 public static InputStream convertAndRetrieveUserConfigStoreFile( 306 @UserStoreFileId int storeFileId, @NonNull UserHandle userHandle) { 307 if (storeFileId != STORE_FILE_USER_GENERAL 308 && storeFileId != STORE_FILE_USER_NETWORK_SUGGESTIONS) { 309 throw new IllegalArgumentException("Invalid user store file id"); 310 } 311 Objects.requireNonNull(userHandle); 312 try { 313 // OEMs should do conversions necessary here before returning the stream. 314 return getUserAtomicFile(storeFileId, userHandle.getIdentifier()).openRead(); 315 } catch (FileNotFoundException e) { 316 return null; 317 } 318 } 319 320 /** 321 * Remove the legacy user wifi config store file. 322 * 323 * @param storeFileId Identifier for the config store file. One of 324 * {@link #STORE_FILE_USER_GENERAL} or {@link #STORE_FILE_USER_NETWORK_SUGGESTIONS} 325 * @param userHandle User handle. 326 * @throws IllegalArgumentException on invalid storeFileId or userHandle. 327 */ removeUserConfigStoreFile( @serStoreFileId int storeFileId, @NonNull UserHandle userHandle)328 public static void removeUserConfigStoreFile( 329 @UserStoreFileId int storeFileId, @NonNull UserHandle userHandle) { 330 if (storeFileId != STORE_FILE_USER_GENERAL 331 && storeFileId != STORE_FILE_USER_NETWORK_SUGGESTIONS) { 332 throw new IllegalArgumentException("Invalid user store file id"); 333 } 334 Objects.requireNonNull(userHandle); 335 AtomicFile file = getUserAtomicFile(storeFileId, userHandle.getIdentifier()); 336 if (file.exists()) { 337 file.delete(); 338 } 339 } 340 341 /** 342 * Container for all the wifi settings data to migrate. 343 */ 344 public static final class SettingsMigrationData implements Parcelable { 345 private final boolean mScanAlwaysAvailable; 346 private final boolean mP2pFactoryResetPending; 347 private final String mP2pDeviceName; 348 private final boolean mSoftApTimeoutEnabled; 349 private final boolean mWakeupEnabled; 350 private final boolean mScanThrottleEnabled; 351 private final boolean mVerboseLoggingEnabled; 352 SettingsMigrationData(boolean scanAlwaysAvailable, boolean p2pFactoryResetPending, @Nullable String p2pDeviceName, boolean softApTimeoutEnabled, boolean wakeupEnabled, boolean scanThrottleEnabled, boolean verboseLoggingEnabled)353 private SettingsMigrationData(boolean scanAlwaysAvailable, boolean p2pFactoryResetPending, 354 @Nullable String p2pDeviceName, boolean softApTimeoutEnabled, boolean wakeupEnabled, 355 boolean scanThrottleEnabled, boolean verboseLoggingEnabled) { 356 mScanAlwaysAvailable = scanAlwaysAvailable; 357 mP2pFactoryResetPending = p2pFactoryResetPending; 358 mP2pDeviceName = p2pDeviceName; 359 mSoftApTimeoutEnabled = softApTimeoutEnabled; 360 mWakeupEnabled = wakeupEnabled; 361 mScanThrottleEnabled = scanThrottleEnabled; 362 mVerboseLoggingEnabled = verboseLoggingEnabled; 363 } 364 365 public static final @NonNull Parcelable.Creator<SettingsMigrationData> CREATOR = 366 new Parcelable.Creator<SettingsMigrationData>() { 367 @Override 368 public SettingsMigrationData createFromParcel(Parcel in) { 369 boolean scanAlwaysAvailable = in.readBoolean(); 370 boolean p2pFactoryResetPending = in.readBoolean(); 371 String p2pDeviceName = in.readString(); 372 boolean softApTimeoutEnabled = in.readBoolean(); 373 boolean wakeupEnabled = in.readBoolean(); 374 boolean scanThrottleEnabled = in.readBoolean(); 375 boolean verboseLoggingEnabled = in.readBoolean(); 376 return new SettingsMigrationData( 377 scanAlwaysAvailable, p2pFactoryResetPending, 378 p2pDeviceName, softApTimeoutEnabled, wakeupEnabled, 379 scanThrottleEnabled, verboseLoggingEnabled); 380 } 381 382 @Override 383 public SettingsMigrationData[] newArray(int size) { 384 return new SettingsMigrationData[size]; 385 } 386 }; 387 388 @Override describeContents()389 public int describeContents() { 390 return 0; 391 } 392 393 @Override writeToParcel(@onNull Parcel dest, int flags)394 public void writeToParcel(@NonNull Parcel dest, int flags) { 395 dest.writeBoolean(mScanAlwaysAvailable); 396 dest.writeBoolean(mP2pFactoryResetPending); 397 dest.writeString(mP2pDeviceName); 398 dest.writeBoolean(mSoftApTimeoutEnabled); 399 dest.writeBoolean(mWakeupEnabled); 400 dest.writeBoolean(mScanThrottleEnabled); 401 dest.writeBoolean(mVerboseLoggingEnabled); 402 } 403 404 /** 405 * @return True if scans are allowed even when wifi is toggled off, false otherwise. 406 */ isScanAlwaysAvailable()407 public boolean isScanAlwaysAvailable() { 408 return mScanAlwaysAvailable; 409 } 410 411 /** 412 * @return indicate whether factory reset request is pending. 413 */ isP2pFactoryResetPending()414 public boolean isP2pFactoryResetPending() { 415 return mP2pFactoryResetPending; 416 } 417 418 /** 419 * @return the Wi-Fi peer-to-peer device name 420 */ getP2pDeviceName()421 public @Nullable String getP2pDeviceName() { 422 return mP2pDeviceName; 423 } 424 425 /** 426 * @return Whether soft AP will shut down after a timeout period when no devices are 427 * connected. 428 */ isSoftApTimeoutEnabled()429 public boolean isSoftApTimeoutEnabled() { 430 return mSoftApTimeoutEnabled; 431 } 432 433 /** 434 * @return whether Wi-Fi Wakeup feature is enabled. 435 */ isWakeUpEnabled()436 public boolean isWakeUpEnabled() { 437 return mWakeupEnabled; 438 } 439 440 /** 441 * @return Whether wifi scan throttle is enabled or not. 442 */ isScanThrottleEnabled()443 public boolean isScanThrottleEnabled() { 444 return mScanThrottleEnabled; 445 } 446 447 /** 448 * @return Whether to enable verbose logging in Wi-Fi. 449 */ isVerboseLoggingEnabled()450 public boolean isVerboseLoggingEnabled() { 451 return mVerboseLoggingEnabled; 452 } 453 454 /** 455 * Builder to create instance of {@link SettingsMigrationData}. 456 */ 457 public static final class Builder { 458 private boolean mScanAlwaysAvailable; 459 private boolean mP2pFactoryResetPending; 460 private String mP2pDeviceName; 461 private boolean mSoftApTimeoutEnabled; 462 private boolean mWakeupEnabled; 463 private boolean mScanThrottleEnabled; 464 private boolean mVerboseLoggingEnabled; 465 Builder()466 public Builder() { 467 } 468 469 /** 470 * Setting to allow scans even when wifi is toggled off. 471 * 472 * @param available true if available, false otherwise. 473 * @return Instance of {@link Builder} to enable chaining of the builder method. 474 */ setScanAlwaysAvailable(boolean available)475 public @NonNull Builder setScanAlwaysAvailable(boolean available) { 476 mScanAlwaysAvailable = available; 477 return this; 478 } 479 480 /** 481 * Indicate whether factory reset request is pending. 482 * 483 * @param pending true if pending, false otherwise. 484 * @return Instance of {@link Builder} to enable chaining of the builder method. 485 */ setP2pFactoryResetPending(boolean pending)486 public @NonNull Builder setP2pFactoryResetPending(boolean pending) { 487 mP2pFactoryResetPending = pending; 488 return this; 489 } 490 491 /** 492 * The Wi-Fi peer-to-peer device name 493 * 494 * @param name Name if set, null otherwise. 495 * @return Instance of {@link Builder} to enable chaining of the builder method. 496 */ setP2pDeviceName(@ullable String name)497 public @NonNull Builder setP2pDeviceName(@Nullable String name) { 498 mP2pDeviceName = name; 499 return this; 500 } 501 502 /** 503 * Whether soft AP will shut down after a timeout period when no devices are connected. 504 * 505 * @param enabled true if enabled, false otherwise. 506 * @return Instance of {@link Builder} to enable chaining of the builder method. 507 */ setSoftApTimeoutEnabled(boolean enabled)508 public @NonNull Builder setSoftApTimeoutEnabled(boolean enabled) { 509 mSoftApTimeoutEnabled = enabled; 510 return this; 511 } 512 513 /** 514 * Value to specify if Wi-Fi Wakeup feature is enabled. 515 * 516 * @param enabled true if enabled, false otherwise. 517 * @return Instance of {@link Builder} to enable chaining of the builder method. 518 */ setWakeUpEnabled(boolean enabled)519 public @NonNull Builder setWakeUpEnabled(boolean enabled) { 520 mWakeupEnabled = enabled; 521 return this; 522 } 523 524 /** 525 * Whether wifi scan throttle is enabled or not. 526 * 527 * @param enabled true if enabled, false otherwise. 528 * @return Instance of {@link Builder} to enable chaining of the builder method. 529 */ setScanThrottleEnabled(boolean enabled)530 public @NonNull Builder setScanThrottleEnabled(boolean enabled) { 531 mScanThrottleEnabled = enabled; 532 return this; 533 } 534 535 /** 536 * Setting to enable verbose logging in Wi-Fi. 537 * 538 * @param enabled true if enabled, false otherwise. 539 * @return Instance of {@link Builder} to enable chaining of the builder method. 540 */ setVerboseLoggingEnabled(boolean enabled)541 public @NonNull Builder setVerboseLoggingEnabled(boolean enabled) { 542 mVerboseLoggingEnabled = enabled; 543 return this; 544 } 545 546 /** 547 * Build an instance of {@link SettingsMigrationData}. 548 * 549 * @return Instance of {@link SettingsMigrationData}. 550 */ build()551 public @NonNull SettingsMigrationData build() { 552 return new SettingsMigrationData(mScanAlwaysAvailable, mP2pFactoryResetPending, 553 mP2pDeviceName, mSoftApTimeoutEnabled, mWakeupEnabled, mScanThrottleEnabled, 554 mVerboseLoggingEnabled); 555 } 556 } 557 } 558 559 /** 560 * Load data from Settings.Global values. 561 * 562 * <p> 563 * Note: 564 * <li> This is method is invoked once on the first bootup. OEM can safely delete these settings 565 * once the migration is complete. The first & only relevant invocation of 566 * {@link #loadFromSettings(Context)} ()} occurs when a previously released 567 * device upgrades to the wifi apex from an OEM implementation of the wifi stack. 568 * </li> 569 * 570 * @param context Context to use for loading the settings provider. 571 * @return Instance of {@link SettingsMigrationData} for migrating data. 572 */ 573 @NonNull loadFromSettings(@onNull Context context)574 public static SettingsMigrationData loadFromSettings(@NonNull Context context) { 575 if (Settings.Global.getInt( 576 context.getContentResolver(), Settings.Global.WIFI_MIGRATION_COMPLETED, 0) == 1) { 577 // migration already complete, ignore. 578 return null; 579 } 580 SettingsMigrationData data = new SettingsMigrationData.Builder() 581 .setScanAlwaysAvailable( 582 Settings.Global.getInt(context.getContentResolver(), 583 Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) 584 .setP2pFactoryResetPending( 585 Settings.Global.getInt(context.getContentResolver(), 586 Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET, 0) == 1) 587 .setP2pDeviceName( 588 Settings.Global.getString(context.getContentResolver(), 589 Settings.Global.WIFI_P2P_DEVICE_NAME)) 590 .setSoftApTimeoutEnabled( 591 Settings.Global.getInt(context.getContentResolver(), 592 Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1) == 1) 593 .setWakeUpEnabled( 594 Settings.Global.getInt(context.getContentResolver(), 595 Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1) 596 .setScanThrottleEnabled( 597 Settings.Global.getInt(context.getContentResolver(), 598 Settings.Global.WIFI_SCAN_THROTTLE_ENABLED, 1) == 1) 599 .setVerboseLoggingEnabled( 600 Settings.Global.getInt(context.getContentResolver(), 601 Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0) == 1) 602 .build(); 603 Settings.Global.putInt( 604 context.getContentResolver(), Settings.Global.WIFI_MIGRATION_COMPLETED, 1); 605 return data; 606 607 } 608 609 /** 610 * Migrate any certificates in Legacy Keystore to the newer WifiBlobstore database. 611 * 612 * Operation will be handled on the BackgroundThread, and the result will be posted 613 * to the provided Executor. 614 * 615 * @param executor The executor on which callback will be invoked 616 * @param resultsCallback Callback to receive the status code 617 * 618 * @hide 619 */ 620 @FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION_READ_ONLY) 621 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) migrateLegacyKeystoreToWifiBlobstore( @onNull Executor executor, @NonNull IntConsumer resultsCallback)622 public static void migrateLegacyKeystoreToWifiBlobstore( 623 @NonNull Executor executor, @NonNull IntConsumer resultsCallback) { 624 Objects.requireNonNull(executor, "executor cannot be null"); 625 Objects.requireNonNull(resultsCallback, "resultsCallback cannot be null"); 626 BackgroundThread.getHandler().post(() -> { 627 int status = migrateLegacyKeystoreToWifiBlobstoreInternal(); 628 executor.execute(() -> { 629 resultsCallback.accept(status); 630 }); 631 }); 632 } 633 634 /** 635 * Synchronously perform the Keystore migration described in 636 * {@link #migrateLegacyKeystoreToWifiBlobstore(Executor, IntConsumer)} 637 * 638 * @hide 639 */ migrateLegacyKeystoreToWifiBlobstoreInternal()640 public static @KeystoreMigrationStatus int migrateLegacyKeystoreToWifiBlobstoreInternal() { 641 if (!WifiBlobStore.supplicantCanAccessBlobstore()) { 642 // Supplicant cannot access WifiBlobstore, so keep the certs in Legacy Keystore 643 Log.i(TAG, "Avoiding migration since supplicant cannot access WifiBlobstore"); 644 return KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED; 645 } 646 final long identity = Binder.clearCallingIdentity(); 647 try { 648 ILegacyKeystore legacyKeystore = WifiBlobStore.getLegacyKeystore(); 649 String[] legacyAliases = legacyKeystore.list("", Process.WIFI_UID); 650 if (legacyAliases == null || legacyAliases.length == 0) { 651 Log.i(TAG, "No aliases need to be migrated"); 652 return KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED; 653 } 654 655 WifiBlobStore wifiBlobStore = WifiBlobStore.getInstance(); 656 List<String> blobstoreAliasList = Arrays.asList(wifiBlobStore.list("")); 657 Set<String> blobstoreAliases = new HashSet<>(); 658 blobstoreAliases.addAll(blobstoreAliasList); 659 660 for (String legacyAlias : legacyAliases) { 661 // Only migrate if the alias is not already in WifiBlobstore, 662 // since WifiBlobstore should already contain the latest value. 663 if (!blobstoreAliases.contains(legacyAlias)) { 664 byte[] value = legacyKeystore.get(legacyAlias, Process.WIFI_UID); 665 wifiBlobStore.put(legacyAlias, value); 666 } 667 legacyKeystore.remove(legacyAlias, Process.WIFI_UID); 668 } 669 Log.i(TAG, "Successfully migrated aliases from Legacy Keystore"); 670 return KEYSTORE_MIGRATION_SUCCESS_MIGRATION_COMPLETE; 671 } catch (ServiceSpecificException e) { 672 if (e.errorCode == ILegacyKeystore.ERROR_SYSTEM_ERROR) { 673 Log.i(TAG, "Legacy Keystore service has been deprecated"); 674 return KEYSTORE_MIGRATION_SUCCESS_MIGRATION_NOT_NEEDED; 675 } 676 Log.e(TAG, "Encountered a ServiceSpecificException while migrating aliases. " + e); 677 return KEYSTORE_MIGRATION_FAILURE_ENCOUNTERED_EXCEPTION; 678 } catch (Exception e) { 679 Log.e(TAG, "Encountered an exception while migrating aliases. " + e); 680 return KEYSTORE_MIGRATION_FAILURE_ENCOUNTERED_EXCEPTION; 681 } finally { 682 Binder.restoreCallingIdentity(identity); 683 } 684 } 685 } 686