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