diff --git a/quickstep/src/com/android/quickstep/BootAwarePreloader.kt b/quickstep/src/com/android/quickstep/BootAwarePreloader.kt new file mode 100644 index 0000000000..35404a9925 --- /dev/null +++ b/quickstep/src/com/android/quickstep/BootAwarePreloader.kt @@ -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() + } + } + } + } +} diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index ff81e08e8b..accab38bf3 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -410,6 +410,7 @@ public class TouchInteractionService extends Service mDeviceState = new RecentsAnimationDeviceState(this, true); mTaskbarManager = new TaskbarManager(this); mRotationTouchHelper = mDeviceState.getRotationTouchHelper(); + BootAwarePreloader.start(this); // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized. mDeviceState.runOnUserUnlocked(this::onUserUnlocked); diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 4ac7f078bf..a498323140 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -59,6 +59,7 @@ import com.android.launcher3.testing.shared.ResourceUtils; import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.LockedUserState; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.Partner; import com.android.launcher3.util.Themes; @@ -205,7 +206,9 @@ public class InvariantDeviceProfile { if (!newGridName.equals(gridName)) { 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( (displayContext, info, flags) -> { diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 2b98d983a6..c81ad01f2e 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -44,6 +44,7 @@ import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.pm.InstallSessionHelper; import com.android.launcher3.pm.InstallSessionTracker; import com.android.launcher3.pm.UserCache; +import com.android.launcher3.util.LockedUserState; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.RunnableList; @@ -106,25 +107,27 @@ public class LauncherAppState implements SafeCloseable { } mOnTerminateCallback.add(() -> mContext.unregisterReceiver(modelChangeReceiver)); - CustomWidgetManager.INSTANCE.get(mContext) - .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts); - SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext) .addUserChangeListener(mModel::forceReload); mOnTerminateCallback.add(userChangeListener::close); - IconObserver observer = new IconObserver(); - SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener( - 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)); + LockedUserState.get(context).runOnUserUnlocked(() -> { + CustomWidgetManager.INSTANCE.get(mContext) + .setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts); - InstallSessionTracker installSessionTracker = - InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(mModel); - mOnTerminateCallback.add(installSessionTracker::unregister); + IconObserver observer = new IconObserver(); + SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener( + 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. SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext); diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt index 5680c18abb..e675adde47 100644 --- a/src/com/android/launcher3/LauncherPrefs.kt +++ b/src/com/android/launcher3/LauncherPrefs.kt @@ -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 import android.content.Context +import android.content.Context.MODE_PRIVATE import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import android.util.Log import androidx.annotation.VisibleForTesting import com.android.launcher3.LauncherFiles.DEVICE_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. */ -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` */ - fun get(item: ContextualItem): T = getInner(item, item.defaultValueFromContext(context)) + fun get(item: ContextualItem): T = + getInner(item, item.defaultValueFromContext(encryptedContext)) /** Wrapper around `getInner` for an `Item` */ fun get(item: ConstantItem): T = getInner(item, item.defaultValue) @@ -35,7 +76,7 @@ class LauncherPrefs(private val context: Context) { */ @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST") private fun getInner(item: Item, default: T): T { - val sp = context.getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE) + val sp = chooseSharedPreferences(item) return when (item.type) { 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): Unit = prepareToPutValues(itemsToValues).forEach { it.apply() } - /** - * Stores the value provided in `SharedPreferences` according to the item configuration provided - * It is asynchronous, so the caller can't assume that the value put is immediately available. - */ - fun put(item: Item, value: T): Unit = - context - .getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE) - .edit() - .putValue(item, value) - .apply() + /** See referenced `put` method above. */ + fun put(item: Item, value: T): Unit = put(item.to(value)) /** * 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() } /** - * Update each shared preference file with the item - value pairs provided. This method is - * optimized to avoid retrieving the same shared preference file multiple times. + * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If + * 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` 1 for each distinct shared preference file among the - * items given as part of the itemsToValues parameter + * Returns a list of editors with all transactions added so that the caller can determine to use + * .apply() or .commit() */ private fun prepareToPutValues( - itemsToValues: Array> - ): List = - itemsToValues - .groupBy { it.first.sharedPrefFile } - .map { fileToItemValueList -> - context - .getSharedPreferences(fileToItemValueList.key, Context.MODE_PRIVATE) - .edit() - .apply { - fileToItemValueList.value.forEach { itemToValue -> - putValue(itemToValue.first, itemToValue.second) - } - } + updates: Array> + ): List { + val updatesPerPrefFile = updates.groupBy { it.first.encryptedPrefs }.toMutableMap() + + if (isBootAwareStartupDataEnabled) { + val bootAwareUpdates = updates.filter { it.first.isBootAware } + if (bootAwareUpdates.isNotEmpty()) { + updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates } + } + + return updatesPerPrefFile.map { prefToItemValueList -> + prefToItemValueList.key.edit().apply { + prefToItemValueList.value.forEach { itemToValue: Pair -> + putValue(itemToValue.first, itemToValue.second) + } + } + } + } /** * 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") private fun SharedPreferences.Editor.putValue( item: Item, - value: Any + value: Any? ): SharedPreferences.Editor = - when (value::class.java) { - String::class.java -> putString(item.sharedPrefKey, value as String) + when (item.type) { + String::class.java -> putString(item.sharedPrefKey, value as? String) Boolean::class.java, java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean) Int::class.java, @@ -129,10 +170,10 @@ class LauncherPrefs(private val context: Context) { java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float) Long::class.java, java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long) - Set::class.java -> putStringSet(item.sharedPrefKey, value as Set) + Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set) else -> 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) { items - .map { it.sharedPrefFile } + .map { chooseSharedPreferences(it) } .distinct() - .forEach { - context - .getSharedPreferences(it, Context.MODE_PRIVATE) - .registerOnSharedPreferenceChangeListener(listener) - } + .forEach { it.registerOnSharedPreferenceChangeListener(listener) } } /** @@ -159,13 +196,9 @@ class LauncherPrefs(private val context: Context) { fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item) { // If a listener is not registered to a SharedPreference, unregistering it does nothing items - .map { it.sharedPrefFile } + .map { chooseSharedPreferences(it) } .distinct() - .forEach { - context - .getSharedPreferences(it, Context.MODE_PRIVATE) - .unregisterOnSharedPreferenceChangeListener(listener) - } + .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) } } /** @@ -174,10 +207,8 @@ class LauncherPrefs(private val context: Context) { */ fun has(vararg items: Item): Boolean { items - .groupBy { it.sharedPrefFile } - .forEach { (file, itemsSublist) -> - val prefs: SharedPreferences = - context.getSharedPreferences(file, Context.MODE_PRIVATE) + .groupBy { chooseSharedPreferences(it) } + .forEach { (prefs, itemsSublist) -> if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false } return true @@ -192,62 +223,128 @@ class LauncherPrefs(private val context: Context) { fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() } /** - * Creates `SharedPreferences.Editor` transactions for removing all the provided [Item] values - * from their respective `SharedPreferences` files. These returned `Editors` can then be - * committed or applied for synchronous or async behavior. + * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the + * item is boot aware, this method removes the data from both the boot aware and encrypted + * 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): List = - items - .groupBy { it.sharedPrefFile } - .map { (file, items) -> - context.getSharedPreferences(file, Context.MODE_PRIVATE).edit().also { editor -> - items.forEach { item -> editor.remove(item.sharedPrefKey) } - } + private fun prepareToRemove(items: Array): List { + val itemsPerFile = items.groupBy { it.encryptedPrefs }.toMutableMap() + + if (isBootAwareStartupDataEnabled) { + val bootAwareUpdates = items.filter { it.isBootAware } + 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 { + private const val TAG = "LauncherPrefs" + @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs" + @JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) } @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context) - @JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "") - @JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false) + @JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "", true) + @JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, true) @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "") @JvmField val WORK_EDU_STEP = backedUpItem(WorkProfileManager.KEY_WORK_EDU_STEP, 0) - @JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "") - @JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1) + @JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", true) + @JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, true) @JvmField val DEVICE_TYPE = - backedUpItem(DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE) - @JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "") + backedUpItem(DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, true) + @JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", true) @JvmField 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 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 val ALLOW_ROTATION = backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) { 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 @JvmStatic - fun backedUpItem(sharedPrefKey: String, defaultValue: T): ConstantItem = - ConstantItem(sharedPrefKey, true, defaultValue) + fun backedUpItem( + sharedPrefKey: String, + defaultValue: T, + isBootAware: Boolean = false + ): ConstantItem = + ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, isBootAware) @JvmStatic fun backedUpItem( sharedPrefKey: String, type: Class, + isBootAware: Boolean = false, defaultValueFromContext: (c: Context) -> T - ): ContextualItem = ContextualItem(sharedPrefKey, true, defaultValueFromContext, type) + ): ContextualItem = + ContextualItem( + sharedPrefKey, + isBackedUp = true, + defaultValueFromContext, + isBootAware, + type + ) @VisibleForTesting @JvmStatic - fun nonRestorableItem(sharedPrefKey: String, defaultValue: T): ConstantItem = - ConstantItem(sharedPrefKey, false, defaultValue) + fun nonRestorableItem( + sharedPrefKey: String, + defaultValue: T, + isBootAware: Boolean = false + ): ConstantItem = + ConstantItem(sharedPrefKey, isBackedUp = false, defaultValue, isBootAware) @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.") @JvmStatic @@ -255,7 +352,7 @@ class LauncherPrefs(private val context: Context) { // Use application context for shared preferences, so we use single cached instance return context.applicationContext.getSharedPreferences( 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 return context.applicationContext.getSharedPreferences( 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> = mutableSetOf() + abstract class Item { abstract val sharedPrefKey: String abstract val isBackedUp: Boolean abstract val type: Class<*> + abstract val isBootAware: Boolean val sharedPrefFile: String get() = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY @@ -285,14 +389,22 @@ data class ConstantItem( override val sharedPrefKey: String, override val isBackedUp: Boolean, 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 override val type: Class = defaultValue!!::class.java -) : Item() +) : Item() { + init { + if (isBootAware && isBootAwareStartupDataEnabled) { + BOOT_AWARE_ITEMS.add(this) + } + } +} data class ContextualItem( override val sharedPrefKey: String, override val isBackedUp: Boolean, private val defaultSupplier: (c: Context) -> T, + override val isBootAware: Boolean, override val type: Class ) : Item() { private var default: T? = null diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt index 7b49583b86..f5e13d2487 100644 --- a/src/com/android/launcher3/util/LockedUserState.kt +++ b/src/com/android/launcher3/util/LockedUserState.kt @@ -50,7 +50,9 @@ class LockedUserState(private val mContext: Context) : SafeCloseable { } companion object { - @VisibleForTesting val INSTANCE = MainThreadInitializedObject { LockedUserState(it) } + @VisibleForTesting + @JvmField + val INSTANCE = MainThreadInitializedObject { LockedUserState(it) } @JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context) } diff --git a/tests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/src/com/android/launcher3/LauncherPrefsTest.kt index e8372b1f24..41ef3de267 100644 --- a/tests/src/com/android/launcher3/LauncherPrefsTest.kt +++ b/tests/src/com/android/launcher3/LauncherPrefsTest.kt @@ -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 +import android.content.Context +import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry +import com.android.launcher3.LauncherPrefs.Companion.BOOT_AWARE_PREFS_KEY import com.google.common.truth.Truth.assertThat import java.util.concurrent.CountDownLatch 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_STRING_ITEM = LauncherPrefs.nonRestorableItem("2", "( ͡❛ ͜ʖ ͡❛)") 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_PREF_KEY = "test_pref_key" +private const val WAIT_TIME_IN_SECONDS = 3L + @SmallTest @RunWith(AndroidJUnit4::class) class LauncherPrefsTest { - private val launcherPrefs by lazy { - LauncherPrefs.get(InstrumentationRegistry.getInstrumentation().targetContext).apply { - remove(TEST_BOOLEAN_ITEM, TEST_STRING_ITEM, TEST_INT_ITEM) - } + private val context by lazy { InstrumentationRegistry.getInstrumentation().targetContext } + private val launcherPrefs by lazy { LauncherPrefs.get(context) } + + init { + isBootAwareStartupDataEnabled = true } @Test @@ -52,7 +73,7 @@ class LauncherPrefsTest { addListener(listener, TEST_STRING_ITEM) 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) } } @@ -68,7 +89,7 @@ class LauncherPrefsTest { 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 - assertThat(latch.await(2, TimeUnit.SECONDS)).isFalse() + assertThat(latch.await(WAIT_TIME_IN_SECONDS, TimeUnit.SECONDS)).isFalse() remove(TEST_STRING_ITEM) } } @@ -85,7 +106,7 @@ class LauncherPrefsTest { TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "abc"), 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) latch = CountDownLatch(1) @@ -96,7 +117,7 @@ class LauncherPrefsTest { ) 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) 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() + } } diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java index fa4c5193c8..545b6456e2 100644 --- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java +++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java @@ -510,7 +510,7 @@ public class LauncherModelHelper { UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE, LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE, DisplayController.INSTANCE, CustomWidgetManager.INSTANCE, - SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE, + SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE, LockedUserState.INSTANCE, ItemInstallQueue.INSTANCE, WindowManagerProxy.INSTANCE); mPm = spy(getBaseContext().getPackageManager()); mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());