Merge "Moving mState from Workspace to StateManager" into ub-launcher3-master
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index cd72fba..364b204 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -178,6 +178,15 @@
 
     }
 
+    /**
+     * Resets the view so it can be recycled.
+     */
+    public void reset() {
+        mBadgeInfo = null;
+        mBadgePalette = null;
+        mForceHideBadge = false;
+    }
+
     public void applyFromShortcutInfo(ShortcutInfo info) {
         applyFromShortcutInfo(info, false);
     }
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index bebdbdb..6f40408 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -201,6 +201,10 @@
 
     protected abstract boolean supportsDrop(ItemInfo info);
 
+    public boolean supportsAccessibilityDrop(ItemInfo info) {
+        return supportsDrop(info);
+    }
+
     @Override
     public boolean isDropEnabled() {
         return mActive && (mAccessibleDrag ||
@@ -241,9 +245,13 @@
                 DragLayer.ANIMATION_END_DISAPPEAR, null);
     }
 
+    public abstract int getAccessibilityAction();
+
     @Override
     public void prepareAccessibilityDrop() { }
 
+    public abstract void onAccessibilityDrop(View view, ItemInfo item);
+
     public abstract void completeDrop(DragObject d);
 
     @Override
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index fdd4f34..c12ea57 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -21,6 +21,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
 
@@ -49,14 +50,22 @@
         setTextBasedOnDragSource(dragObject.dragInfo);
     }
 
-    /** @return true for items that should have a "Remove" action in accessibility. */
-    public static boolean supportsAccessibleDrop(ItemInfo info) {
+    /**
+     * @return true for items that should have a "Remove" action in accessibility.
+     */
+    @Override
+    public boolean supportsAccessibilityDrop(ItemInfo info) {
         return (info instanceof ShortcutInfo)
                 || (info instanceof LauncherAppWidgetInfo)
                 || (info instanceof FolderInfo);
     }
 
     @Override
+    public int getAccessibilityAction() {
+        return LauncherAccessibilityDelegate.REMOVE;
+    }
+
+    @Override
     protected boolean supportsDrop(ItemInfo info) {
         return true;
     }
@@ -77,19 +86,21 @@
     public void completeDrop(DragObject d) {
         ItemInfo item = d.dragInfo;
         if ((d.dragSource instanceof Workspace) || (d.dragSource instanceof Folder)) {
-            removeWorkspaceOrFolderItem(mLauncher, item, null);
+            onAccessibilityDrop(null, item);
         }
     }
 
     /**
      * Removes the item from the workspace. If the view is not null, it also removes the view.
      */
-    public static void removeWorkspaceOrFolderItem(Launcher launcher, ItemInfo item, View view) {
+    @Override
+    public void onAccessibilityDrop(View view, ItemInfo item) {
         // Remove the item from launcher and the db, we can ignore the containerInfo in this call
         // because we already remove the drag view from the folder (if the drag originated from
         // a folder) in Folder.beginDrag()
-        launcher.removeItem(view, item, true /* deleteFromDb */);
-        launcher.getWorkspace().stripEmptyScreens();
-        launcher.getDragLayer().announceForAccessibility(launcher.getString(R.string.item_removed));
+        mLauncher.removeItem(view, item, true /* deleteFromDb */);
+        mLauncher.getWorkspace().stripEmptyScreens();
+        mLauncher.getDragLayer()
+                .announceForAccessibility(getContext().getString(R.string.item_removed));
     }
 }
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 29a1349..2f8374a 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.AlphaUpdateListener.updateVisibility;
+import static com.android.launcher3.Utilities.isAccessibilityEnabled;
+
 import android.animation.TimeInterpolator;
 import android.content.Context;
 import android.util.AttributeSet;
@@ -23,13 +26,14 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
-import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AccelerateInterpolator;
 import android.widget.LinearLayout;
 
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 
+import java.util.ArrayList;
+
 /*
  * The top bar containing various drop targets: Delete/App Info/Uninstall.
  */
@@ -42,10 +46,7 @@
 
         @Override
         public void run() {
-            AccessibilityManager am = (AccessibilityManager)
-                    getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-            boolean accessibilityEnabled = am.isEnabled();
-            AlphaUpdateListener.updateVisibility(DropTargetBar.this, accessibilityEnabled);
+            updateVisibility(DropTargetBar.this, isAccessibilityEnabled(getContext()));
         }
     };
 
@@ -55,6 +56,7 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     protected boolean mVisible = false;
 
+    private ButtonDropTarget[] mDropTargets;
     private ViewPropertyAnimator mCurrentAnimation;
 
     public DropTargetBar(Context context, AttributeSet attrs) {
@@ -75,7 +77,27 @@
 
     public void setup(DragController dragController) {
         dragController.addDragListener(this);
-        setupButtonDropTarget(this, dragController);
+        ArrayList<ButtonDropTarget> outList = new ArrayList<>();
+        findDropTargets(this, outList);
+
+        mDropTargets = new ButtonDropTarget[outList.size()];
+        for (int i = 0; i < mDropTargets.length; i++) {
+            mDropTargets[i] = outList.get(i);
+            mDropTargets[i].setDropTargetBar(this);
+            dragController.addDragListener(mDropTargets[i]);
+            dragController.addDropTarget(mDropTargets[i]);
+        }
+    }
+
+    private static void findDropTargets(View view, ArrayList<ButtonDropTarget> outTargets) {
+        if (view instanceof ButtonDropTarget) {
+            outTargets.add((ButtonDropTarget) view);
+        } else if (view instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) view;
+            for (int i = vg.getChildCount() - 1; i >= 0; i--) {
+                findDropTargets(vg.getChildAt(i), outTargets);
+            }
+        }
     }
 
     @Override
@@ -130,20 +152,6 @@
         return result;
     }
 
-    private void setupButtonDropTarget(View view, DragController dragController) {
-        if (view instanceof ButtonDropTarget) {
-            ButtonDropTarget bdt = (ButtonDropTarget) view;
-            bdt.setDropTargetBar(this);
-            dragController.addDragListener(bdt);
-            dragController.addDropTarget(bdt);
-        } else if (view instanceof ViewGroup) {
-            ViewGroup vg = (ViewGroup) view;
-            for (int i = vg.getChildCount() - 1; i >= 0; i--) {
-                setupButtonDropTarget(vg.getChildAt(i), dragController);
-            }
-        }
-    }
-
     private void animateToVisibility(boolean isVisible) {
         if (mVisible != isVisible) {
             mVisible = isVisible;
@@ -190,4 +198,8 @@
             mDeferOnDragEnd = false;
         }
     }
+
+    public ButtonDropTarget[] getDropTargets() {
+        return mDropTargets;
+    }
 }
diff --git a/src/com/android/launcher3/InfoDropTarget.java b/src/com/android/launcher3/InfoDropTarget.java
index f78cde5..289242f 100644
--- a/src/com/android/launcher3/InfoDropTarget.java
+++ b/src/com/android/launcher3/InfoDropTarget.java
@@ -26,6 +26,7 @@
 import android.util.Log;
 import android.widget.Toast;
 
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.util.Themes;
 
@@ -49,8 +50,8 @@
     }
 
     @Override
-    protected ComponentName performDropAction(DragObject d) {
-        return performDropAction(mLauncher, d.dragInfo, null, null);
+    protected ComponentName performDropAction(ItemInfo item) {
+        return performDropAction(mLauncher, item, null, null);
     }
 
     /**
@@ -96,13 +97,15 @@
     }
 
     @Override
-    protected boolean supportsDrop(ItemInfo info) {
-        return supportsDrop(getContext(), info);
+    public int getAccessibilityAction() {
+        return LauncherAccessibilityDelegate.INFO;
     }
 
-    public static boolean supportsDrop(Context context, ItemInfo info) {
+    @Override
+    protected boolean supportsDrop(ItemInfo info) {
         // Only show the App Info drop target if developer settings are enabled.
-        boolean developmentSettingsEnabled = Settings.Global.getInt(context.getContentResolver(),
+        boolean developmentSettingsEnabled = Settings.Global.getInt(
+                getContext().getContentResolver(),
                 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1;
         if (!developmentSettingsEnabled) {
             return false;
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index fc75fe3..d1a2538 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -16,30 +16,22 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 
 import android.animation.Animator;
-import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.annotation.TargetApi;
-import android.os.Build;
-import android.util.Range;
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
 import android.view.ScaleGestureDetector.OnScaleGestureListener;
 
+import com.android.launcher3.compat.AnimatorSetCompat;
 import com.android.launcher3.util.TouchController;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
 /**
  * Detects pinches and animates the Workspace to/from overview mode.
  */
-@TargetApi(Build.VERSION_CODES.O)
 public class PinchToOverviewListener
         implements TouchController, OnScaleGestureListener, Runnable {
 
@@ -55,9 +47,8 @@
     private Workspace mWorkspace = null;
     private boolean mPinchStarted = false;
 
-    private AnimatorSet mCurrentAnimation;
+    private AnimatorSetCompat mCurrentAnimation;
     private float mCurrentScale;
-    private Range<Integer> mDurationRange;
     private boolean mShouldGoToFinalState;
 
     private LauncherState mToState;
@@ -109,14 +100,13 @@
         }
 
         mToState = mLauncher.isInState(OVERVIEW) ? NORMAL : OVERVIEW;
-        mCurrentAnimation = mLauncher.getStateManager()
-                .createAnimationToNewWorkspace(mToState, this);
+        mCurrentAnimation = AnimatorSetCompat.wrap(mLauncher.getStateManager()
+                .createAnimationToNewWorkspace(mToState, this), OVERVIEW_TRANSITION_MS);
         mPinchStarted = true;
         mCurrentScale = 1;
-        mDurationRange = Range.create(0, LauncherAnimUtils.OVERVIEW_TRANSITION_MS);
         mShouldGoToFinalState = false;
 
-        dispatchOnStart(mCurrentAnimation);
+        mCurrentAnimation.dispatchOnStart();
         return true;
     }
 
@@ -160,26 +150,7 @@
         }
 
         // Move the transition animation to that duration.
-        long playPosition = mDurationRange.clamp(
-                (int) ((1 - animationFraction) * mDurationRange.getUpper()));
-        mCurrentAnimation.setCurrentPlayTime(playPosition);
-
+        mCurrentAnimation.setPlayFraction(1 - animationFraction);
         return true;
     }
-
-    private void dispatchOnStart(Animator animator) {
-        for (AnimatorListener l : nonNullList(animator.getListeners())) {
-            l.onAnimationStart(animator);
-        }
-
-        if (animator instanceof AnimatorSet) {
-            for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
-                dispatchOnStart(anim);
-            }
-        }
-    }
-
-    private static <T> List<T> nonNullList(ArrayList<T> list) {
-        return list == null ? Collections.<T>emptyList() : list;
-    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 4393819..8e83a30 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -10,22 +10,28 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.widget.Toast;
 
 import com.android.launcher3.Launcher.OnResumeCallback;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 
 import java.net.URISyntaxException;
 
-public class UninstallDropTarget extends ButtonDropTarget {
+public class UninstallDropTarget extends ButtonDropTarget implements OnAlarmListener {
 
     private static final String TAG = "UninstallDropTarget";
-    private static Boolean sUninstallDisabled;
+
+    private static final long CACHE_EXPIRE_TIMEOUT = 5000;
+    private final ArrayMap<UserHandle, Boolean> mUninstallDisabledCache = new ArrayMap<>(1);
+
+    private final Alarm mCacheExpireAlarm;
 
     public UninstallDropTarget(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -33,6 +39,9 @@
 
     public UninstallDropTarget(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+
+        mCacheExpireAlarm = new Alarm();
+        mCacheExpireAlarm.setOnAlarmListener(this);
     }
 
     @Override
@@ -48,18 +57,29 @@
     }
 
     @Override
-    protected boolean supportsDrop(ItemInfo info) {
-        return supportsDrop(getContext(), info);
+    public void onAlarm(Alarm alarm) {
+        mUninstallDisabledCache.clear();
     }
 
-    public static boolean supportsDrop(Context context, ItemInfo info) {
-        if (sUninstallDisabled == null) {
-            UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-            Bundle restrictions = userManager.getUserRestrictions();
-            sUninstallDisabled = restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
+    @Override
+    public int getAccessibilityAction() {
+        return LauncherAccessibilityDelegate.UNINSTALL;
+    }
+
+    @Override
+    protected boolean supportsDrop(ItemInfo info) {
+        Boolean uninstallDisabled = mUninstallDisabledCache.get(info.user);
+        if (uninstallDisabled == null) {
+            UserManager userManager =
+                    (UserManager) getContext().getSystemService(Context.USER_SERVICE);
+            Bundle restrictions = userManager.getUserRestrictions(info.user);
+            uninstallDisabled = restrictions.getBoolean(UserManager.DISALLOW_APPS_CONTROL, false)
                     || restrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS, false);
+            mUninstallDisabledCache.put(info.user, uninstallDisabled);
         }
-        if (sUninstallDisabled) {
+        // Cancel any pending alarm and set cache expiry after some time
+        mCacheExpireAlarm.setAlarm(CACHE_EXPIRE_TIMEOUT);
+        if (uninstallDisabled) {
             return false;
         }
 
@@ -69,13 +89,13 @@
                 return (appInfo.isSystemApp & AppInfo.FLAG_SYSTEM_NO) != 0;
             }
         }
-        return getUninstallTarget(context, info) != null;
+        return getUninstallTarget(info) != null;
     }
 
     /**
      * @return the component name that should be uninstalled or null.
      */
-    private static ComponentName getUninstallTarget(Context context, ItemInfo item) {
+    private ComponentName getUninstallTarget(ItemInfo item) {
         Intent intent = null;
         UserHandle user = null;
         if (item != null &&
@@ -84,7 +104,7 @@
             user = item.user;
         }
         if (intent != null) {
-            LauncherActivityInfo info = LauncherAppsCompat.getInstance(context)
+            LauncherActivityInfo info = LauncherAppsCompat.getInstance(mLauncher)
                     .resolveActivity(intent, user);
             if (info != null
                     && (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
@@ -103,7 +123,7 @@
 
     @Override
     public void completeDrop(final DragObject d) {
-        ComponentName target = performDropAction(d);
+        ComponentName target = performDropAction(d.dragInfo);
         if (d.dragSource instanceof DeferredOnComplete) {
             DeferredOnComplete deferred = (DeferredOnComplete) d.dragSource;
             if (target != null) {
@@ -119,27 +139,19 @@
      * Performs the drop action and returns the target component for the dragObject or null if
      * the action was not performed.
      */
-    protected ComponentName performDropAction(DragObject d) {
-        return performDropAction(mLauncher, d.dragInfo);
-    }
-
-    /**
-     * Performs the drop action and returns the target component for the dragObject or null if
-     * the action was not performed.
-     */
-    private static ComponentName performDropAction(Context context, ItemInfo info) {
-        ComponentName cn = getUninstallTarget(context, info);
+    protected ComponentName performDropAction(ItemInfo info) {
+        ComponentName cn = getUninstallTarget(info);
         if (cn == null) {
             // System applications cannot be installed. For now, show a toast explaining that.
             // We may give them the option of disabling apps this way.
-            Toast.makeText(context, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
+            Toast.makeText(mLauncher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
             return null;
         }
         try {
-            Intent i = Intent.parseUri(context.getString(R.string.delete_package_intent), 0)
+            Intent i = Intent.parseUri(mLauncher.getString(R.string.delete_package_intent), 0)
                     .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
                     .putExtra(Intent.EXTRA_USER, info.user);
-            context.startActivity(i);
+            mLauncher.startActivity(i);
             return cn;
         } catch (URISyntaxException e) {
             Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info);
@@ -147,8 +159,9 @@
         }
     }
 
-    public static boolean startUninstallActivity(Launcher launcher, ItemInfo info) {
-        return performDropAction(launcher, info) != null;
+    @Override
+    public void onAccessibilityDrop(View view, ItemInfo item) {
+        performDropAction(item);
     }
 
     /**
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 2b3a113..512db72 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -19,6 +19,7 @@
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ButtonDropTarget;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DropTarget.DragObject;
@@ -48,9 +49,9 @@
 
     private static final String TAG = "LauncherAccessibilityDelegate";
 
-    protected static final int REMOVE = R.id.action_remove;
-    protected static final int INFO = R.id.action_info;
-    protected static final int UNINSTALL = R.id.action_uninstall;
+    public static final int REMOVE = R.id.action_remove;
+    public static final int INFO = R.id.action_info;
+    public static final int UNINSTALL = R.id.action_uninstall;
     protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
     protected static final int MOVE = R.id.action_move;
     protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
@@ -111,14 +112,10 @@
             info.addAction(mActions.get(DEEP_SHORTCUTS));
         }
 
-        if (DeleteDropTarget.supportsAccessibleDrop(item)) {
-            info.addAction(mActions.get(REMOVE));
-        }
-        if (UninstallDropTarget.supportsDrop(host.getContext(), item)) {
-            info.addAction(mActions.get(UNINSTALL));
-        }
-        if (InfoDropTarget.supportsDrop(host.getContext(), item)) {
-            info.addAction(mActions.get(INFO));
+        for (ButtonDropTarget target : mLauncher.getDropTargetBar().getDropTargets()) {
+            if (target.supportsAccessibilityDrop(item)) {
+                info.addAction(mActions.get(target.getAccessibilityAction()));
+            }
         }
 
         // Do not add move actions for keyboard request as this uses virtual nodes.
@@ -151,15 +148,7 @@
     }
 
     public boolean performAction(final View host, final ItemInfo item, int action) {
-        if (action == REMOVE) {
-            DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host);
-            return true;
-        } else if (action == INFO) {
-            InfoDropTarget.startDetailsActivityForInfo(item, mLauncher, null, null);
-            return true;
-        } else if (action == UNINSTALL) {
-            return UninstallDropTarget.startUninstallActivity(mLauncher, item);
-        } else if (action == MOVE) {
+        if (action == MOVE) {
             beginAccessibleDrag(host, item);
         } else if (action == ADD_TO_WORKSPACE) {
             final int[] coordinates = new int[2];
@@ -234,6 +223,13 @@
             return true;
         } else if (action == DEEP_SHORTCUTS) {
             return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null;
+        } else {
+            for (ButtonDropTarget dropTarget : mLauncher.getDropTargetBar().getDropTargets()) {
+                if (action == dropTarget.getAccessibilityAction()) {
+                    dropTarget.onAccessibilityDrop(host, item);
+                    return true;
+                }
+            }
         }
         return false;
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index f7ce8c1..ed5bf9f 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -336,6 +336,7 @@
             case VIEW_TYPE_PREDICTION_ICON:
                 AppInfo info = mApps.getAdapterItems().get(position).appInfo;
                 BubbleTextView icon = (BubbleTextView) holder.itemView;
+                icon.reset();
                 icon.applyFromApplicationInfo(info);
                 break;
             case VIEW_TYPE_DISCOVERY_ITEM:
diff --git a/src/com/android/launcher3/compat/AnimatorSetCompat.java b/src/com/android/launcher3/compat/AnimatorSetCompat.java
new file mode 100644
index 0000000..497dd14
--- /dev/null
+++ b/src/com/android/launcher3/compat/AnimatorSetCompat.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2017 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.compat;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.view.animation.LinearInterpolator;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Compat implementation for various new APIs in {@link AnimatorSet}
+ *
+ * Note: The compat implementation does not support start delays on child animations or
+ * sequential playbacks.
+ */
+public abstract class AnimatorSetCompat implements ValueAnimator.AnimatorUpdateListener {
+
+    public static AnimatorSetCompat wrap(AnimatorSet anim, int duration) {
+        if (Utilities.ATLEAST_OREO) {
+            return new AnimatorSetCompatVO(anim, duration);
+        } else {
+            return new AnimatorSetCompatVL(anim, duration);
+        }
+    }
+
+    private final ValueAnimator mAnimationPlayer;
+    private final long mDuration;
+
+    protected final AnimatorSet mAnim;
+
+    protected float mCurrentFraction;
+
+    protected AnimatorSetCompat(AnimatorSet anim, int duration) {
+        mAnim = anim;
+        mDuration = duration;
+
+        mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
+        mAnimationPlayer.setInterpolator(new LinearInterpolator());
+        mAnimationPlayer.addUpdateListener(this);
+    }
+
+    /**
+     * Starts playing the animation forward from current position.
+     */
+    public void start() {
+        mAnimationPlayer.setFloatValues(mCurrentFraction, 1);
+        mAnimationPlayer.setDuration(clampDuration(1 - mCurrentFraction));
+        mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
+        mAnimationPlayer.start();
+    }
+
+    /**
+     * Starts playing the animation backwards from current position
+     */
+    public void reverse() {
+        mAnimationPlayer.setFloatValues(mCurrentFraction, 0);
+        mAnimationPlayer.setDuration(clampDuration(mCurrentFraction));
+        mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
+        mAnimationPlayer.start();
+    }
+
+    /**
+     * Sets the current animation position and updates all the child animators accordingly.
+     */
+    public abstract void setPlayFraction(float fraction);
+
+    /**
+     * @see Animator#addListener(AnimatorListener)
+     */
+    public void addListener(Animator.AnimatorListener listener) {
+        mAnimationPlayer.addListener(listener);
+    }
+
+    @Override
+    public void onAnimationUpdate(ValueAnimator valueAnimator) {
+        setPlayFraction((float) valueAnimator.getAnimatedValue());
+    }
+
+    protected long clampDuration(float fraction) {
+        float playPos = mDuration * fraction;
+        if (playPos <= 0) {
+            return 0;
+        } else {
+            return Math.min((long) playPos, mDuration);
+        }
+    }
+
+    public void dispatchOnStart() {
+        dispatchOnStartRecursively(mAnim);
+    }
+
+    private void dispatchOnStartRecursively(Animator animator) {
+        for (AnimatorListener l : nonNullList(animator.getListeners())) {
+            l.onAnimationStart(animator);
+        }
+
+        if (animator instanceof AnimatorSet) {
+            for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
+                dispatchOnStartRecursively(anim);
+            }
+        }
+    }
+
+    public static class AnimatorSetCompatVL extends AnimatorSetCompat {
+
+        private final ValueAnimator[] mChildAnimations;
+
+        private AnimatorSetCompatVL(AnimatorSet anim, int duration) {
+            super(anim, duration);
+
+            // Build animation list
+            ArrayList<ValueAnimator> childAnims = new ArrayList<>();
+            getAnimationsRecur(mAnim, childAnims);
+            mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]);
+        }
+
+        private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out) {
+            long forceDuration = anim.getDuration();
+            for (Animator child : anim.getChildAnimations()) {
+                if (forceDuration > 0) {
+                    child.setDuration(forceDuration);
+                }
+                if (child instanceof ValueAnimator) {
+                    out.add((ValueAnimator) child);
+                } else if (child instanceof AnimatorSet) {
+                    getAnimationsRecur((AnimatorSet) child, out);
+                } else {
+                    throw new RuntimeException("Unknown animation type " + child);
+                }
+            }
+        }
+
+        @Override
+        public void setPlayFraction(float fraction) {
+            mCurrentFraction = fraction;
+            long playPos = clampDuration(fraction);
+            for (ValueAnimator anim : mChildAnimations) {
+                anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration()));
+            }
+        }
+
+    }
+
+    @TargetApi(Build.VERSION_CODES.O)
+    private static class AnimatorSetCompatVO extends AnimatorSetCompat {
+
+        private AnimatorSetCompatVO(AnimatorSet anim, int duration) {
+            super(anim, duration);
+        }
+
+        @Override
+        public void setPlayFraction(float fraction) {
+            mCurrentFraction = fraction;
+            mAnim.setCurrentPlayTime(clampDuration(fraction));
+        }
+    }
+
+    private class OnAnimationEndDispatcher extends AnimationSuccessListener {
+
+        @Override
+        public void onAnimationSuccess(Animator animator) {
+            dispatchOnEndRecursively(mAnim);
+        }
+
+        private void dispatchOnEndRecursively(Animator animator) {
+            for (AnimatorListener l : nonNullList(animator.getListeners())) {
+                l.onAnimationEnd(animator);
+            }
+
+            if (animator instanceof AnimatorSet) {
+                for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) {
+                    dispatchOnEndRecursively(anim);
+                }
+            }
+        }
+    }
+
+    private static <T> List<T> nonNullList(ArrayList<T> list) {
+        return list == null ? Collections.<T>emptyList() : list;
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index f2bad6b..bc5aafc 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -143,7 +143,7 @@
 
     public void onAccessibilityStateChanged(boolean isAccessibilityEnabled) {
         mPinchListener = FeatureFlags.LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW || isAccessibilityEnabled
-                || !Utilities.ATLEAST_OREO ? null : new PinchToOverviewListener(mLauncher);
+                ? null : new PinchToOverviewListener(mLauncher);
     }
 
     public boolean isEventOverHotseat(MotionEvent ev) {