Snap for 9682161 from 52aa680e18 to udc-release
Change-Id: I8ede7b7a863446a9fe32b7ff3f2e24960a045693
This commit is contained in:
@@ -36,4 +36,4 @@ winsonc@google.com
|
||||
xuqiu@google.com
|
||||
|
||||
per-file FeatureFlags.java, globs = set noparent
|
||||
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com
|
||||
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
|
||||
|
||||
@@ -88,6 +88,7 @@ import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.touch.ItemClickHandler;
|
||||
import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.NavigationMode;
|
||||
import com.android.launcher3.util.PackageManagerHelper;
|
||||
@@ -105,7 +106,6 @@ import com.android.systemui.unfold.updates.RotationChangeProvider;
|
||||
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
|
||||
@@ -878,8 +878,9 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
|
||||
* (potentially breaking a split pair).
|
||||
*/
|
||||
private void launchFromTaskbarPreservingSplitIfVisible(RecentsView recents, ItemInfo info) {
|
||||
ComponentKey componentToBeLaunched = new ComponentKey(info.getTargetComponent(), info.user);
|
||||
recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
|
||||
info.getTargetComponent(),
|
||||
componentToBeLaunched,
|
||||
foundTask -> {
|
||||
if (foundTask != null) {
|
||||
TaskView foundTaskView =
|
||||
|
||||
@@ -723,7 +723,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
}
|
||||
play(as, mControllers.taskbarViewController
|
||||
.createRevealAnimToIsStashed(isStashed, isInApp()), 0, duration, EMPHASIZED);
|
||||
.createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED);
|
||||
|
||||
if (skipInterpolator != null) {
|
||||
as.setInterpolator(skipInterpolator);
|
||||
|
||||
@@ -30,16 +30,15 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Base class for providing different taskbar UI
|
||||
@@ -189,8 +188,12 @@ public class TaskbarUIController {
|
||||
if (recentsView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ComponentKey componentToBeStaged = new ComponentKey(
|
||||
splitSelectSource.itemInfo.getTargetComponent(),
|
||||
splitSelectSource.itemInfo.user);
|
||||
recentsView.getSplitSelectController().findLastActiveTaskAndRunCallback(
|
||||
splitSelectSource.intent.getComponent(),
|
||||
componentToBeStaged,
|
||||
foundTask -> {
|
||||
splitSelectSource.alreadyRunningTaskId = foundTask == null
|
||||
? INVALID_TASK_ID
|
||||
@@ -206,8 +209,9 @@ public class TaskbarUIController {
|
||||
*/
|
||||
public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) {
|
||||
RecentsView recents = getRecentsView();
|
||||
ComponentKey secondAppComponent = new ComponentKey(info.getTargetComponent(), info.user);
|
||||
recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
|
||||
info.getTargetComponent(),
|
||||
secondAppComponent,
|
||||
foundTask -> {
|
||||
if (foundTask != null) {
|
||||
TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
|
||||
|
||||
@@ -17,6 +17,7 @@ package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
|
||||
import static com.android.launcher3.Utilities.squaredHypot;
|
||||
import static com.android.launcher3.anim.AnimatedFloat.VALUE;
|
||||
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
|
||||
@@ -303,7 +304,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
* @param isStashed When true, the icon crops vertically to the size of the stashed handle.
|
||||
* When false, the reverse happens.
|
||||
*/
|
||||
public AnimatorSet createRevealAnimToIsStashed(boolean isStashed, boolean isInApp) {
|
||||
public AnimatorSet createRevealAnimToIsStashed(boolean isStashed) {
|
||||
AnimatorSet as = new AnimatorSet();
|
||||
|
||||
Rect stashedBounds = new Rect();
|
||||
@@ -317,6 +318,10 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
for (int i = numChildren - 1; i >= 0; i--) {
|
||||
View child = mTaskbarView.getChildAt(i);
|
||||
|
||||
if (child == mTaskbarView.getQsb()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Crop the icons to/from the nav handle shape.
|
||||
as.play(createRevealAnimForView(child, isStashed));
|
||||
|
||||
@@ -437,7 +442,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
|
||||
float scale = ((float) taskbarDp.iconSizePx) / launcherDp.hotseatQsbVisualHeight;
|
||||
setter.addFloat(child, SCALE_PROPERTY, scale, 1f, interpolator);
|
||||
|
||||
setter.setFloat(child, ICON_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
|
||||
setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
|
||||
|
||||
if (mIsHotseatIconOnTopWhenAligned) {
|
||||
setter.addFloat(child, VIEW_ALPHA, 0f, 1f,
|
||||
|
||||
@@ -136,6 +136,7 @@ import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControlle
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
|
||||
import com.android.launcher3.util.ActivityOptionsWrapper;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
import com.android.launcher3.util.NavigationMode;
|
||||
@@ -577,10 +578,13 @@ public class QuickstepLauncher extends Launcher {
|
||||
@Override
|
||||
public void startSplitSelection(SplitSelectSource splitSelectSource) {
|
||||
RecentsView recentsView = getOverviewPanel();
|
||||
ComponentKey componentToBeStaged = new ComponentKey(
|
||||
splitSelectSource.itemInfo.getTargetComponent(),
|
||||
splitSelectSource.itemInfo.user);
|
||||
// Check if there is already an instance of this app running, if so, initiate the split
|
||||
// using that.
|
||||
mSplitSelectStateController.findLastActiveTaskAndRunCallback(
|
||||
splitSelectSource.intent.getComponent(),
|
||||
componentToBeStaged,
|
||||
foundTask -> {
|
||||
splitSelectSource.alreadyRunningTaskId = foundTask == null
|
||||
? INVALID_TASK_ID
|
||||
|
||||
@@ -18,28 +18,53 @@ package com.android.launcher3.uioverrides.flags;
|
||||
|
||||
import static android.app.ActivityThread.currentApplication;
|
||||
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.provider.DeviceConfig.Properties;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags.BooleanFlag;
|
||||
import com.android.launcher3.config.FeatureFlags.IntFlag;
|
||||
import com.android.launcher3.util.ScreenOnTracker;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Helper class to create various flags for system build
|
||||
*/
|
||||
public class FlagsFactory {
|
||||
|
||||
private static final String TAG = "FlagsFactory";
|
||||
|
||||
private static final FlagsFactory INSTANCE = new FlagsFactory();
|
||||
private static final boolean FLAG_AUTO_APPLY_ENABLED = false;
|
||||
|
||||
public static final String FLAGS_PREF_NAME = "featureFlags";
|
||||
public static final String NAMESPACE_LAUNCHER = "launcher";
|
||||
|
||||
private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
|
||||
|
||||
|
||||
private final Set<String> mKeySet = new HashSet<>();
|
||||
private boolean mRestartRequested = false;
|
||||
|
||||
private FlagsFactory() {
|
||||
if (!FLAG_AUTO_APPLY_ENABLED) {
|
||||
return;
|
||||
}
|
||||
DeviceConfig.addOnPropertiesChangedListener(
|
||||
NAMESPACE_LAUNCHER, UI_HELPER_EXECUTOR, this::onPropertiesChanged);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new debug flag
|
||||
*/
|
||||
@@ -63,6 +88,7 @@ public class FlagsFactory {
|
||||
*/
|
||||
public static BooleanFlag getReleaseFlag(
|
||||
int bugId, String key, boolean defaultValueInCode, String description) {
|
||||
INSTANCE.mKeySet.add(key);
|
||||
boolean defaultValue = DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, defaultValueInCode);
|
||||
if (Utilities.IS_DEBUG_DEVICE) {
|
||||
SharedPreferences prefs = currentApplication()
|
||||
@@ -78,6 +104,15 @@ public class FlagsFactory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new integer flag. Integer flags are always release flags
|
||||
*/
|
||||
public static IntFlag getIntFlag(
|
||||
int bugId, String key, int defaultValueInCode, String description) {
|
||||
INSTANCE.mKeySet.add(key);
|
||||
return new IntFlag(DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode));
|
||||
}
|
||||
|
||||
static List<DebugFlag> getDebugFlags() {
|
||||
if (!Utilities.IS_DEBUG_DEVICE) {
|
||||
return Collections.emptyList();
|
||||
@@ -121,4 +156,28 @@ public class FlagsFactory {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onPropertiesChanged(Properties properties) {
|
||||
if (!Collections.disjoint(properties.getKeyset(), mKeySet)) {
|
||||
// Schedule a restart
|
||||
if (mRestartRequested) {
|
||||
return;
|
||||
}
|
||||
Log.e(TAG, "Flag changed, scheduling restart");
|
||||
mRestartRequested = true;
|
||||
ScreenOnTracker sot = ScreenOnTracker.INSTANCE.get(currentApplication());
|
||||
if (sot.isScreenOn()) {
|
||||
sot.addListener(this::onScreenOnChanged);
|
||||
} else {
|
||||
onScreenOnChanged(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onScreenOnChanged(boolean isOn) {
|
||||
if (mRestartRequested && !isOn) {
|
||||
Log.e(TAG, "Restart requested, killing process");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,7 +108,8 @@ public class FallbackSwipeHandler extends
|
||||
super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
|
||||
continuingLastGesture, inputConsumer);
|
||||
|
||||
mRunningOverHome = mGestureState.getRunningTask().isHomeTask();
|
||||
mRunningOverHome = mGestureState.getRunningTask() != null
|
||||
&& mGestureState.getRunningTask().isHomeTask();
|
||||
if (mRunningOverHome) {
|
||||
runActionOnRemoteHandles(remoteTargetHandle ->
|
||||
remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
|
||||
|
||||
@@ -81,7 +81,8 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
|
||||
boolean isTablet = activity.getDeviceProfile().isTablet;
|
||||
|
||||
boolean isGridOnlyOverview = isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get();
|
||||
// Add overview actions to the menu when in in-place rotate landscape mode.
|
||||
// Add overview actions to the menu when in in-place rotate landscape mode, or in
|
||||
// grid-only overview.
|
||||
if ((!canLauncherRotate && isInLandscape) || isGridOnlyOverview) {
|
||||
// Add screenshot action to task menu.
|
||||
List<SystemShortcut> screenshotShortcuts = TaskShortcutFactory.SCREENSHOT
|
||||
@@ -90,8 +91,9 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
|
||||
shortcuts.addAll(screenshotShortcuts);
|
||||
}
|
||||
|
||||
// Add modal action only if display orientation is the same as the device orientation.
|
||||
if (orientedState.getDisplayRotation() == ROTATION_0) {
|
||||
// Add modal action only if display orientation is the same as the device orientation,
|
||||
// or in grid-only overview.
|
||||
if (orientedState.getDisplayRotation() == ROTATION_0 || isGridOnlyOverview) {
|
||||
List<SystemShortcut> modalShortcuts = TaskShortcutFactory.MODAL
|
||||
.getShortcuts(activity, taskContainer);
|
||||
if (modalShortcuts != null) {
|
||||
|
||||
@@ -419,6 +419,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);
|
||||
|
||||
@@ -30,7 +30,6 @@ import android.app.ActivityManager;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.ActivityThread;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -59,6 +58,7 @@ import com.android.launcher3.statehandlers.DepthController;
|
||||
import com.android.launcher3.statemanager.StateManager;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
@@ -163,7 +163,7 @@ public class SplitSelectStateController {
|
||||
* Used in various task-switching or splitscreen operations when we need to check if there is a
|
||||
* currently running Task of a certain type and use the most recent one.
|
||||
*/
|
||||
public void findLastActiveTaskAndRunCallback(ComponentName componentName,
|
||||
public void findLastActiveTaskAndRunCallback(ComponentKey componentKey,
|
||||
Consumer<Task> callback) {
|
||||
mRecentTasksModel.getTasks(taskGroups -> {
|
||||
Task lastActiveTask = null;
|
||||
@@ -171,12 +171,12 @@ public class SplitSelectStateController {
|
||||
for (int i = taskGroups.size() - 1; i >= 0; i--) {
|
||||
GroupTask groupTask = taskGroups.get(i);
|
||||
Task task1 = groupTask.task1;
|
||||
if (isInstanceOfComponent(task1, componentName)) {
|
||||
if (isInstanceOfComponent(task1, componentKey)) {
|
||||
lastActiveTask = task1;
|
||||
break;
|
||||
}
|
||||
Task task2 = groupTask.task2;
|
||||
if (isInstanceOfComponent(task2, componentName)) {
|
||||
if (isInstanceOfComponent(task2, componentKey)) {
|
||||
lastActiveTask = task2;
|
||||
break;
|
||||
}
|
||||
@@ -190,13 +190,14 @@ public class SplitSelectStateController {
|
||||
* Checks if a given Task is the most recently-active Task of type componentName. Used for
|
||||
* selecting already-running Tasks for splitscreen.
|
||||
*/
|
||||
public boolean isInstanceOfComponent(@Nullable Task task, ComponentName componentName) {
|
||||
public boolean isInstanceOfComponent(@Nullable Task task, ComponentKey componentKey) {
|
||||
// Exclude the task that is already staged
|
||||
if (task == null || task.key.id == mInitialTaskId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return task.key.baseIntent.getComponent().equals(componentName);
|
||||
return task.key.baseIntent.getComponent().equals(componentKey.componentName)
|
||||
&& task.key.userId == componentKey.user.getIdentifier();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,12 +23,14 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.os.Handler
|
||||
import android.os.UserHandle
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.launcher3.LauncherState
|
||||
import com.android.launcher3.logging.StatsLogManager
|
||||
import com.android.launcher3.model.data.ItemInfo
|
||||
import com.android.launcher3.statehandlers.DepthController
|
||||
import com.android.launcher3.statemanager.StateManager
|
||||
import com.android.launcher3.util.ComponentKey
|
||||
import com.android.launcher3.util.SplitConfigurationOptions
|
||||
import com.android.launcher3.util.withArgCaptor
|
||||
import com.android.quickstep.RecentsModel
|
||||
@@ -60,6 +62,9 @@ class SplitSelectStateControllerTest {
|
||||
|
||||
lateinit var splitSelectStateController: SplitSelectStateController
|
||||
|
||||
private val primaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId)
|
||||
private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
@@ -77,6 +82,7 @@ class SplitSelectStateControllerTest {
|
||||
|
||||
@Test
|
||||
fun activeTasks_noMatchingTasks() {
|
||||
val nonMatchingComponent = ComponentKey(ComponentName("no", "match"), primaryUserHandle)
|
||||
val groupTask1 =
|
||||
generateGroupTask(
|
||||
ComponentName("pomegranate", "juice"),
|
||||
@@ -100,7 +106,7 @@ class SplitSelectStateControllerTest {
|
||||
val consumer =
|
||||
withArgCaptor<Consumer<ArrayList<GroupTask>>> {
|
||||
splitSelectStateController.findLastActiveTaskAndRunCallback(
|
||||
ComponentName("no", "match"),
|
||||
nonMatchingComponent,
|
||||
taskConsumer
|
||||
)
|
||||
verify(recentsModel).getTasks(capture())
|
||||
@@ -114,6 +120,8 @@ class SplitSelectStateControllerTest {
|
||||
fun activeTasks_singleMatchingTask() {
|
||||
val matchingPackage = "hotdog"
|
||||
val matchingClass = "juice"
|
||||
val matchingComponent =
|
||||
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
|
||||
val groupTask1 =
|
||||
generateGroupTask(
|
||||
ComponentName(matchingPackage, matchingClass),
|
||||
@@ -149,7 +157,100 @@ class SplitSelectStateControllerTest {
|
||||
val consumer =
|
||||
withArgCaptor<Consumer<ArrayList<GroupTask>>> {
|
||||
splitSelectStateController.findLastActiveTaskAndRunCallback(
|
||||
ComponentName(matchingPackage, matchingClass),
|
||||
matchingComponent,
|
||||
taskConsumer
|
||||
)
|
||||
verify(recentsModel).getTasks(capture())
|
||||
}
|
||||
|
||||
// Send our mocked tasks
|
||||
consumer.accept(tasks)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activeTasks_skipTaskWithDifferentUser() {
|
||||
val matchingPackage = "hotdog"
|
||||
val matchingClass = "juice"
|
||||
val nonPrimaryUserComponent =
|
||||
ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
|
||||
val groupTask1 =
|
||||
generateGroupTask(
|
||||
ComponentName(matchingPackage, matchingClass),
|
||||
ComponentName("pomegranate", "juice")
|
||||
)
|
||||
val groupTask2 =
|
||||
generateGroupTask(
|
||||
ComponentName("pumpkin", "pie"),
|
||||
ComponentName("personal", "computer")
|
||||
)
|
||||
val tasks: ArrayList<GroupTask> = ArrayList()
|
||||
tasks.add(groupTask1)
|
||||
tasks.add(groupTask2)
|
||||
|
||||
// Assertions happen in the callback we get from what we pass into
|
||||
// #findLastActiveTaskAndRunCallback
|
||||
val taskConsumer =
|
||||
Consumer<Task> { assertNull("No tasks should have matched", it /*task*/) }
|
||||
|
||||
// Capture callback from recentsModel#getTasks()
|
||||
val consumer =
|
||||
withArgCaptor<Consumer<ArrayList<GroupTask>>> {
|
||||
splitSelectStateController.findLastActiveTaskAndRunCallback(
|
||||
nonPrimaryUserComponent,
|
||||
taskConsumer
|
||||
)
|
||||
verify(recentsModel).getTasks(capture())
|
||||
}
|
||||
|
||||
// Send our mocked tasks
|
||||
consumer.accept(tasks)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun activeTasks_findTaskAsNonPrimaryUser() {
|
||||
val matchingPackage = "hotdog"
|
||||
val matchingClass = "juice"
|
||||
val nonPrimaryUserComponent =
|
||||
ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
|
||||
val groupTask1 =
|
||||
generateGroupTask(
|
||||
ComponentName(matchingPackage, matchingClass),
|
||||
nonPrimaryUserHandle,
|
||||
ComponentName("pomegranate", "juice"),
|
||||
nonPrimaryUserHandle
|
||||
)
|
||||
val groupTask2 =
|
||||
generateGroupTask(
|
||||
ComponentName("pumpkin", "pie"),
|
||||
ComponentName("personal", "computer")
|
||||
)
|
||||
val tasks: ArrayList<GroupTask> = ArrayList()
|
||||
tasks.add(groupTask1)
|
||||
tasks.add(groupTask2)
|
||||
|
||||
// Assertions happen in the callback we get from what we pass into
|
||||
// #findLastActiveTaskAndRunCallback
|
||||
val taskConsumer =
|
||||
Consumer<Task> {
|
||||
assertEquals(
|
||||
"ComponentName package mismatched",
|
||||
it.key.baseIntent.component.packageName,
|
||||
matchingPackage
|
||||
)
|
||||
assertEquals(
|
||||
"ComponentName class mismatched",
|
||||
it.key.baseIntent.component.className,
|
||||
matchingClass
|
||||
)
|
||||
assertEquals("userId mismatched", it.key.userId, nonPrimaryUserHandle.identifier)
|
||||
assertEquals(it, groupTask1.task1)
|
||||
}
|
||||
|
||||
// Capture callback from recentsModel#getTasks()
|
||||
val consumer =
|
||||
withArgCaptor<Consumer<ArrayList<GroupTask>>> {
|
||||
splitSelectStateController.findLastActiveTaskAndRunCallback(
|
||||
nonPrimaryUserComponent,
|
||||
taskConsumer
|
||||
)
|
||||
verify(recentsModel).getTasks(capture())
|
||||
@@ -163,6 +264,8 @@ class SplitSelectStateControllerTest {
|
||||
fun activeTasks_multipleMatchMostRecentTask() {
|
||||
val matchingPackage = "hotdog"
|
||||
val matchingClass = "juice"
|
||||
val matchingComponent =
|
||||
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
|
||||
val groupTask1 =
|
||||
generateGroupTask(
|
||||
ComponentName(matchingPackage, matchingClass),
|
||||
@@ -198,7 +301,7 @@ class SplitSelectStateControllerTest {
|
||||
val consumer =
|
||||
withArgCaptor<Consumer<ArrayList<GroupTask>>> {
|
||||
splitSelectStateController.findLastActiveTaskAndRunCallback(
|
||||
ComponentName(matchingPackage, matchingClass),
|
||||
matchingComponent,
|
||||
taskConsumer
|
||||
)
|
||||
verify(recentsModel).getTasks(capture())
|
||||
@@ -245,6 +348,7 @@ class SplitSelectStateControllerTest {
|
||||
assertFalse(splitSelectStateController.isSplitSelectActive)
|
||||
}
|
||||
|
||||
// Generate GroupTask with default userId.
|
||||
private fun generateGroupTask(
|
||||
task1ComponentName: ComponentName,
|
||||
task2ComponentName: ComponentName
|
||||
@@ -268,4 +372,34 @@ class SplitSelectStateControllerTest {
|
||||
SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1)
|
||||
)
|
||||
}
|
||||
|
||||
// Generate GroupTask with custom user handles.
|
||||
private fun generateGroupTask(
|
||||
task1ComponentName: ComponentName,
|
||||
userHandle1: UserHandle,
|
||||
task2ComponentName: ComponentName,
|
||||
userHandle2: UserHandle
|
||||
): GroupTask {
|
||||
val task1 = Task()
|
||||
var taskInfo = ActivityManager.RunningTaskInfo()
|
||||
// Apply custom userHandle1
|
||||
taskInfo.userId = userHandle1.identifier
|
||||
var intent = Intent()
|
||||
intent.component = task1ComponentName
|
||||
taskInfo.baseIntent = intent
|
||||
task1.key = Task.TaskKey(taskInfo)
|
||||
val task2 = Task()
|
||||
taskInfo = ActivityManager.RunningTaskInfo()
|
||||
// Apply custom userHandle2
|
||||
taskInfo.userId = userHandle2.identifier
|
||||
intent = Intent()
|
||||
intent.component = task2ComponentName
|
||||
taskInfo.baseIntent = intent
|
||||
task2.key = Task.TaskKey(taskInfo)
|
||||
return GroupTask(
|
||||
task1,
|
||||
task2,
|
||||
SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) -> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 <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` */
|
||||
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")
|
||||
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) {
|
||||
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 =
|
||||
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 <T : Any> put(item: Item, value: T): Unit =
|
||||
context
|
||||
.getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putValue(item, value)
|
||||
.apply()
|
||||
/** See referenced `put` method above. */
|
||||
fun <T : Any> 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<SharedPreferences.Editor>` 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<out Pair<Item, Any>>
|
||||
): List<SharedPreferences.Editor> =
|
||||
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<out Pair<Item, Any>>
|
||||
): List<SharedPreferences.Editor> {
|
||||
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<Item, Any> ->
|
||||
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<String>)
|
||||
Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>)
|
||||
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<out Item>): List<SharedPreferences.Editor> =
|
||||
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<out Item>): List<SharedPreferences.Editor> {
|
||||
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 <T> backedUpItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
|
||||
ConstantItem(sharedPrefKey, true, defaultValue)
|
||||
fun <T> backedUpItem(
|
||||
sharedPrefKey: String,
|
||||
defaultValue: T,
|
||||
isBootAware: Boolean = false
|
||||
): ConstantItem<T> =
|
||||
ConstantItem(sharedPrefKey, isBackedUp = true, defaultValue, isBootAware)
|
||||
|
||||
@JvmStatic
|
||||
fun <T> backedUpItem(
|
||||
sharedPrefKey: String,
|
||||
type: Class<out T>,
|
||||
isBootAware: Boolean = false,
|
||||
defaultValueFromContext: (c: Context) -> T
|
||||
): ContextualItem<T> = ContextualItem(sharedPrefKey, true, defaultValueFromContext, type)
|
||||
): ContextualItem<T> =
|
||||
ContextualItem(
|
||||
sharedPrefKey,
|
||||
isBackedUp = true,
|
||||
defaultValueFromContext,
|
||||
isBootAware,
|
||||
type
|
||||
)
|
||||
|
||||
@VisibleForTesting
|
||||
@JvmStatic
|
||||
fun <T> nonRestorableItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
|
||||
ConstantItem(sharedPrefKey, false, defaultValue)
|
||||
fun <T> nonRestorableItem(
|
||||
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.")
|
||||
@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<ConstantItem<*>> = 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<T>(
|
||||
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<out T> = defaultValue!!::class.java
|
||||
) : Item()
|
||||
) : Item() {
|
||||
init {
|
||||
if (isBootAware && isBootAwareStartupDataEnabled) {
|
||||
BOOT_AWARE_ITEMS.add(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ContextualItem<T>(
|
||||
override val sharedPrefKey: String,
|
||||
override val isBackedUp: Boolean,
|
||||
private val defaultSupplier: (c: Context) -> T,
|
||||
override val isBootAware: Boolean,
|
||||
override val type: Class<out T>
|
||||
) : Item() {
|
||||
private var default: T? = null
|
||||
|
||||
@@ -251,10 +251,6 @@ public final class FeatureFlags {
|
||||
270394392, "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", false,
|
||||
"Enable option to launch search results using the new view container transitions");
|
||||
|
||||
public static final BooleanFlag TWO_PREDICTED_ROWS_ALL_APPS_SEARCH = getReleaseFlag(270394225,
|
||||
"TWO_PREDICTED_ROWS_ALL_APPS_SEARCH", false,
|
||||
"Use 2 rows of app predictions in All Apps search zero-state");
|
||||
|
||||
public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = getReleaseFlag(
|
||||
270394468, "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", true,
|
||||
"Enable option to show keyboard when going to all-apps");
|
||||
@@ -361,10 +357,6 @@ public final class FeatureFlags {
|
||||
"Enable the ability to generate monochromatic icons, if it is not provided by the app"
|
||||
);
|
||||
|
||||
public static final BooleanFlag ENABLE_DREAM_TRANSITION = getDebugFlag(270396364,
|
||||
"ENABLE_DREAM_TRANSITION", true,
|
||||
"Enable the launcher transition when the device enters a dream");
|
||||
|
||||
public static final BooleanFlag ENABLE_TASKBAR_EDU_TOOLTIP = getDebugFlag(270396268,
|
||||
"ENABLE_TASKBAR_EDU_TOOLTIP", true,
|
||||
"Enable the tooltip version of the Taskbar education flow.");
|
||||
@@ -391,10 +383,6 @@ public final class FeatureFlags {
|
||||
"ENABLE_KEYBOARD_QUICK_SWITCH", true,
|
||||
"Enables keyboard quick switching");
|
||||
|
||||
public static final BooleanFlag ENABLE_SMARTSPACE_DEFAULT_DATE_REMOVED = getDebugFlag(269761613,
|
||||
"ENABLE_SMARTSPACE_DEFAULT_DATE_REMOVED", false,
|
||||
"Enables remove smartspace default date");
|
||||
|
||||
public static class BooleanFlag {
|
||||
|
||||
private final boolean mCurrentValue;
|
||||
@@ -407,4 +395,20 @@ public final class FeatureFlags {
|
||||
return mCurrentValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class representing an integer flag
|
||||
*/
|
||||
public static class IntFlag {
|
||||
|
||||
private final int mCurrentValue;
|
||||
|
||||
public IntFlag(int currentValue) {
|
||||
mCurrentValue = currentValue;
|
||||
}
|
||||
|
||||
public int get() {
|
||||
return mCurrentValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.launcher3.uioverrides.flags;
|
||||
|
||||
import com.android.launcher3.config.FeatureFlags.BooleanFlag;
|
||||
import com.android.launcher3.config.FeatureFlags.IntFlag;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -42,6 +43,14 @@ public class FlagsFactory {
|
||||
return new BooleanFlag(defaultValueInCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new integer flag. Integer flags are always release flags
|
||||
*/
|
||||
public static IntFlag getIntFlag(
|
||||
int bugId, String key, int defaultValueInCode, String description) {
|
||||
return new IntFlag(defaultValueInCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the current flags state to the print writer
|
||||
*/
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user