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.SurfaceControl;
     54 import android.view.WindowManager;
     55 
     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 String TAG_LOCAL = "TaskPositioner";
     65     private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
     66 
     67     // The margin the pointer position has to be within the side of the screen to be
     68     // considered at the side of the screen.
     69     static final int SIDE_MARGIN_DIP = 100;
     70 
     71     @IntDef(flag = true,
     72             value = {
     73                     CTRL_NONE,
     74                     CTRL_LEFT,
     75                     CTRL_RIGHT,
     76                     CTRL_TOP,
     77                     CTRL_BOTTOM
     78             })
     79     @Retention(RetentionPolicy.SOURCE)
     80     @interface CtrlType {}
     81 
     82     private static final int CTRL_NONE   = 0x0;
     83     private static final int CTRL_LEFT   = 0x1;
     84     private static final int CTRL_RIGHT  = 0x2;
     85     private static final int CTRL_TOP    = 0x4;
     86     private static final int CTRL_BOTTOM = 0x8;
     87 
     88     public static final float RESIZING_HINT_ALPHA = 0.5f;
     89 
     90     public static final int RESIZING_HINT_DURATION_MS = 0;
     91 
     92     private final WindowManagerService mService;
     93     private WindowPositionerEventReceiver mInputEventReceiver;
     94     private Display mDisplay;
     95     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     96     private DimLayer mDimLayer;
     97     @CtrlType
     98     private int mCurrentDimSide;
     99     private Rect mTmpRect = new Rect();
    100     private int mSideMargin;
    101     private int mMinVisibleWidth;
    102     private int mMinVisibleHeight;
    103 
    104     private Task mTask;
    105     private boolean mResizing;
    106     private final Rect mWindowOriginalBounds = new Rect();
    107     private final Rect mWindowDragBounds = new Rect();
    108     private float mStartDragX;
    109     private float mStartDragY;
    110     @CtrlType
    111     private int mCtrlType = CTRL_NONE;
    112     private boolean mDragEnded = false;
    113 
    114     InputChannel mServerChannel;
    115     InputChannel mClientChannel;
    116     InputApplicationHandle mDragApplicationHandle;
    117     InputWindowHandle mDragWindowHandle;
    118 
    119     private final class WindowPositionerEventReceiver extends BatchedInputEventReceiver {
    120         public WindowPositionerEventReceiver(
    121                 InputChannel inputChannel, Looper looper, Choreographer choreographer) {
    122             super(inputChannel, looper, choreographer);
    123         }
    124 
    125         @Override
    126         public void onInputEvent(InputEvent event) {
    127             if (!(event instanceof MotionEvent)
    128                     || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
    129                 return;
    130             }
    131             final MotionEvent motionEvent = (MotionEvent) event;
    132             boolean handled = false;
    133 
    134             try {
    135                 if (mDragEnded) {
    136                     // The drag has ended but the clean-up message has not been processed by
    137                     // window manager. Drop events that occur after this until window manager
    138                     // has a chance to clean-up the input handle.
    139                     handled = true;
    140                     return;
    141                 }
    142 
    143                 final float newX = motionEvent.getRawX();
    144                 final float newY = motionEvent.getRawY();
    145 
    146                 switch (motionEvent.getAction()) {
    147                     case MotionEvent.ACTION_DOWN: {
    148                         if (DEBUG_TASK_POSITIONING) {
    149                             Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}");
    150                         }
    151                     } break;
    152 
    153                     case MotionEvent.ACTION_MOVE: {
    154                         if (DEBUG_TASK_POSITIONING){
    155                             Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}");
    156                         }
    157                         synchronized (mService.mWindowMap) {
    158                             mDragEnded = notifyMoveLocked(newX, newY);
    159                             mTask.getDimBounds(mTmpRect);
    160                         }
    161                         if (!mTmpRect.equals(mWindowDragBounds)) {
    162                             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
    163                                     "wm.TaskPositioner.resizeTask");
    164                             try {
    165                                 mService.mActivityManager.resizeTask(
    166                                         mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER);
    167                             } catch (RemoteException e) {
    168                             }
    169                             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
    170                         }
    171                     } break;
    172 
    173                     case MotionEvent.ACTION_UP: {
    174                         if (DEBUG_TASK_POSITIONING) {
    175                             Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}");
    176                         }
    177                         mDragEnded = true;
    178                     } break;
    179 
    180                     case MotionEvent.ACTION_CANCEL: {
    181                         if (DEBUG_TASK_POSITIONING) {
    182                             Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}");
    183                         }
    184                         mDragEnded = true;
    185                     } break;
    186                 }
    187 
    188                 if (mDragEnded) {
    189                     final boolean wasResizing = mResizing;
    190                     synchronized (mService.mWindowMap) {
    191                         endDragLocked();
    192                     }
    193                     try {
    194                         if (wasResizing) {
    195                             // We were using fullscreen surface during resizing. Request
    196                             // resizeTask() one last time to restore surface to window size.
    197                             mService.mActivityManager.resizeTask(
    198                                     mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
    199                         }
    200 
    201                         if (mCurrentDimSide != CTRL_NONE) {
    202                             final int createMode = mCurrentDimSide == CTRL_LEFT
    203                                     ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
    204                                     : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
    205                             mService.mActivityManager.moveTaskToDockedStack(
    206                                     mTask.mTaskId, createMode, true /*toTop*/, true /* animate */,
    207                                     null /* initialBounds */, false /* moveHomeStackFront */);
    208                         }
    209                     } catch(RemoteException e) {}
    210 
    211                     // Post back to WM to handle clean-ups. We still need the input
    212                     // event handler for the last finishInputEvent()!
    213                     mService.mH.sendEmptyMessage(H.FINISH_TASK_POSITIONING);
    214                 }
    215                 handled = true;
    216             } catch (Exception e) {
    217                 Slog.e(TAG, "Exception caught by drag handleMotion", e);
    218             } finally {
    219                 finishInputEvent(event, handled);
    220             }
    221         }
    222     }
    223 
    224     TaskPositioner(WindowManagerService service) {
    225         mService = service;
    226     }
    227 
    228     /**
    229      * @param display The Display that the window being dragged is on.
    230      */
    231     void register(Display display) {
    232         if (DEBUG_TASK_POSITIONING) {
    233             Slog.d(TAG, "Registering task positioner");
    234         }
    235 
    236         if (mClientChannel != null) {
    237             Slog.e(TAG, "Task positioner already registered");
    238             return;
    239         }
    240 
    241         mDisplay = display;
    242         mDisplay.getMetrics(mDisplayMetrics);
    243         final InputChannel[] channels = InputChannel.openInputChannelPair(TAG);
    244         mServerChannel = channels[0];
    245         mClientChannel = channels[1];
    246         mService.mInputManager.registerInputChannel(mServerChannel, null);
    247 
    248         mInputEventReceiver = new WindowPositionerEventReceiver(
    249                 mClientChannel, mService.mH.getLooper(), mService.mChoreographer);
    250 
    251         mDragApplicationHandle = new InputApplicationHandle(null);
    252         mDragApplicationHandle.name = TAG;
    253         mDragApplicationHandle.dispatchingTimeoutNanos =
    254                 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
    255 
    256         mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
    257                 mDisplay.getDisplayId());
    258         mDragWindowHandle.name = TAG;
    259         mDragWindowHandle.inputChannel = mServerChannel;
    260         mDragWindowHandle.layer = mService.getDragLayerLocked();
    261         mDragWindowHandle.layoutParamsFlags = 0;
    262         mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
    263         mDragWindowHandle.dispatchingTimeoutNanos =
    264                 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
    265         mDragWindowHandle.visible = true;
    266         mDragWindowHandle.canReceiveKeys = false;
    267         mDragWindowHandle.hasFocus = true;
    268         mDragWindowHandle.hasWallpaper = false;
    269         mDragWindowHandle.paused = false;
    270         mDragWindowHandle.ownerPid = Process.myPid();
    271         mDragWindowHandle.ownerUid = Process.myUid();
    272         mDragWindowHandle.inputFeatures = 0;
    273         mDragWindowHandle.scaleFactor = 1.0f;
    274 
    275         // The drag window cannot receive new touches.
    276         mDragWindowHandle.touchableRegion.setEmpty();
    277 
    278         // The drag window covers the entire display
    279         mDragWindowHandle.frameLeft = 0;
    280         mDragWindowHandle.frameTop = 0;
    281         final Point p = new Point();
    282         mDisplay.getRealSize(p);
    283         mDragWindowHandle.frameRight = p.x;
    284         mDragWindowHandle.frameBottom = p.y;
    285 
    286         // Pause rotations before a drag.
    287         if (DEBUG_ORIENTATION) {
    288             Slog.d(TAG, "Pausing rotation during re-position");
    289         }
    290         mService.pauseRotationLocked();
    291 
    292         mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId(), TAG_LOCAL);
    293         mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
    294         mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
    295         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
    296 
    297         mDragEnded = false;
    298     }
    299 
    300     void unregister() {
    301         if (DEBUG_TASK_POSITIONING) {
    302             Slog.d(TAG, "Unregistering task positioner");
    303         }
    304 
    305         if (mClientChannel == null) {
    306             Slog.e(TAG, "Task positioner not registered");
    307             return;
    308         }
    309 
    310         mService.mInputManager.unregisterInputChannel(mServerChannel);
    311 
    312         mInputEventReceiver.dispose();
    313         mInputEventReceiver = null;
    314         mClientChannel.dispose();
    315         mServerChannel.dispose();
    316         mClientChannel = null;
    317         mServerChannel = null;
    318 
    319         mDragWindowHandle = null;
    320         mDragApplicationHandle = null;
    321         mDisplay = null;
    322 
    323         if (mDimLayer != null) {
    324             mDimLayer.destroySurface();
    325             mDimLayer = null;
    326         }
    327         mCurrentDimSide = CTRL_NONE;
    328         mDragEnded = true;
    329 
    330         // Resume rotations after a drag.
    331         if (DEBUG_ORIENTATION) {
    332             Slog.d(TAG, "Resuming rotation after re-position");
    333         }
    334         mService.resumeRotationLocked();
    335     }
    336 
    337     void startDragLocked(WindowState win, boolean resize, float startX, float startY) {
    338         if (DEBUG_TASK_POSITIONING) {
    339             Slog.d(TAG, "startDragLocked: win=" + win + ", resize=" + resize
    340                 + ", {" + startX + ", " + startY + "}");
    341         }
    342         mCtrlType = CTRL_NONE;
    343         mTask = win.getTask();
    344         mStartDragX = startX;
    345         mStartDragY = startY;
    346 
    347         if (mTask.isDockedInEffect()) {
    348             // If this is a docked task or if task size is affected by docked stack changing size,
    349             // we can only be here if the task is not resizeable and we're handling a two-finger
    350             // scrolling. Use the original task bounds to position the task, the dim bounds
    351             // is cropped and doesn't move.
    352             mTask.getBounds(mTmpRect);
    353         } else {
    354             // Use the dim bounds, not the original task bounds. The cursor
    355             // movement should be calculated relative to the visible bounds.
    356             // Also, use the dim bounds of the task which accounts for
    357             // multiple app windows. Don't use any bounds from win itself as it
    358             // may not be the same size as the task.
    359             mTask.getDimBounds(mTmpRect);
    360         }
    361 
    362         if (resize) {
    363             if (startX < mTmpRect.left) {
    364                 mCtrlType |= CTRL_LEFT;
    365             }
    366             if (startX > mTmpRect.right) {
    367                 mCtrlType |= CTRL_RIGHT;
    368             }
    369             if (startY < mTmpRect.top) {
    370                 mCtrlType |= CTRL_TOP;
    371             }
    372             if (startY > mTmpRect.bottom) {
    373                 mCtrlType |= CTRL_BOTTOM;
    374             }
    375             mResizing = true;
    376         }
    377 
    378         mWindowOriginalBounds.set(mTmpRect);
    379     }
    380 
    381     private void endDragLocked() {
    382         mResizing = false;
    383         mTask.setDragResizing(false, DRAG_RESIZE_MODE_FREEFORM);
    384     }
    385 
    386     /** Returns true if the move operation should be ended. */
    387     private boolean notifyMoveLocked(float x, float y) {
    388         if (DEBUG_TASK_POSITIONING) {
    389             Slog.d(TAG, "notifyMoveLocked: {" + x + "," + y + "}");
    390         }
    391 
    392         if (mCtrlType != CTRL_NONE) {
    393             // This is a resizing operation.
    394             final int deltaX = Math.round(x - mStartDragX);
    395             final int deltaY = Math.round(y - mStartDragY);
    396             int left = mWindowOriginalBounds.left;
    397             int top = mWindowOriginalBounds.top;
    398             int right = mWindowOriginalBounds.right;
    399             int bottom = mWindowOriginalBounds.bottom;
    400             if ((mCtrlType & CTRL_LEFT) != 0) {
    401                 left = Math.min(left + deltaX, right - mMinVisibleWidth);
    402             }
    403             if ((mCtrlType & CTRL_TOP) != 0) {
    404                 top = Math.min(top + deltaY, bottom - mMinVisibleHeight);
    405             }
    406             if ((mCtrlType & CTRL_RIGHT) != 0) {
    407                 right = Math.max(left + mMinVisibleWidth, right + deltaX);
    408             }
    409             if ((mCtrlType & CTRL_BOTTOM) != 0) {
    410                 bottom = Math.max(top + mMinVisibleHeight, bottom + deltaY);
    411             }
    412             mWindowDragBounds.set(left, top, right, bottom);
    413             mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM);
    414             return false;
    415         }
    416 
    417         // This is a moving operation.
    418         mTask.mStack.getDimBounds(mTmpRect);
    419 
    420         // If this is a non-resizeable task put into side-by-side mode, we are
    421         // handling a two-finger scrolling action. No need to shrink the bounds.
    422         if (!mTask.isDockedInEffect()) {
    423             mTmpRect.inset(mMinVisibleWidth, mMinVisibleHeight);
    424         }
    425 
    426         boolean dragEnded = false;
    427         final int nX = (int) x;
    428         final int nY = (int) y;
    429         if (!mTmpRect.contains(nX, nY)) {
    430             // We end the moving operation if position is outside the stack bounds.
    431             // In this case we need to clamp the position to stack bounds and calculate
    432             // the final window drag bounds.
    433             x = Math.min(Math.max(x, mTmpRect.left), mTmpRect.right);
    434             y = Math.min(Math.max(y, mTmpRect.top), mTmpRect.bottom);
    435             dragEnded = true;
    436         }
    437 
    438         updateWindowDragBounds(nX, nY);
    439         updateDimLayerVisibility(nX);
    440         return dragEnded;
    441     }
    442 
    443     private void updateWindowDragBounds(int x, int y) {
    444         mWindowDragBounds.set(mWindowOriginalBounds);
    445         if (mTask.isDockedInEffect()) {
    446             // Offset the bounds without clamp, the bounds will be shifted later
    447             // by window manager before applying the scrolling.
    448             if (mService.mCurConfiguration.orientation == ORIENTATION_LANDSCAPE) {
    449                 mWindowDragBounds.offset(Math.round(x - mStartDragX), 0);
    450             } else {
    451                 mWindowDragBounds.offset(0, Math.round(y - mStartDragY));
    452             }
    453         } else {
    454             mWindowDragBounds.offset(Math.round(x - mStartDragX), Math.round(y - mStartDragY));
    455         }
    456         if (DEBUG_TASK_POSITIONING) Slog.d(TAG,
    457                 "updateWindowDragBounds: " + mWindowDragBounds);
    458     }
    459 
    460     private void updateDimLayerVisibility(int x) {
    461         @CtrlType
    462         int dimSide = getDimSide(x);
    463         if (dimSide == mCurrentDimSide) {
    464             return;
    465         }
    466 
    467         mCurrentDimSide = dimSide;
    468 
    469         if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility");
    470         SurfaceControl.openTransaction();
    471         if (mCurrentDimSide == CTRL_NONE) {
    472             mDimLayer.hide();
    473         } else {
    474             showDimLayer();
    475         }
    476         SurfaceControl.closeTransaction();
    477     }
    478 
    479     /**
    480      * Returns the side of the screen the dim layer should be shown.
    481      * @param x horizontal coordinate used to determine if the dim layer should be shown
    482      * @return Returns {@link #CTRL_LEFT} if the dim layer should be shown on the left half of the
    483      * screen, {@link #CTRL_RIGHT} if on the right side, or {@link #CTRL_NONE} if the dim layer
    484      * shouldn't be shown.
    485      */
    486     private int getDimSide(int x) {
    487         if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
    488                 || !mTask.mStack.isFullscreen()
    489                 || mService.mCurConfiguration.orientation != ORIENTATION_LANDSCAPE) {
    490             return CTRL_NONE;
    491         }
    492 
    493         mTask.mStack.getDimBounds(mTmpRect);
    494         if (x - mSideMargin <= mTmpRect.left) {
    495             return CTRL_LEFT;
    496         }
    497         if (x + mSideMargin >= mTmpRect.right) {
    498             return CTRL_RIGHT;
    499         }
    500 
    501         return CTRL_NONE;
    502     }
    503 
    504     private void showDimLayer() {
    505         mTask.mStack.getDimBounds(mTmpRect);
    506         if (mCurrentDimSide == CTRL_LEFT) {
    507             mTmpRect.right = mTmpRect.centerX();
    508         } else if (mCurrentDimSide == CTRL_RIGHT) {
    509             mTmpRect.left = mTmpRect.centerX();
    510         }
    511 
    512         mDimLayer.setBounds(mTmpRect);
    513         mDimLayer.show(mService.getDragLayerLocked(), RESIZING_HINT_ALPHA,
    514                 RESIZING_HINT_DURATION_MS);
    515     }
    516 
    517     @Override /** {@link DimLayer.DimLayerUser} */
    518     public boolean dimFullscreen() {
    519         return isFullscreen();
    520     }
    521 
    522     boolean isFullscreen() {
    523         return false;
    524     }
    525 
    526     @Override /** {@link DimLayer.DimLayerUser} */
    527     public DisplayInfo getDisplayInfo() {
    528         return mTask.mStack.getDisplayInfo();
    529     }
    530 
    531     @Override
    532     public void getDimBounds(Rect out) {
    533         // This dim layer user doesn't need this.
    534     }
    535 
    536     @Override
    537     public String toShortString() {
    538         return TAG;
    539     }
    540 }
    541