Merge "Import translations. DO NOT MERGE ANYWHERE" into tm-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherLifecycleListener.java
similarity index 78%
rename from quickstep/src/com/android/launcher3/LauncherInitListener.java
rename to quickstep/src/com/android/launcher3/LauncherLifecycleListener.java
index 28bd701..82c646d 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherLifecycleListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -16,19 +16,23 @@
package com.android.launcher3;
import android.animation.AnimatorSet;
+import android.annotation.Nullable;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.CancellationSignal;
import android.view.RemoteAnimationTarget;
import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.ActivityLifecycleListener;
import com.android.quickstep.util.RemoteAnimationProvider;
import java.util.function.BiPredicate;
+/**
+ * {@link ActivityLifecycleListener} for the in-launcher recents.
+ */
@TargetApi(Build.VERSION_CODES.P)
-public class LauncherInitListener extends ActivityInitListener<Launcher> {
+public class LauncherLifecycleListener extends ActivityLifecycleListener<Launcher> {
private RemoteAnimationProvider mRemoteAnimationProvider;
@@ -36,13 +40,16 @@
* @param onInitListener a callback made when the activity is initialized. The callback should
* return true to continue receiving callbacks (ie. for if the activity is
* recreated).
+ * @param onDestroyListener a callback made when the activity is destroyed.
*/
- public LauncherInitListener(BiPredicate<Launcher, Boolean> onInitListener) {
- super(onInitListener, Launcher.ACTIVITY_TRACKER);
+ public LauncherLifecycleListener(
+ @Nullable BiPredicate<Launcher, Boolean> onInitListener,
+ @Nullable Runnable onDestroyListener) {
+ super(onInitListener, onDestroyListener, Launcher.ACTIVITY_TRACKER);
}
@Override
- public boolean handleInit(Launcher launcher, boolean alreadyOnHome) {
+ public boolean handleActivityReady(Launcher launcher, boolean alreadyOnHome) {
if (mRemoteAnimationProvider != null) {
QuickstepTransitionManager appTransitionManager =
((QuickstepLauncher) launcher).getAppTransitionManager();
@@ -68,7 +75,7 @@
}, cancellationSignal);
}
launcher.deferOverlayCallbacksUntilNextResumeOrStop();
- return super.handleInit(launcher, alreadyOnHome);
+ return super.handleActivityReady(launcher, alreadyOnHome);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 3d8ffc4..e41841a 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -50,6 +50,7 @@
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.LAUNCHER_DESTROYED;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
@@ -113,7 +114,7 @@
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.ActivityLifecycleListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.InputProxyHandlerFactory;
@@ -159,7 +160,7 @@
protected final BaseActivityInterface<S, T> mActivityInterface;
protected final InputConsumerProxy mInputConsumerProxy;
- protected final ActivityInitListener mActivityInitListener;
+ protected final ActivityLifecycleListener mActivityInitListener;
// Callbacks to be made once the recents animation starts
private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
private final OnScrollChangedListener mOnRecentsScrollListener = this::onRecentsViewScroll;
@@ -329,7 +330,8 @@
InputConsumerController inputConsumer) {
super(context, deviceState, gestureState);
mActivityInterface = gestureState.getActivityInterface();
- mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
+ mActivityInitListener = mActivityInterface.createActivityLifecycleListener(
+ this::onActivityInit, this::onActivityDestroy);
mInputConsumerProxy =
new InputConsumerProxy(context, /* rotationSupplier = */ () -> {
if (mRecentsView == null) {
@@ -520,6 +522,11 @@
return true;
}
+ private void onActivityDestroy() {
+ ActiveGestureLog.INSTANCE.addLog("Launcher activity destroyed", LAUNCHER_DESTROYED);
+ onGestureCancelled();
+ }
+
/**
* Return true if the window should be translated horizontally if the recents view scrolls
*/
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 274b686..5523518 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -60,7 +60,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.views.ScrimView;
-import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.ActivityLifecycleListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -123,8 +123,17 @@
public abstract AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback);
- public abstract ActivityInitListener createActivityInitListener(
- Predicate<Boolean> onInitListener);
+ /**
+ * Creates a activity listener for activity initialized and/or destroyed. One or both of these
+ * listeners must be provided.
+ *
+ * @param onInitListener a callback made when the activity is initialized. The callback should
+ * return true to continue receiving callbacks (ie. for if the activity is
+ * recreated).
+ * @param onDestroyListener a callback made when the activity is destroyed.
+ */
+ public abstract ActivityLifecycleListener createActivityLifecycleListener(
+ @Nullable Predicate<Boolean> onInitListener, @Nullable Runnable onDestroyListener);
/**
* Sets a callback to be run when an activity launch happens while launcher is not yet resumed.
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index ae9fb0b..a658566 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -36,7 +36,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.fallback.RecentsState;
-import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.ActivityLifecycleListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.views.RecentsView;
@@ -88,10 +88,12 @@
}
@Override
- public ActivityInitListener createActivityInitListener(
- Predicate<Boolean> onInitListener) {
- return new ActivityInitListener<>((activity, alreadyOnHome) ->
- onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER);
+ public ActivityLifecycleListener createActivityLifecycleListener(
+ @Nullable Predicate<Boolean> onInitListener, @Nullable Runnable onDestroyListener) {
+ return new ActivityLifecycleListener<>(
+ (activity, alreadyOnHome) -> onInitListener.test(alreadyOnHome),
+ onDestroyListener,
+ RecentsActivity.ACTIVITY_TRACKER);
}
@Nullable
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 9ff9416..cb54d2e 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -37,7 +37,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
-import com.android.launcher3.LauncherInitListener;
+import com.android.launcher3.LauncherLifecycleListener;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statehandlers.DepthController;
@@ -49,7 +49,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.NavigationMode;
import com.android.quickstep.GestureState.GestureEndTarget;
-import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.ActivityLifecycleListener;
import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -136,9 +136,10 @@
}
@Override
- public ActivityInitListener createActivityInitListener(Predicate<Boolean> onInitListener) {
- return new LauncherInitListener((activity, alreadyOnHome) ->
- onInitListener.test(alreadyOnHome));
+ public ActivityLifecycleListener createActivityLifecycleListener(
+ @Nullable Predicate<Boolean> onInitListener, @Nullable Runnable onDestroyListener) {
+ return new LauncherLifecycleListener((activity, alreadyOnHome) ->
+ onInitListener.test(alreadyOnHome), onDestroyListener);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 60065fb..0fdd8b5 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -37,7 +37,7 @@
ON_SETTLED_ON_END_TARGET, START_RECENTS_ANIMATION, FINISH_RECENTS_ANIMATION,
CANCEL_RECENTS_ANIMATION, SET_ON_PAGE_TRANSITION_END_CALLBACK, CANCEL_CURRENT_ANIMATION,
CLEANUP_SCREENSHOT, SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED,
- FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER,
+ FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED,
/**
* These GestureEvents are specifically associated to state flags that get set in
@@ -162,6 +162,13 @@
+ "before/without setting end target to new task",
writer);
break;
+ case LAUNCHER_DESTROYED:
+ errorDetected |= printErrorIfTrue(
+ true,
+ prefix,
+ /* errorMessage= */ "Launcher destroyed mid-gesture",
+ writer);
+ break;
case STATE_GESTURE_COMPLETED:
errorDetected |= printErrorIfTrue(
!encounteredEvents.contains(GestureEvent.MOTION_UP),
diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
deleted file mode 100644
index aeec36f..0000000
--- a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 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.util;
-
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.util.ActivityTracker;
-import com.android.launcher3.util.ActivityTracker.SchedulerCallback;
-
-import java.util.function.BiPredicate;
-
-public class ActivityInitListener<T extends BaseActivity> implements
- SchedulerCallback<T> {
-
- private BiPredicate<T, Boolean> mOnInitListener;
- private final ActivityTracker<T> mActivityTracker;
-
- private boolean mIsRegistered = false;
-
- /**
- * @param onInitListener a callback made when the activity is initialized. The callback should
- * return true to continue receiving callbacks (ie. for if the activity is
- * recreated).
- */
- public ActivityInitListener(BiPredicate<T, Boolean> onInitListener,
- ActivityTracker<T> tracker) {
- mOnInitListener = onInitListener;
- mActivityTracker = tracker;
- }
-
- @Override
- public final boolean init(T activity, boolean alreadyOnHome) {
- if (!mIsRegistered) {
- // Don't receive any more updates
- return false;
- }
- return handleInit(activity, alreadyOnHome);
- }
-
- protected boolean handleInit(T activity, boolean alreadyOnHome) {
- return mOnInitListener.test(activity, alreadyOnHome);
- }
-
- /**
- * Registers the activity-created listener. If the activity is already created, then the
- * callback provided in the constructor will be called synchronously.
- */
- public void register() {
- mIsRegistered = true;
- mActivityTracker.registerCallback(this);
- }
-
- /**
- * After calling this, we won't {@link #init} even when the activity is ready.
- */
- public void unregister() {
- mActivityTracker.unregisterCallback(this);
- mIsRegistered = false;
- mOnInitListener = null;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/ActivityLifecycleListener.java b/quickstep/src/com/android/quickstep/util/ActivityLifecycleListener.java
new file mode 100644
index 0000000..1edf188
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ActivityLifecycleListener.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 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.util;
+
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.util.ActivityTracker;
+import com.android.launcher3.util.ActivityTracker.SchedulerCallback;
+
+import java.util.function.BiPredicate;
+
+/**
+ * Listener for activity initialized and/or destroyed.
+ */
+public class ActivityLifecycleListener<T extends BaseActivity> implements
+ SchedulerCallback<T> {
+
+ private static final String TAG = "ActivityLifecycleListener";
+
+ @Nullable private final BiPredicate<T, Boolean> mOnInitListener;
+ @Nullable private final Runnable mOnDestroyListener;
+ private final ActivityTracker<T> mActivityTracker;
+
+ private boolean mIsRegistered = false;
+
+ /**
+ * One or both of {@code onInitListener} and {@code onInitListener} must be provided, otherwise
+ * the created instance will effectively be a no-op.
+ *
+ * @param onInitListener a callback made when the activity is initialized. The callback should
+ * return true to continue receiving callbacks (ie. for if the activity is
+ * recreated).
+ * @param onDestroyListener a callback made when the activity is destroyed.
+ */
+ public ActivityLifecycleListener(
+ @Nullable BiPredicate<T, Boolean> onInitListener,
+ @Nullable Runnable onDestroyListener,
+ ActivityTracker<T> tracker) {
+ if (onInitListener == null && onDestroyListener == null) {
+ throw new IllegalArgumentException("Both listeners cannot be null");
+ }
+ mOnInitListener = onInitListener;
+ mOnDestroyListener = onDestroyListener;
+ mActivityTracker = tracker;
+ }
+
+ @Override
+ public final boolean onActivityReady(T activity, boolean alreadyOnHome) {
+ if (!mIsRegistered) {
+ // Don't receive any more updates
+ return false;
+ }
+ return handleActivityReady(activity, alreadyOnHome);
+ }
+
+ protected boolean handleActivityReady(T activity, boolean alreadyOnHome) {
+ if (mOnInitListener == null) {
+ Log.e(TAG, "Cannot handle init: init listener is null", new Exception());
+ return false;
+ }
+ return mOnInitListener.test(activity, alreadyOnHome);
+ }
+
+ @Override
+ public void onActivityDestroyed() {
+ if (mOnDestroyListener == null) {
+ Log.e(TAG, "Cannot clean up: destroy listener is null", new Exception());
+ return;
+ }
+ mOnDestroyListener.run();
+ }
+
+ /**
+ * Registers the activity-created listener. If the activity is already created, then the
+ * callback provided in the constructor will be called synchronously.
+ */
+ public void register() {
+ mIsRegistered = true;
+ mActivityTracker.registerCallback(this, getType());
+ }
+
+ /**
+ * After calling this, we won't call {@link #onActivityReady} even when the activity is ready.
+ */
+ public void unregister() {
+ mActivityTracker.unregisterCallback(this, getType());
+ mIsRegistered = false;
+ }
+
+ private int getType() {
+ return mOnInitListener != null && mOnDestroyListener != null
+ ? ActivityTracker.TYPE_BOTH
+ : (mOnInitListener != null
+ ? ActivityTracker.TYPE_INIT
+ : ActivityTracker.TYPE_DESTROY);
+ }
+}
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index 981e3a6..6d127b3 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -74,7 +74,7 @@
}
@Override
- public boolean init(Launcher launcher, boolean alreadyOnHome) {
+ public boolean onActivityReady(Launcher launcher, boolean alreadyOnHome) {
AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
launcher.getStateManager().goToState(NORMAL, alreadyOnHome /* animated */);
launcher.getDragLayer().setOnDragListener(this);
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index af43ae8..fb924c1 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -72,8 +72,8 @@
}
@Override
- public boolean init(Launcher launcher, boolean alreadyOnHome) {
- super.init(launcher, alreadyOnHome);
+ public boolean onActivityReady(Launcher launcher, boolean alreadyOnHome) {
+ super.onActivityReady(launcher, alreadyOnHome);
if (!alreadyOnHome) {
launcher.useFadeOutAnimationForLauncherStart(mCancelSignal);
}
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index d63408b..eded5ea 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -66,7 +66,7 @@
public class GridSizeMigrationUtil {
private static final String TAG = "GridSizeMigrationUtil";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private GridSizeMigrationUtil() {
// Util class should not be instantiated
@@ -188,27 +188,54 @@
@NonNull final DeviceGridState srcDeviceState,
@NonNull final DeviceGridState destDeviceState) {
- final List<DbEntry> hotseatItems = destReader.loadHotseatEntries();
- final List<DbEntry> workspaceItems = destReader.loadAllWorkspaceEntries();
- final List<DbEntry> hotseatDiff =
- calcDiff(srcReader.loadHotseatEntries(), hotseatItems);
- final List<DbEntry> workspaceDiff =
- calcDiff(srcReader.loadAllWorkspaceEntries(), workspaceItems);
+ final List<DbEntry> srcHotseatItems = srcReader.loadHotseatEntries();
+ final List<DbEntry> srcWorkspaceItems = srcReader.loadAllWorkspaceEntries();
+ final List<DbEntry> dstHotseatItems = destReader.loadHotseatEntries();
+ final List<DbEntry> dstWorkspaceItems = destReader.loadAllWorkspaceEntries();
+ final List<DbEntry> hotseatToBeAdded = new ArrayList<>(1);
+ final List<DbEntry> workspaceToBeAdded = new ArrayList<>(1);
+ final IntArray toBeRemoved = new IntArray();
+
+ calcDiff(srcHotseatItems, dstHotseatItems, hotseatToBeAdded, toBeRemoved);
+ calcDiff(srcWorkspaceItems, dstWorkspaceItems, workspaceToBeAdded, toBeRemoved);
final int trgX = targetSize.x;
final int trgY = targetSize.y;
- if (hotseatDiff.isEmpty() && workspaceDiff.isEmpty()) {
+ if (DEBUG) {
+ Log.d(TAG, "Start migration:"
+ + "\n Source Device:"
+ + srcWorkspaceItems.stream().map(DbEntry::toString).collect(
+ Collectors.joining(",\n", "[", "]"))
+ + "\n Target Device:"
+ + dstWorkspaceItems.stream().map(DbEntry::toString).collect(
+ Collectors.joining(",\n", "[", "]"))
+ + "\n Removing Items:"
+ + dstWorkspaceItems.stream().filter(entry ->
+ toBeRemoved.contains(entry.id)).map(DbEntry::toString).collect(
+ Collectors.joining(",\n", "[", "]"))
+ + "\n Adding Workspace Items:"
+ + workspaceToBeAdded.stream().map(DbEntry::toString).collect(
+ Collectors.joining(",\n", "[", "]"))
+ + "\n Adding Hotseat Items:"
+ + hotseatToBeAdded.stream().map(DbEntry::toString).collect(
+ Collectors.joining(",\n", "[", "]"))
+ );
+ }
+ if (!toBeRemoved.isEmpty()) {
+ removeEntryFromDb(destReader.mDb, destReader.mTableName, toBeRemoved);
+ }
+ if (hotseatToBeAdded.isEmpty() && workspaceToBeAdded.isEmpty()) {
return false;
}
// Sort the items by the reading order.
- Collections.sort(hotseatDiff);
- Collections.sort(workspaceDiff);
+ Collections.sort(hotseatToBeAdded);
+ Collections.sort(workspaceToBeAdded);
// Migrate hotseat
solveHotseatPlacement(db, srcReader,
- destReader, context, destHotseatSize, hotseatItems, hotseatDiff);
+ destReader, context, destHotseatSize, dstHotseatItems, hotseatToBeAdded);
// Migrate workspace.
// First we create a collection of the screens
@@ -229,8 +256,8 @@
Log.d(TAG, "Migrating " + screenId);
}
solveGridPlacement(db, srcReader,
- destReader, context, screenId, trgX, trgY, workspaceDiff, false);
- if (workspaceDiff.isEmpty()) {
+ destReader, context, screenId, trgX, trgY, workspaceToBeAdded, false);
+ if (workspaceToBeAdded.isEmpty()) {
break;
}
}
@@ -238,42 +265,37 @@
// In case the new grid is smaller, there might be some leftover items that don't fit on
// any of the screens, in this case we add them to new screens until all of them are placed.
int screenId = destReader.mLastScreenId + 1;
- while (!workspaceDiff.isEmpty()) {
+ while (!workspaceToBeAdded.isEmpty()) {
solveGridPlacement(db, srcReader,
- destReader, context, screenId, trgX, trgY, workspaceDiff, preservePages);
+ destReader, context, screenId, trgX, trgY, workspaceToBeAdded, preservePages);
screenId++;
}
return true;
}
- /** Return what's in the src but not in the dest */
- private static List<DbEntry> calcDiff(List<DbEntry> src, List<DbEntry> dest) {
- Map<String, Integer> destIdSet = new HashMap<>();
- for (DbEntry entry : dest) {
- String entryID = entry.getEntryMigrationId();
- if (destIdSet.containsKey(entryID)) {
- destIdSet.put(entryID, destIdSet.get(entryID) + 1);
- } else {
- destIdSet.put(entryID, 1);
+ /**
+ * Calculate the differences between {@code src} (denoted by A) and {@code dest}
+ * (denoted by B).
+ * All DbEntry in A - B will be added to {@code toBeAdded}
+ * All DbEntry.id in B - A will be added to {@code toBeRemoved}
+ */
+ private static void calcDiff(@NonNull final List<DbEntry> src,
+ @NonNull final List<DbEntry> dest, @NonNull final List<DbEntry> toBeAdded,
+ @NonNull final IntArray toBeRemoved) {
+ src.forEach(entry -> {
+ if (!dest.contains(entry)) {
+ toBeAdded.add(entry);
}
- }
- List<DbEntry> diff = new ArrayList<>();
- for (DbEntry entry : src) {
- String entryID = entry.getEntryMigrationId();
- if (destIdSet.containsKey(entryID)) {
- Integer count = destIdSet.get(entryID);
- if (count <= 0) {
- diff.add(entry);
- destIdSet.remove(entryID);
- } else {
- destIdSet.put(entryID, count - 1);
+ });
+ dest.forEach(entry -> {
+ if (!src.contains(entry)) {
+ toBeRemoved.add(entry.id);
+ if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+ entry.mFolderItems.values().forEach(ids -> ids.forEach(toBeRemoved::add));
}
- } else {
- diff.add(entry);
}
- }
- return diff;
+ });
}
private static void insertEntryInDb(SQLiteDatabase db, Context context, DbEntry entry,
@@ -682,12 +704,12 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DbEntry entry = (DbEntry) o;
- return Objects.equals(mIntent, entry.mIntent);
+ return Objects.equals(getEntryMigrationId(), entry.getEntryMigrationId());
}
@Override
public int hashCode() {
- return Objects.hash(mIntent);
+ return Objects.hash(getEntryMigrationId());
}
public void updateContentValues(ContentValues values) {
diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java
index 7af1a13..5f93a66 100644
--- a/src/com/android/launcher3/util/ActivityTracker.java
+++ b/src/com/android/launcher3/util/ActivityTracker.java
@@ -20,8 +20,6 @@
import com.android.launcher3.BaseActivity;
import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.HashSet;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -30,8 +28,15 @@
*/
public final class ActivityTracker<T extends BaseActivity> {
+ public static final int TYPE_INIT = 0;
+ public static final int TYPE_DESTROY = 1;
+ public static final int TYPE_BOTH = 2;
+
private WeakReference<T> mCurrentActivity = new WeakReference<>(null);
- private CopyOnWriteArrayList<SchedulerCallback<T>> mCallbacks = new CopyOnWriteArrayList<>();
+ private CopyOnWriteArrayList<SchedulerCallback<T>> mActivityReadyCallbacks =
+ new CopyOnWriteArrayList<>();
+ private CopyOnWriteArrayList<SchedulerCallback<T>> mActivityDestroyedCallbacks =
+ new CopyOnWriteArrayList<>();
@Nullable
public <R extends T> R getCreatedActivity() {
@@ -42,32 +47,74 @@
if (mCurrentActivity.get() == activity) {
mCurrentActivity.clear();
}
+ for (SchedulerCallback<T> cb : mActivityDestroyedCallbacks) {
+ cb.onActivityDestroyed();
+ unregisterCallback(cb, TYPE_DESTROY);
+ }
+ }
+
+ /** Registers an activity create callback. */
+ public void registerCallback(SchedulerCallback<T> callback) {
+ registerCallback(callback, TYPE_INIT);
}
/**
- * Call {@link SchedulerCallback#init(BaseActivity, boolean)} when the
- * activity is ready. If the activity is already created, this is called immediately.
+ * Call {@link SchedulerCallback#onActivityReady(BaseActivity, boolean)} when the
+ * activity is ready and/or {@link SchedulerCallback#onActivityDestroyed()} when the activity
+ * is destroyed.
*
- * The tracker maintains a strong ref to the callback, so it is up to the caller to return
- * {@code false} in the callback OR to unregister the callback explicitly.
+ * If type is {@link ActivityTracker#TYPE_INIT} TYPE_INIT or
+ * {@link ActivityTracker#TYPE_BOTH} and the activity is already created, this
+ * {@link SchedulerCallback#onActivityReady(BaseActivity, boolean)} is called immediately.
*
- * @param callback The callback to call init() on when the activity is ready.
+ * If type is {@link ActivityTracker#TYPE_DESTROY} or
+ * {@link ActivityTracker#TYPE_BOTH} and the activity is already destroyed,
+ * {@link SchedulerCallback#onActivityDestroyed()} is called immediately.
+ *
+ * The tracker maintains a strong ref to the callbacks, so it is up to the caller to return
+ * {@code false} in {@link SchedulerCallback#onActivityReady(BaseActivity, boolean)} OR to
+ * unregister the callback explicitly.
+ *
+ * @param callback The callback to call init() or cleanUp() on when the activity is ready or
+ * destroyed.
+ * @param type whether to use this callback on activity create, destroy or both.
*/
- public void registerCallback(SchedulerCallback<T> callback) {
+ public void registerCallback(SchedulerCallback<T> callback, int type) {
T activity = mCurrentActivity.get();
- mCallbacks.add(callback);
- if (activity != null) {
- if (!callback.init(activity, activity.isStarted())) {
- unregisterCallback(callback);
+ if (type == TYPE_INIT || type == TYPE_BOTH) {
+ mActivityReadyCallbacks.add(callback);
+ if (activity != null) {
+ if (!callback.onActivityReady(activity, activity.isStarted())) {
+ unregisterCallback(callback, TYPE_INIT);
+ }
+ }
+ }
+ if (type == TYPE_DESTROY || type == TYPE_BOTH) {
+ mActivityDestroyedCallbacks.add(callback);
+ if (activity == null) {
+ callback.onActivityDestroyed();
+ unregisterCallback(callback, TYPE_DESTROY);
}
}
}
/**
- * Unregisters a registered callback.
+ * Unregisters a registered activity create callback.
*/
public void unregisterCallback(SchedulerCallback<T> callback) {
- mCallbacks.remove(callback);
+ unregisterCallback(callback, TYPE_INIT);
+ }
+
+ /**
+ * Unregisters a registered callback.
+ */
+ public void unregisterCallback(SchedulerCallback<T> callback, int type) {
+ if (type == TYPE_INIT || type == TYPE_BOTH) {
+ mActivityReadyCallbacks.remove(callback);
+ }
+ if (type == TYPE_DESTROY || type == TYPE_BOTH) {
+ mActivityDestroyedCallbacks.remove(callback);
+ }
}
public boolean handleCreate(T activity) {
@@ -81,8 +128,8 @@
private boolean handleIntent(T activity, boolean alreadyOnHome) {
boolean handled = false;
- for (SchedulerCallback<T> cb : mCallbacks) {
- if (!cb.init(activity, alreadyOnHome)) {
+ for (SchedulerCallback<T> cb : mActivityReadyCallbacks) {
+ if (!cb.onActivityReady(activity, alreadyOnHome)) {
// Callback doesn't want any more updates
unregisterCallback(cb);
}
@@ -98,6 +145,11 @@
* @param alreadyOnHome Whether the activity is already started.
* @return Whether to continue receiving callbacks (i.e. if the activity is recreated).
*/
- boolean init(T activity, boolean alreadyOnHome);
+ boolean onActivityReady(T activity, boolean alreadyOnHome);
+
+ /**
+ * Called then the activity gets destroyed.
+ */
+ default void onActivityDestroyed() { }
}
}
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt b/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
index 85d7bf9..76a186b 100644
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
@@ -17,6 +17,7 @@
import android.content.Context
import android.content.Intent
+import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase
import android.graphics.Point
import android.os.Process
@@ -183,15 +184,232 @@
// Expected dest grid icons
// _ _ _ _
// 5 6 7 8
- // 9 _ 10_
+ // 9 _ _ _
// _ _ _ _
- assertThat(locMap.size.toLong()).isEqualTo(6)
+ assertThat(locMap.size.toLong()).isEqualTo(5)
assertThat(locMap[testPackage5]).isEqualTo(Point(0, 1))
assertThat(locMap[testPackage6]).isEqualTo(Point(1, 1))
assertThat(locMap[testPackage7]).isEqualTo(Point(2, 1))
assertThat(locMap[testPackage8]).isEqualTo(Point(3, 1))
assertThat(locMap[testPackage9]).isEqualTo(Point(0, 2))
- assertThat(locMap[testPackage10]).isEqualTo(Point(2, 2))
+ }
+
+ /**
+ * Old migration logic, should be modified once [FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC] is
+ * not needed anymore
+ */
+ @Test
+ @Throws(Exception::class)
+ fun testMigrationBackAndForth() {
+ // Hotseat items in grid A
+ // 1 2 _ 3 4
+ modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI)
+ modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI)
+ modelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
+ // Workspace items in grid A
+ // _ _ _ _ _
+ // _ _ _ _ 5
+ // _ _ 6 _ 7
+ // _ _ 8 _ _
+ // _ _ _ _ _
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 1, testPackage5, 5, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage6, 6, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 2, testPackage7, 7, TMP_CONTENT_URI)
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage8, 8, TMP_CONTENT_URI)
+
+ // Hotseat items in grid B
+ // 2 _ _ _
+ modelHelper.addItem(SHORTCUT, 0, HOTSEAT, 0, 0, testPackage2)
+ // Workspace items in grid B
+ // _ _ _ _
+ // _ _ _ 10
+ // _ _ _ _
+ // _ _ _ _
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 1, 3, testPackage10)
+
+ idp.numDatabaseHotseatIcons = 4
+ idp.numColumns = 4
+ idp.numRows = 4
+ val readerGridA = DbReader(db, TMP_TABLE, context, validPackages)
+ val readerGridB = DbReader(db, TABLE_NAME, context, validPackages)
+ // migrate from A -> B
+ GridSizeMigrationUtil.migrate(
+ context,
+ db,
+ readerGridA,
+ readerGridB,
+ idp.numDatabaseHotseatIcons,
+ Point(idp.numColumns, idp.numRows),
+ DeviceGridState(context),
+ DeviceGridState(idp)
+ )
+
+ // Check hotseat items in grid B
+ var c = context.contentResolver.query(
+ CONTENT_URI,
+ arrayOf(SCREEN, INTENT),
+ "container=$CONTAINER_HOTSEAT",
+ null,
+ SCREEN,
+ null
+ ) ?: throw IllegalStateException()
+ // Expected hotseat items in grid B
+ // 2 1 3 4
+ verifyHotseat(c, idp,
+ mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList())
+
+ // Check workspace items in grid B
+ c = context.contentResolver.query(
+ CONTENT_URI,
+ arrayOf(SCREEN, CELLX, CELLY, INTENT),
+ "container=$CONTAINER_DESKTOP",
+ null,
+ null,
+ null
+ ) ?: throw IllegalStateException()
+ var locMap = parseLocMap(context, c)
+ // Expected items in grid B
+ // _ _ _ _
+ // 5 6 7 8
+ // _ _ _ _
+ // _ _ _ _
+ assertThat(locMap.size.toLong()).isEqualTo(4)
+ assertThat(locMap[testPackage5]).isEqualTo(Triple(0, 0, 1))
+ assertThat(locMap[testPackage6]).isEqualTo(Triple(0, 1, 1))
+ assertThat(locMap[testPackage7]).isEqualTo(Triple(0, 2, 1))
+ assertThat(locMap[testPackage8]).isEqualTo(Triple(0, 3, 1))
+
+ // add item in B
+ modelHelper.addItem(APP_ICON, 0, DESKTOP, 0, 2, testPackage9)
+
+ // migrate from B -> A
+ GridSizeMigrationUtil.migrate(
+ context,
+ db,
+ readerGridB,
+ readerGridA,
+ 5,
+ Point(5, 5),
+ DeviceGridState(idp),
+ DeviceGridState(context)
+ )
+ // Check hotseat items in grid A
+ c = context.contentResolver.query(
+ TMP_CONTENT_URI,
+ arrayOf(SCREEN, INTENT),
+ "container=$CONTAINER_HOTSEAT",
+ null,
+ SCREEN,
+ null
+ ) ?: throw IllegalStateException()
+ // Expected hotseat items in grid A
+ // 1 2 _ 3 4
+ verifyHotseat(c, idp, mutableListOf(
+ testPackage1, testPackage2, null, testPackage3, testPackage4).toList())
+
+ // Check workspace items in grid A
+ c = context.contentResolver.query(
+ TMP_CONTENT_URI,
+ arrayOf(SCREEN, CELLX, CELLY, INTENT),
+ "container=$CONTAINER_DESKTOP",
+ null,
+ null,
+ null
+ ) ?: throw IllegalStateException()
+ locMap = parseLocMap(context, c)
+ // Expected workspace items in grid A
+ // _ _ _ _ _
+ // _ _ _ _ 5
+ // 9 _ 6 _ 7
+ // _ _ 8 _ _
+ // _ _ _ _ _
+ assertThat(locMap.size.toLong()).isEqualTo(5)
+ // Verify items that existed in grid A remains in same position
+ assertThat(locMap[testPackage5]).isEqualTo(Triple(0, 4, 1))
+ assertThat(locMap[testPackage6]).isEqualTo(Triple(0, 2, 2))
+ assertThat(locMap[testPackage7]).isEqualTo(Triple(0, 4, 2))
+ assertThat(locMap[testPackage8]).isEqualTo(Triple(0, 2, 3))
+ // Verify items that didn't exist in grid A are added in new screen
+ assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 2))
+
+ // remove item from B
+ modelHelper.deleteItem(7, TMP_TABLE)
+
+ // migrate from A -> B
+ GridSizeMigrationUtil.migrate(
+ context,
+ db,
+ readerGridA,
+ readerGridB,
+ idp.numDatabaseHotseatIcons,
+ Point(idp.numColumns, idp.numRows),
+ DeviceGridState(context),
+ DeviceGridState(idp)
+ )
+
+ // Check hotseat items in grid B
+ c = context.contentResolver.query(
+ CONTENT_URI,
+ arrayOf(SCREEN, INTENT),
+ "container=$CONTAINER_HOTSEAT",
+ null,
+ SCREEN,
+ null
+ ) ?: throw IllegalStateException()
+ // Expected hotseat items in grid B
+ // 2 1 3 4
+ verifyHotseat(c, idp,
+ mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList())
+
+ // Check workspace items in grid B
+ c = context.contentResolver.query(
+ CONTENT_URI,
+ arrayOf(SCREEN, CELLX, CELLY, INTENT),
+ "container=$CONTAINER_DESKTOP",
+ null,
+ null,
+ null
+ ) ?: throw IllegalStateException()
+ locMap = parseLocMap(context, c)
+ // Expected workspace items in grid B
+ // _ _ _ _
+ // 5 6 _ 8
+ // 9 _ _ _
+ // _ _ _ _
+ assertThat(locMap.size.toLong()).isEqualTo(4)
+ assertThat(locMap[testPackage5]).isEqualTo(Triple(0, 0, 1))
+ assertThat(locMap[testPackage6]).isEqualTo(Triple(0, 1, 1))
+ assertThat(locMap[testPackage8]).isEqualTo(Triple(0, 3, 1))
+ assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 2))
+ }
+
+ private fun verifyHotseat(c: Cursor, idp: InvariantDeviceProfile, expected: List<String?>) {
+ assertThat(c.count).isEqualTo(idp.numDatabaseHotseatIcons)
+ val screenIndex = c.getColumnIndex(SCREEN)
+ val intentIndex = c.getColumnIndex(INTENT)
+ expected.forEachIndexed { idx, pkg ->
+ if (pkg == null) return@forEachIndexed
+ c.moveToNext()
+ assertThat(c.getInt(screenIndex).toLong()).isEqualTo(idx)
+ assertThat(c.getString(intentIndex)).contains(pkg)
+ }
+ c.close()
+ }
+
+ private fun parseLocMap(context: Context, c: Cursor): Map<String, Triple<Int, Int, Int>> {
+ // Check workspace items
+ val intentIndex = c.getColumnIndex(INTENT)
+ val screenIndex = c.getColumnIndex(SCREEN)
+ val cellXIndex = c.getColumnIndex(CELLX)
+ val cellYIndex = c.getColumnIndex(CELLY)
+ val locMap = mutableMapOf<String, Triple<Int, Int, Int>>()
+ while (c.moveToNext()) {
+ locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] =
+ Triple(c.getInt(screenIndex), c.getInt(cellXIndex), c.getInt(cellYIndex))
+ }
+ c.close()
+ return locMap.toMap()
}
@Test
diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
index e7e551f..93bf312 100644
--- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -362,6 +362,12 @@
sandboxContext.getContentResolver().insert(contentUri, values);
}
+ public void deleteItem(int itemId, @NonNull final String tableName) {
+ final Uri uri = Uri.parse("content://"
+ + LauncherProvider.AUTHORITY + "/" + tableName + "/" + itemId);
+ sandboxContext.getContentResolver().delete(uri, null, null);
+ }
+
public int[][][] createGrid(int[][][] typeArray) {
return createGrid(typeArray, 1);
}