Merge "Using support lib implementation for launcher preference" into ub-launcher3-master
diff --git a/Android.bp b/Android.bp
index aefc1f0..2608280 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,6 +23,7 @@
     srcs: [
         "tests/tapl/**/*.java",
         "quickstep/src/com/android/quickstep/SwipeUpSetting.java",
+        "src/com/android/launcher3/util/SecureSettingsObserver.java",
         "src/com/android/launcher3/TestProtocol.java",
     ],
     platform_apis: true,
diff --git a/Android.mk b/Android.mk
index bf9b8a0..fbe19b0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -53,7 +53,6 @@
 LOCAL_SDK_VERSION := current
 LOCAL_MIN_SDK_VERSION := 28
 LOCAL_MODULE := LauncherPluginLib
-LOCAL_PRIVILEGED_MODULE := true
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
@@ -70,7 +69,7 @@
     androidx.dynamicanimation_dynamicanimation \
     androidx.preference_preference
 
-LOCAL_STATIC_JAVA_LIBRARIES := libPluginCore
+LOCAL_STATIC_JAVA_LIBRARIES := LauncherPluginLib
 
 LOCAL_SRC_FILES := \
     $(call all-proto-files-under, protos) \
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index ca12951..88c362d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -43,7 +43,12 @@
     }
 
     public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
-        mPluginManager.addPluginListener(listener, pluginClass);
+        addPluginListener(listener, pluginClass, false);
+    }
+
+    public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass,
+            boolean allowMultiple) {
+        mPluginManager.addPluginListener(listener, pluginClass, allowMultiple);
     }
 
     public void removePluginListener(PluginListener<? extends Plugin> listener) {
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 4417a3d..94ec69a 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -52,6 +52,7 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.AssistDataReceiver;
 import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.NavigationBarCompat;
 import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -80,6 +81,7 @@
     private final OverviewCallbacks mOverviewCallbacks;
     private final TaskOverlayFactory mTaskOverlayFactory;
     private final TouchInteractionLog mTouchInteractionLog;
+    private final InputConsumerController mInputConsumer;
 
     private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
@@ -101,8 +103,8 @@
             RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
             MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
             @HitTarget int downHitTarget, OverviewCallbacks overviewCallbacks,
-            TaskOverlayFactory taskOverlayFactory, VelocityTracker velocityTracker,
-            TouchInteractionLog touchInteractionLog) {
+            TaskOverlayFactory taskOverlayFactory, InputConsumerController inputConsumer,
+            VelocityTracker velocityTracker, TouchInteractionLog touchInteractionLog) {
         super(base);
 
         mRunningTask = runningTaskInfo;
@@ -117,6 +119,7 @@
         mTaskOverlayFactory = taskOverlayFactory;
         mTouchInteractionLog = touchInteractionLog;
         mTouchInteractionLog.setTouchConsumer(this);
+        mInputConsumer = inputConsumer;
     }
 
     @Override
@@ -226,7 +229,7 @@
         RecentsAnimationState animationState = new RecentsAnimationState();
         final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
                 animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper,
-                mTouchInteractionLog);
+                mInputConsumer, mTouchInteractionLog);
 
         // Preload the plan
         mRecentsModel.loadTasks(mRunningTask.id, null);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
index eea3971..2f3cb5f 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -53,24 +53,18 @@
             new LooperExecutor(UiThreadHelper.getBackgroundLooper());
 
     private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
-    private InputConsumerController mInputConsumer =
-            InputConsumerController.getRecentsAnimationInputConsumer();
+    private final InputConsumerController mInputConsumer;
     private final Supplier<TouchConsumer> mTouchProxySupplier;
 
-    private boolean mInputConsumerUnregistered;
-    private boolean mTouchProxyEnabled;
-
     private TouchConsumer mTouchConsumer;
     private boolean mTouchInProgress;
-    private boolean mInputConsumerUnregisterPending;
 
     private boolean mFinishPending;
 
-    public RecentsAnimationWrapper(Supplier<TouchConsumer> touchProxySupplier) {
-        // Register the input consumer on the UI thread, to ensure that it runs after any pending
-        // unregister calls
+    public RecentsAnimationWrapper(InputConsumerController inputConsumer,
+            Supplier<TouchConsumer> touchProxySupplier) {
+        mInputConsumer = inputConsumer;
         mTouchProxySupplier = touchProxySupplier;
-        mMainThreadExecutor.execute(mInputConsumer::registerInputConsumer);
     }
 
     public synchronized void setController(
@@ -109,6 +103,7 @@
     public void finish(boolean toHome, Runnable onFinishComplete) {
         if (!toHome) {
             mExecutorService.submit(() -> finishBg(false, onFinishComplete));
+            return;
         }
 
         mMainThreadExecutor.execute(() -> {
@@ -152,28 +147,12 @@
         }
     }
 
-    public void unregisterInputConsumer() {
-        mMainThreadExecutor.execute(this::unregisterInputConsumerUi);
-    }
-
-    private void unregisterInputConsumerUi() {
-        if (mTouchProxyEnabled && mTouchInProgress) {
-            mInputConsumerUnregisterPending = true;
-        } else {
-            mInputConsumerUnregistered = true;
-            mInputConsumer.unregisterInputConsumer();
-        }
-    }
-
     public void enableTouchProxy() {
         mMainThreadExecutor.execute(this::enableTouchProxyUi);
     }
 
     private void enableTouchProxyUi() {
-        if (!mInputConsumerUnregistered) {
-            mTouchProxyEnabled = true;
-            mInputConsumer.setTouchListener(this::onInputConsumerTouch);
-        }
+        mInputConsumer.setTouchListener(this::onInputConsumerTouch);
     }
 
     private boolean onInputConsumerTouch(MotionEvent ev) {
@@ -184,10 +163,6 @@
         } else if (action == ACTION_CANCEL || action == ACTION_UP) {
             // Finish any pending actions
             mTouchInProgress = false;
-            if (mInputConsumerUnregisterPending) {
-                mInputConsumerUnregisterPending = false;
-                mInputConsumer.unregisterInputConsumer();
-            }
             if (mFinishPending) {
                 mFinishPending = false;
                 mExecutorService.submit(() -> finishBg(true, null));
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 7289516..59a937f 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -20,21 +20,34 @@
 import android.graphics.Matrix;
 import android.view.View;
 
+import androidx.annotation.AnyThread;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
-import androidx.annotation.AnyThread;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Factory class to create and add an overlays on the TaskView
  */
 public class TaskOverlayFactory implements ResourceBasedOverride {
-
     private static TaskOverlayFactory sInstance;
 
+    /** Note that these will be shown in order from top to bottom, if available for the task. */
+    private static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[]{
+            new TaskSystemShortcut.AppInfo(),
+            new TaskSystemShortcut.SplitScreen(),
+            new TaskSystemShortcut.Pin(),
+            new TaskSystemShortcut.Install(),
+    };
+
     public static TaskOverlayFactory get(Context context) {
         Preconditions.assertUIThread();
         if (sInstance == null) {
@@ -55,9 +68,23 @@
 
     public static class TaskOverlay {
 
-        public void setTaskInfo(Task task, ThumbnailData thumbnail, Matrix matrix) { }
+        public void setTaskInfo(Task task, ThumbnailData thumbnail, Matrix matrix) {
+        }
 
-        public void reset() { }
+        public void reset() {
+        }
 
+        public List<TaskSystemShortcut> getEnabledShortcuts(TaskView taskView) {
+            final ArrayList<TaskSystemShortcut> shortcuts = new ArrayList<>();
+            final BaseDraggingActivity activity = BaseActivity.fromContext(taskView.getContext());
+            for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
+                View.OnClickListener onClickListener =
+                        menuOption.getOnClickListener(activity, taskView);
+                if (onClickListener != null) {
+                    shortcuts.add(menuOption);
+                }
+            }
+            return shortcuts;
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index e64d04a..66ce4c3 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -64,7 +64,7 @@
     protected T mSystemShortcut;
 
     protected TaskSystemShortcut(T systemShortcut) {
-        super(systemShortcut.iconResId, systemShortcut.labelResId);
+        super(systemShortcut);
         mSystemShortcut = systemShortcut;
     }
 
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index cded799..9371a4c 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -51,6 +51,7 @@
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.ChoreographerCompat;
+import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
 
 import java.io.FileDescriptor;
@@ -185,6 +186,7 @@
     private OverviewCallbacks mOverviewCallbacks;
     private TaskOverlayFactory mTaskOverlayFactory;
     private TouchInteractionLog mTouchInteractionLog;
+    private InputConsumerController mInputConsumer;
 
     private Choreographer mMainThreadChoreographer;
     private Choreographer mBackgroundThreadChoreographer;
@@ -203,6 +205,8 @@
         mOverviewCallbacks = OverviewCallbacks.get(this);
         mTaskOverlayFactory = TaskOverlayFactory.get(this);
         mTouchInteractionLog = new TouchInteractionLog();
+        mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
+        mInputConsumer.registerInputConsumer();
 
         sConnected = true;
 
@@ -213,6 +217,7 @@
 
     @Override
     public void onDestroy() {
+        mInputConsumer.unregisterInputConsumer();
         mOverviewCommandHelper.onDestroy();
         sConnected = false;
         super.onDestroy();
@@ -256,7 +261,7 @@
                             mOverviewCommandHelper.overviewIntent,
                             mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor,
                             mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks,
-                            mTaskOverlayFactory, tracker, mTouchInteractionLog);
+                            mTaskOverlayFactory, mInputConsumer, tracker, mTouchInteractionLog);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 9991552..1c79f44 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -78,6 +78,7 @@
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -226,8 +227,7 @@
 
     private @InteractionType int mInteractionType = INTERACTION_NORMAL;
 
-    private final RecentsAnimationWrapper mRecentsAnimationWrapper =
-            new RecentsAnimationWrapper(this::createNewTouchProxyHandler);
+    private final RecentsAnimationWrapper mRecentsAnimationWrapper;
 
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
@@ -241,7 +241,7 @@
 
     WindowTransformSwipeHandler(int id, RunningTaskInfo runningTaskInfo, Context context,
             long touchTimeMs, ActivityControlHelper<T> controller,
-            TouchInteractionLog touchInteractionLog) {
+            InputConsumerController inputConsumer, TouchInteractionLog touchInteractionLog) {
         this.id = id;
         mContext = context;
         mRunningTaskInfo = runningTaskInfo;
@@ -251,6 +251,8 @@
         mActivityInitListener = mActivityControlHelper
                 .createActivityInitListener(this::onActivityInit);
         mTouchInteractionLog = touchInteractionLog;
+        mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
+                this::createNewTouchProxyHandler);
 
         initStateCallbacks();
     }
@@ -538,7 +540,7 @@
     public void updateDisplacement(float displacement) {
         // We are moving in the negative x/y direction
         displacement = -displacement;
-        if (displacement > mTransitionDragLength) {
+        if (displacement > mTransitionDragLength && mTransitionDragLength > 0) {
             mCurrentShift.updateValue(1);
 
             if (!mBgLongSwipeMode) {
@@ -813,8 +815,12 @@
         long startMillis = SystemClock.uptimeMillis();
         executeOnUiThread(() -> {
             // Animate the launcher components at the same time as the window, always on UI thread.
-            if (mLauncherTransitionController != null && !mWasLauncherAlreadyVisible
-                    && start != end && duration > 0) {
+            if (mLauncherTransitionController == null) {
+                return;
+            }
+            if (start == end || duration <= 0) {
+                mLauncherTransitionController.getAnimationPlayer().end();
+            } else {
                 // Adjust start progress and duration in case we are on a different thread.
                 long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
                 elapsedMillis = Utilities.boundToRange(elapsedMillis, 0, duration);
@@ -860,7 +866,6 @@
         }
 
         mActivityInitListener.unregister();
-        mRecentsAnimationWrapper.unregisterInputConsumer();
         mTaskSnapshot = null;
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 28928a8..667165b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -43,6 +43,8 @@
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.views.IconView.OnScaleUpdateListener;
 
+import java.util.List;
+
 /**
  * Contains options for a recent task when long-pressing its icon.
  */
@@ -50,14 +52,6 @@
 
     private static final Rect sTempRect = new Rect();
 
-    /** Note that these will be shown in order from top to bottom, if available for the task. */
-    public static final TaskSystemShortcut[] MENU_OPTIONS = new TaskSystemShortcut[] {
-            new TaskSystemShortcut.AppInfo(),
-            new TaskSystemShortcut.SplitScreen(),
-            new TaskSystemShortcut.Pin(),
-            new TaskSystemShortcut.Install(),
-    };
-
     private final OnScaleUpdateListener mTaskViewIconScaleListener = new OnScaleUpdateListener() {
         @Override
         public void onScaleUpdate(float scale) {
@@ -197,19 +191,21 @@
         params.topMargin = (int) -mThumbnailTopMargin;
         mTaskIcon.setLayoutParams(params);
 
-        for (TaskSystemShortcut menuOption : MENU_OPTIONS) {
-            OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, taskView);
-            if (onClickListener != null) {
-                addMenuOption(menuOption, onClickListener);
-            }
+        final BaseDraggingActivity activity = BaseDraggingActivity.fromContext(getContext());
+        final List<TaskSystemShortcut> shortcuts =
+                taskView.getTaskOverlay().getEnabledShortcuts(taskView);
+        final int count = shortcuts.size();
+        for (int i = 0; i < count; ++i) {
+            final TaskSystemShortcut menuOption = shortcuts.get(i);
+            addMenuOption(menuOption, menuOption.getOnClickListener(activity, taskView));
         }
     }
 
     private void addMenuOption(TaskSystemShortcut menuOption, OnClickListener onClickListener) {
         ViewGroup menuOptionView = (ViewGroup) mActivity.getLayoutInflater().inflate(
                 R.layout.task_view_menu_option, this, false);
-        menuOptionView.findViewById(R.id.icon).setBackgroundResource(menuOption.iconResId);
-        ((TextView) menuOptionView.findViewById(R.id.text)).setText(menuOption.labelResId);
+        menuOption.setIconAndLabelFor(
+                menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
         menuOptionView.setOnClickListener(onClickListener);
         mOptionLayout.addView(menuOptionView);
     }
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 7223f97..ce65de1 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -173,6 +173,10 @@
         return 0;
     }
 
+    public TaskOverlay getTaskOverlay() {
+        return mOverlay;
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index a0615f5..56074f0 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -42,12 +42,12 @@
 import android.widget.FrameLayout;
 import android.widget.Toast;
 
-import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskSystemShortcut;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.views.RecentsView.PageCallbacks;
@@ -56,8 +56,9 @@
 import com.android.systemui.shared.recents.model.Task.TaskCallbacks;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-
 import com.android.systemui.shared.system.ActivityOptionsCompat;
+
+import java.util.List;
 import java.util.function.Consumer;
 
 /**
@@ -190,6 +191,10 @@
         return mIconView;
     }
 
+    public TaskOverlayFactory.TaskOverlay getTaskOverlay() {
+        return mSnapshotView.getTaskOverlay();
+    }
+
     public void launchTask(boolean animate) {
         launchTask(animate, (result) -> {
             if (!result) {
@@ -384,11 +389,14 @@
 
         final Context context = getContext();
         final BaseDraggingActivity activity = fromContext(context);
-        for (TaskSystemShortcut menuOption : TaskMenuView.MENU_OPTIONS) {
+        final List<TaskSystemShortcut> shortcuts =
+                mSnapshotView.getTaskOverlay().getEnabledShortcuts(this);
+        final int count = shortcuts.size();
+        for (int i = 0; i < count; ++i) {
+            final TaskSystemShortcut menuOption = shortcuts.get(i);
             OnClickListener onClickListener = menuOption.getOnClickListener(activity, this);
             if (onClickListener != null) {
-                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(menuOption.labelResId,
-                        context.getText(menuOption.labelResId)));
+                info.addAction(menuOption.createAccessibilityAction(context));
             }
         }
 
@@ -408,8 +416,12 @@
             return true;
         }
 
-        for (TaskSystemShortcut menuOption : TaskMenuView.MENU_OPTIONS) {
-            if (action == menuOption.labelResId) {
+        final List<TaskSystemShortcut> shortcuts =
+                mSnapshotView.getTaskOverlay().getEnabledShortcuts(this);
+        final int count = shortcuts.size();
+        for (int i = 0; i < count; ++i) {
+            final TaskSystemShortcut menuOption = shortcuts.get(i);
+            if (menuOption.hasHandlerForAction(action)) {
                 OnClickListener onClickListener = menuOption.getOnClickListener(
                         fromContext(getContext()), this);
                 if (onClickListener != null) {
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 5348349..90e195b 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -25,16 +25,22 @@
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
-
-import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertySetter;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.recyclerview.widget.RecyclerView;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.systemui.plugins.AllAppsRow;
+import com.android.systemui.plugins.PluginListener;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 public class FloatingHeaderView extends LinearLayout implements
-        ValueAnimator.AnimatorUpdateListener {
+        ValueAnimator.AnimatorUpdateListener, PluginListener<AllAppsRow> {
 
     private final Rect mClip = new Rect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
     private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0);
@@ -64,6 +70,9 @@
     private AllAppsRecyclerView mMainRV;
     private AllAppsRecyclerView mWorkRV;
     private AllAppsRecyclerView mCurrentRV;
+    protected final Map<AllAppsRow, View> mPluginRows;
+    // Contains just the values of the above map so we can iterate without extracting a new list.
+    protected final List<View> mPluginRowViews;
     private ViewGroup mParent;
     private boolean mHeaderCollapsed;
     private int mSnappedScrolledY;
@@ -82,6 +91,8 @@
 
     public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
+        mPluginRows = new HashMap<>();
+        mPluginRowViews = new ArrayList<>();
     }
 
     @Override
@@ -90,6 +101,38 @@
         mTabLayout = findViewById(R.id.tabs);
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        PluginManagerWrapper.INSTANCE.get(getContext()).addPluginListener(this,
+                AllAppsRow.class, true /* allowMultiple */);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(this);
+    }
+
+    @Override
+    public void onPluginConnected(AllAppsRow allAppsRowPlugin, Context context) {
+        mPluginRows.put(allAppsRowPlugin, null);
+        setupPluginRows();
+        allAppsRowPlugin.setOnHeightUpdatedListener(this::onPluginRowHeightUpdated);
+    }
+
+    protected void onPluginRowHeightUpdated() {
+    }
+
+    @Override
+    public void onPluginDisconnected(AllAppsRow plugin) {
+        View pluginRowView = mPluginRows.get(plugin);
+        removeView(pluginRowView);
+        mPluginRows.remove(plugin);
+        mPluginRowViews.remove(pluginRowView);
+        onPluginRowHeightUpdated();
+    }
+
     public void setup(AllAppsContainerView.AdapterHolder[] mAH, boolean tabsHidden) {
         mTabsHidden = tabsHidden;
         mTabLayout.setVisibility(tabsHidden ? View.GONE : View.VISIBLE);
@@ -97,9 +140,24 @@
         mWorkRV = setupRV(mWorkRV, mAH[AllAppsContainerView.AdapterHolder.WORK].recyclerView);
         mParent = (ViewGroup) mMainRV.getParent();
         setMainActive(mMainRVActive || mWorkRV == null);
+        setupPluginRows();
         reset(false);
     }
 
+    private void setupPluginRows() {
+        for (Map.Entry<AllAppsRow, View> rowPluginEntry : mPluginRows.entrySet()) {
+            if (rowPluginEntry.getValue() == null) {
+                View pluginRow = rowPluginEntry.getKey().setup(this);
+                addView(pluginRow, indexOfChild(mTabLayout));
+                rowPluginEntry.setValue(pluginRow);
+                mPluginRowViews.add(pluginRow);
+            }
+        }
+        for (View plugin : mPluginRowViews) {
+            plugin.setVisibility(mHeaderCollapsed ? GONE : VISIBLE);
+        }
+    }
+
     private AllAppsRecyclerView setupRV(AllAppsRecyclerView old, AllAppsRecyclerView updated) {
         if (old != updated && updated != null ) {
             updated.addOnScrollListener(mOnScrollListener);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 6877cc4..b9e6a98 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -391,13 +391,10 @@
         if (view instanceof DeepShortcutView) {
             // Expanded system shortcut, with both icon and text shown on white background.
             final DeepShortcutView shortcutView = (DeepShortcutView) view;
-            shortcutView.getIconView().setBackgroundResource(info.iconResId);
-            shortcutView.getBubbleText().setText(info.labelResId);
+            info.setIconAndLabelFor(shortcutView.getIconView(), shortcutView.getBubbleText());
         } else if (view instanceof ImageView) {
             // Only the system shortcut icon shows on a gray background header.
-            final ImageView shortcutIcon = (ImageView) view;
-            shortcutIcon.setImageResource(info.iconResId);
-            shortcutIcon.setContentDescription(getContext().getText(info.labelResId));
+            info.setIconAndContentDescriptionFor((ImageView) view);
         }
         view.setTag(info);
         view.setOnClickListener(info.getOnClickListener(mLauncher,
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 693e532..b80ba8a 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -3,10 +3,15 @@
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 
+import android.content.Context;
 import android.content.Intent;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.ImageView;
+import android.widget.TextView;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
@@ -23,18 +28,82 @@
 import java.util.List;
 
 /**
- * Represents a system shortcut for a given app. The shortcut should have a static label and
- * icon, and an onClickListener that depends on the item that the shortcut services.
+ * Represents a system shortcut for a given app. The shortcut should have a label and icon, and an
+ * onClickListener that depends on the item that the shortcut services.
  *
  * Example system shortcuts, defined as inner classes, include Widgets and AppInfo.
  */
 public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo {
-    public final int iconResId;
-    public final int labelResId;
+    private final int mIconResId;
+    private final int mLabelResId;
+    private final Drawable mIcon;
+    private final CharSequence mLabel;
+    private final CharSequence mContentDescription;
+    private final int mAccessibilityActionId;
 
     public SystemShortcut(int iconResId, int labelResId) {
-        this.iconResId = iconResId;
-        this.labelResId = labelResId;
+        mIconResId = iconResId;
+        mLabelResId = labelResId;
+        mAccessibilityActionId = labelResId;
+        mIcon = null;
+        mLabel = null;
+        mContentDescription = null;
+    }
+
+    public SystemShortcut(Drawable icon, CharSequence label, CharSequence contentDescription,
+            int accessibilityActionId) {
+        mIcon = icon;
+        mLabel = label;
+        mContentDescription = contentDescription;
+        mAccessibilityActionId = accessibilityActionId;
+        mIconResId = 0;
+        mLabelResId = 0;
+    }
+
+    public SystemShortcut(SystemShortcut other) {
+        mIconResId = other.mIconResId;
+        mLabelResId = other.mLabelResId;
+        mIcon = other.mIcon;
+        mLabel = other.mLabel;
+        mContentDescription = other.mContentDescription;
+        mAccessibilityActionId = other.mAccessibilityActionId;
+    }
+
+    public void setIconAndLabelFor(View iconView, TextView labelView) {
+        if (mIcon != null) {
+            iconView.setBackground(mIcon);
+        } else {
+            iconView.setBackgroundResource(mIconResId);
+        }
+
+        if (mLabel != null) {
+            labelView.setText(mLabel);
+        } else {
+            labelView.setText(mLabelResId);
+        }
+    }
+
+    public void setIconAndContentDescriptionFor(ImageView view) {
+        if (mIcon != null) {
+            view.setImageDrawable(mIcon);
+        } else {
+            view.setImageResource(mIconResId);
+        }
+
+        view.setContentDescription(getContentDescription(view.getContext()));
+    }
+
+    private CharSequence getContentDescription(Context context) {
+        return mContentDescription != null ? mContentDescription : context.getText(mLabelResId);
+    }
+
+    public AccessibilityNodeInfo.AccessibilityAction createAccessibilityAction(Context context) {
+        return new AccessibilityNodeInfo.AccessibilityAction(mAccessibilityActionId,
+                getContentDescription(context));
+    }
+
+    public boolean hasHandlerForAction(int action) {
+        return mAccessibilityActionId == action;
     }
 
     public abstract View.OnClickListener getOnClickListener(T activity, ItemInfo itemInfo);
diff --git a/src_plugins/com/android/systemui/plugins/AllAppsRow.java b/src_plugins/com/android/systemui/plugins/AllAppsRow.java
new file mode 100644
index 0000000..c003fc1
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/AllAppsRow.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 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.systemui.plugins;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+/**
+ * Implement this plugin interface to add a row of views to the top of the all apps drawer.
+ */
+@ProvidesInterface(action = AllAppsRow.ACTION, version = AllAppsRow.VERSION)
+public interface AllAppsRow extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_ALL_APPS_ACTIONS";
+    int VERSION = 1;
+
+    /**
+     * Setup the row and return the parent view.
+     * @param parent The ViewGroup to which launcher will add this row.
+     */
+    View setup(ViewGroup parent);
+
+    /**
+     * @return The height to reserve in all apps for your views.
+     */
+    int getExpectedHeight();
+
+    /**
+     * Update launcher whenever {@link #getExpectedHeight()} changes.
+     */
+    void setOnHeightUpdatedListener(OnHeightUpdatedListener onHeightUpdatedListener);
+
+    interface OnHeightUpdatedListener {
+        void onHeightUpdated();
+    }
+}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index fcb2abe..31dbb34 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -31,6 +31,10 @@
     public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass) {
     }
 
+    public void addPluginListener(PluginListener<? extends Plugin> listener, Class<?> pluginClass,
+            boolean allowMultiple) {
+    }
+
     public void removePluginListener(PluginListener<? extends Plugin> listener) {
     }
 }