Home | History | Annotate | Download | only in wm
      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 
     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
     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
     83     public void removeContainer() {
     84         synchronized (mWindowMap) {
     85             if (mContainer != null) {
     86                 mContainer.removeIfPossible();
     87                 super.removeContainer();
     88             }
     89         }
     90     }
     91 
     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 
    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 
    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 
    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      */
    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 
    195     public void onPipAnimationEndResize() {
    196         synchronized (mService.mWindowMap) {
    197             mContainer.onPipAnimationEndResize();
    198         }
    199     }
    200 
    201     /**
    202      * @see TaskStack.getStackDockedModeBoundsLocked(Rect, Rect, Rect, boolean)
    203      */
    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 
    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 
    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 
    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 
    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      */
    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      */
    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      */
    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 
    376     void requestResize(Rect bounds) {
    377         mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget();
    378     }
    379 
    380     @Override
    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 
    391         H(WeakReference<StackWindowController> controller, Looper looper) {
    392             super(looper);
    393             mController = controller;
    394         }
    395 
    396         @Override
    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