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 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 
     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
     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
     89     public void removeContainer() {
     90         synchronized (mWindowMap) {
     91             if (mContainer != null) {
     92                 mContainer.removeIfPossible();
     93                 super.removeContainer();
     94             }
     95         }
     96     }
     97 
     98     public boolean isVisible() {
     99         synchronized (mWindowMap) {
    100             return mContainer != null && mContainer.isVisible();
    101         }
    102     }
    103 
    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 
    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 
    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 
    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      */
    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      */
    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 
    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 
    235     private void getRawBounds(Rect outBounds) {
    236         if (mContainer.getRawFullscreen()) {
    237             outBounds.setEmpty();
    238         } else {
    239             mContainer.getRawBounds(outBounds);
    240         }
    241     }
    242 
    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 
    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      */
    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      */
    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      */
    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 
    375     void requestResize(Rect bounds) {
    376         mHandler.obtainMessage(H.REQUEST_RESIZE, bounds).sendToTarget();
    377     }
    378 
    379     @Override
    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 
    390         H(WeakReference<StackWindowController> controller, Looper looper) {
    391             super(looper);
    392             mController = controller;
    393         }
    394 
    395         @Override
    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