• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.server.wm;
18 
19 import android.app.WindowConfiguration;
20 import android.content.res.Configuration;
21 import android.graphics.Rect;
22 import android.os.Handler;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.util.Slog;
26 import android.util.SparseArray;
27 import android.view.DisplayCutout;
28 import android.view.DisplayInfo;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 
32 import java.lang.ref.WeakReference;
33 
34 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
35 import static com.android.server.wm.WindowContainer.POSITION_TOP;
36 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
37 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
38 
39 /**
40  * Controller for the stack container. This is created by activity manager to link activity stacks
41  * to the stack container they use in window manager.
42  *
43  * Test class: {@link StackWindowControllerTests}
44  */
45 public class StackWindowController
46         extends WindowContainerController<TaskStack, StackWindowListener> {
47 
48     private final int mStackId;
49 
50     private final H mHandler;
51 
52     // Temp bounds only used in adjustConfigurationForBounds()
53     private final Rect mTmpRect = new Rect();
54     private final Rect mTmpStableInsets = new Rect();
55     private final Rect mTmpNonDecorInsets = new Rect();
56     private final Rect mTmpDisplayBounds = new Rect();
57 
StackWindowController(int stackId, StackWindowListener listener, int displayId, boolean onTop, Rect outBounds)58     public StackWindowController(int stackId, StackWindowListener listener, int displayId,
59             boolean onTop, Rect outBounds) {
60         this(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
61     }
62 
63     @VisibleForTesting
StackWindowController(int stackId, StackWindowListener listener, int displayId, boolean onTop, Rect outBounds, WindowManagerService service)64     public StackWindowController(int stackId, StackWindowListener listener,
65             int displayId, boolean onTop, Rect outBounds, WindowManagerService service) {
66         super(listener, service);
67         mStackId = stackId;
68         mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
69 
70         synchronized (mWindowMap) {
71             final DisplayContent dc = mRoot.getDisplayContent(displayId);
72             if (dc == null) {
73                 throw new IllegalArgumentException("Trying to add stackId=" + stackId
74                         + " to unknown displayId=" + displayId);
75             }
76 
77             dc.createStack(stackId, onTop, this);
78             getRawBounds(outBounds);
79         }
80     }
81 
82     @Override
removeContainer()83     public void removeContainer() {
84         synchronized (mWindowMap) {
85             if (mContainer != null) {
86                 mContainer.removeIfPossible();
87                 super.removeContainer();
88             }
89         }
90     }
91 
reparent(int displayId, Rect outStackBounds, boolean onTop)92     public void reparent(int displayId, Rect outStackBounds, boolean onTop) {
93         synchronized (mWindowMap) {
94             if (mContainer == null) {
95                 throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId
96                         + " to displayId=" + displayId);
97             }
98 
99             final DisplayContent targetDc = mRoot.getDisplayContent(displayId);
100             if (targetDc == null) {
101                 throw new IllegalArgumentException("Trying to move stackId=" + mStackId
102                         + " to unknown displayId=" + displayId);
103             }
104 
105             targetDc.moveStackToDisplay(mContainer, onTop);
106             getRawBounds(outStackBounds);
107         }
108     }
109 
positionChildAt(TaskWindowContainerController child, int position)110     public void positionChildAt(TaskWindowContainerController child, int position) {
111         synchronized (mWindowMap) {
112             if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child
113                     + " at " + position);
114             if (child.mContainer == null) {
115                 if (DEBUG_STACK) Slog.i(TAG_WM,
116                         "positionChildAt: could not find task=" + this);
117                 return;
118             }
119             if (mContainer == null) {
120                 if (DEBUG_STACK) Slog.i(TAG_WM,
121                         "positionChildAt: could not find stack for task=" + mContainer);
122                 return;
123             }
124             child.mContainer.positionAt(position);
125             mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
126         }
127     }
128 
positionChildAtTop(TaskWindowContainerController child, boolean includingParents)129     public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) {
130         if (child == null) {
131             // TODO: Fix the call-points that cause this to happen.
132             return;
133         }
134 
135         synchronized(mWindowMap) {
136             final Task childTask = child.mContainer;
137             if (childTask == null) {
138                 Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found");
139                 return;
140             }
141             mContainer.positionChildAt(POSITION_TOP, childTask, includingParents);
142 
143             if (mService.mAppTransition.isTransitionSet()) {
144                 childTask.setSendingToBottom(false);
145             }
146             mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
147         }
148     }
149 
positionChildAtBottom(TaskWindowContainerController child, boolean includingParents)150     public void positionChildAtBottom(TaskWindowContainerController child,
151             boolean includingParents) {
152         if (child == null) {
153             // TODO: Fix the call-points that cause this to happen.
154             return;
155         }
156 
157         synchronized(mWindowMap) {
158             final Task childTask = child.mContainer;
159             if (childTask == null) {
160                 Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found");
161                 return;
162             }
163             mContainer.positionChildAt(POSITION_BOTTOM, childTask, includingParents);
164 
165             if (mService.mAppTransition.isTransitionSet()) {
166                 childTask.setSendingToBottom(true);
167             }
168             mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
169         }
170     }
171 
172     /**
173      * Re-sizes a stack and its containing tasks.
174      *
175      * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen.
176      * @param taskBounds Bounds for tasks in the resized stack, keyed by task id.
177      * @param taskTempInsetBounds Inset bounds for individual tasks, keyed by task id.
178      */
resize(Rect bounds, SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds)179     public void resize(Rect bounds, SparseArray<Rect> taskBounds,
180             SparseArray<Rect> taskTempInsetBounds) {
181         synchronized (mWindowMap) {
182             if (mContainer == null) {
183                 throw new IllegalArgumentException("resizeStack: stack " + this + " not found.");
184             }
185             // We might trigger a configuration change. Save the current task bounds for freezing.
186             mContainer.prepareFreezingTaskBounds();
187             if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds)
188                     && mContainer.isVisible()) {
189                 mContainer.getDisplayContent().setLayoutNeeded();
190                 mService.mWindowPlacerLocked.performSurfacePlacement();
191             }
192         }
193     }
194 
onPipAnimationEndResize()195     public void onPipAnimationEndResize() {
196         synchronized (mService.mWindowMap) {
197             mContainer.onPipAnimationEndResize();
198         }
199     }
200 
201     /**
202      * @see TaskStack.getStackDockedModeBoundsLocked(Rect, Rect, Rect, boolean)
203      */
getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds, Rect outTempTaskBounds, boolean ignoreVisibility)204    public void getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds,
205            Rect outTempTaskBounds, boolean ignoreVisibility) {
206         synchronized (mWindowMap) {
207             if (mContainer != null) {
208                 mContainer.getStackDockedModeBoundsLocked(currentTempTaskBounds, outStackBounds,
209                         outTempTaskBounds, ignoreVisibility);
210                 return;
211             }
212             outStackBounds.setEmpty();
213             outTempTaskBounds.setEmpty();
214         }
215     }
216 
prepareFreezingTaskBounds()217     public void prepareFreezingTaskBounds() {
218         synchronized (mWindowMap) {
219             if (mContainer == null) {
220                 throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this
221                         + " not found.");
222             }
223             mContainer.prepareFreezingTaskBounds();
224         }
225     }
226 
getRawBounds(Rect outBounds)227     public void getRawBounds(Rect outBounds) {
228         synchronized (mWindowMap) {
229             if (mContainer.matchParentBounds()) {
230                 outBounds.setEmpty();
231             } else {
232                 mContainer.getRawBounds(outBounds);
233             }
234         }
235     }
236 
getBounds(Rect outBounds)237     public void getBounds(Rect outBounds) {
238         synchronized (mWindowMap) {
239             if (mContainer != null) {
240                 mContainer.getBounds(outBounds);
241                 return;
242             }
243             outBounds.setEmpty();
244         }
245     }
246 
getBoundsForNewConfiguration(Rect outBounds)247     public void getBoundsForNewConfiguration(Rect outBounds) {
248         synchronized(mWindowMap) {
249             mContainer.getBoundsForNewConfiguration(outBounds);
250         }
251     }
252 
253     /**
254      * Adjusts the screen size in dp's for the {@param config} for the given params. The provided
255      * params represent the desired state of a configuration change. Since this utility is used
256      * before mContainer has been updated, any relevant properties (like {@param windowingMode})
257      * need to be passed in.
258      */
adjustConfigurationForBounds(Rect bounds, Rect insetBounds, Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth, boolean overrideHeight, float density, Configuration config, Configuration parentConfig, int windowingMode)259     public void adjustConfigurationForBounds(Rect bounds, Rect insetBounds,
260             Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth,
261             boolean overrideHeight, float density, Configuration config,
262             Configuration parentConfig, int windowingMode) {
263         synchronized (mWindowMap) {
264             final TaskStack stack = mContainer;
265             final DisplayContent displayContent = stack.getDisplayContent();
266             final DisplayInfo di = displayContent.getDisplayInfo();
267             final DisplayCutout displayCutout = di.displayCutout;
268 
269             // Get the insets and display bounds
270             mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
271                     displayCutout, mTmpStableInsets);
272             mService.mPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
273                     displayCutout, mTmpNonDecorInsets);
274             mTmpDisplayBounds.set(0, 0, di.logicalWidth, di.logicalHeight);
275 
276             int width;
277             int height;
278 
279             final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
280 
281             config.windowConfiguration.setBounds(bounds);
282             config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null);
283             boolean intersectParentBounds = false;
284 
285             if (WindowConfiguration.isFloating(windowingMode)) {
286                 // Floating tasks should not be resized to the screen's bounds.
287 
288                 if (windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED
289                         && bounds.width() == mTmpDisplayBounds.width()
290                         && bounds.height() == mTmpDisplayBounds.height()) {
291                     // If the bounds we are animating is the same as the fullscreen stack
292                     // dimensions, then apply the same inset calculations that we normally do for
293                     // the fullscreen stack, without intersecting it with the display bounds
294                     stableBounds.inset(mTmpStableInsets);
295                     nonDecorBounds.inset(mTmpNonDecorInsets);
296                     // Move app bounds to zero to apply intersection with parent correctly. They are
297                     // used only for evaluating width and height, so it's OK to move them around.
298                     config.windowConfiguration.getAppBounds().offsetTo(0, 0);
299                     intersectParentBounds = true;
300                 }
301                 width = (int) (stableBounds.width() / density);
302                 height = (int) (stableBounds.height() / density);
303             } else {
304                 // For calculating screenWidthDp, screenWidthDp, we use the stable inset screen
305                 // area, i.e. the screen area without the system bars.
306                 // Additionally task dimensions should not be bigger than its parents dimensions.
307                 // The non decor inset are areas that could never be removed in Honeycomb. See
308                 // {@link WindowManagerPolicy#getNonDecorInsetsLw}.
309                 intersectDisplayBoundsExcludeInsets(nonDecorBounds,
310                         insetBounds != null ? insetBounds : bounds, mTmpNonDecorInsets,
311                         mTmpDisplayBounds, overrideWidth, overrideHeight);
312                 intersectDisplayBoundsExcludeInsets(stableBounds,
313                         insetBounds != null ? insetBounds : bounds, mTmpStableInsets,
314                         mTmpDisplayBounds, overrideWidth, overrideHeight);
315                 width = Math.min((int) (stableBounds.width() / density),
316                         parentConfig.screenWidthDp);
317                 height = Math.min((int) (stableBounds.height() / density),
318                         parentConfig.screenHeightDp);
319                 intersectParentBounds = true;
320             }
321 
322             if (intersectParentBounds && config.windowConfiguration.getAppBounds() != null) {
323                 config.windowConfiguration.getAppBounds().intersect(parentAppBounds);
324             }
325 
326             config.screenWidthDp = width;
327             config.screenHeightDp = height;
328             config.smallestScreenWidthDp = getSmallestWidthForTaskBounds(
329                     insetBounds != null ? insetBounds : bounds, density, windowingMode);
330         }
331     }
332 
333     /**
334      * Intersects the specified {@code inOutBounds} with the display frame that excludes the stable
335      * inset areas.
336      *
337      * @param inOutBounds The inOutBounds to subtract the stable inset areas from.
338      */
intersectDisplayBoundsExcludeInsets(Rect inOutBounds, Rect inInsetBounds, Rect stableInsets, Rect displayBounds, boolean overrideWidth, boolean overrideHeight)339     private void intersectDisplayBoundsExcludeInsets(Rect inOutBounds, Rect inInsetBounds,
340             Rect stableInsets, Rect displayBounds, boolean overrideWidth, boolean overrideHeight) {
341         mTmpRect.set(inInsetBounds);
342         mService.intersectDisplayInsetBounds(displayBounds, stableInsets, mTmpRect);
343         int leftInset = mTmpRect.left - inInsetBounds.left;
344         int topInset = mTmpRect.top - inInsetBounds.top;
345         int rightInset = overrideWidth ? 0 : inInsetBounds.right - mTmpRect.right;
346         int bottomInset = overrideHeight ? 0 : inInsetBounds.bottom - mTmpRect.bottom;
347         inOutBounds.inset(leftInset, topInset, rightInset, bottomInset);
348     }
349 
350     /**
351      * Calculates the smallest width for a task given the target {@param bounds} and
352      * {@param windowingMode}. Avoid using values from mContainer since they can be out-of-date.
353      *
354      * @return the smallest width to be used in the Configuration, in dips
355      */
getSmallestWidthForTaskBounds(Rect bounds, float density, int windowingMode)356     private int getSmallestWidthForTaskBounds(Rect bounds, float density, int windowingMode) {
357         final DisplayContent displayContent = mContainer.getDisplayContent();
358         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
359 
360         if (bounds == null || (bounds.width() == displayInfo.logicalWidth &&
361                 bounds.height() == displayInfo.logicalHeight)) {
362             // If the bounds are fullscreen, return the value of the fullscreen configuration
363             return displayContent.getConfiguration().smallestScreenWidthDp;
364         } else if (WindowConfiguration.isFloating(windowingMode)) {
365             // For floating tasks, calculate the smallest width from the bounds of the task
366             return (int) (Math.min(bounds.width(), bounds.height()) / density);
367         } else {
368             // Iterating across all screen orientations, and return the minimum of the task
369             // width taking into account that the bounds might change because the snap algorithm
370             // snaps to a different value
371             return displayContent.getDockedDividerController()
372                     .getSmallestWidthDpForBounds(bounds);
373         }
374     }
375 
requestResize(Rect bounds)376     void requestResize(Rect bounds) {
377         mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget();
378     }
379 
380     @Override
toString()381     public String toString() {
382         return "{StackWindowController stackId=" + mStackId + "}";
383     }
384 
385     private static final class H extends Handler {
386 
387         static final int REQUEST_RESIZE = 0;
388 
389         private final WeakReference<StackWindowController> mController;
390 
H(WeakReference<StackWindowController> controller, Looper looper)391         H(WeakReference<StackWindowController> controller, Looper looper) {
392             super(looper);
393             mController = controller;
394         }
395 
396         @Override
handleMessage(Message msg)397         public void handleMessage(Message msg) {
398             final StackWindowController controller = mController.get();
399             final StackWindowListener listener = (controller != null)
400                     ? controller.mListener : null;
401             if (listener == null) {
402                 return;
403             }
404             switch (msg.what) {
405                 case REQUEST_RESIZE:
406                     listener.requestResize((Rect) msg.obj);
407                     break;
408             }
409         }
410     }
411 }
412