• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.compatui;
18 
19 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
20 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
21 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
22 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
23 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
24 
25 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
26 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
27 import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED;
28 
29 import android.annotation.Nullable;
30 import android.app.TaskInfo;
31 import android.content.Context;
32 import android.content.res.Configuration;
33 import android.graphics.PixelFormat;
34 import android.graphics.Rect;
35 import android.os.Binder;
36 import android.util.Log;
37 import android.view.IWindow;
38 import android.view.SurfaceControl;
39 import android.view.SurfaceControlViewHost;
40 import android.view.SurfaceSession;
41 import android.view.View;
42 import android.view.WindowManager;
43 import android.view.WindowlessWindowManager;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.wm.shell.ShellTaskOrganizer;
47 import com.android.wm.shell.common.DisplayLayout;
48 import com.android.wm.shell.common.SyncTransactionQueue;
49 
50 /**
51  * A superclass for all Compat UI {@link WindowlessWindowManager}s that holds shared logic and
52  * exposes general API for {@link CompatUIController}.
53  *
54  * <p>Holds view hierarchy of a root surface and helps to inflate and manage layout.
55  */
56 public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
57 
58     protected final int mTaskId;
59     protected Context mContext;
60 
61     private final SyncTransactionQueue mSyncQueue;
62     private final int mDisplayId;
63     private Configuration mTaskConfig;
64     private ShellTaskOrganizer.TaskListener mTaskListener;
65     private DisplayLayout mDisplayLayout;
66     private final Rect mStableBounds;
67 
68     /**
69      * Utility class for adding and releasing a View hierarchy for this {@link
70      * WindowlessWindowManager} to {@code mLeash}.
71      */
72     @Nullable
73     protected SurfaceControlViewHost mViewHost;
74 
75     /**
76      * A surface leash to position the layout relative to the task, since we can't set position for
77      * the {@code mViewHost} directly.
78      */
79     @Nullable
80     protected SurfaceControl mLeash;
81 
CompatUIWindowManagerAbstract(Context context, TaskInfo taskInfo, SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout)82     protected CompatUIWindowManagerAbstract(Context context, TaskInfo taskInfo,
83             SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
84             DisplayLayout displayLayout) {
85         super(taskInfo.configuration, null /* rootSurface */, null /* hostInputToken */);
86         mContext = context;
87         mSyncQueue = syncQueue;
88         mTaskConfig = taskInfo.configuration;
89         mDisplayId = mContext.getDisplayId();
90         mTaskId = taskInfo.taskId;
91         mTaskListener = taskListener;
92         mDisplayLayout = displayLayout;
93         mStableBounds = new Rect();
94         mDisplayLayout.getStableBounds(mStableBounds);
95     }
96 
97     /**
98      * Returns the z-order of this window which will be passed to the {@link SurfaceControl} once
99      * {@link #attachToParentSurface} is called.
100      *
101      * <p>See {@link SurfaceControl.Transaction#setLayer}.
102      */
getZOrder()103     protected abstract int getZOrder();
104 
105     /** Returns the layout of this window manager. */
getLayout()106     protected abstract @Nullable View getLayout();
107 
108     /**
109      * Inflates and inits the layout of this window manager on to the root surface if both {@code
110      * canShow} and {@link #eligibleToShowLayout} are true.
111      *
112      * <p>Doesn't do anything if layout is not eligible to be shown.
113      *
114      * @param canShow whether the layout is allowed to be shown by the parent controller.
115      * @return whether the layout is eligible to be shown.
116      */
117     @VisibleForTesting(visibility = PROTECTED)
createLayout(boolean canShow)118     public boolean createLayout(boolean canShow) {
119         if (!eligibleToShowLayout()) {
120             return false;
121         }
122         if (!canShow || getLayout() != null) {
123             // Wait until layout should be visible, or layout was already created.
124             return true;
125         }
126 
127         if (mViewHost != null) {
128             throw new IllegalStateException(
129                     "A UI has already been created with this window manager.");
130         }
131 
132         // Construction extracted into separate methods to allow injection for tests.
133         mViewHost = createSurfaceViewHost();
134         mViewHost.setView(createLayout(), getWindowLayoutParams());
135 
136         updateSurfacePosition();
137 
138         return true;
139     }
140 
141     /** Inflates and inits the layout of this window manager. */
createLayout()142     protected abstract View createLayout();
143 
removeLayout()144     protected abstract void removeLayout();
145 
146     /**
147      * Whether the layout is eligible to be shown according to the internal state of the subclass.
148      */
eligibleToShowLayout()149     protected abstract boolean eligibleToShowLayout();
150 
151     @Override
setConfiguration(Configuration configuration)152     public void setConfiguration(Configuration configuration) {
153         super.setConfiguration(configuration);
154         mContext = mContext.createConfigurationContext(configuration);
155     }
156 
157     @Override
attachToParentSurface(IWindow window, SurfaceControl.Builder b)158     protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
159         String className = getClass().getSimpleName();
160         final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
161                 .setContainerLayer()
162                 .setName(className + "Leash")
163                 .setHidden(false)
164                 .setCallsite(className + "#attachToParentSurface");
165         attachToParentSurface(builder);
166         mLeash = builder.build();
167         b.setParent(mLeash);
168 
169         initSurface(mLeash);
170     }
171 
getTaskListener()172     protected ShellTaskOrganizer.TaskListener getTaskListener() {
173         return mTaskListener;
174     }
175 
176     /** Inits the z-order of the surface. */
initSurface(SurfaceControl leash)177     private void initSurface(SurfaceControl leash) {
178         final int z = getZOrder();
179         mSyncQueue.runInSync(t -> {
180             if (leash == null || !leash.isValid()) {
181                 Log.w(getTag(), "The leash has been released.");
182                 return;
183             }
184             t.setLayer(leash, z);
185         });
186     }
187 
188     /**
189      * Called when compat info changed.
190      *
191      * <p>The window manager is released if the layout is no longer eligible to be shown.
192      *
193      * @param canShow whether the layout is allowed to be shown by the parent controller.
194      * @return whether the layout is eligible to be shown.
195      */
196     @VisibleForTesting(visibility = PROTECTED)
updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, boolean canShow)197     public boolean updateCompatInfo(TaskInfo taskInfo,
198             ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
199         final Configuration prevTaskConfig = mTaskConfig;
200         final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
201         mTaskConfig = taskInfo.configuration;
202         mTaskListener = taskListener;
203 
204         // Update configuration.
205         setConfiguration(mTaskConfig);
206 
207         if (!eligibleToShowLayout()) {
208             release();
209             return false;
210         }
211 
212         View layout = getLayout();
213         if (layout == null || prevTaskListener != taskListener
214                 || mTaskConfig.uiMode != prevTaskConfig.uiMode) {
215             // Layout wasn't created yet or TaskListener changed, recreate the layout for new
216             // surface parent.
217             release();
218             return createLayout(canShow);
219         }
220 
221         boolean boundsUpdated = !mTaskConfig.windowConfiguration.getBounds().equals(
222                 prevTaskConfig.windowConfiguration.getBounds());
223         boolean layoutDirectionUpdated =
224                 mTaskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection();
225         if (boundsUpdated || layoutDirectionUpdated) {
226             onParentBoundsChanged();
227         }
228 
229         if (layout != null && layoutDirectionUpdated) {
230             // Update layout for RTL.
231             layout.setLayoutDirection(mTaskConfig.getLayoutDirection());
232         }
233 
234         return true;
235     }
236 
237     /**
238      * Updates the visibility of the layout.
239      *
240      * @param canShow whether the layout is allowed to be shown by the parent controller.
241      */
242     @VisibleForTesting(visibility = PACKAGE)
updateVisibility(boolean canShow)243     public void updateVisibility(boolean canShow) {
244         View layout = getLayout();
245         if (layout == null) {
246             // Layout may not have been created because it was hidden previously.
247             createLayout(canShow);
248             return;
249         }
250 
251         final int newVisibility = canShow && eligibleToShowLayout() ? View.VISIBLE : View.GONE;
252         if (layout.getVisibility() != newVisibility) {
253             layout.setVisibility(newVisibility);
254         }
255     }
256 
257     /** Called when display layout changed. */
258     @VisibleForTesting(visibility = PACKAGE)
updateDisplayLayout(DisplayLayout displayLayout)259     public void updateDisplayLayout(DisplayLayout displayLayout) {
260         final Rect prevStableBounds = mStableBounds;
261         final Rect curStableBounds = new Rect();
262         displayLayout.getStableBounds(curStableBounds);
263         mDisplayLayout = displayLayout;
264         if (!prevStableBounds.equals(curStableBounds)) {
265             // mStableBounds should be updated before we call onParentBoundsChanged.
266             mStableBounds.set(curStableBounds);
267             onParentBoundsChanged();
268         }
269     }
270 
271     /** Called when the surface is ready to be placed under the task surface. */
272     @VisibleForTesting(visibility = PRIVATE)
attachToParentSurface(SurfaceControl.Builder b)273     void attachToParentSurface(SurfaceControl.Builder b) {
274         mTaskListener.attachChildSurfaceToTask(mTaskId, b);
275     }
276 
getDisplayId()277     public int getDisplayId() {
278         return mDisplayId;
279     }
280 
getTaskId()281     public int getTaskId() {
282         return mTaskId;
283     }
284 
285     /** Releases the surface control and tears down the view hierarchy. */
release()286     public void release() {
287         // Hiding before releasing to avoid flickering when transitioning to the Home screen.
288         View layout = getLayout();
289         if (layout != null) {
290             layout.setVisibility(View.GONE);
291         }
292         removeLayout();
293 
294         if (mViewHost != null) {
295             mViewHost.release();
296             mViewHost = null;
297         }
298 
299         if (mLeash != null) {
300             final SurfaceControl leash = mLeash;
301             mSyncQueue.runInSync(t -> t.remove(leash));
302             mLeash = null;
303         }
304     }
305 
306     /** Re-layouts the view host and updates the surface position. */
relayout()307     void relayout() {
308         relayout(getWindowLayoutParams());
309     }
310 
relayout(WindowManager.LayoutParams windowLayoutParams)311     protected void relayout(WindowManager.LayoutParams windowLayoutParams) {
312         if (mViewHost == null) {
313             return;
314         }
315         mViewHost.relayout(windowLayoutParams);
316         updateSurfacePosition();
317     }
318 
319     /**
320      * Called following a change in the task bounds, display layout stable bounds, or the layout
321      * direction.
322      */
onParentBoundsChanged()323     protected void onParentBoundsChanged() {
324         updateSurfacePosition();
325     }
326 
327     /**
328      * Updates the position of the surface with respect to the parent bounds.
329      */
updateSurfacePosition()330     protected abstract void updateSurfacePosition();
331 
332     /**
333      * Updates the position of the surface with respect to the given {@code positionX} and {@code
334      * positionY}.
335      */
updateSurfacePosition(int positionX, int positionY)336     protected void updateSurfacePosition(int positionX, int positionY) {
337         if (mLeash == null) {
338             return;
339         }
340         mSyncQueue.runInSync(t -> {
341             if (mLeash == null || !mLeash.isValid()) {
342                 Log.w(getTag(), "The leash has been released.");
343                 return;
344             }
345             t.setPosition(mLeash, positionX, positionY);
346         });
347     }
348 
getLayoutDirection()349     protected int getLayoutDirection() {
350         return mContext.getResources().getConfiguration().getLayoutDirection();
351     }
352 
getTaskBounds()353     protected Rect getTaskBounds() {
354         return mTaskConfig.windowConfiguration.getBounds();
355     }
356 
357     /** Returns the intersection between the task bounds and the display layout stable bounds. */
getTaskStableBounds()358     protected Rect getTaskStableBounds() {
359         final Rect result = new Rect(mStableBounds);
360         result.intersect(getTaskBounds());
361         return result;
362     }
363 
364     /** Creates a {@link SurfaceControlViewHost} for this window manager. */
365     @VisibleForTesting(visibility = PRIVATE)
createSurfaceViewHost()366     public SurfaceControlViewHost createSurfaceViewHost() {
367         return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
368     }
369 
370     /** Gets the layout params. */
getWindowLayoutParams()371     protected WindowManager.LayoutParams getWindowLayoutParams() {
372         View layout = getLayout();
373         if (layout == null) {
374             return new WindowManager.LayoutParams();
375         }
376         // Measure how big the hint is since its size depends on the text size.
377         layout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
378         return getWindowLayoutParams(layout.getMeasuredWidth(), layout.getMeasuredHeight());
379     }
380 
381     /** Gets the layout params given the width and height of the layout. */
getWindowLayoutParams(int width, int height)382     protected WindowManager.LayoutParams getWindowLayoutParams(int width, int height) {
383         final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
384                 // Cannot be wrap_content as this determines the actual window size
385                 width, height,
386                 TYPE_APPLICATION_OVERLAY,
387                 getWindowManagerLayoutParamsFlags(),
388                 PixelFormat.TRANSLUCENT);
389         winParams.token = new Binder();
390         winParams.setTitle(getClass().getSimpleName() + mTaskId);
391         winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
392         return winParams;
393     }
394 
395     /**
396      * @return Flags to use for the {@link WindowManager} layout
397      */
getWindowManagerLayoutParamsFlags()398     protected int getWindowManagerLayoutParamsFlags() {
399         return FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL;
400     }
401 
getTag()402     protected final String getTag() {
403         return getClass().getSimpleName();
404     }
405 }
406