Home | History | Annotate | Download | only in wm
      1 /*
      2  * Copyright (C) 2015 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.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
     20 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
     21 import static android.app.ActivityManager.RESIZE_MODE_USER;
     22 import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
     23 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
     24 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
     25 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
     26 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
     27 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
     28 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
     29 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
     30 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
     31 import static com.android.server.wm.WindowManagerService.dipToPixel;
     32 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
     33 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
     34 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
     35 
     36 import android.annotation.IntDef;
     37 import android.graphics.Point;
     38 import android.graphics.Rect;
     39 import android.os.Looper;
     40 import android.os.Process;
     41 import android.os.RemoteException;
     42 import android.os.Trace;
     43 import android.util.DisplayMetrics;
     44 import android.util.Slog;
     45 import android.view.BatchedInputEventReceiver;
     46 import android.view.Choreographer;
     47 import android.view.Display;
     48 import android.view.DisplayInfo;
     49 import android.view.InputChannel;
     50 import android.view.InputDevice;
     51 import android.view.InputEvent;
     52 import android.view.MotionEvent;
     53 import android.view.WindowManager;
     54 
     55 import com.android.internal.annotations.VisibleForTesting;
     56 import com.android.server.input.InputApplicationHandle;
     57 import com.android.server.input.InputWindowHandle;
     58 import com.android.server.wm.WindowManagerService.H;
     59 
     60 import java.lang.annotation.Retention;
     61 import java.lang.annotation.RetentionPolicy;
     62 
     63 class TaskPositioner implements DimLayer.DimLayerUser {
     64     private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
     65     private static final String TAG_LOCAL = "TaskPositioner";
     66     private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
     67 
     68     // The margin the pointer position has to be within the side of the screen to be
     69     // considered at the side of the screen.
     70     static final int SIDE_MARGIN_DIP = 100;
     71 
     72     @IntDef(flag = true,
     73             value = {
     74                     CTRL_NONE,
     75                     CTRL_LEFT,
     76                     CTRL_RIGHT,
     77                     CTRL_TOP,
     78                     CTRL_BOTTOM
     79             })
     80     @Retention(RetentionPolicy.SOURCE)
     81     @interface CtrlType {}
     82 
     83     private static final int CTRL_NONE   = 0x0;
     84     private static final int CTRL_LEFT   = 0x1;
     85     private static final int CTRL_RIGHT  = 0x2;
     86     private static final int CTRL_TOP    = 0x4;
     87     private static final int CTRL_BOTTOM = 0x8;
     88 
     89     public static final float RESIZING_HINT_ALPHA = 0.5f;
     90 
     91     public static final int RESIZING_HINT_DURATION_MS = 0;
     92 
     93     // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait).
     94     // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever
     95     // aspect he desires.
     96     @VisibleForTesting
     97     static final float MIN_ASPECT = 1.2f;
     98 
     99     private final WindowManagerService mService;
    100     private WindowPositionerEventReceiver mInputEventReceiver;
    101     private Display mDisplay;
    102     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
    103     private DimLayer mDimLayer;
    104     @CtrlType
    105     private int mCurrentDimSide;
    106     private Rect mTmpRect = new Rect();
    107     private int mSideMargin;
    108     private int mMinVisibleWidth;
    109     private int mMinVisibleHeight;
    110 
    111     private Task mTask;
    112     private boolean mResizing;
    113     private boolean mPreserveOrientation;
    114     private boolean mStartOrientationWasLandscape;
    115     private final Rect mWindowOriginalBounds = new Rect();
    116     private final Rect mWindowDragBounds = new Rect();
    117     private final Point mMaxVisibleSize = new Point();
    118     private float mStartDragX;
    119     private float mStartDragY;
    120     @CtrlType
    121     private int mCtrlType = CTRL_NONE;
    122     private boolean mDragEnded = false;
    123 
    124     InputChannel mServerChannel;
    125     InputChannel mClientChannel;
    126     InputApplicationHandle mDragApplicationHandle;
    127     InputWindowHandle mDragWindowHandle;
    128 
    129     private final class WindowPositionerEventReceiver extends BatchedInputEventReceiver {
    130         public WindowPositionerEventReceiver(
    131                 InputChannel inputChannel, Looper looper, Choreographer choreographer) {
    132             super(inputChannel, looper, choreographer);
    133         }
    134 
    135         @Override
    136         public void onInputEvent(InputEvent event, int displayId) {
    137             if (!(event instanceof MotionEvent)
    138                     || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
    139                 return;
    140             }
    141             final MotionEvent motionEvent = (MotionEvent) event;
    142             boolean handled = false;
    143 
    144             try {
    145                 if (mDragEnded) {
    146                     // The drag has ended but the clean-up message has not been processed by
    147                     // window manager. Drop events that occur after this until window manager
    148                     // has a chance to clean-up the input handle.
    149                     handled = true;
    150                     return;
    151                 }
    152 
    153                 final float newX = motionEvent.getRawX();
    154                 final float newY = motionEvent.getRawY();
    155 
    156                 switch (motionEvent.getAction()) {
    157                     case MotionEvent.ACTION_DOWN: {
    158                         if (DEBUG_TASK_POSITIONING) {
    159                             Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}");
    160                         }
    161                     } break;
    162 
    163                     case MotionEvent.ACTION_MOVE: {
    164                         if (DEBUG_TASK_POSITIONING){
    165                             Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}");
    166                         }
    167                         synchronized (mService.mWindowMap) {
    168                             mDragEnded = notifyMoveLocked(newX, newY);
    169                             mTask.getDimBounds(mTmpRect);
    170                         }
    171                         if (!mTmpRect.equals(mWindowDragBounds)) {
    172                             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
    173                                     "wm.TaskPositioner.resizeTask");
    174                             try {
    175                                 mService.mActivityManager.resizeTask(
    176                                         mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER);
    177                             } catch (RemoteException e) {
    178                             }
    179                             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
    180                         }
    181                     } break;
    182 
    183                     case MotionEvent.ACTION_UP: {
    184                         if (DEBUG_TASK_POSITIONING) {
    185                             Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}");
    186                         }
    187                         mDragEnded = true;
    188                     } break;
    189 
    190                     case MotionEvent.ACTION_CANCEL: {
    191                         if (DEBUG_TASK_POSITIONING) {
    192                             Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}");
    193                         }
    194                         mDragEnded = true;
    195                     } break;
    196                 }
    197 
    198                 if (mDragEnded) {
    199                     final boolean wasResizing = mResizing;
    200                     synchronized (mService.mWindowMap) {
    201                         endDragLocked();
    202                         mTask.getDimBounds(mTmpRect);
    203                     }
    204                     try {
    205                         if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) {
    206                             // We were using fullscreen surface during resizing. Request
    207                             // resizeTask() one last time to restore surface to window size.
    208                             mService.mActivityManager.resizeTask(
    209                                     mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
    210                         }
    211 
    212                         if (mCurrentDimSide != CTRL_NONE) {
    213                             final int createMode = mCurrentDimSide == CTRL_LEFT
    214                                     ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
    215                                     : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
    216                             mService.mActivityManager.moveTaskToDockedStack(
    217                                     mTask.mTaskId, createMode, true /*toTop*/, true /* animate */,
    218                                     null /* initialBounds */);
    219                         }
    220                     } catch(RemoteException e) {}
    221 
    222                     // Post back to WM to handle clean-ups. We still need the input
    223                     // event handler for the last finishInputEvent()!
    224                     mService.mH.sendEmptyMessage(H.FINISH_TASK_POSITIONING);
    225                 }
    226                 handled = true;
    227             } catch (Exception e) {
    228                 Slog.e(TAG, "Exception caught by drag handleMotion", e);
    229             } finally {
    230                 finishInputEvent(event, handled);
    231             }
    232         }
    233     }
    234 
    235     TaskPositioner(WindowManagerService service) {
    236         mService = service;
    237     }
    238 
    239     @VisibleForTesting
    240     Rect getWindowDragBounds() {
    241         return mWindowDragBounds;
    242     }
    243 
    244     /**
    245      * @param display The Display that the window being dragged is on.
    246      */
    247     void register(Display display) {
    248         if (DEBUG_TASK_POSITIONING) {
    249             Slog.d(TAG, "Registering task positioner");
    250         }
    251 
    252         if (mClientChannel != null) {
    253             Slog.e(TAG, "Task positioner already registered");
    254             return;
    255         }
    256 
    257         mDisplay = display;
    258         mDisplay.getMetrics(mDisplayMetrics);
    259         final InputChannel[] channels = InputChannel.openInputChannelPair(TAG);
    260         mServerChannel = channels[0];
    261         mClientChannel = channels[1];
    262         mService.mInputManager.registerInputChannel(mServerChannel, null);
    263 
    264         mInputEventReceiver = new WindowPositionerEventReceiver(
    265                 mClientChannel, mService.mAnimationHandler.getLooper(),
    266                 mService.mAnimator.getChoreographer());
    267 
    268         mDragApplicationHandle = new InputApplicationHandle(null);
    269         mDragApplicationHandle.name = TAG;
    270         mDragApplicationHandle.dispatchingTimeoutNanos =
    271                 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
    272 
    273         mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null,
    274                 mDisplay.getDisplayId());
    275         mDragWindowHandle.name = TAG;
    276         mDragWindowHandle.inputChannel = mServerChannel;
    277         mDragWindowHandle.layer = mService.getDragLayerLocked();
    278         mDragWindowHandle.layoutParamsFlags = 0;
    279         mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
    280         mDragWindowHandle.dispatchingTimeoutNanos =
    281                 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
    282         mDragWindowHandle.visible = true;
    283         mDragWindowHandle.canReceiveKeys = false;
    284         mDragWindowHandle.hasFocus = true;
    285         mDragWindowHandle.hasWallpaper = false;
    286         mDragWindowHandle.paused = false;
    287         mDragWindowHandle.ownerPid = Process.myPid();
    288         mDragWindowHandle.ownerUid = Process.myUid();
    289         mDragWindowHandle.inputFeatures = 0;
    290         mDragWindowHandle.scaleFactor = 1.0f;
    291 
    292         // The drag window cannot receive new touches.
    293         mDragWindowHandle.touchableRegion.setEmpty();
    294 
    295         // The drag window covers the entire display
    296         mDragWindowHandle.frameLeft = 0;
    297         mDragWindowHandle.frameTop = 0;
    298         final Point p = new Point();
    299         mDisplay.getRealSize(p);
    300         mDragWindowHandle.frameRight = p.x;
    301         mDragWindowHandle.frameBottom = p.y;
    302 
    303         // Pause rotations before a drag.
    304         if (DEBUG_ORIENTATION) {
    305             Slog.d(TAG, "Pausing rotation during re-position");
    306         }
    307         mService.pauseRotationLocked();
    308 
    309         mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId(), TAG_LOCAL);
    310         mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
    311         mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
    312         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
    313         mDisplay.getRealSize(mMaxVisibleSize);
    314 
    315         mDragEnded = false;
    316     }
    317 
    318     void unregister() {
    319         if (DEBUG_TASK_POSITIONING) {
    320             Slog.d(TAG, "Unregistering task positioner");
    321         }
    322 
    323         if (mClientChannel == null) {
    324             Slog.e(TAG, "Task positioner not registered");
    325             return;
    326         }
    327 
    328         mService.mInputManager.unregisterInputChannel(mServerChannel);
    329 
    330         mInputEventReceiver.dispose();
    331         mInputEventReceiver = null;
    332         mClientChannel.dispose();
    333         mServerChannel.dispose();
    334         mClientChannel = null;
    335         mServerChannel = null;
    336 
    337         mDragWindowHandle = null;
    338         mDragApplicationHandle = null;
    339         mDisplay = null;
    340 
    341         if (mDimLayer != null) {
    342             mDimLayer.destroySurface();
    343             mDimLayer = null;
    344         }
    345         mCurrentDimSide = CTRL_NONE;
    346         mDragEnded = true;
    347 
    348         // Resume rotations after a drag.
    349         if (DEBUG_ORIENTATION) {
    350             Slog.d(TAG, "Resuming rotation after re-position");
    351         }
    352         mService.resumeRotationLocked();
    353     }
    354 
    355     void startDrag(WindowState win, boolean resize, boolean preserveOrientation, float startX,
    356                    float startY) {
    357         if (DEBUG_TASK_POSITIONING) {
    358             Slog.d(TAG, "startDrag: win=" + win + ", resize=" + resize
    359                     + ", preserveOrientation=" + preserveOrientation + ", {" + startX + ", "
    360                     + startY + "}");
    361         }
    362         mTask = win.getTask();
    363         // Use the dim bounds, not the original task bounds. The cursor
    364         // movement should be calculated relative to the visible bounds.
    365         // Also, use the dim bounds of the task which accounts for
    366         // multiple app windows. Don't use any bounds from win itself as it
    367         // may not be the same size as the task.
    368         mTask.getDimBounds(mTmpRect);
    369         startDrag(resize, preserveOrientation, startX, startY, mTmpRect);
    370     }
    371 
    372     @VisibleForTesting
    373     void startDrag(boolean resize, boolean preserveOrientation,
    374                    float startX, float startY, Rect startBounds) {
    375         mCtrlType = CTRL_NONE;
    376         mStartDragX = startX;
    377         mStartDragY = startY;
    378         mPreserveOrientation = preserveOrientation;
    379 
    380         if (resize) {
    381             if (startX < startBounds.left) {
    382                 mCtrlType |= CTRL_LEFT;
    383             }
    384             if (startX > startBounds.right) {
    385                 mCtrlType |= CTRL_RIGHT;
    386             }
    387             if (startY < startBounds.top) {
    388                 mCtrlType |= CTRL_TOP;
    389             }
    390             if (startY > startBounds.bottom) {
    391                 mCtrlType |= CTRL_BOTTOM;
    392             }
    393             mResizing = mCtrlType != CTRL_NONE;
    394         }
    395 
    396         // In case of !isDockedInEffect we are using the union of all task bounds. These might be
    397         // made up out of multiple windows which are only partially overlapping. When that happens,
    398         // the orientation from the window of interest to the entire stack might diverge. However
    399         // for now we treat them as the same.
    400         mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
    401         mWindowOriginalBounds.set(startBounds);
    402 
    403         // Make sure we always have valid drag bounds even if the drag ends before any move events
    404         // have been handled.
    405         mWindowDragBounds.set(startBounds);
    406     }
    407 
    408     private void endDragLocked() {
    409         mResizing = false;
    410         mTask.setDragResizing(false, DRAG_RESIZE_MODE_FREEFORM);
    411     }
    412 
    413     /** Returns true if the move operation should be ended. */
    414     private boolean notifyMoveLocked(float x, float y) {
    415         if (DEBUG_TASK_POSITIONING) {
    416             Slog.d(TAG, "notifyMoveLocked: {" + x + "," + y + "}");
    417         }
    418 
    419         if (mCtrlType != CTRL_NONE) {
    420             resizeDrag(x, y);
    421             mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM);
    422             return false;
    423         }
    424 
    425         // This is a moving or scrolling operation.
    426         mTask.mStack.getDimBounds(mTmpRect);
    427 
    428         int nX = (int) x;
    429         int nY = (int) y;
    430         if (!mTmpRect.contains(nX, nY)) {
    431             // For a moving operation we allow the pointer to go out of the stack bounds, but
    432             // use the clamped pointer position for the drag bounds computation.
    433             nX = Math.min(Math.max(nX, mTmpRect.left), mTmpRect.right);
    434             nY = Math.min(Math.max(nY, mTmpRect.top), mTmpRect.bottom);
    435         }
    436 
    437         updateWindowDragBounds(nX, nY, mTmpRect);
    438         updateDimLayerVisibility(nX);
    439         return false;
    440     }
    441 
    442     /**
    443      * The user is drag - resizing the window.
    444      *
    445      * @param x The x coordinate of the current drag coordinate.
    446      * @param y the y coordinate of the current drag coordinate.
    447      */
    448     @VisibleForTesting
    449     void resizeDrag(float x, float y) {
    450         // This is a resizing operation.
    451         // We need to keep various constraints:
    452         // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y]
    453         // 2. The orientation is kept - if required.
    454         final int deltaX = Math.round(x - mStartDragX);
    455         final int deltaY = Math.round(y - mStartDragY);
    456         int left = mWindowOriginalBounds.left;
    457         int top = mWindowOriginalBounds.top;
    458         int right = mWindowOriginalBounds.right;
    459         int bottom = mWindowOriginalBounds.bottom;
    460 
    461         // The aspect which we have to respect. Note that if the orientation does not need to be
    462         // preserved the aspect will be calculated as 1.0 which neutralizes the following
    463         // computations.
    464         final float minAspect = !mPreserveOrientation
    465                 ? 1.0f
    466                 : (mStartOrientationWasLandscape ? MIN_ASPECT : (1.0f / MIN_ASPECT));
    467         // Calculate the resulting width and height of the drag operation.
    468         int width = right - left;
    469         int height = bottom - top;
    470         if ((mCtrlType & CTRL_LEFT) != 0) {
    471             width = Math.max(mMinVisibleWidth, width - deltaX);
    472         } else if ((mCtrlType & CTRL_RIGHT) != 0) {
    473             width = Math.max(mMinVisibleWidth, width + deltaX);
    474         }
    475         if ((mCtrlType & CTRL_TOP) != 0) {
    476             height = Math.max(mMinVisibleHeight, height - deltaY);
    477         } else if ((mCtrlType & CTRL_BOTTOM) != 0) {
    478             height = Math.max(mMinVisibleHeight, height + deltaY);
    479         }
    480 
    481         // If we have to preserve the orientation - check that we are doing so.
    482         final float aspect = (float) width / (float) height;
    483         if (mPreserveOrientation && ((mStartOrientationWasLandscape && aspect < MIN_ASPECT)
    484                 || (!mStartOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) {
    485             // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major
    486             // drag axis. What ever is producing the bigger rectangle will be chosen.
    487             int width1;
    488             int width2;
    489             int height1;
    490             int height2;
    491             if (mStartOrientationWasLandscape) {
    492                 // Assuming that the width is our target we calculate the height.
    493                 width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
    494                 height1 = Math.min(height, Math.round((float)width1 / MIN_ASPECT));
    495                 if (height1 < mMinVisibleHeight) {
    496                     // If the resulting height is too small we adjust to the minimal size.
    497                     height1 = mMinVisibleHeight;
    498                     width1 = Math.max(mMinVisibleWidth,
    499                             Math.min(mMaxVisibleSize.x, Math.round((float)height1 * MIN_ASPECT)));
    500                 }
    501                 // Assuming that the height is our target we calculate the width.
    502                 height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
    503                 width2 = Math.max(width, Math.round((float)height2 * MIN_ASPECT));
    504                 if (width2 < mMinVisibleWidth) {
    505                     // If the resulting width is too small we adjust to the minimal size.
    506                     width2 = mMinVisibleWidth;
    507                     height2 = Math.max(mMinVisibleHeight,
    508                             Math.min(mMaxVisibleSize.y, Math.round((float)width2 / MIN_ASPECT)));
    509                 }
    510             } else {
    511                 // Assuming that the width is our target we calculate the height.
    512                 width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
    513                 height1 = Math.max(height, Math.round((float)width1 * MIN_ASPECT));
    514                 if (height1 < mMinVisibleHeight) {
    515                     // If the resulting height is too small we adjust to the minimal size.
    516                     height1 = mMinVisibleHeight;
    517                     width1 = Math.max(mMinVisibleWidth,
    518                             Math.min(mMaxVisibleSize.x, Math.round((float)height1 / MIN_ASPECT)));
    519                 }
    520                 // Assuming that the height is our target we calculate the width.
    521                 height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
    522                 width2 = Math.min(width, Math.round((float)height2 / MIN_ASPECT));
    523                 if (width2 < mMinVisibleWidth) {
    524                     // If the resulting width is too small we adjust to the minimal size.
    525                     width2 = mMinVisibleWidth;
    526                     height2 = Math.max(mMinVisibleHeight,
    527                             Math.min(mMaxVisibleSize.y, Math.round((float)width2 * MIN_ASPECT)));
    528                 }
    529             }
    530 
    531             // Use the bigger of the two rectangles if the major change was positive, otherwise
    532             // do the opposite.
    533             final boolean grows = width > (right - left) || height > (bottom - top);
    534             if (grows == (width1 * height1 > width2 * height2)) {
    535                 width = width1;
    536                 height = height1;
    537             } else {
    538                 width = width2;
    539                 height = height2;
    540             }
    541         }
    542 
    543         // Update mWindowDragBounds to the new drag size.
    544         updateDraggedBounds(left, top, right, bottom, width, height);
    545     }
    546 
    547     /**
    548      * Given the old coordinates and the new width and height, update the mWindowDragBounds.
    549      *
    550      * @param left      The original left bound before the user started dragging.
    551      * @param top       The original top bound before the user started dragging.
    552      * @param right     The original right bound before the user started dragging.
    553      * @param bottom    The original bottom bound before the user started dragging.
    554      * @param newWidth  The new dragged width.
    555      * @param newHeight The new dragged height.
    556      */
    557     void updateDraggedBounds(int left, int top, int right, int bottom, int newWidth,
    558                              int newHeight) {
    559         // Generate the final bounds by keeping the opposite drag edge constant.
    560         if ((mCtrlType & CTRL_LEFT) != 0) {
    561             left = right - newWidth;
    562         } else { // Note: The right might have changed - if we pulled at the right or not.
    563             right = left + newWidth;
    564         }
    565         if ((mCtrlType & CTRL_TOP) != 0) {
    566             top = bottom - newHeight;
    567         } else { // Note: The height might have changed - if we pulled at the bottom or not.
    568             bottom = top + newHeight;
    569         }
    570 
    571         mWindowDragBounds.set(left, top, right, bottom);
    572 
    573         checkBoundsForOrientationViolations(mWindowDragBounds);
    574     }
    575 
    576     /**
    577      * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set).
    578      *
    579      * @param bounds The bounds to be checked.
    580      */
    581     private void checkBoundsForOrientationViolations(Rect bounds) {
    582         // When using debug check that we are not violating the given constraints.
    583         if (DEBUG_ORIENTATION_VIOLATIONS) {
    584             if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) {
    585                 Slog.e(TAG, "Orientation violation detected! should be "
    586                         + (mStartOrientationWasLandscape ? "landscape" : "portrait")
    587                         + " but is the other");
    588             } else {
    589                 Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height());
    590             }
    591             if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) {
    592                 Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth
    593                         + ", " + bounds.width() + ") Height(min,is)=("
    594                         + mMinVisibleHeight + ", " + bounds.height() + ")");
    595             }
    596             if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) {
    597                 Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x
    598                         + ", " + bounds.width() + ") Height(min,is)=("
    599                         + mMaxVisibleSize.y + ", " + bounds.height() + ")");
    600             }
    601         }
    602     }
    603 
    604     private void updateWindowDragBounds(int x, int y, Rect stackBounds) {
    605         final int offsetX = Math.round(x - mStartDragX);
    606         final int offsetY = Math.round(y - mStartDragY);
    607         mWindowDragBounds.set(mWindowOriginalBounds);
    608         // Horizontally, at least mMinVisibleWidth pixels of the window should remain visible.
    609         final int maxLeft = stackBounds.right - mMinVisibleWidth;
    610         final int minLeft = stackBounds.left + mMinVisibleWidth - mWindowOriginalBounds.width();
    611 
    612         // Vertically, the top mMinVisibleHeight of the window should remain visible.
    613         // (This assumes that the window caption bar is at the top of the window).
    614         final int minTop = stackBounds.top;
    615         final int maxTop = stackBounds.bottom - mMinVisibleHeight;
    616 
    617         mWindowDragBounds.offsetTo(
    618                 Math.min(Math.max(mWindowOriginalBounds.left + offsetX, minLeft), maxLeft),
    619                 Math.min(Math.max(mWindowOriginalBounds.top + offsetY, minTop), maxTop));
    620 
    621         if (DEBUG_TASK_POSITIONING) Slog.d(TAG,
    622                 "updateWindowDragBounds: " + mWindowDragBounds);
    623     }
    624 
    625     private void updateDimLayerVisibility(int x) {
    626         @CtrlType
    627         int dimSide = getDimSide(x);
    628         if (dimSide == mCurrentDimSide) {
    629             return;
    630         }
    631 
    632         mCurrentDimSide = dimSide;
    633 
    634         if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility");
    635         mService.openSurfaceTransaction();
    636         if (mCurrentDimSide == CTRL_NONE) {
    637             mDimLayer.hide();
    638         } else {
    639             showDimLayer();
    640         }
    641         mService.closeSurfaceTransaction();
    642     }
    643 
    644     /**
    645      * Returns the side of the screen the dim layer should be shown.
    646      * @param x horizontal coordinate used to determine if the dim layer should be shown
    647      * @return Returns {@link #CTRL_LEFT} if the dim layer should be shown on the left half of the
    648      * screen, {@link #CTRL_RIGHT} if on the right side, or {@link #CTRL_NONE} if the dim layer
    649      * shouldn't be shown.
    650      */
    651     private int getDimSide(int x) {
    652         if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
    653                 || !mTask.mStack.fillsParent()
    654                 || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) {
    655             return CTRL_NONE;
    656         }
    657 
    658         mTask.mStack.getDimBounds(mTmpRect);
    659         if (x - mSideMargin <= mTmpRect.left) {
    660             return CTRL_LEFT;
    661         }
    662         if (x + mSideMargin >= mTmpRect.right) {
    663             return CTRL_RIGHT;
    664         }
    665 
    666         return CTRL_NONE;
    667     }
    668 
    669     private void showDimLayer() {
    670         mTask.mStack.getDimBounds(mTmpRect);
    671         if (mCurrentDimSide == CTRL_LEFT) {
    672             mTmpRect.right = mTmpRect.centerX();
    673         } else if (mCurrentDimSide == CTRL_RIGHT) {
    674             mTmpRect.left = mTmpRect.centerX();
    675         }
    676 
    677         mDimLayer.setBounds(mTmpRect);
    678         mDimLayer.show(mService.getDragLayerLocked(), RESIZING_HINT_ALPHA,
    679                 RESIZING_HINT_DURATION_MS);
    680     }
    681 
    682     @Override /** {@link DimLayer.DimLayerUser} */
    683     public boolean dimFullscreen() {
    684         return isFullscreen();
    685     }
    686 
    687     boolean isFullscreen() {
    688         return false;
    689     }
    690 
    691     @Override /** {@link DimLayer.DimLayerUser} */
    692     public DisplayInfo getDisplayInfo() {
    693         return mTask.mStack.getDisplayInfo();
    694     }
    695 
    696     @Override
    697     public boolean isAttachedToDisplay() {
    698         return mTask != null && mTask.getDisplayContent() != null;
    699     }
    700 
    701     @Override
    702     public void getDimBounds(Rect out) {
    703         // This dim layer user doesn't need this.
    704     }
    705 
    706     @Override
    707     public String toShortString() {
    708         return TAG;
    709     }
    710 }
    711