Add support for SystemUI Window Management

Enables SystemUI to create/position its own ui elements.

First of all, this adds, to WM, the concept of a ShellRoot
which represents a piece of the hierarchy that a client shell can
do whatever it needs with. For now, multiple of these roots can
be registered at various "levels" (which correspond to window
types for now). This is needed because not everything will live
in this piece of the hierarchy, so handling z-order will still
be a shared effort between the Shell and WM for a while.

On the SystemUI side, a new Dependency called SystemWindows
provides simplistic window management for these system-ui
windows via WindowlessWindowManagers per-display-per-layer.
The benefit of this is that manipulation of these windows lives
entirely in SystemUI making synchronization easier and making
it possible to move a lot of the special handling code out
of wm (eg. DOCK_DIVIDER). As a result, SystemUI becomes
more customizable and WM becomes simpler.

Early clients of this are going to be display-level IME
handling and Split-screen.

Bug: 133381284
Test: manual test after later CLs
Change-Id: I1602d9b9b69d38b9ff15806e509cc8128c837748
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index b829c9f..9496827 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -44,6 +44,7 @@
 import android.view.IRotationWatcher;
 import android.view.ISystemGestureExclusionListener;
 import android.view.IWallpaperVisibilityListener;
+import android.view.IWindow;
 import android.view.IWindowSession;
 import android.view.IWindowSessionCallback;
 import android.view.KeyEvent;
@@ -120,6 +121,17 @@
     void setDisplayWindowRotationController(IDisplayWindowRotationController controller);
 
     /**
+     * Adds a root container that a client shell can populate with its own windows (usually via
+     * WindowlessWindowManager).
+     *
+     * @param client an IWindow used for window-level communication (ime, finish draw, etc.).
+     * @param windowType used by WM to determine the z-order. This is the same as the window type
+     *                   used in {@link WindowManager.LayoutParams}.
+     * @return a SurfaceControl to add things to.
+     */
+    SurfaceControl addShellRoot(int displayId, IWindow client, int windowType);
+
+    /**
      * Like overridePendingAppTransitionMultiThumb, but uses a future to supply the specs. This is
      * used for recents, where generating the thumbnails of the specs takes a non-trivial amount of
      * time, so we want to move that off the critical path for starting the new activity.
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 6821265..dd38a33 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -124,6 +124,7 @@
 import com.android.systemui.util.leak.LeakReporter;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 import com.android.systemui.wm.DisplayWindowController;
+import com.android.systemui.wm.SystemWindows;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -323,6 +324,7 @@
     @Inject Lazy<Recents> mRecents;
     @Inject Lazy<StatusBar> mStatusBar;
     @Inject Lazy<DisplayWindowController> mDisplayWindowController;
+    @Inject Lazy<SystemWindows> mSystemWindows;
 
     @Inject
     public Dependency() {
@@ -513,6 +515,7 @@
         mProviders.put(Recents.class, mRecents::get);
         mProviders.put(StatusBar.class, mStatusBar::get);
         mProviders.put(DisplayWindowController.class, mDisplayWindowController::get);
+        mProviders.put(SystemWindows.class, mSystemWindows::get);
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
new file mode 100644
index 0000000..5ec61c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wm;
+
+import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.MergedConfiguration;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.DragEvent;
+import android.view.IWindow;
+import android.view.IWindowManager;
+import android.view.IWindowSession;
+import android.view.IWindowSessionCallback;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowlessViewRoot;
+import android.view.WindowlessWindowManager;
+
+import com.android.internal.os.IResultReceiver;
+
+import java.util.HashMap;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Represents the "windowing" layer of the System-UI. This layer allows system-ui components to
+ * place and manipulate windows without talking to WindowManager.
+ */
+@Singleton
+public class SystemWindows {
+    private static final String TAG = "SystemWindows";
+
+    private final SparseArray<PerDisplay> mPerDisplay = new SparseArray<>();
+    final HashMap<View, WindowlessViewRoot> mViewRoots = new HashMap<>();
+    Context mContext;
+    IWindowSession mSession;
+    DisplayWindowController mDisplayController;
+    IWindowManager mWmService;
+
+    private final DisplayWindowController.DisplayWindowListener mDisplayListener =
+            new DisplayWindowController.DisplayWindowListener() {
+                @Override
+                public void onDisplayAdded(int displayId) { }
+
+                @Override
+                public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+                    PerDisplay pd = mPerDisplay.get(displayId);
+                    if (pd == null) {
+                        return;
+                    }
+                    pd.updateConfiguration(newConfig);
+                }
+
+                @Override
+                public void onDisplayRemoved(int displayId) { }
+            };
+
+    @Inject
+    public SystemWindows(Context context, DisplayWindowController displayController,
+            IWindowManager wmService) {
+        mContext = context;
+        mWmService = wmService;
+        mDisplayController = displayController;
+        mDisplayController.addDisplayWindowListener(mDisplayListener);
+        try {
+            mSession = wmService.openSession(
+                    new IWindowSessionCallback.Stub() {
+                        @Override
+                        public void onAnimatorScaleChanged(float scale) {}
+                    });
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to create layer", e);
+        }
+    }
+
+    /**
+     * Adds a view to system-ui window management.
+     */
+    public void addView(View view, WindowManager.LayoutParams attrs, int displayId,
+            int windowType) {
+        PerDisplay pd = mPerDisplay.get(displayId);
+        if (pd == null) {
+            pd = new PerDisplay(displayId);
+            mPerDisplay.put(displayId, pd);
+        }
+        pd.addView(view, attrs, windowType);
+    }
+
+    /**
+     * Removes a view from system-ui window management.
+     * @param view
+     */
+    public void removeView(View view) {
+        WindowlessViewRoot root = mViewRoots.remove(view);
+        root.die();
+    }
+
+    /**
+     * Updates the layout params of a view.
+     */
+    public void updateViewLayout(@NonNull View view, ViewGroup.LayoutParams params) {
+        WindowlessViewRoot root = mViewRoots.get(view);
+        if (root == null || !(params instanceof WindowManager.LayoutParams)) {
+            return;
+        }
+        view.setLayoutParams(params);
+        root.relayout((WindowManager.LayoutParams) params);
+    }
+
+    /**
+     * Adds a root for system-ui window management with no views. Only useful for IME.
+     */
+    public void addRoot(int displayId, int windowType) {
+        PerDisplay pd = mPerDisplay.get(displayId);
+        if (pd == null) {
+            pd = new PerDisplay(displayId);
+            mPerDisplay.put(displayId, pd);
+        }
+        pd.addRoot(windowType);
+    }
+
+    /**
+     * Get the IWindow token for a specific root.
+     *
+     * @param windowType A window type from {@link android.view.WindowManager}.
+     */
+    IWindow getWindow(int displayId, int windowType) {
+        PerDisplay pd = mPerDisplay.get(displayId);
+        if (pd == null) {
+            return null;
+        }
+        return pd.getWindow(windowType);
+    }
+
+    private class PerDisplay {
+        final int mDisplayId;
+        private final SparseArray<SysUiWindowManager> mWwms = new SparseArray<>();
+
+        PerDisplay(int displayId) {
+            mDisplayId = displayId;
+        }
+
+        public void addView(View view, WindowManager.LayoutParams attrs, int windowType) {
+            SysUiWindowManager wwm = addRoot(windowType);
+            if (wwm == null) {
+                Slog.e(TAG, "Unable to create systemui root");
+                return;
+            }
+            final Display display = mDisplayController.getDisplay(mDisplayId);
+            WindowlessViewRoot viewRoot = new WindowlessViewRoot(mContext, display, wwm);
+            attrs.flags |= FLAG_HARDWARE_ACCELERATED;
+            viewRoot.addView(view, attrs);
+            mViewRoots.put(view, viewRoot);
+        }
+
+        SysUiWindowManager addRoot(int windowType) {
+            SysUiWindowManager wwm = mWwms.get(windowType);
+            if (wwm != null) {
+                return wwm;
+            }
+            SurfaceControl rootSurface = null;
+            ContainerWindow win = new ContainerWindow();
+            try {
+                rootSurface = mWmService.addShellRoot(mDisplayId, win, windowType);
+            } catch (RemoteException e) {
+            }
+            if (rootSurface == null) {
+                Slog.e(TAG, "Unable to get root surfacecontrol for systemui");
+                return null;
+            }
+            Context displayContext = mDisplayController.getDisplayContext(mDisplayId);
+            wwm = new SysUiWindowManager(mDisplayId, displayContext, rootSurface, win);
+            mWwms.put(windowType, wwm);
+            return wwm;
+        }
+
+        IWindow getWindow(int windowType) {
+            SysUiWindowManager wwm = mWwms.get(windowType);
+            if (wwm == null) {
+                return null;
+            }
+            return wwm.mContainerWindow;
+        }
+
+        void updateConfiguration(Configuration configuration) {
+            for (int i = 0; i < mWwms.size(); ++i) {
+                mWwms.valueAt(i).updateConfiguration(configuration);
+            }
+        }
+    }
+
+    /**
+     * A subclass of WindowlessWindowManager that provides insets to its viewroots.
+     */
+    public class SysUiWindowManager extends WindowlessWindowManager {
+        final int mDisplayId;
+        ContainerWindow mContainerWindow;
+        public SysUiWindowManager(int displayId, Context ctx, SurfaceControl rootSurface,
+                ContainerWindow container) {
+            super(ctx.getResources().getConfiguration(), rootSurface, null /* hostInputToken */);
+            mContainerWindow = container;
+            mDisplayId = displayId;
+        }
+
+        @Override
+        public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
+                int requestedWidth, int requestedHeight, int viewVisibility, int flags,
+                long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
+                Rect outVisibleInsets, Rect outStableInsets,
+                DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration,
+                SurfaceControl outSurfaceControl, InsetsState outInsetsState) {
+            int res = super.relayout(window, seq, attrs, requestedWidth, requestedHeight,
+                    viewVisibility, flags, frameNumber, outFrame, outOverscanInsets,
+                    outContentInsets, outVisibleInsets, outStableInsets,
+                    cutout, mergedConfiguration, outSurfaceControl, outInsetsState);
+            if (res != 0) {
+                return res;
+            }
+            DisplayLayout dl = mDisplayController.getDisplayLayout(mDisplayId);
+            outStableInsets.set(dl.stableInsets());
+            return 0;
+        }
+
+        void updateConfiguration(Configuration configuration) {
+            setConfiguration(configuration);
+        }
+    }
+
+    class ContainerWindow extends IWindow.Stub {
+        ContainerWindow() {}
+
+        @Override
+        public void resized(Rect frame, Rect contentInsets, Rect visibleInsets, Rect stableInsets,
+                boolean reportDraw, MergedConfiguration newMergedConfiguration, Rect backDropFrame,
+                boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
+                DisplayCutout.ParcelableWrapper displayCutout) {}
+
+        @Override
+        public void locationInParentDisplayChanged(Point offset) {}
+
+        @Override
+        public void insetsChanged(InsetsState insetsState) {}
+
+        @Override
+        public void insetsControlChanged(InsetsState insetsState,
+                InsetsSourceControl[] activeControls) {}
+
+        @Override
+        public void showInsets(int types, boolean fromIme) {}
+
+        @Override
+        public void hideInsets(int types, boolean fromIme) {}
+
+        @Override
+        public void moved(int newX, int newY) {}
+
+        @Override
+        public void dispatchAppVisibility(boolean visible) {}
+
+        @Override
+        public void dispatchGetNewSurface() {}
+
+        @Override
+        public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {}
+
+        @Override
+        public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {}
+
+        @Override
+        public void closeSystemDialogs(String reason) {}
+
+        @Override
+        public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
+                boolean sync) {}
+
+        @Override
+        public void dispatchWallpaperCommand(String action, int x, int y,
+                int z, Bundle extras, boolean sync) {}
+
+        /* Drag/drop */
+        @Override
+        public void dispatchDragEvent(DragEvent event) {}
+
+        @Override
+        public void updatePointerIcon(float x, float y) {}
+
+        @Override
+        public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
+                int localValue, int localChanges) {}
+
+        @Override
+        public void dispatchWindowShown() {}
+
+        @Override
+        public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {}
+
+        @Override
+        public void dispatchPointerCaptureChanged(boolean hasCapture) {}
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index aa0e973..9e5734b 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -167,6 +167,7 @@
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
@@ -174,6 +175,7 @@
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.ISystemGestureExclusionListener;
+import android.view.IWindow;
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputWindowHandle;
@@ -563,6 +565,8 @@
     /** Corner radius that windows should have in order to match the display. */
     private final float mWindowCornerRadius;
 
+    private final SparseArray<ShellRoot> mShellRoots = new SparseArray<>();
+
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
         WindowStateAnimator winAnimator = w.mWinAnimator;
         final ActivityRecord activity = w.mActivityRecord;
@@ -1017,6 +1021,37 @@
         return token;
     }
 
+    SurfaceControl addShellRoot(@NonNull IWindow client, int windowType) {
+        ShellRoot root = mShellRoots.get(windowType);
+        if (root != null) {
+            if (root.getClient() == client) {
+                return root.getSurfaceControl();
+            }
+            root.clear();
+            mShellRoots.remove(windowType);
+        }
+        root = new ShellRoot(client, this, windowType);
+        SurfaceControl rootLeash = root.getSurfaceControl();
+        if (rootLeash == null) {
+            // Root didn't finish initializing, so don't add it.
+            root.clear();
+            return null;
+        }
+        mShellRoots.put(windowType, root);
+        SurfaceControl out = new SurfaceControl();
+        out.copyFrom(rootLeash);
+        return out;
+    }
+
+    void removeShellRoot(int windowType) {
+        ShellRoot root = mShellRoots.get(windowType);
+        if (root == null) {
+            return;
+        }
+        root.clear();
+        mShellRoots.remove(windowType);
+    }
+
     /** Changes the display the input window token is housed on to this one. */
     void reParentWindowToken(WindowToken token) {
         final DisplayContent prevDc = token.getDisplayContent();
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
new file mode 100644
index 0000000..9732637
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.IWindow;
+import android.view.SurfaceControl;
+
+/**
+ * Represents a piece of the hierarchy under which a client Shell can manage sub-windows.
+ */
+public class ShellRoot {
+    private static final String TAG = "ShellRoot";
+    private final DisplayContent mDisplayContent;
+    private IWindow mClient;
+    private WindowToken mToken;
+    private final IBinder.DeathRecipient mDeathRecipient;
+    private SurfaceControl mSurfaceControl = null;
+
+    ShellRoot(@NonNull IWindow client, @NonNull DisplayContent dc, final int windowType) {
+        mDisplayContent = dc;
+        mDeathRecipient = () -> mDisplayContent.removeShellRoot(windowType);
+        try {
+            client.asBinder().linkToDeath(mDeathRecipient, 0);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to add shell root for layer " + windowType + " on display "
+                    + dc.getDisplayId(), e);
+            return;
+        }
+        mClient = client;
+        mToken = new WindowToken(
+                dc.mWmService, client.asBinder(), windowType, true, dc, true, false);
+        mSurfaceControl = mToken.makeChildSurface(null)
+                .setContainerLayer().setName("Shell Root Leash " + dc.getDisplayId()).build();
+        mToken.getPendingTransaction().show(mSurfaceControl);
+    }
+
+    void clear() {
+        if (mClient != null) {
+            mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
+            mClient = null;
+        }
+        if (mToken != null) {
+            mToken.removeImmediately();
+            mToken = null;
+        }
+    }
+
+    SurfaceControl getSurfaceControl() {
+        return mSurfaceControl;
+    }
+
+    IWindow getClient() {
+        return mClient;
+    }
+}
+
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d1bd745..be62b68 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3712,6 +3712,26 @@
     }
 
     @Override
+    public SurfaceControl addShellRoot(int displayId, IWindow client, int windowType) {
+        if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
+        }
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                if (dc == null) {
+                    return null;
+                }
+                return dc.addShellRoot(client, windowType);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
     public int watchRotation(IRotationWatcher watcher, int displayId) {
         final DisplayContent displayContent;
         synchronized (mGlobalLock) {