Merge "Preload expensive objects before user is unlocked." into tm-qpr-dev am: 86d8db485f
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/20391179 Change-Id: I41b2afd3616f2f8fb92e6b9a1bb31359e0d4e258 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.android.quickstep
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import com.android.launcher3.LauncherAppState
|
||||||
|
import com.android.launcher3.LauncherPrefs
|
||||||
|
import com.android.launcher3.isBootAwareStartupDataEnabled
|
||||||
|
import com.android.launcher3.util.LockedUserState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads expensive objects in memory before the user is unlocked. This decreases experienced latency
|
||||||
|
* when starting the launcher for the first time after a reboot.
|
||||||
|
*/
|
||||||
|
object BootAwarePreloader {
|
||||||
|
private const val TAG = "BootAwarePreloader"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun start(context: Context) {
|
||||||
|
val lp = LauncherPrefs.get(context)
|
||||||
|
when {
|
||||||
|
LockedUserState.get(context).isUserUnlocked || !isBootAwareStartupDataEnabled -> {
|
||||||
|
/* No-Op */
|
||||||
|
}
|
||||||
|
lp.isStartupDataMigrated -> {
|
||||||
|
Log.d(TAG, "preloading start up data")
|
||||||
|
LauncherAppState.INSTANCE.get(context)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.d(TAG, "queuing start up data migration to boot aware prefs")
|
||||||
|
LockedUserState.get(context).runOnUserUnlocked {
|
||||||
|
lp.migrateStartupDataToDeviceProtectedStorage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -410,6 +410,7 @@ public class TouchInteractionService extends Service
|
|||||||
mDeviceState = new RecentsAnimationDeviceState(this, true);
|
mDeviceState = new RecentsAnimationDeviceState(this, true);
|
||||||
mTaskbarManager = new TaskbarManager(this);
|
mTaskbarManager = new TaskbarManager(this);
|
||||||
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
|
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
|
||||||
|
BootAwarePreloader.start(this);
|
||||||
|
|
||||||
// Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
|
// Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
|
||||||
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
|
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import com.android.launcher3.testing.shared.ResourceUtils;
|
|||||||
import com.android.launcher3.util.DisplayController;
|
import com.android.launcher3.util.DisplayController;
|
||||||
import com.android.launcher3.util.DisplayController.Info;
|
import com.android.launcher3.util.DisplayController.Info;
|
||||||
import com.android.launcher3.util.IntArray;
|
import com.android.launcher3.util.IntArray;
|
||||||
|
import com.android.launcher3.util.LockedUserState;
|
||||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||||
import com.android.launcher3.util.Partner;
|
import com.android.launcher3.util.Partner;
|
||||||
import com.android.launcher3.util.Themes;
|
import com.android.launcher3.util.Themes;
|
||||||
@@ -205,7 +206,9 @@ public class InvariantDeviceProfile {
|
|||||||
if (!newGridName.equals(gridName)) {
|
if (!newGridName.equals(gridName)) {
|
||||||
LauncherPrefs.get(context).put(GRID_NAME, newGridName);
|
LauncherPrefs.get(context).put(GRID_NAME, newGridName);
|
||||||
}
|
}
|
||||||
new DeviceGridState(this).writeToPrefs(context);
|
LockedUserState.get(context).runOnUserUnlocked(() -> {
|
||||||
|
new DeviceGridState(this).writeToPrefs(context);
|
||||||
|
});
|
||||||
|
|
||||||
DisplayController.INSTANCE.get(context).setPriorityListener(
|
DisplayController.INSTANCE.get(context).setPriorityListener(
|
||||||
(displayContext, info, flags) -> {
|
(displayContext, info, flags) -> {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import com.android.launcher3.notification.NotificationListener;
|
|||||||
import com.android.launcher3.pm.InstallSessionHelper;
|
import com.android.launcher3.pm.InstallSessionHelper;
|
||||||
import com.android.launcher3.pm.InstallSessionTracker;
|
import com.android.launcher3.pm.InstallSessionTracker;
|
||||||
import com.android.launcher3.pm.UserCache;
|
import com.android.launcher3.pm.UserCache;
|
||||||
|
import com.android.launcher3.util.LockedUserState;
|
||||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||||
import com.android.launcher3.util.Preconditions;
|
import com.android.launcher3.util.Preconditions;
|
||||||
import com.android.launcher3.util.RunnableList;
|
import com.android.launcher3.util.RunnableList;
|
||||||
@@ -106,25 +107,27 @@ public class LauncherAppState implements SafeCloseable {
|
|||||||
}
|
}
|
||||||
mOnTerminateCallback.add(() -> mContext.unregisterReceiver(modelChangeReceiver));
|
mOnTerminateCallback.add(() -> mContext.unregisterReceiver(modelChangeReceiver));
|
||||||
|
|
||||||
CustomWidgetManager.INSTANCE.get(mContext)
|
|
||||||
.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
|
|
||||||
|
|
||||||
SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
|
SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
|
||||||
.addUserChangeListener(mModel::forceReload);
|
.addUserChangeListener(mModel::forceReload);
|
||||||
mOnTerminateCallback.add(userChangeListener::close);
|
mOnTerminateCallback.add(userChangeListener::close);
|
||||||
|
|
||||||
IconObserver observer = new IconObserver();
|
LockedUserState.get(context).runOnUserUnlocked(() -> {
|
||||||
SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
|
CustomWidgetManager.INSTANCE.get(mContext)
|
||||||
observer, MODEL_EXECUTOR.getHandler());
|
.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
|
||||||
mOnTerminateCallback.add(iconChangeTracker::close);
|
|
||||||
MODEL_EXECUTOR.execute(observer::verifyIconChanged);
|
|
||||||
LauncherPrefs.get(context).addListener(observer, THEMED_ICONS);
|
|
||||||
mOnTerminateCallback.add(
|
|
||||||
() -> LauncherPrefs.get(mContext).removeListener(observer, THEMED_ICONS));
|
|
||||||
|
|
||||||
InstallSessionTracker installSessionTracker =
|
IconObserver observer = new IconObserver();
|
||||||
InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(mModel);
|
SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
|
||||||
mOnTerminateCallback.add(installSessionTracker::unregister);
|
observer, MODEL_EXECUTOR.getHandler());
|
||||||
|
mOnTerminateCallback.add(iconChangeTracker::close);
|
||||||
|
MODEL_EXECUTOR.execute(observer::verifyIconChanged);
|
||||||
|
LauncherPrefs.get(context).addListener(observer, THEMED_ICONS);
|
||||||
|
mOnTerminateCallback.add(
|
||||||
|
() -> LauncherPrefs.get(mContext).removeListener(observer, THEMED_ICONS));
|
||||||
|
|
||||||
|
InstallSessionTracker installSessionTracker =
|
||||||
|
InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(mModel);
|
||||||
|
mOnTerminateCallback.add(installSessionTracker::unregister);
|
||||||
|
});
|
||||||
|
|
||||||
// Register an observer to rebind the notification listener when dots are re-enabled.
|
// Register an observer to rebind the notification listener when dots are re-enabled.
|
||||||
SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
|
SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
|
||||||
|
|||||||
@@ -1,8 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
package com.android.launcher3
|
package com.android.launcher3
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Context.MODE_PRIVATE
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||||
|
import android.util.Log
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
|
import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
|
||||||
import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
|
import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
|
||||||
@@ -20,10 +37,34 @@ import com.android.launcher3.util.Themes
|
|||||||
*
|
*
|
||||||
* TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
|
* TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
|
||||||
*/
|
*/
|
||||||
class LauncherPrefs(private val context: Context) {
|
class LauncherPrefs(private val encryptedContext: Context) {
|
||||||
|
private val deviceProtectedStorageContext =
|
||||||
|
encryptedContext.createDeviceProtectedStorageContext()
|
||||||
|
|
||||||
|
private val bootAwarePrefs
|
||||||
|
get() =
|
||||||
|
deviceProtectedStorageContext.getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
|
||||||
|
|
||||||
|
private val Item.encryptedPrefs
|
||||||
|
get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
|
||||||
|
|
||||||
|
// This call to `SharedPreferences` needs to be explicit rather than using `get` since doing so
|
||||||
|
// would result in a circular dependency between `isStartupDataMigrated` and `choosePreferences`
|
||||||
|
val isStartupDataMigrated: Boolean
|
||||||
|
get() =
|
||||||
|
bootAwarePrefs.getBoolean(
|
||||||
|
IS_STARTUP_DATA_MIGRATED.sharedPrefKey,
|
||||||
|
IS_STARTUP_DATA_MIGRATED.defaultValue
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun chooseSharedPreferences(item: Item): SharedPreferences =
|
||||||
|
if (isBootAwareStartupDataEnabled && item.isBootAware && isStartupDataMigrated)
|
||||||
|
bootAwarePrefs
|
||||||
|
else item.encryptedPrefs
|
||||||
|
|
||||||
/** Wrapper around `getInner` for a `ContextualItem` */
|
/** Wrapper around `getInner` for a `ContextualItem` */
|
||||||
fun <T> get(item: ContextualItem<T>): T = getInner(item, item.defaultValueFromContext(context))
|
fun <T> get(item: ContextualItem<T>): T =
|
||||||
|
getInner(item, item.defaultValueFromContext(encryptedContext))
|
||||||
|
|
||||||
/** Wrapper around `getInner` for an `Item` */
|
/** Wrapper around `getInner` for an `Item` */
|
||||||
fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
|
fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
|
||||||
@@ -35,7 +76,7 @@ class LauncherPrefs(private val context: Context) {
|
|||||||
*/
|
*/
|
||||||
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
|
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
|
||||||
private fun <T> getInner(item: Item, default: T): T {
|
private fun <T> getInner(item: Item, default: T): T {
|
||||||
val sp = context.getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE)
|
val sp = chooseSharedPreferences(item)
|
||||||
|
|
||||||
return when (item.type) {
|
return when (item.type) {
|
||||||
String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
|
String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
|
||||||
@@ -68,16 +109,8 @@ class LauncherPrefs(private val context: Context) {
|
|||||||
fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
|
fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
|
||||||
prepareToPutValues(itemsToValues).forEach { it.apply() }
|
prepareToPutValues(itemsToValues).forEach { it.apply() }
|
||||||
|
|
||||||
/**
|
/** See referenced `put` method above. */
|
||||||
* Stores the value provided in `SharedPreferences` according to the item configuration provided
|
fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
|
||||||
* It is asynchronous, so the caller can't assume that the value put is immediately available.
|
|
||||||
*/
|
|
||||||
fun <T : Any> put(item: Item, value: T): Unit =
|
|
||||||
context
|
|
||||||
.getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE)
|
|
||||||
.edit()
|
|
||||||
.putValue(item, value)
|
|
||||||
.apply()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronously stores all the values provided according to their associated Item
|
* Synchronously stores all the values provided according to their associated Item
|
||||||
@@ -87,27 +120,35 @@ class LauncherPrefs(private val context: Context) {
|
|||||||
prepareToPutValues(itemsToValues).forEach { it.commit() }
|
prepareToPutValues(itemsToValues).forEach { it.commit() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update each shared preference file with the item - value pairs provided. This method is
|
* Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If
|
||||||
* optimized to avoid retrieving the same shared preference file multiple times.
|
* the item is boot aware, this method updates both the boot aware and the encrypted files. This
|
||||||
|
* is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs
|
||||||
|
* and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which
|
||||||
|
* already points to encrypted storage.
|
||||||
*
|
*
|
||||||
* @return `List<SharedPreferences.Editor>` 1 for each distinct shared preference file among the
|
* Returns a list of editors with all transactions added so that the caller can determine to use
|
||||||
* items given as part of the itemsToValues parameter
|
* .apply() or .commit()
|
||||||
*/
|
*/
|
||||||
private fun prepareToPutValues(
|
private fun prepareToPutValues(
|
||||||
itemsToValues: Array<out Pair<Item, Any>>
|
updates: Array<out Pair<Item, Any>>
|
||||||
): List<SharedPreferences.Editor> =
|
): List<SharedPreferences.Editor> {
|
||||||
itemsToValues
|
val updatesPerPrefFile = updates.groupBy { it.first.encryptedPrefs }.toMutableMap()
|
||||||
.groupBy { it.first.sharedPrefFile }
|
|
||||||
.map { fileToItemValueList ->
|
if (isBootAwareStartupDataEnabled) {
|
||||||
context
|
val bootAwareUpdates = updates.filter { it.first.isBootAware }
|
||||||
.getSharedPreferences(fileToItemValueList.key, Context.MODE_PRIVATE)
|
if (bootAwareUpdates.isNotEmpty()) {
|
||||||
.edit()
|
updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
|
||||||
.apply {
|
|
||||||
fileToItemValueList.value.forEach { itemToValue ->
|
|
||||||
putValue(itemToValue.first, itemToValue.second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatesPerPrefFile.map { prefToItemValueList ->
|
||||||
|
prefToItemValueList.key.edit().apply {
|
||||||
|
prefToItemValueList.value.forEach { itemToValue: Pair<Item, Any> ->
|
||||||
|
putValue(itemToValue.first, itemToValue.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles adding values to `SharedPreferences` regardless of type. This method is especially
|
* Handles adding values to `SharedPreferences` regardless of type. This method is especially
|
||||||
@@ -117,10 +158,10 @@ class LauncherPrefs(private val context: Context) {
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
private fun SharedPreferences.Editor.putValue(
|
private fun SharedPreferences.Editor.putValue(
|
||||||
item: Item,
|
item: Item,
|
||||||
value: Any
|
value: Any?
|
||||||
): SharedPreferences.Editor =
|
): SharedPreferences.Editor =
|
||||||
when (value::class.java) {
|
when (item.type) {
|
||||||
String::class.java -> putString(item.sharedPrefKey, value as String)
|
String::class.java -> putString(item.sharedPrefKey, value as? String)
|
||||||
Boolean::class.java,
|
Boolean::class.java,
|
||||||
java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
|
java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
|
||||||
Int::class.java,
|
Int::class.java,
|
||||||
@@ -129,10 +170,10 @@ class LauncherPrefs(private val context: Context) {
|
|||||||
java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
|
java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
|
||||||
Long::class.java,
|
Long::class.java,
|
||||||
java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
|
java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
|
||||||
Set::class.java -> putStringSet(item.sharedPrefKey, value as Set<String>)
|
Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>)
|
||||||
else ->
|
else ->
|
||||||
throw IllegalArgumentException(
|
throw IllegalArgumentException(
|
||||||
"item type: ${value::class} is not compatible with sharedPref methods"
|
"item type: ${item.type} is not compatible with sharedPref methods"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,13 +184,9 @@ class LauncherPrefs(private val context: Context) {
|
|||||||
*/
|
*/
|
||||||
fun addListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
|
fun addListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
|
||||||
items
|
items
|
||||||
.map { it.sharedPrefFile }
|
.map { chooseSharedPreferences(it) }
|
||||||
.distinct()
|
.distinct()
|
||||||
.forEach {
|
.forEach { it.registerOnSharedPreferenceChangeListener(listener) }
|
||||||
context
|
|
||||||
.getSharedPreferences(it, Context.MODE_PRIVATE)
|
|
||||||
.registerOnSharedPreferenceChangeListener(listener)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -159,13 +196,9 @@ class LauncherPrefs(private val context: Context) {
|
|||||||
fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
|
fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) {
|
||||||
// If a listener is not registered to a SharedPreference, unregistering it does nothing
|
// If a listener is not registered to a SharedPreference, unregistering it does nothing
|
||||||
items
|
items
|
||||||
.map { it.sharedPrefFile }
|
.map { chooseSharedPreferences(it) }
|
||||||
.distinct()
|
.distinct()
|
||||||
.forEach {
|
.forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
|
||||||
context
|
|
||||||
.getSharedPreferences(it, Context.MODE_PRIVATE)
|
|
||||||
.unregisterOnSharedPreferenceChangeListener(listener)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -174,10 +207,8 @@ class LauncherPrefs(private val context: Context) {
|
|||||||
*/
|
*/
|
||||||
fun has(vararg items: Item): Boolean {
|
fun has(vararg items: Item): Boolean {
|
||||||
items
|
items
|
||||||
.groupBy { it.sharedPrefFile }
|
.groupBy { chooseSharedPreferences(it) }
|
||||||
.forEach { (file, itemsSublist) ->
|
.forEach { (prefs, itemsSublist) ->
|
||||||
val prefs: SharedPreferences =
|
|
||||||
context.getSharedPreferences(file, Context.MODE_PRIVATE)
|
|
||||||
if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
|
if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -192,62 +223,128 @@ class LauncherPrefs(private val context: Context) {
|
|||||||
fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
|
fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates `SharedPreferences.Editor` transactions for removing all the provided [Item] values
|
* Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
|
||||||
* from their respective `SharedPreferences` files. These returned `Editors` can then be
|
* item is boot aware, this method removes the data from both the boot aware and encrypted
|
||||||
* committed or applied for synchronous or async behavior.
|
* files.
|
||||||
|
*
|
||||||
|
* @return a list of editors with all transactions added so that the caller can determine to use
|
||||||
|
* .apply() or .commit()
|
||||||
*/
|
*/
|
||||||
private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> =
|
private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
|
||||||
items
|
val itemsPerFile = items.groupBy { it.encryptedPrefs }.toMutableMap()
|
||||||
.groupBy { it.sharedPrefFile }
|
|
||||||
.map { (file, items) ->
|
if (isBootAwareStartupDataEnabled) {
|
||||||
context.getSharedPreferences(file, Context.MODE_PRIVATE).edit().also { editor ->
|
val bootAwareUpdates = items.filter { it.isBootAware }
|
||||||
items.forEach { item -> editor.remove(item.sharedPrefKey) }
|
if (bootAwareUpdates.isNotEmpty()) {
|
||||||
}
|
itemsPerFile[bootAwarePrefs] = bootAwareUpdates
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemsPerFile.map { (prefs, items) ->
|
||||||
|
prefs.edit().also { editor ->
|
||||||
|
items.forEach { item -> editor.remove(item.sharedPrefKey) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun migrateStartupDataToDeviceProtectedStorage() {
|
||||||
|
if (!isBootAwareStartupDataEnabled) return
|
||||||
|
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"Migrating data to unencrypted shared preferences to enable preloading " +
|
||||||
|
"while the user is locked the next time the device reboots."
|
||||||
|
)
|
||||||
|
|
||||||
|
with(bootAwarePrefs.edit()) {
|
||||||
|
BOOT_AWARE_ITEMS.forEach { putValue(it, get(it)) }
|
||||||
|
putBoolean(IS_STARTUP_DATA_MIGRATED.sharedPrefKey, true)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val TAG = "LauncherPrefs"
|
||||||
|
@VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
|
||||||
|
|
||||||
@JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) }
|
@JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) }
|
||||||
|
|
||||||
@JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
|
@JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
|
||||||
|
|
||||||
@JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "")
|
@JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "", true)
|
||||||
@JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false)
|
@JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, true)
|
||||||
@JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
|
@JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
|
||||||
@JvmField val WORK_EDU_STEP = backedUpItem(WorkProfileManager.KEY_WORK_EDU_STEP, 0)
|
@JvmField val WORK_EDU_STEP = backedUpItem(WorkProfileManager.KEY_WORK_EDU_STEP, 0)
|
||||||
@JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "")
|
@JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", true)
|
||||||
@JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1)
|
@JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, true)
|
||||||
@JvmField
|
@JvmField
|
||||||
val DEVICE_TYPE =
|
val DEVICE_TYPE =
|
||||||
backedUpItem(DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE)
|
backedUpItem(DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, true)
|
||||||
@JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "")
|
@JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", true)
|
||||||
@JvmField
|
@JvmField
|
||||||
val RESTORE_DEVICE =
|
val RESTORE_DEVICE =
|
||||||
backedUpItem(RestoreDbTask.RESTORED_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE)
|
backedUpItem(
|
||||||
|
RestoreDbTask.RESTORED_DEVICE_TYPE,
|
||||||
|
InvariantDeviceProfile.TYPE_PHONE,
|
||||||
|
true
|
||||||
|
)
|
||||||
@JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
|
@JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
|
||||||
@JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
|
@JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
|
||||||
@JvmField val GRID_NAME = ConstantItem("idp_grid_name", true, null, String::class.java)
|
@JvmField
|
||||||
|
val GRID_NAME =
|
||||||
|
ConstantItem(
|
||||||
|
"idp_grid_name",
|
||||||
|
isBackedUp = true,
|
||||||
|
defaultValue = null,
|
||||||
|
isBootAware = true,
|
||||||
|
type = String::class.java
|
||||||
|
)
|
||||||
@JvmField
|
@JvmField
|
||||||
val ALLOW_ROTATION =
|
val ALLOW_ROTATION =
|
||||||
backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
|
backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
|
||||||
RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
|
RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
|
||||||
}
|
}
|
||||||
|
@JvmField
|
||||||
|
val IS_STARTUP_DATA_MIGRATED =
|
||||||
|
ConstantItem(
|
||||||
|
"is_startup_data_boot_aware",
|
||||||
|
isBackedUp = false,
|
||||||
|
defaultValue = false,
|
||||||
|
isBootAware = true
|
||||||
|
)
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun <T> backedUpItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
|
fun <T> backedUpItem(
|
||||||
ConstantItem(sharedPrefKey, true, defaultValue)
|
sharedPrefKey: String,
|
||||||
|
defaultValue: T,
|
||||||
|
isBootAware: Boolean = false
|
||||||
|
): ConstantItem<T> =
|
||||||
|
ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, isBootAware)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun <T> backedUpItem(
|
fun <T> backedUpItem(
|
||||||
sharedPrefKey: String,
|
sharedPrefKey: String,
|
||||||
type: Class<out T>,
|
type: Class<out T>,
|
||||||
|
isBootAware: Boolean = false,
|
||||||
defaultValueFromContext: (c: Context) -> T
|
defaultValueFromContext: (c: Context) -> T
|
||||||
): ContextualItem<T> = ContextualItem(sharedPrefKey, true, defaultValueFromContext, type)
|
): ContextualItem<T> =
|
||||||
|
ContextualItem(
|
||||||
|
sharedPrefKey,
|
||||||
|
isBackedUp = true,
|
||||||
|
defaultValueFromContext,
|
||||||
|
isBootAware,
|
||||||
|
type
|
||||||
|
)
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun <T> nonRestorableItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
|
fun <T> nonRestorableItem(
|
||||||
ConstantItem(sharedPrefKey, false, defaultValue)
|
sharedPrefKey: String,
|
||||||
|
defaultValue: T,
|
||||||
|
isBootAware: Boolean = false
|
||||||
|
): ConstantItem<T> =
|
||||||
|
ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, isBootAware)
|
||||||
|
|
||||||
@Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
|
@Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@@ -255,7 +352,7 @@ class LauncherPrefs(private val context: Context) {
|
|||||||
// Use application context for shared preferences, so we use single cached instance
|
// Use application context for shared preferences, so we use single cached instance
|
||||||
return context.applicationContext.getSharedPreferences(
|
return context.applicationContext.getSharedPreferences(
|
||||||
SHARED_PREFERENCES_KEY,
|
SHARED_PREFERENCES_KEY,
|
||||||
Context.MODE_PRIVATE
|
MODE_PRIVATE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,16 +362,23 @@ class LauncherPrefs(private val context: Context) {
|
|||||||
// Use application context for shared preferences, so we use a single cached instance
|
// Use application context for shared preferences, so we use a single cached instance
|
||||||
return context.applicationContext.getSharedPreferences(
|
return context.applicationContext.getSharedPreferences(
|
||||||
DEVICE_PREFERENCES_KEY,
|
DEVICE_PREFERENCES_KEY,
|
||||||
Context.MODE_PRIVATE
|
MODE_PRIVATE
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is hard-coded to false for now until it is time to release this optimization. It is only
|
||||||
|
// a var because the unit tests are setting this to true so they can run.
|
||||||
|
@VisibleForTesting var isBootAwareStartupDataEnabled: Boolean = false
|
||||||
|
|
||||||
|
private val BOOT_AWARE_ITEMS: MutableSet<ConstantItem<*>> = mutableSetOf()
|
||||||
|
|
||||||
abstract class Item {
|
abstract class Item {
|
||||||
abstract val sharedPrefKey: String
|
abstract val sharedPrefKey: String
|
||||||
abstract val isBackedUp: Boolean
|
abstract val isBackedUp: Boolean
|
||||||
abstract val type: Class<*>
|
abstract val type: Class<*>
|
||||||
|
abstract val isBootAware: Boolean
|
||||||
val sharedPrefFile: String
|
val sharedPrefFile: String
|
||||||
get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY
|
get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY
|
||||||
|
|
||||||
@@ -285,14 +389,22 @@ data class ConstantItem<T>(
|
|||||||
override val sharedPrefKey: String,
|
override val sharedPrefKey: String,
|
||||||
override val isBackedUp: Boolean,
|
override val isBackedUp: Boolean,
|
||||||
val defaultValue: T,
|
val defaultValue: T,
|
||||||
|
override val isBootAware: Boolean,
|
||||||
// The default value can be null. If so, the type needs to be explicitly stated, or else NPE
|
// The default value can be null. If so, the type needs to be explicitly stated, or else NPE
|
||||||
override val type: Class<out T> = defaultValue!!::class.java
|
override val type: Class<out T> = defaultValue!!::class.java
|
||||||
) : Item()
|
) : Item() {
|
||||||
|
init {
|
||||||
|
if (isBootAware && isBootAwareStartupDataEnabled) {
|
||||||
|
BOOT_AWARE_ITEMS.add(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class ContextualItem<T>(
|
data class ContextualItem<T>(
|
||||||
override val sharedPrefKey: String,
|
override val sharedPrefKey: String,
|
||||||
override val isBackedUp: Boolean,
|
override val isBackedUp: Boolean,
|
||||||
private val defaultSupplier: (c: Context) -> T,
|
private val defaultSupplier: (c: Context) -> T,
|
||||||
|
override val isBootAware: Boolean,
|
||||||
override val type: Class<out T>
|
override val type: Class<out T>
|
||||||
) : Item() {
|
) : Item() {
|
||||||
private var default: T? = null
|
private var default: T? = null
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ class LockedUserState(private val mContext: Context) : SafeCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@VisibleForTesting val INSTANCE = MainThreadInitializedObject { LockedUserState(it) }
|
@VisibleForTesting
|
||||||
|
@JvmField
|
||||||
|
val INSTANCE = MainThreadInitializedObject { LockedUserState(it) }
|
||||||
|
|
||||||
@JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context)
|
@JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
package com.android.launcher3
|
package com.android.launcher3
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import com.android.launcher3.LauncherPrefs.Companion.BOOT_AWARE_PREFS_KEY
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -13,19 +31,22 @@ import org.junit.runner.RunWith
|
|||||||
private val TEST_BOOLEAN_ITEM = LauncherPrefs.nonRestorableItem("1", false)
|
private val TEST_BOOLEAN_ITEM = LauncherPrefs.nonRestorableItem("1", false)
|
||||||
private val TEST_STRING_ITEM = LauncherPrefs.nonRestorableItem("2", "( ͡❛ ͜ʖ ͡❛)")
|
private val TEST_STRING_ITEM = LauncherPrefs.nonRestorableItem("2", "( ͡❛ ͜ʖ ͡❛)")
|
||||||
private val TEST_INT_ITEM = LauncherPrefs.nonRestorableItem("3", -1)
|
private val TEST_INT_ITEM = LauncherPrefs.nonRestorableItem("3", -1)
|
||||||
private val TEST_CONTEXTUAL_ITEM = ContextualItem("4", true, { true }, Boolean::class.java)
|
private val TEST_CONTEXTUAL_ITEM = ContextualItem("4", true, { true }, false, Boolean::class.java)
|
||||||
|
|
||||||
private const val TEST_DEFAULT_VALUE = "default"
|
private const val TEST_DEFAULT_VALUE = "default"
|
||||||
private const val TEST_PREF_KEY = "test_pref_key"
|
private const val TEST_PREF_KEY = "test_pref_key"
|
||||||
|
|
||||||
|
private const val WAIT_TIME_IN_SECONDS = 3L
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class LauncherPrefsTest {
|
class LauncherPrefsTest {
|
||||||
|
|
||||||
private val launcherPrefs by lazy {
|
private val context by lazy { InstrumentationRegistry.getInstrumentation().targetContext }
|
||||||
LauncherPrefs.get(InstrumentationRegistry.getInstrumentation().targetContext).apply {
|
private val launcherPrefs by lazy { LauncherPrefs.get(context) }
|
||||||
remove(TEST_BOOLEAN_ITEM, TEST_STRING_ITEM, TEST_INT_ITEM)
|
|
||||||
}
|
init {
|
||||||
|
isBootAwareStartupDataEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -52,7 +73,7 @@ class LauncherPrefsTest {
|
|||||||
addListener(listener, TEST_STRING_ITEM)
|
addListener(listener, TEST_STRING_ITEM)
|
||||||
putSync(TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "abc"))
|
putSync(TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "abc"))
|
||||||
|
|
||||||
assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue()
|
assertThat(latch.await(WAIT_TIME_IN_SECONDS, TimeUnit.SECONDS)).isTrue()
|
||||||
remove(TEST_STRING_ITEM)
|
remove(TEST_STRING_ITEM)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,7 +89,7 @@ class LauncherPrefsTest {
|
|||||||
putSync(TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "hello."))
|
putSync(TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "hello."))
|
||||||
|
|
||||||
// latch will be still be 1 (and await will return false) if the listener was not called
|
// latch will be still be 1 (and await will return false) if the listener was not called
|
||||||
assertThat(latch.await(2, TimeUnit.SECONDS)).isFalse()
|
assertThat(latch.await(WAIT_TIME_IN_SECONDS, TimeUnit.SECONDS)).isFalse()
|
||||||
remove(TEST_STRING_ITEM)
|
remove(TEST_STRING_ITEM)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,7 +106,7 @@ class LauncherPrefsTest {
|
|||||||
TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "abc"),
|
TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "abc"),
|
||||||
TEST_BOOLEAN_ITEM.to(!TEST_BOOLEAN_ITEM.defaultValue)
|
TEST_BOOLEAN_ITEM.to(!TEST_BOOLEAN_ITEM.defaultValue)
|
||||||
)
|
)
|
||||||
assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue()
|
assertThat(latch.await(WAIT_TIME_IN_SECONDS, TimeUnit.SECONDS)).isTrue()
|
||||||
|
|
||||||
removeListener(listener, TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM)
|
removeListener(listener, TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM)
|
||||||
latch = CountDownLatch(1)
|
latch = CountDownLatch(1)
|
||||||
@@ -96,7 +117,7 @@ class LauncherPrefsTest {
|
|||||||
)
|
)
|
||||||
remove(TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM)
|
remove(TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM)
|
||||||
|
|
||||||
assertThat(latch.await(2, TimeUnit.SECONDS)).isFalse()
|
assertThat(latch.await(WAIT_TIME_IN_SECONDS, TimeUnit.SECONDS)).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,4 +187,133 @@ class LauncherPrefsTest {
|
|||||||
val backedUpItem = LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE)
|
val backedUpItem = LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE)
|
||||||
assertThat(backedUpItem.sharedPrefFile).isEqualTo(LauncherFiles.SHARED_PREFERENCES_KEY)
|
assertThat(backedUpItem.sharedPrefFile).isEqualTo(LauncherFiles.SHARED_PREFERENCES_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun put_bootAwareItem_updatesDeviceProtectedStorage() {
|
||||||
|
val bootAwareItem =
|
||||||
|
LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
|
||||||
|
|
||||||
|
val bootAwarePrefs: SharedPreferences =
|
||||||
|
context
|
||||||
|
.createDeviceProtectedStorageContext()
|
||||||
|
.getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE)
|
||||||
|
bootAwarePrefs.edit().remove(bootAwareItem.sharedPrefKey).commit()
|
||||||
|
|
||||||
|
launcherPrefs.putSync(bootAwareItem.to(bootAwareItem.defaultValue))
|
||||||
|
assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isTrue()
|
||||||
|
|
||||||
|
launcherPrefs.removeSync(bootAwareItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun put_bootAwareItem_updatesEncryptedStorage() {
|
||||||
|
val bootAwareItem =
|
||||||
|
LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
|
||||||
|
|
||||||
|
val encryptedPrefs: SharedPreferences =
|
||||||
|
context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE)
|
||||||
|
encryptedPrefs.edit().remove(bootAwareItem.sharedPrefKey).commit()
|
||||||
|
|
||||||
|
launcherPrefs.putSync(bootAwareItem.to(TEST_STRING_ITEM.defaultValue))
|
||||||
|
assertThat(encryptedPrefs.contains(bootAwareItem.sharedPrefKey)).isTrue()
|
||||||
|
|
||||||
|
launcherPrefs.removeSync(bootAwareItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun remove_bootAwareItem_removesFromDeviceProtectedStorage() {
|
||||||
|
val bootAwareItem =
|
||||||
|
LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
|
||||||
|
|
||||||
|
val bootAwarePrefs: SharedPreferences =
|
||||||
|
context
|
||||||
|
.createDeviceProtectedStorageContext()
|
||||||
|
.getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
bootAwarePrefs
|
||||||
|
.edit()
|
||||||
|
.putString(bootAwareItem.sharedPrefKey, bootAwareItem.defaultValue)
|
||||||
|
.commit()
|
||||||
|
|
||||||
|
launcherPrefs.removeSync(bootAwareItem)
|
||||||
|
assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun remove_bootAwareItem_removesFromEncryptedStorage() {
|
||||||
|
val bootAwareItem =
|
||||||
|
LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
|
||||||
|
|
||||||
|
val encryptedPrefs: SharedPreferences =
|
||||||
|
context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
encryptedPrefs
|
||||||
|
.edit()
|
||||||
|
.putString(bootAwareItem.sharedPrefKey, bootAwareItem.defaultValue)
|
||||||
|
.commit()
|
||||||
|
|
||||||
|
launcherPrefs.removeSync(bootAwareItem)
|
||||||
|
assertThat(encryptedPrefs.contains(bootAwareItem.sharedPrefKey)).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun migrate_bootAwareItemsToDeviceProtectedStorage_worksAsIntended() {
|
||||||
|
val bootAwareItem =
|
||||||
|
LauncherPrefs.backedUpItem(TEST_PREF_KEY, TEST_DEFAULT_VALUE, isBootAware = true)
|
||||||
|
launcherPrefs.removeSync(bootAwareItem)
|
||||||
|
|
||||||
|
val bootAwarePrefs: SharedPreferences =
|
||||||
|
context
|
||||||
|
.createDeviceProtectedStorageContext()
|
||||||
|
.getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
if (bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)) {
|
||||||
|
bootAwarePrefs.edit().remove(bootAwareItem.sharedPrefKey).commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
val encryptedPrefs: SharedPreferences =
|
||||||
|
context.getSharedPreferences(bootAwareItem.sharedPrefFile, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
encryptedPrefs
|
||||||
|
.edit()
|
||||||
|
.putString(bootAwareItem.sharedPrefKey, bootAwareItem.defaultValue)
|
||||||
|
.commit()
|
||||||
|
|
||||||
|
launcherPrefs.migrateStartupDataToDeviceProtectedStorage()
|
||||||
|
assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isTrue()
|
||||||
|
|
||||||
|
launcherPrefs.removeSync(bootAwareItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun migrate_onlyEncryptedItemsToDeviceProtectedStorage_doesNotHappen() {
|
||||||
|
val onlyEncryptedItem =
|
||||||
|
LauncherPrefs.backedUpItem(
|
||||||
|
TEST_PREF_KEY + "_",
|
||||||
|
TEST_DEFAULT_VALUE + "_",
|
||||||
|
isBootAware = false
|
||||||
|
)
|
||||||
|
|
||||||
|
val bootAwarePrefs: SharedPreferences =
|
||||||
|
context
|
||||||
|
.createDeviceProtectedStorageContext()
|
||||||
|
.getSharedPreferences(BOOT_AWARE_PREFS_KEY, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
if (bootAwarePrefs.contains(onlyEncryptedItem.sharedPrefKey)) {
|
||||||
|
bootAwarePrefs.edit().remove(onlyEncryptedItem.sharedPrefKey).commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
val encryptedPrefs: SharedPreferences =
|
||||||
|
context.getSharedPreferences(onlyEncryptedItem.sharedPrefFile, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
encryptedPrefs
|
||||||
|
.edit()
|
||||||
|
.putString(onlyEncryptedItem.sharedPrefKey, onlyEncryptedItem.defaultValue)
|
||||||
|
.commit()
|
||||||
|
|
||||||
|
launcherPrefs.migrateStartupDataToDeviceProtectedStorage()
|
||||||
|
assertThat(bootAwarePrefs.contains(onlyEncryptedItem.sharedPrefKey)).isFalse()
|
||||||
|
|
||||||
|
encryptedPrefs.edit().remove(onlyEncryptedItem.sharedPrefKey).commit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -510,7 +510,7 @@ public class LauncherModelHelper {
|
|||||||
UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE,
|
UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE,
|
||||||
LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
|
LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
|
||||||
DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
|
DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
|
||||||
SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE,
|
SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE, LockedUserState.INSTANCE,
|
||||||
ItemInstallQueue.INSTANCE, WindowManagerProxy.INSTANCE);
|
ItemInstallQueue.INSTANCE, WindowManagerProxy.INSTANCE);
|
||||||
mPm = spy(getBaseContext().getPackageManager());
|
mPm = spy(getBaseContext().getPackageManager());
|
||||||
mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
|
mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
|
||||||
|
|||||||
Reference in New Issue
Block a user