• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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