Merge "Add additional fake task views on overview gesture completion."
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
index 8f31c22..080633a 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
@@ -52,8 +52,7 @@
     public static Bundle convertDataModelToAppTargetBundle(Context context, BgDataModel dataModel) {
         Bundle bundle = new Bundle();
         ArrayList<AppTargetEvent> events = new ArrayList<>();
-        ArrayList<ItemInfo> workspaceItems = new ArrayList<>(dataModel.workspaceItems);
-        workspaceItems.addAll(dataModel.appWidgets);
+        ArrayList<ItemInfo> workspaceItems = dataModel.getAllWorkspaceItems();
         for (ItemInfo item : workspaceItems) {
             AppTarget target = getAppTargetFromInfo(context, item);
             if (target != null && !isTrackedForPrediction(item)) continue;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 9a3a67f..7675a79 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -304,7 +304,8 @@
 
         mCurrentAnimation.setEndAction(this::clearState);
         mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
-                velocity, mEndDisplacement, animationDuration);
+                velocity * orientationHandler.getSecondaryTranslationDirectionFactor(),
+                mEndDisplacement, animationDuration);
     }
 
     private void clearState() {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 3c6299f..201dfe7 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -724,6 +724,7 @@
         setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
         mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
         mGestureStarted = true;
+        mTaskViewSimulator.setDrawsBelowRecents(true);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index ffb05df..901040d 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -294,8 +294,12 @@
             if (mSurfaceControl != null) {
                 currentRect.roundOut(mTempRect);
                 Transaction t = new Transaction();
-                t.setGeometry(mSurfaceControl, null, mTempRect, Surface.ROTATION_0);
-                t.apply();
+                try {
+                    t.setGeometry(mSurfaceControl, null, mTempRect, Surface.ROTATION_0);
+                    t.apply();
+                } catch (RuntimeException e) {
+                    // Ignore
+                }
             }
         }
 
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 4bb1bb5..f696efe 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -82,7 +82,6 @@
         mTaskViewSimulator.getOrientationState().update(
                 mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
                 mDeviceState.getRotationTouchHelper().getDisplayRotation());
-        mTaskViewSimulator.setDrawsBelowRecents(true);
 
         mMaxShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.max_shadow_radius);
         mTransformParams.setShadowRadius(mMaxShadowRadius);
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 87fee79..16ca43d 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -16,8 +16,9 @@
 
 package com.android.quickstep.util;
 
+import static com.android.systemui.shared.system.InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_PIP;
+
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
@@ -26,7 +27,9 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.systemui.shared.pip.PipSurfaceTransactionHelper;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 
 /**
  * An {@link Animator} that animates an Activity to PiP (picture-in-picture) window when
@@ -76,9 +79,27 @@
                 startBounds.right - sourceRectHint.right,
                 startBounds.bottom - sourceRectHint.bottom);
 
-        addListener(new AnimatorListenerAdapter() {
+        addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                InteractionJankMonitorWrapper.begin(CUJ_APP_CLOSE_TO_PIP);
+                super.onAnimationStart(animation);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                super.onAnimationCancel(animation);
+                InteractionJankMonitorWrapper.cancel(CUJ_APP_CLOSE_TO_PIP);
+            }
+
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                InteractionJankMonitorWrapper.end(CUJ_APP_CLOSE_TO_PIP);
+            }
+
             @Override
             public void onAnimationEnd(Animator animation) {
+                if (!mHasAnimationEnded) super.onAnimationEnd(animation);
                 SwipePipToHomeAnimator.this.onAnimationEnd();
             }
         });
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index aeed16a..e151777 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -1099,11 +1099,20 @@
      * @return the max _id in the provided table.
      */
     @Thunk static int getMaxId(SQLiteDatabase db, String query, Object... args) {
-        int max = (int) DatabaseUtils.longForQuery(db,
-                String.format(Locale.ENGLISH, query, args),
-                null);
-        if (max < 0) {
-            throw new RuntimeException("Error: could not query max id");
+        int max = 0;
+        try (SQLiteStatement prog = db.compileStatement(
+                String.format(Locale.ENGLISH, query, args))) {
+            max = (int) DatabaseUtils.longForQuery(prog, null);
+            if (max < 0) {
+                throw new RuntimeException("Error: could not query max id");
+            }
+        } catch (IllegalArgumentException exception) {
+            String message = exception.getMessage();
+            if (message.contains("re-open") && message.contains("already-closed")) {
+                // Don't crash trying to end a transaction an an already closed DB. See b/173162852.
+            } else {
+                throw exception;
+            }
         }
         return max;
     }
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index edaf51d..f6d1651 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -29,6 +29,8 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 
+import com.android.launcher3.Utilities;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -133,15 +135,20 @@
 
     /**
      * Starts playing the animation with the provided velocity optionally playing any
-     * physics based animations
+     * physics based animations.
+     * @param goingToEnd Whether we are going to the end (progress = 1) or not (progress = 0).
+     * @param velocityPxPerMs The velocity at which to start the animation, in pixels / millisecond.
+     * @param endDistance The distance (pixels) that the animation will travel from progress 0 to 1.
+     * @param animationDuration The duration of the non-physics based animation.
      */
     public void startWithVelocity(Context context, boolean goingToEnd,
-            float velocity, float scale, long animationDuration) {
-        float scaleInverse = 1 / Math.abs(scale);
-        float scaledVelocity = velocity * scaleInverse;
+            float velocityPxPerMs, float endDistance, long animationDuration) {
+        float distanceInverse = 1 / Math.abs(endDistance);
+        float velocityProgressPerMs = velocityPxPerMs * distanceInverse;
 
+        float oneFrameProgress = velocityProgressPerMs * getSingleFrameMs(context);
         float nextFrameProgress = boundToRange(getProgressFraction()
-                + scaledVelocity * getSingleFrameMs(context), 0f, 1f);
+                + oneFrameProgress, 0f, 1f);
 
         // Update setters for spring
         int springFlag = goingToEnd
@@ -154,8 +161,8 @@
                 SpringAnimationBuilder s = new SpringAnimationBuilder(context)
                         .setStartValue(mCurrentFraction)
                         .setEndValue(goingToEnd ? 1 : 0)
-                        .setStartVelocity(scaledVelocity)
-                        .setMinimumVisibleChange(scaleInverse)
+                        .setStartVelocity(velocityProgressPerMs)
+                        .setMinimumVisibleChange(distanceInverse)
                         .setDampingRatio(h.springProperty.mDampingRatio)
                         .setStiffness(h.springProperty.mStiffness)
                         .computeParams();
@@ -164,8 +171,18 @@
                 springDuration = Math.max(expectedDurationL, springDuration);
 
                 float expectedDuration = expectedDurationL;
-                h.mapper = (progress, globalEndProgress) ->
-                        mAnimationPlayer.getCurrentPlayTime() / expectedDuration;
+                h.mapper = (progress, globalEndProgress) -> {
+                    if (expectedDuration <= 0 || oneFrameProgress >= 1) {
+                        return 1;
+                    } else {
+                        // Start from one frame ahead of the current position.
+                        return Utilities.mapToRange(
+                                mAnimationPlayer.getCurrentPlayTime() / expectedDuration,
+                                0, 1,
+                                Math.abs(oneFrameProgress), 1,
+                                LINEAR);
+                    }
+                };
                 h.anim.setInterpolator(s::getInterpolatedValue);
             }
         }
@@ -174,7 +191,7 @@
 
         if (springDuration <= animationDuration) {
             mAnimationPlayer.setDuration(animationDuration);
-            mAnimationPlayer.setInterpolator(scrollInterpolatorForVelocity(velocity));
+            mAnimationPlayer.setInterpolator(scrollInterpolatorForVelocity(velocityPxPerMs));
         } else {
             // Since spring requires more time to run, we let the other animations play with
             // current time and interpolation and by clamping the duration.
@@ -182,7 +199,7 @@
 
             float cutOff = animationDuration / (float) springDuration;
             mAnimationPlayer.setInterpolator(
-                    clampToProgress(scrollInterpolatorForVelocity(velocity), 0, cutOff));
+                    clampToProgress(scrollInterpolatorForVelocity(velocityPxPerMs), 0, cutOff));
         }
         mAnimationPlayer.start();
     }
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index effb3a4..efc1201 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -629,11 +629,13 @@
         private WorkspaceResult(BgDataModel dataModel,
                 WidgetsModel widgetsModel,
                 Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
-            mWorkspaceItems = dataModel.workspaceItems;
-            mAppWidgets = dataModel.appWidgets;
-            mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
-            mWidgetsModel = widgetsModel;
-            mWidgetProvidersMap = widgetProviderInfoMap;
+            synchronized (dataModel) {
+                mWorkspaceItems = dataModel.workspaceItems;
+                mAppWidgets = dataModel.appWidgets;
+                mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
+                mWidgetsModel = widgetsModel;
+                mWidgetProvidersMap = widgetProviderInfoMap;
+            }
         }
     }
 }
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 49b40ed..c217a47 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -376,6 +376,16 @@
     }
 
     /**
+     * Returns a list containing all workspace items including widgets.
+     */
+    public synchronized ArrayList<ItemInfo> getAllWorkspaceItems() {
+        ArrayList<ItemInfo> items = new ArrayList<>(workspaceItems.size() + appWidgets.size());
+        items.addAll(workspaceItems);
+        items.addAll(appWidgets);
+        return items;
+    }
+
+    /**
      * Calls the provided {@code op} for all workspaceItems in the in-memory model (both persisted
      * items and dynamic/predicted items for the provided {@code userHandle}.
      * Note the call is not synchronized over the model, that should be handled by the called.
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index b108788..8e085ce 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -166,12 +166,7 @@
 
     private void sendFirstScreenActiveInstallsBroadcast() {
         ArrayList<ItemInfo> firstScreenItems = new ArrayList<>();
-
-        ArrayList<ItemInfo> allItems = new ArrayList<>();
-        synchronized (mBgDataModel) {
-            allItems.addAll(mBgDataModel.workspaceItems);
-            allItems.addAll(mBgDataModel.appWidgets);
-        }
+        ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems();
         // Screen set is never empty
         final int firstScreen = mBgDataModel.collectWorkspaceScreens().get(0);
 
@@ -858,10 +853,12 @@
                     .call(contentResolver,
                             LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
                     .getIntArray(LauncherSettings.Settings.EXTRA_VALUE);
-            for (int folderId : deletedFolderIds) {
-                mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId));
-                mBgDataModel.folders.remove(folderId);
-                mBgDataModel.itemsIdMap.remove(folderId);
+            synchronized (mBgDataModel) {
+                for (int folderId : deletedFolderIds) {
+                    mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId));
+                    mBgDataModel.folders.remove(folderId);
+                    mBgDataModel.itemsIdMap.remove(folderId);
+                }
             }
 
             // Remove any ghost widgets
diff --git a/src/com/android/launcher3/util/SecureSettingsObserver.java b/src/com/android/launcher3/util/SecureSettingsObserver.java
index 4b22429..9fe72ad 100644
--- a/src/com/android/launcher3/util/SecureSettingsObserver.java
+++ b/src/com/android/launcher3/util/SecureSettingsObserver.java
@@ -88,7 +88,7 @@
     public static SecureSettingsObserver newOneHandedSettingsObserver(Context context,
             OnChangeListener listener) {
         return new SecureSettingsObserver(
-                context.getContentResolver(), listener, ONE_HANDED_ENABLED, 1);
+                context.getContentResolver(), listener, ONE_HANDED_ENABLED, 0);
     }
 
     /**
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index a39f215..1648bdd 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -299,7 +299,9 @@
                     launcher -> assertTrue("Launcher internal state didn't switch to Showing Menu",
                             isOptionsPopupVisible(launcher)));
 
-            menu.getMenuItem(1).launch(getAppPackageName());
+            final AppIconMenuItem menuItem = menu.getMenuItem(1);
+            assertEquals("Wrong menu item", "Shortcut 2", menuItem.getText());
+            menuItem.launch(getAppPackageName());
         } finally {
             allApps.unfreeze();
         }
@@ -342,6 +344,7 @@
                     openMenu().
                     getMenuItem(0);
             final String shortcutName = menuItem.getText();
+            assertEquals("Wrong menu item", "Shortcut 3", shortcutName);
 
             menuItem.dragToWorkspace(false, false);
             mLauncher.getWorkspace().getWorkspaceAppIcon(shortcutName).launch(getAppPackageName());