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 static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
20 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
21 
22 import android.app.ActivityManager.StackId;
23 import android.app.RemoteAction;
24 import android.content.res.Configuration;
25 import android.graphics.Rect;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.os.Message;
29 import android.util.Slog;
30 import android.util.SparseArray;
31 import android.view.DisplayInfo;
32 
33 import com.android.server.UiThread;
34 import com.android.internal.annotations.VisibleForTesting;
35 
36 import java.lang.ref.WeakReference;
37 import java.util.List;
38 
39 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
40 import static com.android.server.wm.WindowContainer.POSITION_TOP;
41 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
42 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
43 
44 /**
45  * Controller for the stack container. This is created by activity manager to link activity stacks
46  * to the stack container they use in window manager.
47  *
48  * Test class: {@link StackWindowControllerTests}
49  */
50 public class StackWindowController
51         extends WindowContainerController<TaskStack, StackWindowListener> {
52 
53     final int mStackId;
54 
55     private final H mHandler;
56 
57     // Temp bounds only used in adjustConfigurationForBounds()
58     private final Rect mTmpRect = new Rect();
59     private final Rect mTmpStableInsets = new Rect();
60     private final Rect mTmpNonDecorInsets = new Rect();
61     private final Rect mTmpDisplayBounds = new Rect();
62 
StackWindowController(int stackId, StackWindowListener listener, int displayId, boolean onTop, Rect outBounds)63     public StackWindowController(int stackId, StackWindowListener listener,
64             int displayId, boolean onTop, Rect outBounds) {
65         this(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
66     }
67 
68     @VisibleForTesting
StackWindowController(int stackId, StackWindowListener listener, int displayId, boolean onTop, Rect outBounds, WindowManagerService service)69     public StackWindowController(int stackId, StackWindowListener listener,
70             int displayId, boolean onTop, Rect outBounds, WindowManagerService service) {
71         super(listener, service);
72         mStackId = stackId;
73         mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
74 
75         synchronized (mWindowMap) {
76             final DisplayContent dc = mRoot.getDisplayContent(displayId);
77             if (dc == null) {
78                 throw new IllegalArgumentException("Trying to add stackId=" + stackId
79                         + " to unknown displayId=" + displayId);
80             }
81 
82             final TaskStack stack = dc.addStackToDisplay(stackId, onTop);
83             stack.setController(this);
84             getRawBounds(outBounds);
85         }
86     }
87 
88     @Override
removeContainer()89     public void removeContainer() {
90         synchronized (mWindowMap) {
91             if (mContainer != null) {
92                 mContainer.removeIfPossible();
93                 super.removeContainer();
94             }
95         }
96     }
97 
isVisible()98     public boolean isVisible() {
99         synchronized (mWindowMap) {
100             return mContainer != null && mContainer.isVisible();
101         }
102     }
103 
reparent(int displayId, Rect outStackBounds, boolean onTop)104     public void reparent(int displayId, Rect outStackBounds, boolean onTop) {
105         synchronized (mWindowMap) {
106             if (mContainer == null) {
107                 throw new IllegalArgumentException("Trying to move unknown stackId=" + mStackId
108                         + " to displayId=" + displayId);
109             }
110 
111             final DisplayContent targetDc = mRoot.getDisplayContent(displayId);
112             if (targetDc == null) {
113                 throw new IllegalArgumentException("Trying to move stackId=" + mStackId
114                         + " to unknown displayId=" + displayId);
115             }
116 
117             targetDc.moveStackToDisplay(mContainer, onTop);
118             getRawBounds(outStackBounds);
119         }
120     }
121 
positionChildAt(TaskWindowContainerController child, int position, Rect bounds, Configuration overrideConfig)122     public void positionChildAt(TaskWindowContainerController child, int position, Rect bounds,
123             Configuration overrideConfig) {
124         synchronized (mWindowMap) {
125             if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child
126                     + " at " + position);
127             if (child.mContainer == null) {
128                 if (DEBUG_STACK) Slog.i(TAG_WM,
129                         "positionChildAt: could not find task=" + this);
130                 return;
131             }
132             if (mContainer == null) {
133                 if (DEBUG_STACK) Slog.i(TAG_WM,
134                         "positionChildAt: could not find stack for task=" + mContainer);
135                 return;
136             }
137             child.mContainer.positionAt(position, bounds, overrideConfig);
138             mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
139         }
140     }
141 
positionChildAtTop(TaskWindowContainerController child, boolean includingParents)142     public void positionChildAtTop(TaskWindowContainerController child, boolean includingParents) {
143         if (child == null) {
144             // TODO: Fix the call-points that cause this to happen.
145             return;
146         }
147 
148         synchronized(mWindowMap) {
149             final Task childTask = child.mContainer;
150             if (childTask == null) {
151                 Slog.e(TAG_WM, "positionChildAtTop: task=" + child + " not found");
152                 return;
153             }
154             mContainer.positionChildAt(POSITION_TOP, childTask, includingParents);
155 
156             if (mService.mAppTransition.isTransitionSet()) {
157                 childTask.setSendingToBottom(false);
158             }
159             mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
160         }
161     }
162 
positionChildAtBottom(TaskWindowContainerController child)163     public void positionChildAtBottom(TaskWindowContainerController child) {
164         if (child == null) {
165             // TODO: Fix the call-points that cause this to happen.
166             return;
167         }
168 
169         synchronized(mWindowMap) {
170             final Task childTask = child.mContainer;
171             if (childTask == null) {
172                 Slog.e(TAG_WM, "positionChildAtBottom: task=" + child + " not found");
173                 return;
174             }
175             mContainer.positionChildAt(POSITION_BOTTOM, childTask, false /* includingParents */);
176 
177             if (mService.mAppTransition.isTransitionSet()) {
178                 childTask.setSendingToBottom(true);
179             }
180             mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
181         }
182     }
183 
184     /**
185      * Re-sizes a stack and its containing tasks.
186      *
187      * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen.
188      * @param configs Configurations for tasks in the resized stack, keyed by task id.
189      * @param taskBounds Bounds for tasks in the resized stack, keyed by task id.
190      * @return True if the stack is now fullscreen.
191      */
resize(Rect bounds, SparseArray<Configuration> configs, SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds)192     public boolean resize(Rect bounds, SparseArray<Configuration> configs,
193             SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) {
194         synchronized (mWindowMap) {
195             if (mContainer == null) {
196                 throw new IllegalArgumentException("resizeStack: stack " + this + " not found.");
197             }
198             // We might trigger a configuration change. Save the current task bounds for freezing.
199             mContainer.prepareFreezingTaskBounds();
200             if (mContainer.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
201                     && mContainer.isVisible()) {
202                 mContainer.getDisplayContent().setLayoutNeeded();
203                 mService.mWindowPlacerLocked.performSurfacePlacement();
204             }
205             return mContainer.getRawFullscreen();
206         }
207     }
208 
209     /**
210      * @see TaskStack.getStackDockedModeBoundsLocked(Rect, Rect, Rect, boolean)
211      */
getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds, Rect outTempTaskBounds, boolean ignoreVisibility)212    public void getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds,
213            Rect outTempTaskBounds, boolean ignoreVisibility) {
214         synchronized (mWindowMap) {
215             if (mContainer != null) {
216                 mContainer.getStackDockedModeBoundsLocked(currentTempTaskBounds, outStackBounds,
217                         outTempTaskBounds, ignoreVisibility);
218                 return;
219             }
220             outStackBounds.setEmpty();
221             outTempTaskBounds.setEmpty();
222         }
223     }
224 
prepareFreezingTaskBounds()225     public void prepareFreezingTaskBounds() {
226         synchronized (mWindowMap) {
227             if (mContainer == null) {
228                 throw new IllegalArgumentException("prepareFreezingTaskBounds: stack " + this
229                         + " not found.");
230             }
231             mContainer.prepareFreezingTaskBounds();
232         }
233     }
234 
getRawBounds(Rect outBounds)235     private void getRawBounds(Rect outBounds) {
236         if (mContainer.getRawFullscreen()) {
237             outBounds.setEmpty();
238         } else {
239             mContainer.getRawBounds(outBounds);
240         }
241     }
242 
getBounds(Rect outBounds)243     public void getBounds(Rect outBounds) {
244         synchronized (mWindowMap) {
245             if (mContainer != null) {
246                 mContainer.getBounds(outBounds);
247                 return;
248             }
249             outBounds.setEmpty();
250         }
251     }
252 
getBoundsForNewConfiguration(Rect outBounds)253     public void getBoundsForNewConfiguration(Rect outBounds) {
254         synchronized(mWindowMap) {
255             mContainer.getBoundsForNewConfiguration(outBounds);
256         }
257     }
258 
259     /**
260      * Adjusts the screen size in dp's for the {@param config} for the given params.
261      */
adjustConfigurationForBounds(Rect bounds, Rect insetBounds, Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth, boolean overrideHeight, float density, Configuration config, Configuration parentConfig)262     public void adjustConfigurationForBounds(Rect bounds, Rect insetBounds,
263             Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth,
264             boolean overrideHeight, float density, Configuration config,
265             Configuration parentConfig) {
266         synchronized (mWindowMap) {
267             final TaskStack stack = mContainer;
268             final DisplayContent displayContent = stack.getDisplayContent();
269             final DisplayInfo di = displayContent.getDisplayInfo();
270 
271             // Get the insets and display bounds
272             mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
273                     mTmpStableInsets);
274             mService.mPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
275                     mTmpNonDecorInsets);
276             mTmpDisplayBounds.set(0, 0, di.logicalWidth, di.logicalHeight);
277 
278             int width;
279             int height;
280 
281             final Rect parentAppBounds = parentConfig.appBounds;
282 
283             config.setAppBounds(!bounds.isEmpty() ? bounds : null);
284             boolean intersectParentBounds = false;
285 
286             if (StackId.tasksAreFloating(mStackId)) {
287                 // Floating tasks should not be resized to the screen's bounds.
288 
289                 if (mStackId == PINNED_STACK_ID && 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.appBounds.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.appBounds != null) {
323                 config.appBounds.intersect(parentAppBounds);
324             }
325 
326             config.screenWidthDp = width;
327             config.screenHeightDp = height;
328             config.smallestScreenWidthDp = getSmallestWidthForTaskBounds(
329                     insetBounds != null ? insetBounds : bounds, density);
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 {@param bounds}.
352      *
353      * @return the smallest width to be used in the Configuration, in dips
354      */
getSmallestWidthForTaskBounds(Rect bounds, float density)355     private int getSmallestWidthForTaskBounds(Rect bounds, float density) {
356         final DisplayContent displayContent = mContainer.getDisplayContent();
357         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
358 
359         if (bounds == null || (bounds.width() == displayInfo.logicalWidth &&
360                 bounds.height() == displayInfo.logicalHeight)) {
361             // If the bounds are fullscreen, return the value of the fullscreen configuration
362             return displayContent.getConfiguration().smallestScreenWidthDp;
363         } else if (StackId.tasksAreFloating(mStackId)) {
364             // For floating tasks, calculate the smallest width from the bounds of the task
365             return (int) (Math.min(bounds.width(), bounds.height()) / density);
366         } else {
367             // Iterating across all screen orientations, and return the minimum of the task
368             // width taking into account that the bounds might change because the snap algorithm
369             // snaps to a different value
370             return displayContent.getDockedDividerController()
371                     .getSmallestWidthDpForBounds(bounds);
372         }
373     }
374 
requestResize(Rect bounds)375     void requestResize(Rect bounds) {
376         mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget();
377     }
378 
379     @Override
toString()380     public String toString() {
381         return "{StackWindowController stackId=" + mStackId + "}";
382     }
383 
384     private static final class H extends Handler {
385 
386         static final int REQUEST_RESIZE = 0;
387 
388         private final WeakReference<StackWindowController> mController;
389 
H(WeakReference<StackWindowController> controller, Looper looper)390         H(WeakReference<StackWindowController> controller, Looper looper) {
391             super(looper);
392             mController = controller;
393         }
394 
395         @Override
handleMessage(Message msg)396         public void handleMessage(Message msg) {
397             final StackWindowController controller = mController.get();
398             final StackWindowListener listener = (controller != null)
399                     ? controller.mListener : null;
400             if (listener == null) {
401                 return;
402             }
403             switch (msg.what) {
404                 case REQUEST_RESIZE:
405                     listener.requestResize((Rect) msg.obj);
406                     break;
407             }
408         }
409     }
410 }
411