Loading quickstep/src/com/android/quickstep/BootAwarePreloader.kt 0 → 100644 +52 −0 Original line number Diff line number Diff line /* * 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.moveStartupDataToDeviceProtectedStorageIsEnabled 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 || !moveStartupDataToDeviceProtectedStorageIsEnabled -> { /* 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() } } } } } quickstep/src/com/android/quickstep/TouchInteractionService.java +1 −0 Original line number Diff line number Diff line Loading @@ -501,6 +501,7 @@ public class TouchInteractionService extends Service { mTaskbarManager = new TaskbarManager(this); mRotationTouchHelper = mDeviceState.getRotationTouchHelper(); mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer(); BootAwarePreloader.start(this); // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized. LockedUserState.get(this).runOnUserUnlocked(this::onUserUnlocked); Loading src/com/android/launcher3/LauncherPrefs.kt +124 −23 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import android.content.Context import android.content.Context.MODE_PRIVATE import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.util.Log import android.view.ViewConfiguration import androidx.annotation.VisibleForTesting import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN Loading @@ -37,6 +38,8 @@ import com.android.launcher3.util.Themes * Use same context for shared preferences, so that we use a single cached instance * * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods. * TODO(b/274501660): Fix ReorderWidgets#simpleReorder test before enabling * isBootAwareStartupDataEnabled */ class LauncherPrefs(private val encryptedContext: Context) { private val deviceProtectedStorageContext = Loading @@ -49,8 +52,22 @@ class LauncherPrefs(private val encryptedContext: Context) { 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 (item.encryptionType == EncryptionType.DEVICE_PROTECTED) bootAwarePrefs if ( (moveStartupDataToDeviceProtectedStorageIsEnabled && item.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && isStartupDataMigrated) || item.encryptionType == EncryptionType.DEVICE_PROTECTED ) bootAwarePrefs else item.encryptedPrefs /** Wrapper around `getInner` for a `ContextualItem` */ Loading Loading @@ -130,7 +147,11 @@ class LauncherPrefs(private val encryptedContext: Context) { .toMutableMap() val bootAwareUpdates = updates.filter { it.first.encryptionType == EncryptionType.DEVICE_PROTECTED } updates.filter { (it.first.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && moveStartupDataToDeviceProtectedStorageIsEnabled) || it.first.encryptionType == EncryptionType.DEVICE_PROTECTED } if (bootAwareUpdates.isNotEmpty()) { updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates } Loading Loading @@ -231,7 +252,12 @@ class LauncherPrefs(private val encryptedContext: Context) { .groupBy { it.encryptedPrefs } .toMutableMap() val bootAwareUpdates = items.filter { it.encryptionType == EncryptionType.DEVICE_PROTECTED } val bootAwareUpdates = items.filter { (it.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && moveStartupDataToDeviceProtectedStorageIsEnabled) || it.encryptionType == EncryptionType.DEVICE_PROTECTED } if (bootAwareUpdates.isNotEmpty()) { itemsPerFile[bootAwarePrefs] = bootAwareUpdates } Loading @@ -243,7 +269,24 @@ class LauncherPrefs(private val encryptedContext: Context) { } } fun migrateStartupDataToDeviceProtectedStorage() { if (!moveStartupDataToDeviceProtectedStorageIsEnabled) 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()) { ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.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) } Loading @@ -253,50 +296,80 @@ class LauncherPrefs(private val encryptedContext: Context) { const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY" const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY" @JvmField val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED) val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val ALL_APPS_OVERVIEW_THRESHOLD = nonRestorableItem("pref_all_apps_overview_threshold", 180, EncryptionType.ENCRYPTED) nonRestorableItem( "pref_all_apps_overview_threshold", 180, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE = nonRestorableItem("LPNH_SLOP_PERCENTAGE", 100, EncryptionType.ENCRYPTED) nonRestorableItem("LPNH_SLOP_PERCENTAGE", 100, EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS = nonRestorableItem( "LPNH_TIMEOUT_MS", ViewConfiguration.getLongPressTimeout(), EncryptionType.ENCRYPTED EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT = nonRestorableItem("LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0, EncryptionType.ENCRYPTED) nonRestorableItem( "LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT = nonRestorableItem("LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100, EncryptionType.ENCRYPTED) nonRestorableItem( "LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT = nonRestorableItem("LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1, EncryptionType.ENCRYPTED) nonRestorableItem( "LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS = nonRestorableItem("LPNH_HAPTIC_HINT_ITERATIONS", 50, EncryptionType.ENCRYPTED) nonRestorableItem( "LPNH_HAPTIC_HINT_ITERATIONS", 50, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY = nonRestorableItem("LPNH_HAPTIC_HINT_DELAY", 0, EncryptionType.ENCRYPTED) nonRestorableItem("LPNH_HAPTIC_HINT_DELAY", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val PRIVATE_SPACE_APPS = nonRestorableItem("pref_private_space_apps", 0, EncryptionType.ENCRYPTED) @JvmField val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false) nonRestorableItem("pref_private_space_apps", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false) @JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED) val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "") @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0) @JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED) backedUpItem( DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED) backedUpItem( DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val TASKBAR_PINNING = backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED) Loading @@ -306,10 +379,11 @@ class LauncherPrefs(private val encryptedContext: Context) { backedUpItem( DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, EncryptionType.ENCRYPTED EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED) val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val SHOULD_SHOW_SMARTSPACE = backedUpItem( Loading @@ -322,11 +396,15 @@ class LauncherPrefs(private val encryptedContext: Context) { backedUpItem( RestoreDbTask.RESTORED_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, EncryptionType.ENCRYPTED EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val IS_FIRST_LOAD_AFTER_RESTORE = nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED) nonRestorableItem( FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "") @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "") @JvmField Loading @@ -335,7 +413,7 @@ class LauncherPrefs(private val encryptedContext: Context) { "idp_grid_name", isBackedUp = true, defaultValue = null, encryptionType = EncryptionType.ENCRYPTED, encryptionType = EncryptionType.MOVE_TO_DEVICE_PROTECTED, type = String::class.java ) @JvmField Loading @@ -343,6 +421,14 @@ class LauncherPrefs(private val encryptedContext: Context) { 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, encryptionType = EncryptionType.DEVICE_PROTECTED ) // Preferences for widget configurations @JvmField Loading Loading @@ -407,6 +493,12 @@ class LauncherPrefs(private val encryptedContext: Context) { } } // It is a var because the unit tests are setting this to true so they can run. var moveStartupDataToDeviceProtectedStorageIsEnabled: Boolean = com.android.launcher3.config.FeatureFlags.MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE.get() private val ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE: MutableSet<ConstantItem<*>> = mutableSetOf() abstract class Item { abstract val sharedPrefKey: String abstract val isBackedUp: Boolean Loading @@ -426,6 +518,14 @@ data class ConstantItem<T>( // 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 ) : Item() { init { if ( encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && moveStartupDataToDeviceProtectedStorageIsEnabled ) { ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.add(this) } } fun get(c: Context): T = LauncherPrefs.get(c).get(this) } Loading @@ -452,4 +552,5 @@ data class ContextualItem<T>( enum class EncryptionType { ENCRYPTED, DEVICE_PROTECTED, MOVE_TO_DEVICE_PROTECTED } src/com/android/launcher3/config/FeatureFlags.java +7 −0 Original line number Diff line number Diff line Loading @@ -100,6 +100,13 @@ public final class FeatureFlags { public static final BooleanFlag ENABLE_DISMISS_PREDICTION_UNDO = getDebugFlag(270394476, "ENABLE_DISMISS_PREDICTION_UNDO", DISABLED, "Show an 'Undo' snackbar when users dismiss a predicted hotseat item"); public static final BooleanFlag MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE = getDebugFlag( 251502424, "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED, "Marks LauncherPref data as (and allows it to) available while the device is" + " locked. Enabling this causes a 1-time movement of certain SharedPreferences" + " data. Improves startup latency."); public static final BooleanFlag CONTINUOUS_VIEW_TREE_CAPTURE = getDebugFlag(270395171, "CONTINUOUS_VIEW_TREE_CAPTURE", ENABLED, "Capture View tree every frame"); Loading tests/src/com/android/launcher3/LauncherPrefsTest.kt +122 −1 Original line number Diff line number Diff line Loading @@ -25,6 +25,8 @@ 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 import org.junit.AfterClass import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith Loading @@ -46,6 +48,20 @@ class LauncherPrefsTest { private val context by lazy { InstrumentationRegistry.getInstrumentation().targetContext } private val launcherPrefs by lazy { LauncherPrefs.get(context) } companion object { @BeforeClass @JvmStatic fun setup() { moveStartupDataToDeviceProtectedStorageIsEnabled = true } @AfterClass @JvmStatic fun teardown() { moveStartupDataToDeviceProtectedStorageIsEnabled = false } } @Test fun has_keyMissingFromLauncherPrefs_returnsFalse() { assertThat(launcherPrefs.has(TEST_BOOLEAN_ITEM)).isFalse() Loading Loading @@ -206,13 +222,32 @@ class LauncherPrefsTest { launcherPrefs.removeSync(bootAwareItem) } @Test fun put_bootAwareItem_updatesEncryptedStorage() { val bootAwareItem = LauncherPrefs.backedUpItem( TEST_PREF_KEY, TEST_DEFAULT_VALUE, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) 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, EncryptionType.DEVICE_PROTECTED EncryptionType.MOVE_TO_DEVICE_PROTECTED ) val bootAwarePrefs: SharedPreferences = Loading @@ -228,4 +263,90 @@ class LauncherPrefsTest { launcherPrefs.removeSync(bootAwareItem) assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isFalse() } @Test fun remove_bootAwareItem_removesFromEncryptedStorage() { val bootAwareItem = LauncherPrefs.backedUpItem( TEST_PREF_KEY, TEST_DEFAULT_VALUE, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) 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, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) 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 + "_", EncryptionType.ENCRYPTED ) 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() } } Loading
quickstep/src/com/android/quickstep/BootAwarePreloader.kt 0 → 100644 +52 −0 Original line number Diff line number Diff line /* * 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.moveStartupDataToDeviceProtectedStorageIsEnabled 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 || !moveStartupDataToDeviceProtectedStorageIsEnabled -> { /* 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() } } } } }
quickstep/src/com/android/quickstep/TouchInteractionService.java +1 −0 Original line number Diff line number Diff line Loading @@ -501,6 +501,7 @@ public class TouchInteractionService extends Service { mTaskbarManager = new TaskbarManager(this); mRotationTouchHelper = mDeviceState.getRotationTouchHelper(); mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer(); BootAwarePreloader.start(this); // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized. LockedUserState.get(this).runOnUserUnlocked(this::onUserUnlocked); Loading
src/com/android/launcher3/LauncherPrefs.kt +124 −23 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import android.content.Context import android.content.Context.MODE_PRIVATE import android.content.SharedPreferences import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.util.Log import android.view.ViewConfiguration import androidx.annotation.VisibleForTesting import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN Loading @@ -37,6 +38,8 @@ import com.android.launcher3.util.Themes * Use same context for shared preferences, so that we use a single cached instance * * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods. * TODO(b/274501660): Fix ReorderWidgets#simpleReorder test before enabling * isBootAwareStartupDataEnabled */ class LauncherPrefs(private val encryptedContext: Context) { private val deviceProtectedStorageContext = Loading @@ -49,8 +52,22 @@ class LauncherPrefs(private val encryptedContext: Context) { 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 (item.encryptionType == EncryptionType.DEVICE_PROTECTED) bootAwarePrefs if ( (moveStartupDataToDeviceProtectedStorageIsEnabled && item.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && isStartupDataMigrated) || item.encryptionType == EncryptionType.DEVICE_PROTECTED ) bootAwarePrefs else item.encryptedPrefs /** Wrapper around `getInner` for a `ContextualItem` */ Loading Loading @@ -130,7 +147,11 @@ class LauncherPrefs(private val encryptedContext: Context) { .toMutableMap() val bootAwareUpdates = updates.filter { it.first.encryptionType == EncryptionType.DEVICE_PROTECTED } updates.filter { (it.first.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && moveStartupDataToDeviceProtectedStorageIsEnabled) || it.first.encryptionType == EncryptionType.DEVICE_PROTECTED } if (bootAwareUpdates.isNotEmpty()) { updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates } Loading Loading @@ -231,7 +252,12 @@ class LauncherPrefs(private val encryptedContext: Context) { .groupBy { it.encryptedPrefs } .toMutableMap() val bootAwareUpdates = items.filter { it.encryptionType == EncryptionType.DEVICE_PROTECTED } val bootAwareUpdates = items.filter { (it.encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && moveStartupDataToDeviceProtectedStorageIsEnabled) || it.encryptionType == EncryptionType.DEVICE_PROTECTED } if (bootAwareUpdates.isNotEmpty()) { itemsPerFile[bootAwarePrefs] = bootAwareUpdates } Loading @@ -243,7 +269,24 @@ class LauncherPrefs(private val encryptedContext: Context) { } } fun migrateStartupDataToDeviceProtectedStorage() { if (!moveStartupDataToDeviceProtectedStorageIsEnabled) 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()) { ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.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) } Loading @@ -253,50 +296,80 @@ class LauncherPrefs(private val encryptedContext: Context) { const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY" const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY" @JvmField val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED) val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val ALL_APPS_OVERVIEW_THRESHOLD = nonRestorableItem("pref_all_apps_overview_threshold", 180, EncryptionType.ENCRYPTED) nonRestorableItem( "pref_all_apps_overview_threshold", 180, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE = nonRestorableItem("LPNH_SLOP_PERCENTAGE", 100, EncryptionType.ENCRYPTED) nonRestorableItem("LPNH_SLOP_PERCENTAGE", 100, EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS = nonRestorableItem( "LPNH_TIMEOUT_MS", ViewConfiguration.getLongPressTimeout(), EncryptionType.ENCRYPTED EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT = nonRestorableItem("LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0, EncryptionType.ENCRYPTED) nonRestorableItem( "LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT = nonRestorableItem("LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100, EncryptionType.ENCRYPTED) nonRestorableItem( "LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT = nonRestorableItem("LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1, EncryptionType.ENCRYPTED) nonRestorableItem( "LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS = nonRestorableItem("LPNH_HAPTIC_HINT_ITERATIONS", 50, EncryptionType.ENCRYPTED) nonRestorableItem( "LPNH_HAPTIC_HINT_ITERATIONS", 50, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY = nonRestorableItem("LPNH_HAPTIC_HINT_DELAY", 0, EncryptionType.ENCRYPTED) nonRestorableItem("LPNH_HAPTIC_HINT_DELAY", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val PRIVATE_SPACE_APPS = nonRestorableItem("pref_private_space_apps", 0, EncryptionType.ENCRYPTED) @JvmField val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false) nonRestorableItem("pref_private_space_apps", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false) @JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED) val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "") @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0) @JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.ENCRYPTED) backedUpItem( DeviceGridState.KEY_WORKSPACE_SIZE, "", EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.ENCRYPTED) backedUpItem( DeviceGridState.KEY_HOTSEAT_COUNT, -1, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val TASKBAR_PINNING = backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED) Loading @@ -306,10 +379,11 @@ class LauncherPrefs(private val encryptedContext: Context) { backedUpItem( DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, EncryptionType.ENCRYPTED EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.ENCRYPTED) val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "", EncryptionType.MOVE_TO_DEVICE_PROTECTED) @JvmField val SHOULD_SHOW_SMARTSPACE = backedUpItem( Loading @@ -322,11 +396,15 @@ class LauncherPrefs(private val encryptedContext: Context) { backedUpItem( RestoreDbTask.RESTORED_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE, EncryptionType.ENCRYPTED EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val IS_FIRST_LOAD_AFTER_RESTORE = nonRestorableItem(FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.ENCRYPTED) nonRestorableItem( FIRST_LOAD_AFTER_RESTORE_KEY, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "") @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "") @JvmField Loading @@ -335,7 +413,7 @@ class LauncherPrefs(private val encryptedContext: Context) { "idp_grid_name", isBackedUp = true, defaultValue = null, encryptionType = EncryptionType.ENCRYPTED, encryptionType = EncryptionType.MOVE_TO_DEVICE_PROTECTED, type = String::class.java ) @JvmField Loading @@ -343,6 +421,14 @@ class LauncherPrefs(private val encryptedContext: Context) { 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, encryptionType = EncryptionType.DEVICE_PROTECTED ) // Preferences for widget configurations @JvmField Loading Loading @@ -407,6 +493,12 @@ class LauncherPrefs(private val encryptedContext: Context) { } } // It is a var because the unit tests are setting this to true so they can run. var moveStartupDataToDeviceProtectedStorageIsEnabled: Boolean = com.android.launcher3.config.FeatureFlags.MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE.get() private val ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE: MutableSet<ConstantItem<*>> = mutableSetOf() abstract class Item { abstract val sharedPrefKey: String abstract val isBackedUp: Boolean Loading @@ -426,6 +518,14 @@ data class ConstantItem<T>( // 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 ) : Item() { init { if ( encryptionType == EncryptionType.MOVE_TO_DEVICE_PROTECTED && moveStartupDataToDeviceProtectedStorageIsEnabled ) { ITEMS_TO_MOVE_TO_DEVICE_PROTECTED_STORAGE.add(this) } } fun get(c: Context): T = LauncherPrefs.get(c).get(this) } Loading @@ -452,4 +552,5 @@ data class ContextualItem<T>( enum class EncryptionType { ENCRYPTED, DEVICE_PROTECTED, MOVE_TO_DEVICE_PROTECTED }
src/com/android/launcher3/config/FeatureFlags.java +7 −0 Original line number Diff line number Diff line Loading @@ -100,6 +100,13 @@ public final class FeatureFlags { public static final BooleanFlag ENABLE_DISMISS_PREDICTION_UNDO = getDebugFlag(270394476, "ENABLE_DISMISS_PREDICTION_UNDO", DISABLED, "Show an 'Undo' snackbar when users dismiss a predicted hotseat item"); public static final BooleanFlag MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE = getDebugFlag( 251502424, "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED, "Marks LauncherPref data as (and allows it to) available while the device is" + " locked. Enabling this causes a 1-time movement of certain SharedPreferences" + " data. Improves startup latency."); public static final BooleanFlag CONTINUOUS_VIEW_TREE_CAPTURE = getDebugFlag(270395171, "CONTINUOUS_VIEW_TREE_CAPTURE", ENABLED, "Capture View tree every frame"); Loading
tests/src/com/android/launcher3/LauncherPrefsTest.kt +122 −1 Original line number Diff line number Diff line Loading @@ -25,6 +25,8 @@ 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 import org.junit.AfterClass import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith Loading @@ -46,6 +48,20 @@ class LauncherPrefsTest { private val context by lazy { InstrumentationRegistry.getInstrumentation().targetContext } private val launcherPrefs by lazy { LauncherPrefs.get(context) } companion object { @BeforeClass @JvmStatic fun setup() { moveStartupDataToDeviceProtectedStorageIsEnabled = true } @AfterClass @JvmStatic fun teardown() { moveStartupDataToDeviceProtectedStorageIsEnabled = false } } @Test fun has_keyMissingFromLauncherPrefs_returnsFalse() { assertThat(launcherPrefs.has(TEST_BOOLEAN_ITEM)).isFalse() Loading Loading @@ -206,13 +222,32 @@ class LauncherPrefsTest { launcherPrefs.removeSync(bootAwareItem) } @Test fun put_bootAwareItem_updatesEncryptedStorage() { val bootAwareItem = LauncherPrefs.backedUpItem( TEST_PREF_KEY, TEST_DEFAULT_VALUE, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) 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, EncryptionType.DEVICE_PROTECTED EncryptionType.MOVE_TO_DEVICE_PROTECTED ) val bootAwarePrefs: SharedPreferences = Loading @@ -228,4 +263,90 @@ class LauncherPrefsTest { launcherPrefs.removeSync(bootAwareItem) assertThat(bootAwarePrefs.contains(bootAwareItem.sharedPrefKey)).isFalse() } @Test fun remove_bootAwareItem_removesFromEncryptedStorage() { val bootAwareItem = LauncherPrefs.backedUpItem( TEST_PREF_KEY, TEST_DEFAULT_VALUE, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) 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, EncryptionType.MOVE_TO_DEVICE_PROTECTED ) 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 + "_", EncryptionType.ENCRYPTED ) 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() } }