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