Merge "Deleting sample code demonstrating how to extend Launcher3" into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index 4cc5e42..282b878 100644
--- a/Android.mk
+++ b/Android.mk
@@ -17,6 +17,14 @@
 LOCAL_PATH := $(call my-dir)
 
 #
+# Prebuilt Java Libraries
+#
+include $(CLEAR_VARS)
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+    libSharedSystemUI:quickstep/libs/sysui_shared.jar
+include $(BUILD_MULTI_PREBUILT)
+
+#
 # Build rule for Launcher3 app.
 #
 include $(CLEAR_VARS)
@@ -112,22 +120,58 @@
 include $(BUILD_PACKAGE)
 
 #
-# Launcher proto buffer jar used for development
+# Build rule for Quickstep app.
 #
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-proto-files-under, protos) $(call all-proto-files-under, proto_overrides)
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-v4 \
+    android-support-v7-recyclerview \
+    android-support-dynamic-animation \
+    libSharedSystemUI
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-java-files-under, src_config) \
+    $(call all-java-files-under, quickstep/src) \
+    $(call all-java-files-under, quickstep/src_flags) \
+    $(call all-proto-files-under, protos) \
+    $(call all-proto-files-under, proto_overrides)
+
+LOCAL_RESOURCE_DIR := \
+    $(LOCAL_PATH)/quickstep/res \
+    $(LOCAL_PATH)/res \
+    prebuilts/sdk/current/support/v7/recyclerview/res \
+
+LOCAL_PROGUARD_ENABLED := disabled
 
 LOCAL_PROTOC_OPTIMIZE_TYPE := nano
 LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/ --proto_path=$(LOCAL_PATH)/proto_overrides/
 LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
 
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE := launcher_proto_lib
-LOCAL_IS_HOST_MODULE := true
-LOCAL_STATIC_JAVA_LIBRARIES := host-libprotobuf-java-nano
+LOCAL_AAPT_FLAGS := \
+    --auto-add-overlay \
+    --extra-packages android.support.v7.recyclerview \
 
-include $(BUILD_HOST_JAVA_LIBRARY)
+LOCAL_SDK_VERSION := system_current
+LOCAL_MIN_SDK_VERSION := 26
+LOCAL_PACKAGE_NAME := Launcher3QuickStep
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
+
+LOCAL_FULL_LIBS_MANIFEST_FILES := \
+    $(LOCAL_PATH)/AndroidManifest.xml \
+    $(LOCAL_PATH)/AndroidManifest-common.xml
+
+LOCAL_MANIFEST_FILE := quickstep/AndroidManifest.xml
+LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.launcher3.*
+
+include $(BUILD_PACKAGE)
+
+
+
 
 # ==================================================
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/build.gradle b/build.gradle
index a1b3a60..f357e5c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -40,7 +40,20 @@
             applicationId 'com.android.launcher3'
             testApplicationId 'com.android.launcher3.tests'
         }
+
+        quickstep {
+            applicationId 'com.android.launcher3'
+            testApplicationId 'com.android.launcher3.tests'
+        }
     }
+
+    // Disable release builds for now
+    android.variantFilter { variant ->
+        if (variant.buildType.name.endsWith('release')) {
+            variant.setIgnore(true);
+        }
+    }
+
     sourceSets {
         main {
             res.srcDirs = ['res']
@@ -52,31 +65,34 @@
             }
         }
 
+        debug {
+            manifest.srcFile "AndroidManifest.xml"
+        }
+
         androidTest {
             res.srcDirs = ['tests/res']
             java.srcDirs = ['tests/src']
             manifest.srcFile "tests/AndroidManifest-common.xml"
         }
 
-        aosp {
-            java.srcDirs = ['src_flags']
-            manifest.srcFile "AndroidManifest.xml"
+        androidTestDebug {
+            manifest.srcFile "tests/AndroidManifest.xml"
         }
 
-        aospAndroidTest {
-            manifest.srcFile "tests/AndroidManifest.xml"
+        aosp {
+            java.srcDirs = ['src_flags']
         }
 
         l3go {
             res.srcDirs = ['go/res']
             java.srcDirs = ['go/src_flags']
-            // Note: we are using the Launcher3 manifest here because the gradle manifest-merger uses
-            // different attributes than the build system.
-            manifest.srcFile "AndroidManifest.xml"
+            manifest.srcFile "go/AndroidManifest.xml"
         }
 
-        l3goAndroidTest {
-            manifest.srcFile "tests/AndroidManifest.xml"
+        quickstep {
+            res.srcDirs = ['quickstep/res']
+            java.srcDirs = ['quickstep/src_flags', 'quickstep/src']
+            manifest.srcFile "quickstep/AndroidManifest.xml"
         }
     }
 }
@@ -93,6 +109,8 @@
     compile "com.android.support:recyclerview-v7:${SUPPORT_LIBS_VERSION}"
     compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7'
 
+    quickstepCompile fileTree(dir: "quickstep/libs", include: 'sysui_shared.jar')
+
     testCompile 'junit:junit:4.12'
     androidTestCompile "org.mockito:mockito-core:1.9.5"
     androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
new file mode 100644
index 0000000..08c740c4
--- /dev/null
+++ b/quickstep/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.launcher3" >
+
+    <uses-sdk android:targetSdkVersion="23" android:minSdkVersion="21"/>
+
+    <application
+        android:backupAgent="com.android.launcher3.LauncherBackupAgent"
+        android:fullBackupOnly="true"
+        android:fullBackupContent="@xml/backupscheme"
+        android:hardwareAccelerated="true"
+        android:icon="@drawable/ic_launcher_home"
+        android:label="@string/derived_app_name"
+        android:theme="@style/LauncherTheme"
+        android:largeHeap="@bool/config_largeHeap"
+        android:restoreAnyVersion="true"
+        android:supportsRtl="true" >
+
+        <service android:name="com.android.quickstep.TouchInteractionService"
+            android:exported="true" />
+
+        <!-- STOPSHIP: Change exported to false once all the integration is complete.
+        It is set to true so that the activity can be started from command line -->
+        <activity android:name="com.android.quickstep.RecentsActivity"
+            android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
new file mode 100644
index 0000000..f9ce6e0
--- /dev/null
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
new file mode 100644
index 0000000..ef61226
--- /dev/null
+++ b/quickstep/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* 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.
+*/
+-->
+<resources>
+
+    <!-- Application name -->
+    <string name="derived_app_name" translatable="false">Quickstep</string>
+</resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
new file mode 100644
index 0000000..8f75980
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -0,0 +1,46 @@
+/*
+ * 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.quickstep;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.support.annotation.Nullable;
+import android.widget.ArrayAdapter;
+
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.RecentsTaskLoader;
+import com.android.systemui.shared.recents.model.Task;
+
+/**
+ * A simple activity to show the recently launched tasks
+ */
+public class RecentsActivity extends ListActivity {
+
+    private ArrayAdapter<Task> mAdapter;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(this);
+        plan.preloadPlan(new RecentsTaskLoader(this, 1, 1, 0), -1, UserHandle.myUserId());
+
+        mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
+        mAdapter.addAll(plan.getTaskStack().getStackTasks());
+        setListAdapter(mAdapter);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
new file mode 100644
index 0000000..091ab54
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -0,0 +1,31 @@
+/*
+ * 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.quickstep;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Service connected by system-UI for handling touch interaction.
+ */
+public class TouchInteractionService extends Service {
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
diff --git a/quickstep/src_flags/com/android/launcher3/config/FeatureFlags.java b/quickstep/src_flags/com/android/launcher3/config/FeatureFlags.java
new file mode 100644
index 0000000..1edf592
--- /dev/null
+++ b/quickstep/src_flags/com/android/launcher3/config/FeatureFlags.java
@@ -0,0 +1,32 @@
+/*
+ * 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.config;
+
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.states.OverviewState;
+
+/**
+ * Defines a set of flags used to control various launcher behaviors
+ */
+public final class FeatureFlags extends BaseFlags {
+
+    private FeatureFlags() {}
+
+    public static LauncherState createOverviewState(int id) {
+        return new OverviewState(id);
+    }
+}
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java
index 8ac8570..5eb6cc7 100644
--- a/src/com/android/launcher3/AllAppsList.java
+++ b/src/com/android/launcher3/AllAppsList.java
@@ -154,7 +154,7 @@
         for (int i = data.size() - 1; i >= 0; i--) {
             AppInfo info = data.get(i);
             if (matcher.matches(info, info.componentName)) {
-                info.isDisabled = op.apply(info.isDisabled);
+                info.runtimeStatusFlags = op.apply(info.runtimeStatusFlags);
                 modified.add(info);
             }
         }
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 7d2f753..a5422aa 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -32,10 +32,6 @@
  */
 public class AppInfo extends ItemInfoWithIcon {
 
-    public static final int FLAG_SYSTEM_UNKNOWN = 0;
-    public static final int FLAG_SYSTEM_YES = 1 << 0;
-    public static final int FLAG_SYSTEM_NO = 1 << 1;
-
     /**
      * The intent used to start the application.
      */
@@ -43,16 +39,6 @@
 
     public ComponentName componentName;
 
-    /**
-     * {@see ShortcutInfo#isDisabled}
-     */
-    public int isDisabled = ShortcutInfo.DEFAULT;
-
-    /**
-     * Stores if the app is a system app or not.
-     */
-    public int isSystemApp;
-
     public AppInfo() {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
     }
@@ -74,15 +60,14 @@
         this.container = ItemInfo.NO_ID;
         this.user = user;
         if (PackageManagerHelper.isAppSuspended(info.getApplicationInfo())) {
-            isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+            runtimeStatusFlags |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
         }
         if (quietModeEnabled) {
-            isDisabled |= ShortcutInfo.FLAG_DISABLED_QUIET_USER;
+            runtimeStatusFlags |= ShortcutInfo.FLAG_DISABLED_QUIET_USER;
         }
 
         intent = makeLaunchIntent(info);
-
-        isSystemApp = (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0
+        runtimeStatusFlags |= (info.getApplicationInfo().flags & ApplicationInfo.FLAG_SYSTEM) == 0
                 ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
 
     }
@@ -92,8 +77,6 @@
         componentName = info.componentName;
         title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
-        isDisabled = info.isDisabled;
-        isSystemApp = info.isSystemApp;
     }
 
     @Override
@@ -119,9 +102,4 @@
                 .setComponent(cn)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
     }
-
-    @Override
-    public boolean isDisabled() {
-        return isDisabled != 0;
-    }
 }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 364b204..a590504 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -184,6 +184,7 @@
     public void reset() {
         mBadgeInfo = null;
         mBadgePalette = null;
+        mBadgeScale = 0f;
         mForceHideBadge = false;
     }
 
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index 1e020e2..1c4e88b 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -33,11 +33,70 @@
      */
     public boolean usingLowResIcon;
 
+    /**
+     * Indicates that the icon is disabled due to safe mode restrictions.
+     */
+    public static final int FLAG_DISABLED_SAFEMODE = 1 << 0;
+
+    /**
+     * Indicates that the icon is disabled as the app is not available.
+     */
+    public static final int FLAG_DISABLED_NOT_AVAILABLE = 1 << 1;
+
+    /**
+     * Indicates that the icon is disabled as the app is suspended
+     */
+    public static final int FLAG_DISABLED_SUSPENDED = 1 << 2;
+
+    /**
+     * Indicates that the icon is disabled as the user is in quiet mode.
+     */
+    public static final int FLAG_DISABLED_QUIET_USER = 1 << 3;
+
+    /**
+     * Indicates that the icon is disabled as the publisher has disabled the actual shortcut.
+     */
+    public static final int FLAG_DISABLED_BY_PUBLISHER = 1 << 4;
+
+    /**
+     * Indicates that the icon is disabled as the user partition is currently locked.
+     */
+    public static final int FLAG_DISABLED_LOCKED_USER = 1 << 5;
+
+    public static final int FLAG_DISABLED_MASK = FLAG_DISABLED_SAFEMODE |
+            FLAG_DISABLED_NOT_AVAILABLE | FLAG_DISABLED_SUSPENDED |
+            FLAG_DISABLED_QUIET_USER | FLAG_DISABLED_BY_PUBLISHER | FLAG_DISABLED_LOCKED_USER;
+
+
+    /**
+     * The item points to a system app.
+     */
+    public static final int FLAG_SYSTEM_YES = 1 << 6;
+
+    /**
+     * The item points to a non system app.
+     */
+    public static final int FLAG_SYSTEM_NO = 1 << 7;
+
+    public static final int FLAG_SYSTEM_MASK = FLAG_SYSTEM_YES | FLAG_SYSTEM_NO;
+
+    /**
+     * Status associated with the system state of the underlying item. This is calculated every
+     * time a new info is created and not persisted on the disk.
+     */
+    public int runtimeStatusFlags = 0;
+
     protected ItemInfoWithIcon() { }
 
     protected ItemInfoWithIcon(ItemInfoWithIcon info) {
         super(info);
         iconBitmap = info.iconBitmap;
         usingLowResIcon = info.usingLowResIcon;
+        runtimeStatusFlags = info.runtimeStatusFlags;
+    }
+
+    @Override
+    public boolean isDisabled() {
+        return (runtimeStatusFlags & FLAG_DISABLED_MASK) != 0;
     }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 6b5ee5e..94d9067 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -19,6 +19,11 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
@@ -2048,10 +2053,10 @@
         // Open shortcut
         final ShortcutInfo shortcut = (ShortcutInfo) tag;
 
-        if (shortcut.isDisabled != 0) {
-            if ((shortcut.isDisabled &
-                    ~ShortcutInfo.FLAG_DISABLED_SUSPENDED &
-                    ~ShortcutInfo.FLAG_DISABLED_QUIET_USER) == 0) {
+        if (shortcut.isDisabled()) {
+            if ((shortcut.runtimeStatusFlags &
+                    ~FLAG_DISABLED_SUSPENDED &
+                    ~FLAG_DISABLED_QUIET_USER) == 0) {
                 // If the app is only disabled because of the above flags, launch activity anyway.
                 // Framework will tell the user why the app is suspended.
             } else {
@@ -2062,10 +2067,10 @@
                 }
                 // Otherwise just use a generic error message.
                 int error = R.string.activity_not_available;
-                if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_SAFEMODE) != 0) {
+                if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
                     error = R.string.safemode_shortcut_error;
-                } else if ((shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_BY_PUBLISHER) != 0 ||
-                        (shortcut.isDisabled & ShortcutInfo.FLAG_DISABLED_LOCKED_USER) != 0) {
+                } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 ||
+                        (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
                     error = R.string.shortcut_not_available;
                 }
                 Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index de3f441..63c232d 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -21,8 +21,8 @@
 
 import android.view.View;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.states.AllAppsState;
-import com.android.launcher3.states.OverviewState;
 import com.android.launcher3.states.SpringLoadedState;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
@@ -39,7 +39,6 @@
     protected static final int FLAG_HIDE_HOTSEAT = 1 << 2;
     protected static final int FLAG_DISABLE_ACCESSIBILITY = 1 << 3;
     protected static final int FLAG_DO_NOT_RESTORE = 1 << 4;
-    protected static final int FLAG_HAS_SPRING = 1 << 5;
 
     private static final LauncherState[] sAllStates = new LauncherState[4];
 
@@ -50,7 +49,7 @@
 
     public static final LauncherState SPRING_LOADED = new SpringLoadedState(2);
 
-    public static final LauncherState OVERVIEW = new OverviewState(3);
+    public static final LauncherState OVERVIEW = FeatureFlags.createOverviewState(3);
 
     public final int ordinal;
 
@@ -90,7 +89,6 @@
      * @see com.android.launcher3.allapps.AllAppsTransitionController
      */
     public final float verticalProgress;
-    public final boolean hasVerticalSpring;
 
     public LauncherState(int id, int containerType, int transitionDuration, float verticalProgress,
             int flags) {
@@ -106,7 +104,6 @@
         this.doNotRestore = (flags & FLAG_DO_NOT_RESTORE) != 0;
 
         this.verticalProgress = verticalProgress;
-        this.hasVerticalSpring = (flags & FLAG_HAS_SPRING) != 0;
 
         this.ordinal = id;
         sAllStates[id] = this;
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 863cae7..fd94067 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -28,6 +28,7 @@
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimationLayerSet;
 import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
 
 /**
  * TODO: figure out what kind of tests we can write for this
@@ -133,8 +134,7 @@
 
     private void goToState(LauncherState state, boolean animated, long delay,
             Runnable onCompleteRunnable) {
-        if (mLauncher.isInState(state) && mConfig.mCurrentAnimation == null
-                && !mAllAppsController.isTransitioning()) {
+        if (mLauncher.isInState(state) && mConfig.mCurrentAnimation == null) {
 
             // Run any queued runnable
             if (onCompleteRunnable != null) {
@@ -159,27 +159,42 @@
             return;
         }
 
-        AnimatorSet animation = createAnimationToNewWorkspace(state, onCompleteRunnable);
+        // Since state NORMAL can be reached from multiple states, just assume that the
+        // transition plays in reverse and use the same duration as previous state.
+        mConfig.duration = state == NORMAL ? mState.transitionDuration : state.transitionDuration;
+
+        AnimatorSet animation = createAnimationToNewWorkspaceInternal(state, onCompleteRunnable);
         Runnable runnable = new StartAnimRunnable(animation, state.getFinalFocus(mLauncher));
         if (delay > 0) {
             mUiHandler.postDelayed(runnable, delay);
-        } else if (mConfig.shouldPost) {
-            mUiHandler.post(runnable);
         } else {
-            runnable.run();
+            mUiHandler.post(runnable);
         }
     }
 
-    protected AnimatorSet createAnimationToNewWorkspace(final LauncherState state,
-            final Runnable onCompleteRunnable) {
+    /**
+     * Creates a {@link AnimatorPlaybackController} that can be used for a controlled
+     * state transition.
+     * @param state the final state for the transition.
+     * @param duration intended duration for normal playback. Use higher duration for better
+     *                accuracy.
+     */
+    protected AnimatorPlaybackController createAnimationToNewWorkspace(
+            LauncherState state, long duration) {
         mConfig.reset();
-        mConfig.duration = state == NORMAL ? mState.transitionDuration : state.transitionDuration;
+        mConfig.userControlled = true;
+        mConfig.duration = duration;
+        return AnimatorPlaybackController.wrap(
+                createAnimationToNewWorkspaceInternal(state, null), duration);
+    }
+
+    protected AnimatorSet createAnimationToNewWorkspaceInternal(final LauncherState state,
+            final Runnable onCompleteRunnable) {
 
         final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
         final AnimationLayerSet layerViews = new AnimationLayerSet();
 
-        mAllAppsController.animateToFinalProgress(state.verticalProgress,
-                state.hasVerticalSpring, animation, mConfig);
+        mAllAppsController.animateToFinalProgress(state.verticalProgress, animation, mConfig);
         mLauncher.getWorkspace().setStateWithAnimation(state,
                 layerViews, animation, mConfig);
 
@@ -242,14 +257,14 @@
     }
 
     public static class AnimationConfig extends AnimatorListenerAdapter {
-        public boolean shouldPost;
         public long duration;
+        public boolean userControlled;
 
         private AnimatorSet mCurrentAnimation;
 
         public void reset() {
-            shouldPost = true;
             duration = 0;
+            userControlled = false;
 
             if (mCurrentAnimation != null) {
                 mCurrentAnimation.setDuration(0);
diff --git a/src/com/android/launcher3/PinchToOverviewListener.java b/src/com/android/launcher3/PinchToOverviewListener.java
index d1a2538..27edaf6 100644
--- a/src/com/android/launcher3/PinchToOverviewListener.java
+++ b/src/com/android/launcher3/PinchToOverviewListener.java
@@ -22,18 +22,19 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
 import android.view.ScaleGestureDetector.OnScaleGestureListener;
 
-import com.android.launcher3.compat.AnimatorSetCompat;
+import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.util.TouchController;
 
 /**
  * Detects pinches and animates the Workspace to/from overview mode.
  */
-public class PinchToOverviewListener
-        implements TouchController, OnScaleGestureListener, Runnable {
+public class PinchToOverviewListener extends AnimatorListenerAdapter
+        implements TouchController, OnScaleGestureListener {
 
     private static final float ACCEPT_THRESHOLD = 0.65f;
     /**
@@ -47,7 +48,7 @@
     private Workspace mWorkspace = null;
     private boolean mPinchStarted = false;
 
-    private AnimatorSetCompat mCurrentAnimation;
+    private AnimatorPlaybackController mCurrentAnimation;
     private float mCurrentScale;
     private boolean mShouldGoToFinalState;
 
@@ -100,8 +101,9 @@
         }
 
         mToState = mLauncher.isInState(OVERVIEW) ? NORMAL : OVERVIEW;
-        mCurrentAnimation = AnimatorSetCompat.wrap(mLauncher.getStateManager()
-                .createAnimationToNewWorkspace(mToState, this), OVERVIEW_TRANSITION_MS);
+        mCurrentAnimation = mLauncher.getStateManager()
+                .createAnimationToNewWorkspace(mToState, OVERVIEW_TRANSITION_MS);
+        mCurrentAnimation.getTarget().addListener(this);
         mPinchStarted = true;
         mCurrentScale = 1;
         mShouldGoToFinalState = false;
@@ -111,7 +113,7 @@
     }
 
     @Override
-    public void run() {
+    public void onAnimationEnd(Animator animation) {
         mCurrentAnimation = null;
         mPinchStarted = false;
     }
@@ -121,9 +123,9 @@
         if (mShouldGoToFinalState) {
             mCurrentAnimation.start();
         } else {
-            mCurrentAnimation.addListener(new AnimatorListenerAdapter() {
+            mCurrentAnimation.setEndAction(new Runnable() {
                 @Override
-                public void onAnimationEnd(Animator animation) {
+                public void run() {
                     mLauncher.getStateManager().goToState(
                             mToState == OVERVIEW ? NORMAL : OVERVIEW, false);
                 }
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index adf008b..ec608ca 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -65,13 +65,6 @@
     public static final int FLAG_SUPPORTS_WEB_UI = 16; //0B10000;
 
     /**
-     * Indicates if it represents a common type mentioned in {@link CommonAppTypeParser}.
-     * Upto 15 different types supported.
-     */
-    @Deprecated
-    public static final int FLAG_RESTORED_APP_TYPE = 0B0011110000;
-
-    /**
      * The intent used to start the application.
      */
     public Intent intent;
@@ -83,42 +76,6 @@
     public Intent.ShortcutIconResource iconResource;
 
     /**
-     * Indicates that the icon is disabled due to safe mode restrictions.
-     */
-    public static final int FLAG_DISABLED_SAFEMODE = 1 << 0;
-
-    /**
-     * Indicates that the icon is disabled as the app is not available.
-     */
-    public static final int FLAG_DISABLED_NOT_AVAILABLE = 1 << 1;
-
-    /**
-     * Indicates that the icon is disabled as the app is suspended
-     */
-    public static final int FLAG_DISABLED_SUSPENDED = 1 << 2;
-
-    /**
-     * Indicates that the icon is disabled as the user is in quiet mode.
-     */
-    public static final int FLAG_DISABLED_QUIET_USER = 1 << 3;
-
-    /**
-     * Indicates that the icon is disabled as the publisher has disabled the actual shortcut.
-     */
-    public static final int FLAG_DISABLED_BY_PUBLISHER = 1 << 4;
-
-    /**
-     * Indicates that the icon is disabled as the user partition is currently locked.
-     */
-    public static final int FLAG_DISABLED_LOCKED_USER = 1 << 5;
-
-    /**
-     * Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when
-     * sd-card is not available).
-     */
-    public int isDisabled = DEFAULT;
-
-    /**
      * A message to display when the user tries to start a disabled shortcut.
      * This is currently only used for deep shortcuts.
      */
@@ -142,7 +99,6 @@
         iconResource = info.iconResource;
         status = info.status;
         mInstallProgress = info.mInstallProgress;
-        isDisabled = info.isDisabled;
     }
 
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
@@ -150,7 +106,6 @@
         super(info);
         title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
-        isDisabled = info.isDisabled;
     }
 
     /**
@@ -219,9 +174,9 @@
         contentDescription = UserManagerCompat.getInstance(context)
                 .getBadgedLabelForUser(label, user);
         if (shortcutInfo.isEnabled()) {
-            isDisabled &= ~FLAG_DISABLED_BY_PUBLISHER;
+            runtimeStatusFlags &= ~FLAG_DISABLED_BY_PUBLISHER;
         } else {
-            isDisabled |= FLAG_DISABLED_BY_PUBLISHER;
+            runtimeStatusFlags |= FLAG_DISABLED_BY_PUBLISHER;
         }
         disabledMessage = shortcutInfo.getDisabledMessage();
     }
@@ -233,11 +188,6 @@
     }
 
     @Override
-    public boolean isDisabled() {
-        return isDisabled != 0;
-    }
-
-    @Override
     public ComponentName getTargetComponent() {
         ComponentName cn = super.getTargetComponent();
         if (cn == null && (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index 8e83a30..68a441a 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -1,5 +1,8 @@
 package com.android.launcher3;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -83,10 +86,10 @@
             return false;
         }
 
-        if (info instanceof AppInfo) {
-            AppInfo appInfo = (AppInfo) info;
-            if (appInfo.isSystemApp != AppInfo.FLAG_SYSTEM_UNKNOWN) {
-                return (appInfo.isSystemApp & AppInfo.FLAG_SYSTEM_NO) != 0;
+        if (info instanceof ItemInfoWithIcon) {
+            ItemInfoWithIcon iconInfo = (ItemInfoWithIcon) info;
+            if ((iconInfo.runtimeStatusFlags & FLAG_SYSTEM_MASK) != 0) {
+                return (iconInfo.runtimeStatusFlags & FLAG_SYSTEM_NO) != 0;
             }
         }
         return getUninstallTarget(info) != null;
diff --git a/src/com/android/launcher3/VerticalSwipeController.java b/src/com/android/launcher3/VerticalSwipeController.java
new file mode 100644
index 0000000..12c6916
--- /dev/null
+++ b/src/com/android/launcher3/VerticalSwipeController.java
@@ -0,0 +1,282 @@
+/*
+ * 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;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.anim.SpringAnimationHandler.Y_DIRECTION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.support.animation.SpringAnimation;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.SpringAnimationHandler;
+import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.TouchController;
+
+import java.util.ArrayList;
+
+/**
+ * Handles vertical touch gesture on the DragLayer
+ */
+public class VerticalSwipeController extends AnimatorListenerAdapter
+        implements TouchController, SwipeDetector.Listener {
+
+    private static final String TAG = "VerticalSwipeController";
+
+    private static final float RECATCH_REJECTION_FRACTION = .0875f;
+    private static final int SINGLE_FRAME_MS = 16;
+
+    // Progress after which the transition is assumed to be a success in case user does not fling
+    private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
+
+    private final Launcher mLauncher;
+    private final SwipeDetector mDetector;
+
+    private boolean mNoIntercept;
+    private int mStartContainerType;
+
+    private AnimatorPlaybackController mCurrentAnimation;
+    private LauncherState mToState;
+
+    private float mStartProgress;
+    // Ratio of transition process [0, 1] to drag displacement (px)
+    private float mProgressMultiplier;
+
+    private SpringAnimationHandler[] mSpringHandlers;
+
+    public VerticalSwipeController(Launcher l) {
+        mLauncher = l;
+        mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
+    }
+
+    private boolean canInterceptTouch(MotionEvent ev) {
+        if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(ALL_APPS)) {
+            // Don't listen for the pinch gesture if on all apps, widget picker, -1, etc.
+            return false;
+        }
+        if (mCurrentAnimation != null) {
+            // If we are already animating from a previous state, we can intercept.
+            return true;
+        }
+        if (mLauncher.isInState(ALL_APPS) && !mLauncher.getAppsView().shouldContainerScroll(ev)) {
+            return false;
+        }
+        if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onAnimationCancel(Animator animation) {
+        if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
+            Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
+            mDetector.finishedScrolling();
+            mCurrentAnimation = null;
+        }
+    }
+
+    private void initSprings() {
+        AllAppsContainerView appsView = mLauncher.getAppsView();
+
+        SpringAnimationHandler handler = appsView.getSpringAnimationHandler();
+        if (handler == null) {
+            mSpringHandlers = new SpringAnimationHandler[0];
+            return;
+        }
+
+        ArrayList<SpringAnimationHandler> handlers = new ArrayList<>();
+        handlers.add(handler);
+
+        SpringAnimation searchSpring = appsView.getSearchUiManager().getSpringForFling();
+        if (searchSpring != null) {
+            SpringAnimationHandler searchHandler =
+                    new SpringAnimationHandler(Y_DIRECTION, handler.getFactory());
+            searchHandler.add(searchSpring, true /* setDefaultValues */);
+            handlers.add(searchHandler);
+        }
+
+        mSpringHandlers = handlers.toArray(new SpringAnimationHandler[handlers.size()]);
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mNoIntercept = !canInterceptTouch(ev);
+            if (mNoIntercept) {
+                return false;
+            }
+
+            // Now figure out which direction scroll events the controller will start
+            // calling the callbacks.
+            final int directionsToDetectScroll;
+            boolean ignoreSlopWhenSettling = false;
+
+            if (mCurrentAnimation != null) {
+                if (mCurrentAnimation.getProgressFraction() > 1 - RECATCH_REJECTION_FRACTION) {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+                } else if (mCurrentAnimation.getProgressFraction() < RECATCH_REJECTION_FRACTION ) {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
+                } else {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
+                    ignoreSlopWhenSettling = true;
+                }
+            } else {
+                if (mLauncher.isInState(ALL_APPS)) {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
+                    mStartContainerType = ContainerType.ALLAPPS;
+                } else {
+                    directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+                    mStartContainerType = mLauncher.getDragLayer().isEventOverHotseat(ev) ?
+                            ContainerType.HOTSEAT : ContainerType.WORKSPACE;
+                }
+            }
+
+            mDetector.setDetectableScrollConditions(
+                    directionsToDetectScroll, ignoreSlopWhenSettling);
+
+            if (mSpringHandlers == null) {
+                initSprings();
+            }
+        }
+
+        if (mNoIntercept) {
+            return false;
+        }
+
+        onControllerTouchEvent(ev);
+        return mDetector.isDraggingOrSettling();
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        for (SpringAnimationHandler h : mSpringHandlers) {
+            h.addMovement(ev);
+        }
+        return mDetector.onTouchEvent(ev);
+    }
+
+    @Override
+    public void onDragStart(boolean start) {
+        if (mCurrentAnimation == null) {
+            float range = getShiftRange();
+            long maxAccuracy = (long) (2 * range);
+
+            // Build current animation
+            mToState = mLauncher.isInState(ALL_APPS) ? NORMAL : ALL_APPS;
+            mCurrentAnimation = mLauncher.getStateManager()
+                    .createAnimationToNewWorkspace(mToState, maxAccuracy);
+            mCurrentAnimation.getTarget().addListener(this);
+            mStartProgress = 0;
+            mProgressMultiplier = (mLauncher.isInState(ALL_APPS) ? 1 : -1) / range;
+            mCurrentAnimation.dispatchOnStart();
+        } else {
+            mCurrentAnimation.pause();
+            mStartProgress = mCurrentAnimation.getProgressFraction();
+        }
+
+        for (SpringAnimationHandler h : mSpringHandlers) {
+            h.skipToEnd();
+        }
+    }
+
+    private float getShiftRange() {
+        return mLauncher.mAllAppsController.getShiftRange();
+    }
+
+    @Override
+    public boolean onDrag(float displacement, float velocity) {
+        float deltaProgress = mProgressMultiplier * displacement;
+        mCurrentAnimation.setPlayFraction(deltaProgress + mStartProgress);
+        return true;
+    }
+
+    @Override
+    public void onDragEnd(float velocity, boolean fling) {
+        final long animationDuration;
+        final int logAction;
+        final LauncherState targetState;
+        final float progress = mCurrentAnimation.getProgressFraction();
+
+        if (fling) {
+            logAction = Touch.FLING;
+            if (velocity < 0) {
+                targetState = ALL_APPS;
+                animationDuration = SwipeDetector.calculateDuration(velocity,
+                        mToState == ALL_APPS ? (1 - progress) : progress);
+            } else {
+                targetState = NORMAL;
+                animationDuration = SwipeDetector.calculateDuration(velocity,
+                        mToState == ALL_APPS ? progress : (1 - progress));
+            }
+            // snap to top or bottom using the release velocity
+        } else {
+            logAction = Touch.SWIPE;
+            if (progress > SUCCESS_TRANSITION_PROGRESS) {
+                targetState = mToState;
+                animationDuration = SwipeDetector.calculateDuration(velocity, 1 - progress);
+            } else {
+                targetState = mToState == ALL_APPS ? NORMAL : ALL_APPS;
+                animationDuration = SwipeDetector.calculateDuration(velocity, progress);
+            }
+        }
+
+        if (fling && targetState == ALL_APPS) {
+            for (SpringAnimationHandler h : mSpringHandlers) {
+                // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
+                h.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
+            }
+        }
+
+        mCurrentAnimation.setEndAction(new Runnable() {
+            @Override
+            public void run() {
+                if (targetState == mToState) {
+                    // Transition complete. log the action
+                    mLauncher.getUserEventDispatcher().logActionOnContainer(logAction,
+                            mToState == ALL_APPS ? Direction.UP : Direction.DOWN,
+                            mStartContainerType, mLauncher.getWorkspace().getCurrentPage());
+                } else {
+                    mLauncher.getStateManager().goToState(
+                            mToState == ALL_APPS ? NORMAL : ALL_APPS, false);
+                }
+                mDetector.finishedScrolling();
+                mCurrentAnimation = null;
+            }
+        });
+
+        float nextFrameProgress = Utilities.boundToRange(
+                progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f);
+
+        ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
+        anim.setFloatValues(nextFrameProgress, targetState == mToState ? 1f : 0f);
+        anim.setDuration(animationDuration);
+        anim.setInterpolator(scrollInterpolatorForVelocity(velocity));
+        anim.start();
+    }
+}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index e72f54e..24c4704 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1582,6 +1582,7 @@
 
         ValueAnimator stepAnimator = ValueAnimator.ofFloat(0, 1);
         stepAnimator.addUpdateListener(listener);
+        stepAnimator.setDuration(config.duration);
         anim.play(stepAnimator);
         anim.addListener(listener);
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index a84172d..9b64043 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -1,25 +1,19 @@
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.support.animation.SpringAnimation;
 import android.util.Property;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.Interpolator;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.R;
@@ -27,16 +21,9 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.SpringAnimationHandler;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.GradientView;
-import com.android.launcher3.touch.SwipeDetector;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.TouchController;
 
 /**
  * Handles AllApps view transition.
@@ -48,8 +35,7 @@
  * If release velocity < THRES1, snap according to either top or bottom depending on whether it's
  * closer to top or closer to the page indicator.
  */
-public class AllAppsTransitionController implements TouchController, SwipeDetector.Listener,
-         SearchUiManager.OnScrollRangeChangeListener {
+public class AllAppsTransitionController implements SearchUiManager.OnScrollRangeChangeListener {
 
     private static final Property<AllAppsTransitionController, Float> PROGRESS =
             new Property<AllAppsTransitionController, Float>(Float.class, "progress") {
@@ -65,24 +51,16 @@
         }
     };
 
-    // Spring values used when the user has not flung all apps.
-    private static final float SPRING_MAX_RELEASE_VELOCITY = 10000;
-    // The delay (as a % of the animation duration) to start the springs.
-    private static final float SPRING_DELAY = 0.3f;
-
     private final Interpolator mWorkspaceAccelnterpolator = Interpolators.ACCEL_2;
     private final Interpolator mHotseatAccelInterpolator = Interpolators.ACCEL_1_5;
-    private final Interpolator mFastOutSlowInInterpolator = Interpolators.FAST_OUT_SLOW_IN;
 
     private static final float PARALLAX_COEFFICIENT = .125f;
-    private static final int SINGLE_FRAME_MS = 16;
 
     private AllAppsContainerView mAppsView;
     private Workspace mWorkspace;
     private Hotseat mHotseat;
 
     private final Launcher mLauncher;
-    private final SwipeDetector mDetector;
     private final boolean mIsDarkTheme;
 
     // Animation in this class is controlled by a single variable {@link mProgress}.
@@ -91,190 +69,25 @@
 
     // When {@link mProgress} is 0, all apps container is pulled up.
     // When {@link mProgress} is 1, all apps container is pulled down.
-    private float mShiftStart;      // [0, mShiftRange]
     private float mShiftRange;      // changes depending on the orientation
     private float mProgress;        // [0, 1], mShiftRange * mProgress = shiftCurrent
 
-    // Velocity of the container. Unit is in px/ms.
-    private float mContainerVelocity;
-
     private static final float DEFAULT_SHIFT_RANGE = 10;
 
-    private static final float RECATCH_REJECTION_FRACTION = .0875f;
-
-    private long mAnimationDuration;
-
-    private boolean mNoIntercept;
-    private boolean mTouchEventStartedOnHotseat;
-
-    // Used in discovery bounce animation to provide the transition without workspace changing.
     private boolean mIsTranslateWithoutWorkspace = false;
     private Animator mDiscoBounceAnimation;
     private GradientView mGradientView;
 
-    private SpringAnimation mSearchSpring;
-    private SpringAnimationHandler mSpringAnimationHandler;
-
     public AllAppsTransitionController(Launcher l) {
         mLauncher = l;
-        mDetector = new SwipeDetector(l, this, SwipeDetector.VERTICAL);
         mShiftRange = DEFAULT_SHIFT_RANGE;
         mProgress = 1f;
 
         mIsDarkTheme = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark);
     }
 
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            mNoIntercept = false;
-            mTouchEventStartedOnHotseat = mLauncher.getDragLayer().isEventOverHotseat(ev);
-            if (!mLauncher.isInState(ALL_APPS) && !mLauncher.isInState(NORMAL)) {
-                mNoIntercept = true;
-            } else if (mLauncher.isInState(ALL_APPS) &&
-                    !mAppsView.shouldContainerScroll(ev)) {
-                mNoIntercept = true;
-            } else if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
-                mNoIntercept = true;
-            } else {
-                // Now figure out which direction scroll events the controller will start
-                // calling the callbacks.
-                int directionsToDetectScroll = 0;
-                boolean ignoreSlopWhenSettling = false;
-
-                if (mDetector.isIdleState()) {
-                    if (mLauncher.isInState(ALL_APPS)) {
-                        directionsToDetectScroll |= SwipeDetector.DIRECTION_NEGATIVE;
-                    } else {
-                        directionsToDetectScroll |= SwipeDetector.DIRECTION_POSITIVE;
-                    }
-                } else {
-                    if (isInDisallowRecatchBottomZone()) {
-                        directionsToDetectScroll |= SwipeDetector.DIRECTION_POSITIVE;
-                    } else if (isInDisallowRecatchTopZone()) {
-                        directionsToDetectScroll |= SwipeDetector.DIRECTION_NEGATIVE;
-                    } else {
-                        directionsToDetectScroll |= SwipeDetector.DIRECTION_BOTH;
-                        ignoreSlopWhenSettling = true;
-                    }
-                }
-                mDetector.setDetectableScrollConditions(directionsToDetectScroll,
-                        ignoreSlopWhenSettling);
-            }
-        }
-
-        if (mNoIntercept) {
-            return false;
-        }
-        mDetector.onTouchEvent(ev);
-        if (mDetector.isSettlingState() && (isInDisallowRecatchBottomZone() || isInDisallowRecatchTopZone())) {
-            return false;
-        }
-        return mDetector.isDraggingOrSettling();
-    }
-
-    @Override
-    public boolean onControllerTouchEvent(MotionEvent ev) {
-        if (hasSpringAnimationHandler()) {
-            mSpringAnimationHandler.addMovement(ev);
-        }
-        return mDetector.onTouchEvent(ev);
-    }
-
-    private boolean isInDisallowRecatchTopZone() {
-        return mProgress < RECATCH_REJECTION_FRACTION;
-    }
-
-    private boolean isInDisallowRecatchBottomZone() {
-        return mProgress > 1 - RECATCH_REJECTION_FRACTION;
-    }
-
-    @Override
-    public void onDragStart(boolean start) {
-        mLauncher.getStateManager().cancelAnimation();
-        cancelDiscoveryAnimation();
-        mShiftStart = mAppsView.getTranslationY();
-        onProgressAnimationStart();
-        if (hasSpringAnimationHandler()) {
-            mSpringAnimationHandler.skipToEnd();
-        }
-    }
-
-    @Override
-    public boolean onDrag(float displacement, float velocity) {
-        if (mAppsView == null) {
-            return false;   // early termination.
-        }
-
-        mContainerVelocity = velocity;
-
-        float shift = Math.min(Math.max(0, mShiftStart + displacement), mShiftRange);
-        setProgress(shift / mShiftRange);
-
-        return true;
-    }
-
-    @Override
-    public void onDragEnd(float velocity, boolean fling) {
-        if (mAppsView == null) {
-            return; // early termination.
-        }
-
-        final int containerType = mTouchEventStartedOnHotseat
-                ? ContainerType.HOTSEAT : ContainerType.WORKSPACE;
-        if (fling) {
-            if (velocity < 0) {
-                calculateDuration(velocity, mAppsView.getTranslationY());
-                if (!mLauncher.isInState(ALL_APPS)) {
-                    logSwipeOnContainer(Touch.FLING, Direction.UP, containerType);
-                }
-                mLauncher.getStateManager().goToState(ALL_APPS);
-                if (hasSpringAnimationHandler()) {
-                    mSpringAnimationHandler.add(mSearchSpring, true /* setDefaultValues */);
-                    // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
-                    mSpringAnimationHandler.animateToFinalPosition(0 /* pos */, 1 /* startValue */);
-                }
-            } else {
-                calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
-                if (mLauncher.isInState(ALL_APPS)) {
-                    logSwipeOnContainer(Touch.FLING, Direction.DOWN, ContainerType.ALLAPPS);
-                }
-                mLauncher.getStateManager().goToState(NORMAL);
-            }
-            // snap to top or bottom using the release velocity
-        } else {
-            if (mAppsView.getTranslationY() > mShiftRange / 2) {
-                calculateDuration(velocity, Math.abs(mShiftRange - mAppsView.getTranslationY()));
-                if (mLauncher.isInState(ALL_APPS)) {
-                    logSwipeOnContainer(Touch.SWIPE, Direction.DOWN, ContainerType.ALLAPPS);
-                }
-                mLauncher.getStateManager().goToState(NORMAL);
-            } else {
-                calculateDuration(velocity, Math.abs(mAppsView.getTranslationY()));
-                if (!mLauncher.isInState(ALL_APPS)) {
-                    logSwipeOnContainer(Touch.SWIPE, Direction.UP, containerType);
-                }
-                mLauncher.getStateManager().goToState(ALL_APPS);
-            }
-        }
-    }
-
-    /**
-     * Important, make sure that this method is called only when actual launcher state transition
-     * happen and not when user swipes in one direction only to cancel that swipe seconds later.
-     *
-     * @param touchType Swipe or Fling
-     * @param direction Up or Down
-     * @param containerType Workspace or Allapps
-     */
-    private void logSwipeOnContainer(int touchType, int direction, int containerType) {
-        mLauncher.getUserEventDispatcher().logActionOnContainer(
-                touchType, direction, containerType,
-                mLauncher.getWorkspace().getCurrentPage());
-    }
-
-    public boolean isTransitioning() {
-        return mDetector.isDraggingOrSettling();
+    public float getShiftRange() {
+        return mShiftRange;
     }
 
     private void onProgressAnimationStart() {
@@ -310,10 +123,9 @@
      * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
      *
      * @see #setFinalProgress(float)
-     * @see #animateToFinalProgress(float, boolean, AnimatorSet, AnimationConfig)
+     * @see #animateToFinalProgress(float, AnimatorSet, AnimationConfig)
      */
     public void setProgress(float progress) {
-        float shiftPrevious = mProgress * mShiftRange;
         mProgress = progress;
         float shiftCurrent = progress * mShiftRange;
 
@@ -341,11 +153,6 @@
         mWorkspace.setWorkspaceYTranslationAndAlpha(
                 PARALLAX_COEFFICIENT * (-mShiftRange + shiftCurrent), workspaceAlpha);
 
-        if (!mDetector.isDraggingState()) {
-            mContainerVelocity = mDetector.computeVelocity(shiftCurrent - shiftPrevious,
-                    System.currentTimeMillis());
-        }
-
         updateLightStatusBar(shiftCurrent);
     }
 
@@ -353,10 +160,6 @@
         return mProgress;
     }
 
-    private void calculateDuration(float velocity, float disp) {
-        mAnimationDuration = SwipeDetector.calculateDuration(velocity, disp / mShiftRange);
-    }
-
     /**
      * Sets the vertical transition progress to {@param progress} and updates all the dependent UI
      * accordingly.
@@ -371,34 +174,20 @@
      * dependent UI using various animation events
      *
      * @param progress the final vertical progress at the end of the animation
-     * @param addSpring should there be an addition spring animation for the sub-views
      * @param animationOut the target AnimatorSet where this animation should be added
      * @param outConfig an in/out configuration which can be shared with other animations
      */
-    public void animateToFinalProgress(float progress, boolean addSpring,
-            AnimatorSet animationOut, AnimationConfig outConfig) {
+    public void animateToFinalProgress(
+            float progress, AnimatorSet animationOut, AnimationConfig outConfig) {
         if (Float.compare(mProgress, progress) == 0) {
             // Fail fast
             onProgressAnimationEnd();
             return;
         }
 
-        outConfig.shouldPost = true;
-        Interpolator interpolator;
-        if (mDetector.isIdleState()) {
-            mAnimationDuration = LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
-            mShiftStart = mAppsView.getTranslationY();
-            interpolator = mFastOutSlowInInterpolator;
-        } else {
-            interpolator = scrollInterpolatorForVelocity(mContainerVelocity);
-            mProgress = Utilities.boundToRange(
-                    mProgress + mContainerVelocity * SINGLE_FRAME_MS / mShiftRange, 0f, 1f);
-            outConfig.shouldPost = false;
-        }
-
-        outConfig.duration = mAnimationDuration;
+        Interpolator interpolator = outConfig.userControlled ? LINEAR : FAST_OUT_SLOW_IN;
         ObjectAnimator anim = ObjectAnimator.ofFloat(this, PROGRESS, mProgress, progress);
-        anim.setDuration(mAnimationDuration);
+        anim.setDuration(outConfig.duration);
         anim.setInterpolator(interpolator);
         anim.addListener(new AnimationSuccessListener() {
             @Override
@@ -413,20 +202,6 @@
         });
 
         animationOut.play(anim);
-        if (addSpring) {
-            ValueAnimator springAnim = ValueAnimator.ofFloat(0, 1);
-            springAnim.setDuration((long) (mAnimationDuration * SPRING_DELAY));
-            springAnim.addListener(new AnimationSuccessListener() {
-                @Override
-                public void onAnimationSuccess(Animator animator) {
-                    if (!mSpringAnimationHandler.isRunning()) {
-                        float velocity = mProgress * SPRING_MAX_RELEASE_VELOCITY;
-                        mSpringAnimationHandler.animateToPositionWithVelocity(0, 1, velocity);
-                    }
-                }
-            });
-            animationOut.play(anim);
-        }
     }
 
     public void showDiscoveryBounce() {
@@ -476,12 +251,6 @@
         mWorkspace = workspace;
         mHotseat.bringToFront();
         mAppsView.getSearchUiManager().addOnScrollRangeChangeListener(this);
-        mSpringAnimationHandler = mAppsView.getSpringAnimationHandler();
-        mSearchSpring = mAppsView.getSearchUiManager().getSpringForFling();
-    }
-
-    private boolean hasSpringAnimationHandler() {
-        return FeatureFlags.LAUNCHER3_PHYSICS && mSpringAnimationHandler != null;
     }
 
     @Override
@@ -503,13 +272,5 @@
             mHotseat.setVisibility(View.INVISIBLE);
             mAppsView.setVisibility(View.VISIBLE);
         }
-        if (hasSpringAnimationHandler()) {
-            mSpringAnimationHandler.remove(mSearchSpring);
-            mSpringAnimationHandler.reset();
-        }
-
-        // TODO: This call should no longer be needed once caret stops animating.
-        setProgress(mProgress);
-        mDetector.finishedScrolling();
     }
 }
diff --git a/src/com/android/launcher3/anim/AnimationSuccessListener.java b/src/com/android/launcher3/anim/AnimationSuccessListener.java
index feebc6c..9448632 100644
--- a/src/com/android/launcher3/anim/AnimationSuccessListener.java
+++ b/src/com/android/launcher3/anim/AnimationSuccessListener.java
@@ -24,7 +24,7 @@
  */
 public abstract class AnimationSuccessListener extends AnimatorListenerAdapter {
 
-    private boolean mCancelled = false;
+    protected boolean mCancelled = false;
 
     @Override
     public void onAnimationCancel(Animator animation) {
diff --git a/src/com/android/launcher3/compat/AnimatorSetCompat.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
similarity index 75%
rename from src/com/android/launcher3/compat/AnimatorSetCompat.java
rename to src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 6676725..826a20e 100644
--- a/src/com/android/launcher3/compat/AnimatorSetCompat.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -13,37 +13,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.compat;
+package com.android.launcher3.anim;
 
 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 com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.Interpolators;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 /**
- * Compat implementation for various new APIs in {@link AnimatorSet}
+ * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators
+ * and durations.
  *
- * Note: The compat implementation does not support start delays on child animations or
+ * Note: The implementation does not support start delays on child animations or
  * sequential playbacks.
  */
-public abstract class AnimatorSetCompat implements ValueAnimator.AnimatorUpdateListener {
+public abstract class AnimatorPlaybackController 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);
-        }
+    public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) {
+
+        /**
+         * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
+         */
+        return new AnimatorPlaybackControllerVL(anim, duration);
     }
 
     private final ValueAnimator mAnimationPlayer;
@@ -52,23 +47,28 @@
     protected final AnimatorSet mAnim;
 
     protected float mCurrentFraction;
+    private Runnable mEndAction;
 
-    protected AnimatorSetCompat(AnimatorSet anim, int duration) {
+    protected AnimatorPlaybackController(AnimatorSet anim, long duration) {
         mAnim = anim;
         mDuration = duration;
 
         mAnimationPlayer = ValueAnimator.ofFloat(0, 1);
         mAnimationPlayer.setInterpolator(Interpolators.LINEAR);
+        mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
         mAnimationPlayer.addUpdateListener(this);
     }
 
+    public AnimatorSet getTarget() {
+        return mAnim;
+    }
+
     /**
      * 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();
     }
 
@@ -78,20 +78,38 @@
     public void reverse() {
         mAnimationPlayer.setFloatValues(mCurrentFraction, 0);
         mAnimationPlayer.setDuration(clampDuration(mCurrentFraction));
-        mAnimationPlayer.addListener(new OnAnimationEndDispatcher());
         mAnimationPlayer.start();
     }
 
     /**
+     * Pauses the currently playing animation.
+     */
+    public void pause() {
+        mAnimationPlayer.cancel();
+    }
+
+    /**
+     * Returns the underlying animation used for controlling the set.
+     */
+    public ValueAnimator getAnimationPlayer() {
+        return mAnimationPlayer;
+    }
+
+    /**
      * Sets the current animation position and updates all the child animators accordingly.
      */
     public abstract void setPlayFraction(float fraction);
 
+    public float getProgressFraction() {
+        return mCurrentFraction;
+    }
+
     /**
-     * @see Animator#addListener(AnimatorListener)
+     * Sets the action to be called when the animation is completed. Also clears any
+     * previously set action.
      */
-    public void addListener(Animator.AnimatorListener listener) {
-        mAnimationPlayer.addListener(listener);
+    public void setEndAction(Runnable runnable) {
+        mEndAction = runnable;
     }
 
     @Override
@@ -124,11 +142,11 @@
         }
     }
 
-    public static class AnimatorSetCompatVL extends AnimatorSetCompat {
+    public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController {
 
         private final ValueAnimator[] mChildAnimations;
 
-        private AnimatorSetCompatVL(AnimatorSet anim, int duration) {
+        private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration) {
             super(anim, duration);
 
             // Build animation list
@@ -164,25 +182,19 @@
 
     }
 
-    @TargetApi(Build.VERSION_CODES.O)
-    private static class AnimatorSetCompatVO extends AnimatorSetCompat {
-
-        private AnimatorSetCompatVO(AnimatorSet anim, int duration) {
-            super(anim, duration);
-        }
+    private class OnAnimationEndDispatcher extends AnimationSuccessListener {
 
         @Override
-        public void setPlayFraction(float fraction) {
-            mCurrentFraction = fraction;
-            mAnim.setCurrentPlayTime(clampDuration(fraction));
+        public void onAnimationStart(Animator animation) {
+            mCancelled = false;
         }
-    }
-
-    private class OnAnimationEndDispatcher extends AnimationSuccessListener {
 
         @Override
         public void onAnimationSuccess(Animator animator) {
             dispatchOnEndRecursively(mAnim);
+            if (mEndAction != null) {
+                mEndAction.run();
+            }
         }
 
         private void dispatchOnEndRecursively(Animator animator) {
diff --git a/src/com/android/launcher3/anim/SpringAnimationHandler.java b/src/com/android/launcher3/anim/SpringAnimationHandler.java
index eec3a48..29a2430 100644
--- a/src/com/android/launcher3/anim/SpringAnimationHandler.java
+++ b/src/com/android/launcher3/anim/SpringAnimationHandler.java
@@ -83,6 +83,10 @@
         mAnimations.add(spring);
     }
 
+    public AnimationFactory<T> getFactory() {
+        return mAnimationFactory;
+    }
+
     /**
      * Adds a new or recycled animation to the list of springs handled by this class.
      *
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
index c2cc215..6ce334e 100644
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ b/src/com/android/launcher3/badge/BadgeRenderer.java
@@ -26,6 +26,7 @@
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.support.annotation.Nullable;
+import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.launcher3.R;
@@ -38,6 +39,8 @@
  */
 public class BadgeRenderer {
 
+    private static final String TAG = "BadgeRenderer";
+
     private static final boolean DOTS_ONLY = true;
 
     // The badge sizes are defined as percentages of the app icon size.
@@ -95,6 +98,10 @@
      */
     public void draw(Canvas canvas, IconPalette palette, @Nullable BadgeInfo badgeInfo,
             Rect iconBounds, float badgeScale, Point spaceForOffset) {
+        if (palette == null || iconBounds == null || spaceForOffset == null) {
+            Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
+            return;
+        }
         mTextPaint.setColor(palette.textColor);
         IconDrawer iconDrawer = badgeInfo != null && badgeInfo.isIconLarge()
                 ? mLargeIconDrawer : mSmallIconDrawer;
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index f01923f..4fbab39 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.config;
 
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.states.OverviewState;
+
 /**
  * Defines a set of flags used to control various launcher behaviors.
  *
@@ -58,4 +61,8 @@
 
     // Features to control Launcher3Go behavior
     public static final boolean GO_DISABLE_WIDGETS = false;
+
+    public static LauncherState createOverviewState(int id) {
+        return new OverviewState(id);
+    }
 }
diff --git a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
index 06493b2..ed5cfeb 100644
--- a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
+++ b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java
@@ -41,7 +41,6 @@
         this.intent = item.isInstantApp ? item.launchIntent : item.installIntent;
         this.title = item.title;
         this.iconBitmap = item.bitmap;
-        this.isDisabled = ShortcutInfo.DEFAULT;
         this.usingLowResIcon = false;
         this.isInstantApp = item.isInstantApp;
         this.isRecent = item.isRecent;
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 5b1a4dc..0f0b20d 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.VerticalSwipeController;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.config.FeatureFlags;
@@ -96,6 +97,7 @@
 
     // Handles all apps pull up interaction
     private AllAppsTransitionController mAllAppsController;
+    private VerticalSwipeController mVerticalSwipeController;
 
     private TouchController mActiveController;
     /**
@@ -121,6 +123,7 @@
         mLauncher = launcher;
         mDragController = dragController;
         mAllAppsController = allAppsTransitionController;
+        mVerticalSwipeController = new VerticalSwipeController(mLauncher);
 
         boolean isAccessibilityEnabled = ((AccessibilityManager) mLauncher.getSystemService(
                 Context.ACCESSIBILITY_SERVICE)).isEnabled();
@@ -191,8 +194,8 @@
             return true;
         }
 
-        if (mAllAppsController.onControllerInterceptTouchEvent(ev)) {
-            mActiveController = mAllAppsController;
+        if (mVerticalSwipeController.onControllerInterceptTouchEvent(ev)) {
+            mActiveController = mVerticalSwipeController;
             return true;
         }
 
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index d471af6..1eddb2c 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -72,7 +72,7 @@
         PackageManager packageManager = context.getPackageManager();
         // the resource
         try {
-            Resources resources = packageManager.getResourcesForApplication(iconRes.resourceName);
+            Resources resources = packageManager.getResourcesForApplication(iconRes.packageName);
             if (resources != null) {
                 final int id = resources.getIdentifier(iconRes.resourceName, null, null);
                 // do not stamp old legacy shortcuts as the app may have already forgotten about it
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index bc7da9b..47f370a 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -16,11 +16,15 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_YES;
+
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.Intent.ShortcutIconResource;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.database.Cursor;
 import android.database.CursorWrapper;
@@ -274,8 +278,13 @@
             info.iconBitmap = icon != null ? icon : info.iconBitmap;
         }
 
-        if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) {
-            info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+        if (lai != null) {
+            ApplicationInfo appInfo = lai.getApplicationInfo();
+            if (PackageManagerHelper.isAppSuspended(appInfo)) {
+                info.runtimeStatusFlags |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+            }
+            info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
+                    ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
         }
 
         // from the db
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 5386fb4..c2cfebb 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -27,8 +27,6 @@
 import android.graphics.Bitmap;
 import android.os.Handler;
 import android.os.Process;
-import android.os.SystemClock;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -76,6 +74,9 @@
 import java.util.Map;
 import java.util.concurrent.CancellationException;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
 
 /**
@@ -466,13 +467,13 @@
                                                     true /* badged */, fallbackIconProvider);
                                     if (pmHelper.isAppSuspended(
                                             pinnedShortcut.getPackage(), info.user)) {
-                                        info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+                                        info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
                                     }
                                     intent = info.intent;
                                 } else {
                                     // Create a shortcut info in disabled mode for now.
                                     info = c.loadSimpleShortcut();
-                                    info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+                                    info.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
                                 }
                             } else { // item type == ITEM_TYPE_SHORTCUT
                                 info = c.loadSimpleShortcut();
@@ -480,7 +481,7 @@
                                 // Shortcuts are only available on the primary profile
                                 if (!TextUtils.isEmpty(targetPkg)
                                         && pmHelper.isAppSuspended(targetPkg, c.user)) {
-                                    disabledState |= ShortcutInfo.FLAG_DISABLED_SUSPENDED;
+                                    disabledState |= FLAG_DISABLED_SUSPENDED;
                                 }
 
                                 // App shortcuts that used to be automatically added to Launcher
@@ -503,9 +504,9 @@
                                 info.rank = c.getInt(rankIndex);
                                 info.spanX = 1;
                                 info.spanY = 1;
-                                info.isDisabled |= disabledState;
+                                info.runtimeStatusFlags |= disabledState;
                                 if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
-                                    info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
+                                    info.runtimeStatusFlags |= FLAG_DISABLED_SAFEMODE;
                                 }
 
                                 if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 53a9862..470dadf 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -244,9 +244,9 @@
                                 infoUpdated = true;
                             }
 
-                            int oldDisabledFlags = si.isDisabled;
-                            si.isDisabled = flagOp.apply(si.isDisabled);
-                            if (si.isDisabled != oldDisabledFlags) {
+                            int oldRuntimeFlags = si.runtimeStatusFlags;
+                            si.runtimeStatusFlags = flagOp.apply(si.runtimeStatusFlags);
+                            if (si.runtimeStatusFlags != oldRuntimeFlags) {
                                 shortcutUpdated = true;
                             }
                         }
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 8170f9a..2e9ac72 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
+
 import android.content.Context;
 import android.os.UserHandle;
 
@@ -87,12 +89,12 @@
                         removedKeys.add(key);
                         continue;
                     }
-                    si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+                    si.runtimeStatusFlags &= ~FLAG_DISABLED_LOCKED_USER;
                     si.updateFromDeepShortcutInfo(shortcut, context);
                     si.iconBitmap = LauncherIcons.createShortcutIcon(shortcut, context,
                             si.iconBitmap);
                 } else {
-                    si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
+                    si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
                 }
                 updatedShortcutInfos.add(si);
             }
diff --git a/src/com/android/launcher3/states/AllAppsState.java b/src/com/android/launcher3/states/AllAppsState.java
index ee35b4d..ed3023a 100644
--- a/src/com/android/launcher3/states/AllAppsState.java
+++ b/src/com/android/launcher3/states/AllAppsState.java
@@ -32,7 +32,7 @@
 
     public static final String APPS_VIEW_SHOWN = "launcher.apps_view_shown";
 
-    private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY | FLAG_HAS_SPRING;
+    private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
 
     public AllAppsState(int id) {
         super(id, ContainerType.ALLAPPS, ALL_APPS_TRANSITION_MS, 0f, STATE_FLAGS);