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