1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.common;
18 
19 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
20 
21 import android.annotation.NonNull;
22 import android.content.Context;
23 import android.content.res.Configuration;
24 import android.graphics.Point;
25 import android.graphics.Region;
26 import android.os.Bundle;
27 import android.os.IBinder;
28 import android.os.ParcelFileDescriptor;
29 import android.os.RemoteException;
30 import android.util.MergedConfiguration;
31 import android.util.Slog;
32 import android.util.SparseArray;
33 import android.view.Display;
34 import android.view.DragEvent;
35 import android.view.IScrollCaptureResponseListener;
36 import android.view.IWindow;
37 import android.view.IWindowManager;
38 import android.view.IWindowSession;
39 import android.view.IWindowSessionCallback;
40 import android.view.InsetsSourceControl;
41 import android.view.InsetsState;
42 import android.view.ScrollCaptureResponse;
43 import android.view.SurfaceControl;
44 import android.view.SurfaceControlViewHost;
45 import android.view.SurfaceSession;
46 import android.view.View;
47 import android.view.ViewGroup;
48 import android.view.WindowManager;
49 import android.view.WindowlessWindowManager;
50 import android.window.ClientWindowFrames;
51 
52 import com.android.internal.os.IResultReceiver;
53 
54 import java.util.HashMap;
55 
56 /**
57  * Represents the "windowing" layer of the WM Shell. This layer allows shell components to place and
58  * manipulate windows without talking to WindowManager.
59  */
60 public class SystemWindows {
61     private static final String TAG = "SystemWindows";
62 
63     private final SparseArray<PerDisplay> mPerDisplay = new SparseArray<>();
64     private final HashMap<View, SurfaceControlViewHost> mViewRoots = new HashMap<>();
65     private final DisplayController mDisplayController;
66     private final IWindowManager mWmService;
67     private IWindowSession mSession;
68 
69     private final DisplayController.OnDisplaysChangedListener mDisplayListener =
70             new DisplayController.OnDisplaysChangedListener() {
71                 @Override
72                 public void onDisplayAdded(int displayId) { }
73 
74                 @Override
75                 public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
76                     PerDisplay pd = mPerDisplay.get(displayId);
77                     if (pd == null) {
78                         return;
79                     }
80                     pd.updateConfiguration(newConfig);
81                 }
82 
83                 @Override
84                 public void onDisplayRemoved(int displayId) { }
85             };
86 
SystemWindows(DisplayController displayController, IWindowManager wmService)87     public SystemWindows(DisplayController displayController, IWindowManager wmService) {
88         mWmService = wmService;
89         mDisplayController = displayController;
90         mDisplayController.addDisplayWindowListener(mDisplayListener);
91         try {
92             mSession = wmService.openSession(
93                     new IWindowSessionCallback.Stub() {
94                         @Override
95                         public void onAnimatorScaleChanged(float scale) {}
96                     });
97         } catch (RemoteException e) {
98             Slog.e(TAG, "Unable to create layer", e);
99         }
100     }
101 
102     /**
103      * Adds a view to system-ui window management.
104      */
addView(View view, WindowManager.LayoutParams attrs, int displayId, @WindowManager.ShellRootLayer int shellRootLayer)105     public void addView(View view, WindowManager.LayoutParams attrs, int displayId,
106             @WindowManager.ShellRootLayer int shellRootLayer) {
107         PerDisplay pd = mPerDisplay.get(displayId);
108         if (pd == null) {
109             pd = new PerDisplay(displayId);
110             mPerDisplay.put(displayId, pd);
111         }
112         pd.addView(view, attrs, shellRootLayer);
113     }
114 
115     /**
116      * Removes a view from system-ui window management.
117      * @param view
118      */
removeView(View view)119     public void removeView(View view) {
120         SurfaceControlViewHost root = mViewRoots.remove(view);
121         root.release();
122     }
123 
124     /**
125      * Updates the layout params of a view.
126      */
updateViewLayout(@onNull View view, ViewGroup.LayoutParams params)127     public void updateViewLayout(@NonNull View view, ViewGroup.LayoutParams params) {
128         SurfaceControlViewHost root = mViewRoots.get(view);
129         if (root == null || !(params instanceof WindowManager.LayoutParams)) {
130             return;
131         }
132         view.setLayoutParams(params);
133         root.relayout((WindowManager.LayoutParams) params);
134     }
135 
136     /**
137      * Sets the accessibility window for the given {@param shellRootLayer}.
138      */
setShellRootAccessibilityWindow(int displayId, @WindowManager.ShellRootLayer int shellRootLayer, View view)139     public void setShellRootAccessibilityWindow(int displayId,
140             @WindowManager.ShellRootLayer int shellRootLayer, View view) {
141         PerDisplay pd = mPerDisplay.get(displayId);
142         if (pd == null) {
143             return;
144         }
145         pd.setShellRootAccessibilityWindow(shellRootLayer, view);
146     }
147 
148     /**
149      * Sets the touchable region of a view's window. This will be cropped to the window size.
150      * @param view
151      * @param region
152      */
setTouchableRegion(@onNull View view, Region region)153     public void setTouchableRegion(@NonNull View view, Region region) {
154         SurfaceControlViewHost root = mViewRoots.get(view);
155         if (root == null) {
156             return;
157         }
158         WindowlessWindowManager wwm = root.getWindowlessWM();
159         if (!(wwm instanceof SysUiWindowManager)) {
160             return;
161         }
162         ((SysUiWindowManager) wwm).setTouchableRegionForWindow(view, region);
163     }
164 
165     /**
166      * Get the IWindow token for a specific root.
167      *
168      * @param windowType A window type from {@link WindowManager}.
169      */
getWindow(int displayId, int windowType)170     IWindow getWindow(int displayId, int windowType) {
171         PerDisplay pd = mPerDisplay.get(displayId);
172         if (pd == null) {
173             return null;
174         }
175         return pd.getWindow(windowType);
176     }
177 
178     /**
179      * Gets the SurfaceControl associated with a root view. This is the same surface that backs the
180      * ViewRootImpl.
181      */
getViewSurface(View rootView)182     public SurfaceControl getViewSurface(View rootView) {
183         for (int i = 0; i < mPerDisplay.size(); ++i) {
184             for (int iWm = 0; iWm < mPerDisplay.valueAt(i).mWwms.size(); ++iWm) {
185                 SurfaceControl out = mPerDisplay.valueAt(i).mWwms.valueAt(iWm)
186                         .getSurfaceControlForWindow(rootView);
187                 if (out != null) {
188                     return out;
189                 }
190             }
191         }
192         return null;
193     }
194 
195     private class PerDisplay {
196         final int mDisplayId;
197         private final SparseArray<SysUiWindowManager> mWwms = new SparseArray<>();
198 
PerDisplay(int displayId)199         PerDisplay(int displayId) {
200             mDisplayId = displayId;
201         }
202 
addView(View view, WindowManager.LayoutParams attrs, @WindowManager.ShellRootLayer int shellRootLayer)203         public void addView(View view, WindowManager.LayoutParams attrs,
204                 @WindowManager.ShellRootLayer int shellRootLayer) {
205             SysUiWindowManager wwm = addRoot(shellRootLayer);
206             if (wwm == null) {
207                 Slog.e(TAG, "Unable to create systemui root");
208                 return;
209             }
210             final Display display = mDisplayController.getDisplay(mDisplayId);
211             SurfaceControlViewHost viewRoot =
212                     new SurfaceControlViewHost(
213                             view.getContext(), display, wwm, true /* useSfChoreographer */);
214             attrs.flags |= FLAG_HARDWARE_ACCELERATED;
215             viewRoot.setView(view, attrs);
216             mViewRoots.put(view, viewRoot);
217             setShellRootAccessibilityWindow(shellRootLayer, view);
218         }
219 
addRoot(@indowManager.ShellRootLayer int shellRootLayer)220         SysUiWindowManager addRoot(@WindowManager.ShellRootLayer int shellRootLayer) {
221             SysUiWindowManager wwm = mWwms.get(shellRootLayer);
222             if (wwm != null) {
223                 return wwm;
224             }
225             SurfaceControl rootSurface = null;
226             ContainerWindow win = new ContainerWindow();
227             try {
228                 rootSurface = mWmService.addShellRoot(mDisplayId, win, shellRootLayer);
229             } catch (RemoteException e) {
230             }
231             if (rootSurface == null) {
232                 Slog.e(TAG, "Unable to get root surfacecontrol for systemui");
233                 return null;
234             }
235             Context displayContext = mDisplayController.getDisplayContext(mDisplayId);
236             wwm = new SysUiWindowManager(mDisplayId, displayContext, rootSurface, win);
237             mWwms.put(shellRootLayer, wwm);
238             return wwm;
239         }
240 
getWindow(int windowType)241         IWindow getWindow(int windowType) {
242             SysUiWindowManager wwm = mWwms.get(windowType);
243             if (wwm == null) {
244                 return null;
245             }
246             return wwm.mContainerWindow;
247         }
248 
setShellRootAccessibilityWindow(@indowManager.ShellRootLayer int shellRootLayer, View view)249         void setShellRootAccessibilityWindow(@WindowManager.ShellRootLayer int shellRootLayer,
250                 View view) {
251             SysUiWindowManager wwm = mWwms.get(shellRootLayer);
252             if (wwm == null) {
253                 return;
254             }
255             try {
256                 mWmService.setShellRootAccessibilityWindow(mDisplayId, shellRootLayer,
257                         view != null ? mViewRoots.get(view).getWindowToken() : null);
258             } catch (RemoteException e) {
259                 Slog.e(TAG, "Error setting accessibility window for " + mDisplayId + ":"
260                         + shellRootLayer, e);
261             }
262         }
263 
updateConfiguration(Configuration configuration)264         void updateConfiguration(Configuration configuration) {
265             for (int i = 0; i < mWwms.size(); ++i) {
266                 mWwms.valueAt(i).updateConfiguration(configuration);
267             }
268         }
269     }
270 
271     /**
272      * A subclass of WindowlessWindowManager that provides insets to its viewroots.
273      */
274     public class SysUiWindowManager extends WindowlessWindowManager {
275         final int mDisplayId;
276         ContainerWindow mContainerWindow;
277         final HashMap<IBinder, SurfaceControl> mLeashForWindow =
278                 new HashMap<IBinder, SurfaceControl>();
SysUiWindowManager(int displayId, Context ctx, SurfaceControl rootSurface, ContainerWindow container)279         public SysUiWindowManager(int displayId, Context ctx, SurfaceControl rootSurface,
280                 ContainerWindow container) {
281             super(ctx.getResources().getConfiguration(), rootSurface, null /* hostInputToken */);
282             mContainerWindow = container;
283             mDisplayId = displayId;
284         }
285 
updateConfiguration(Configuration configuration)286         void updateConfiguration(Configuration configuration) {
287             setConfiguration(configuration);
288         }
289 
getSurfaceControlForWindow(View rootView)290         SurfaceControl getSurfaceControlForWindow(View rootView) {
291             synchronized (this) {
292                 return mLeashForWindow.get(getWindowBinder(rootView));
293             }
294         }
295 
attachToParentSurface(IWindow window, SurfaceControl.Builder b)296         protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
297             SurfaceControl leash = new SurfaceControl.Builder(new SurfaceSession())
298                   .setContainerLayer()
299                   .setName("SystemWindowLeash")
300                   .setHidden(false)
301                   .setParent(mRootSurface)
302                   .setCallsite("SysUiWIndowManager#attachToParentSurface").build();
303             synchronized (this) {
304                 mLeashForWindow.put(window.asBinder(), leash);
305             }
306             b.setParent(leash);
307         }
308 
309         @Override
remove(android.view.IWindow window)310         public void remove(android.view.IWindow window) throws RemoteException {
311             super.remove(window);
312             synchronized(this) {
313                 IBinder token = window.asBinder();
314                 new SurfaceControl.Transaction().remove(mLeashForWindow.get(token))
315                     .apply();
316                 mLeashForWindow.remove(token);
317             }
318         }
319 
setTouchableRegionForWindow(View rootView, Region region)320         void setTouchableRegionForWindow(View rootView, Region region) {
321             IBinder token = rootView.getWindowToken();
322             if (token == null) {
323                 return;
324             }
325             setTouchRegion(token, region);
326         }
327     }
328 
329     static class ContainerWindow extends IWindow.Stub {
ContainerWindow()330         ContainerWindow() {}
331 
332         @Override
resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration newMergedConfiguration, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId)333         public void resized(ClientWindowFrames frames, boolean reportDraw,
334                 MergedConfiguration newMergedConfiguration, boolean forceLayout,
335                 boolean alwaysConsumeSystemBars, int displayId) {}
336 
337         @Override
locationInParentDisplayChanged(Point offset)338         public void locationInParentDisplayChanged(Point offset) {}
339 
340         @Override
insetsChanged(InsetsState insetsState, boolean willMove, boolean willResize)341         public void insetsChanged(InsetsState insetsState, boolean willMove, boolean willResize) {}
342 
343         @Override
insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls, boolean willMove, boolean willResize)344         public void insetsControlChanged(InsetsState insetsState,
345                 InsetsSourceControl[] activeControls, boolean willMove, boolean willResize) {}
346 
347         @Override
showInsets(int types, boolean fromIme)348         public void showInsets(int types, boolean fromIme) {}
349 
350         @Override
hideInsets(int types, boolean fromIme)351         public void hideInsets(int types, boolean fromIme) {}
352 
353         @Override
moved(int newX, int newY)354         public void moved(int newX, int newY) {}
355 
356         @Override
dispatchAppVisibility(boolean visible)357         public void dispatchAppVisibility(boolean visible) {}
358 
359         @Override
dispatchGetNewSurface()360         public void dispatchGetNewSurface() {}
361 
362         @Override
windowFocusChanged(boolean hasFocus, boolean inTouchMode)363         public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {}
364 
365         @Override
executeCommand(String command, String parameters, ParcelFileDescriptor out)366         public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {}
367 
368         @Override
closeSystemDialogs(String reason)369         public void closeSystemDialogs(String reason) {}
370 
371         @Override
dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, float zoom, boolean sync)372         public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
373                 float zoom, boolean sync) {}
374 
375         @Override
dispatchWallpaperCommand(String action, int x, int y, int z, Bundle extras, boolean sync)376         public void dispatchWallpaperCommand(String action, int x, int y,
377                 int z, Bundle extras, boolean sync) {}
378 
379         /* Drag/drop */
380         @Override
dispatchDragEvent(DragEvent event)381         public void dispatchDragEvent(DragEvent event) {}
382 
383         @Override
updatePointerIcon(float x, float y)384         public void updatePointerIcon(float x, float y) {}
385 
386         @Override
dispatchWindowShown()387         public void dispatchWindowShown() {}
388 
389         @Override
requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId)390         public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {}
391 
392         @Override
requestScrollCapture(IScrollCaptureResponseListener listener)393         public void requestScrollCapture(IScrollCaptureResponseListener listener) {
394             try {
395                 listener.onScrollCaptureResponse(
396                         new ScrollCaptureResponse.Builder()
397                                 .setDescription("Not Implemented")
398                                 .build());
399 
400             } catch (RemoteException ex) {
401                 // ignore
402             }
403         }
404     }
405 }
406