TaskPositioner.java revision 29bfbb878d54db14204e9b02dc17bfc6e127b6b2
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
17package com.android.server.wm;
18
19import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
20import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
21import static android.app.ActivityManager.RESIZE_MODE_USER;
22import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
23import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
24import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
25import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
26import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
27import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
28import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
29import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
30import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
31import static com.android.server.wm.WindowManagerService.dipToPixel;
32import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
33import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
34import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
35
36import android.annotation.IntDef;
37import android.graphics.Point;
38import android.graphics.Rect;
39import android.os.Looper;
40import android.os.Process;
41import android.os.RemoteException;
42import android.os.Trace;
43import android.util.DisplayMetrics;
44import android.util.Slog;
45import android.view.BatchedInputEventReceiver;
46import android.view.Choreographer;
47import android.view.Display;
48import android.view.DisplayInfo;
49import android.view.InputChannel;
50import android.view.InputDevice;
51import android.view.InputEvent;
52import android.view.MotionEvent;
53import android.view.SurfaceControl;
54import android.view.WindowManager;
55
56import com.android.server.input.InputApplicationHandle;
57import com.android.server.input.InputWindowHandle;
58import com.android.server.wm.WindowManagerService.H;
59
60import java.lang.annotation.Retention;
61import java.lang.annotation.RetentionPolicy;
62
63class 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