TaskPositioner.java revision f0a60a9c60d9973e10a859dd8c31b8f18adb5a0e
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.WindowManager;
54
55import com.android.internal.annotations.VisibleForTesting;
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 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) {
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 */, false /* moveHomeStackFront */);
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.mH.getLooper(), mService.mChoreographer);
266
267        mDragApplicationHandle = new InputApplicationHandle(null);
268        mDragApplicationHandle.name = TAG;
269        mDragApplicationHandle.dispatchingTimeoutNanos =
270                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
271
272        mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
273                mDisplay.getDisplayId());
274        mDragWindowHandle.name = TAG;
275        mDragWindowHandle.inputChannel = mServerChannel;
276        mDragWindowHandle.layer = mService.getDragLayerLocked();
277        mDragWindowHandle.layoutParamsFlags = 0;
278        mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
279        mDragWindowHandle.dispatchingTimeoutNanos =
280                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
281        mDragWindowHandle.visible = true;
282        mDragWindowHandle.canReceiveKeys = false;
283        mDragWindowHandle.hasFocus = true;
284        mDragWindowHandle.hasWallpaper = false;
285        mDragWindowHandle.paused = false;
286        mDragWindowHandle.ownerPid = Process.myPid();
287        mDragWindowHandle.ownerUid = Process.myUid();
288        mDragWindowHandle.inputFeatures = 0;
289        mDragWindowHandle.scaleFactor = 1.0f;
290
291        // The drag window cannot receive new touches.
292        mDragWindowHandle.touchableRegion.setEmpty();
293
294        // The drag window covers the entire display
295        mDragWindowHandle.frameLeft = 0;
296        mDragWindowHandle.frameTop = 0;
297        final Point p = new Point();
298        mDisplay.getRealSize(p);
299        mDragWindowHandle.frameRight = p.x;
300        mDragWindowHandle.frameBottom = p.y;
301
302        // Pause rotations before a drag.
303        if (DEBUG_ORIENTATION) {
304            Slog.d(TAG, "Pausing rotation during re-position");
305        }
306        mService.pauseRotationLocked();
307
308        mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId(), TAG_LOCAL);
309        mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
310        mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
311        mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
312        mDisplay.getRealSize(mMaxVisibleSize);
313
314        mDragEnded = false;
315    }
316
317    void unregister() {
318        if (DEBUG_TASK_POSITIONING) {
319            Slog.d(TAG, "Unregistering task positioner");
320        }
321
322        if (mClientChannel == null) {
323            Slog.e(TAG, "Task positioner not registered");
324            return;
325        }
326
327        mService.mInputManager.unregisterInputChannel(mServerChannel);
328
329        mInputEventReceiver.dispose();
330        mInputEventReceiver = null;
331        mClientChannel.dispose();
332        mServerChannel.dispose();
333        mClientChannel = null;
334        mServerChannel = null;
335
336        mDragWindowHandle = null;
337        mDragApplicationHandle = null;
338        mDisplay = null;
339
340        if (mDimLayer != null) {
341            mDimLayer.destroySurface();
342            mDimLayer = null;
343        }
344        mCurrentDimSide = CTRL_NONE;
345        mDragEnded = true;
346
347        // Resume rotations after a drag.
348        if (DEBUG_ORIENTATION) {
349            Slog.d(TAG, "Resuming rotation after re-position");
350        }
351        mService.resumeRotationLocked();
352    }
353
354    void startDrag(WindowState win, boolean resize, boolean preserveOrientation, float startX,
355                   float startY) {
356        if (DEBUG_TASK_POSITIONING) {
357            Slog.d(TAG, "startDrag: win=" + win + ", resize=" + resize
358                    + ", preserveOrientation=" + preserveOrientation + ", {" + startX + ", "
359                    + startY + "}");
360        }
361        mTask = win.getTask();
362        // Use the dim bounds, not the original task bounds. The cursor
363        // movement should be calculated relative to the visible bounds.
364        // Also, use the dim bounds of the task which accounts for
365        // multiple app windows. Don't use any bounds from win itself as it
366        // may not be the same size as the task.
367        mTask.getDimBounds(mTmpRect);
368        startDrag(resize, preserveOrientation, startX, startY, mTmpRect);
369    }
370
371    @VisibleForTesting
372    void startDrag(boolean resize, boolean preserveOrientation,
373                   float startX, float startY, Rect startBounds) {
374        mCtrlType = CTRL_NONE;
375        mStartDragX = startX;
376        mStartDragY = startY;
377        mPreserveOrientation = preserveOrientation;
378
379        if (resize) {
380            if (startX < startBounds.left) {
381                mCtrlType |= CTRL_LEFT;
382            }
383            if (startX > startBounds.right) {
384                mCtrlType |= CTRL_RIGHT;
385            }
386            if (startY < startBounds.top) {
387                mCtrlType |= CTRL_TOP;
388            }
389            if (startY > startBounds.bottom) {
390                mCtrlType |= CTRL_BOTTOM;
391            }
392            mResizing = mCtrlType != CTRL_NONE;
393        }
394
395        // In case of !isDockedInEffect we are using the union of all task bounds. These might be
396        // made up out of multiple windows which are only partially overlapping. When that happens,
397        // the orientation from the window of interest to the entire stack might diverge. However
398        // for now we treat them as the same.
399        mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
400        mWindowOriginalBounds.set(startBounds);
401
402        // Make sure we always have valid drag bounds even if the drag ends before any move events
403        // have been handled.
404        mWindowDragBounds.set(startBounds);
405    }
406
407    private void endDragLocked() {
408        mResizing = false;
409        mTask.setDragResizing(false, DRAG_RESIZE_MODE_FREEFORM);
410    }
411
412    /** Returns true if the move operation should be ended. */
413    private boolean notifyMoveLocked(float x, float y) {
414        if (DEBUG_TASK_POSITIONING) {
415            Slog.d(TAG, "notifyMoveLocked: {" + x + "," + y + "}");
416        }
417
418        if (mCtrlType != CTRL_NONE) {
419            resizeDrag(x, y);
420            mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM);
421            return false;
422        }
423
424        // This is a moving or scrolling operation.
425        mTask.mStack.getDimBounds(mTmpRect);
426
427        int nX = (int) x;
428        int nY = (int) y;
429        if (!mTmpRect.contains(nX, nY)) {
430            // For a moving operation we allow the pointer to go out of the stack bounds, but
431            // use the clamped pointer position for the drag bounds computation.
432            nX = Math.min(Math.max(nX, mTmpRect.left), mTmpRect.right);
433            nY = Math.min(Math.max(nY, mTmpRect.top), mTmpRect.bottom);
434        }
435
436        updateWindowDragBounds(nX, nY, mTmpRect);
437        updateDimLayerVisibility(nX);
438        return false;
439    }
440
441    /**
442     * The user is drag - resizing the window.
443     *
444     * @param x The x coordinate of the current drag coordinate.
445     * @param y the y coordinate of the current drag coordinate.
446     */
447    @VisibleForTesting
448    void resizeDrag(float x, float y) {
449        // This is a resizing operation.
450        // We need to keep various constraints:
451        // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y]
452        // 2. The orientation is kept - if required.
453        final int deltaX = Math.round(x - mStartDragX);
454        final int deltaY = Math.round(y - mStartDragY);
455        int left = mWindowOriginalBounds.left;
456        int top = mWindowOriginalBounds.top;
457        int right = mWindowOriginalBounds.right;
458        int bottom = mWindowOriginalBounds.bottom;
459
460        // The aspect which we have to respect. Note that if the orientation does not need to be
461        // preserved the aspect will be calculated as 1.0 which neutralizes the following
462        // computations.
463        final float minAspect = !mPreserveOrientation
464                ? 1.0f
465                : (mStartOrientationWasLandscape ? MIN_ASPECT : (1.0f / MIN_ASPECT));
466        // Calculate the resulting width and height of the drag operation.
467        int width = right - left;
468        int height = bottom - top;
469        if ((mCtrlType & CTRL_LEFT) != 0) {
470            width = Math.max(mMinVisibleWidth, width - deltaX);
471        } else if ((mCtrlType & CTRL_RIGHT) != 0) {
472            width = Math.max(mMinVisibleWidth, width + deltaX);
473        }
474        if ((mCtrlType & CTRL_TOP) != 0) {
475            height = Math.max(mMinVisibleHeight, height - deltaY);
476        } else if ((mCtrlType & CTRL_BOTTOM) != 0) {
477            height = Math.max(mMinVisibleHeight, height + deltaY);
478        }
479
480        // If we have to preserve the orientation - check that we are doing so.
481        final float aspect = (float) width / (float) height;
482        if (mPreserveOrientation && ((mStartOrientationWasLandscape && aspect < MIN_ASPECT)
483                || (!mStartOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) {
484            // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major
485            // drag axis. What ever is producing the bigger rectangle will be chosen.
486            int width1;
487            int width2;
488            int height1;
489            int height2;
490            if (mStartOrientationWasLandscape) {
491                // Assuming that the width is our target we calculate the height.
492                width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
493                height1 = Math.min(height, Math.round((float)width1 / MIN_ASPECT));
494                if (height1 < mMinVisibleHeight) {
495                    // If the resulting height is too small we adjust to the minimal size.
496                    height1 = mMinVisibleHeight;
497                    width1 = Math.max(mMinVisibleWidth,
498                            Math.min(mMaxVisibleSize.x, Math.round((float)height1 * MIN_ASPECT)));
499                }
500                // Assuming that the height is our target we calculate the width.
501                height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
502                width2 = Math.max(width, Math.round((float)height2 * MIN_ASPECT));
503                if (width2 < mMinVisibleWidth) {
504                    // If the resulting width is too small we adjust to the minimal size.
505                    width2 = mMinVisibleWidth;
506                    height2 = Math.max(mMinVisibleHeight,
507                            Math.min(mMaxVisibleSize.y, Math.round((float)width2 / MIN_ASPECT)));
508                }
509            } else {
510                // Assuming that the width is our target we calculate the height.
511                width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
512                height1 = Math.max(height, Math.round((float)width1 * MIN_ASPECT));
513                if (height1 < mMinVisibleHeight) {
514                    // If the resulting height is too small we adjust to the minimal size.
515                    height1 = mMinVisibleHeight;
516                    width1 = Math.max(mMinVisibleWidth,
517                            Math.min(mMaxVisibleSize.x, Math.round((float)height1 / MIN_ASPECT)));
518                }
519                // Assuming that the height is our target we calculate the width.
520                height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
521                width2 = Math.min(width, Math.round((float)height2 / MIN_ASPECT));
522                if (width2 < mMinVisibleWidth) {
523                    // If the resulting width is too small we adjust to the minimal size.
524                    width2 = mMinVisibleWidth;
525                    height2 = Math.max(mMinVisibleHeight,
526                            Math.min(mMaxVisibleSize.y, Math.round((float)width2 * MIN_ASPECT)));
527                }
528            }
529
530            // Use the bigger of the two rectangles if the major change was positive, otherwise
531            // do the opposite.
532            final boolean grows = width > (right - left) || height > (bottom - top);
533            if (grows == (width1 * height1 > width2 * height2)) {
534                width = width1;
535                height = height1;
536            } else {
537                width = width2;
538                height = height2;
539            }
540        }
541
542        // Update mWindowDragBounds to the new drag size.
543        updateDraggedBounds(left, top, right, bottom, width, height);
544    }
545
546    /**
547     * Given the old coordinates and the new width and height, update the mWindowDragBounds.
548     *
549     * @param left      The original left bound before the user started dragging.
550     * @param top       The original top bound before the user started dragging.
551     * @param right     The original right bound before the user started dragging.
552     * @param bottom    The original bottom bound before the user started dragging.
553     * @param newWidth  The new dragged width.
554     * @param newHeight The new dragged height.
555     */
556    void updateDraggedBounds(int left, int top, int right, int bottom, int newWidth,
557                             int newHeight) {
558        // Generate the final bounds by keeping the opposite drag edge constant.
559        if ((mCtrlType & CTRL_LEFT) != 0) {
560            left = right - newWidth;
561        } else { // Note: The right might have changed - if we pulled at the right or not.
562            right = left + newWidth;
563        }
564        if ((mCtrlType & CTRL_TOP) != 0) {
565            top = bottom - newHeight;
566        } else { // Note: The height might have changed - if we pulled at the bottom or not.
567            bottom = top + newHeight;
568        }
569
570        mWindowDragBounds.set(left, top, right, bottom);
571
572        checkBoundsForOrientationViolations(mWindowDragBounds);
573    }
574
575    /**
576     * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set).
577     *
578     * @param bounds The bounds to be checked.
579     */
580    private void checkBoundsForOrientationViolations(Rect bounds) {
581        // When using debug check that we are not violating the given constraints.
582        if (DEBUG_ORIENTATION_VIOLATIONS) {
583            if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) {
584                Slog.e(TAG, "Orientation violation detected! should be "
585                        + (mStartOrientationWasLandscape ? "landscape" : "portrait")
586                        + " but is the other");
587            } else {
588                Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height());
589            }
590            if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) {
591                Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth
592                        + ", " + bounds.width() + ") Height(min,is)=("
593                        + mMinVisibleHeight + ", " + bounds.height() + ")");
594            }
595            if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) {
596                Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x
597                        + ", " + bounds.width() + ") Height(min,is)=("
598                        + mMaxVisibleSize.y + ", " + bounds.height() + ")");
599            }
600        }
601    }
602
603    private void updateWindowDragBounds(int x, int y, Rect stackBounds) {
604        final int offsetX = Math.round(x - mStartDragX);
605        final int offsetY = Math.round(y - mStartDragY);
606        mWindowDragBounds.set(mWindowOriginalBounds);
607        // Horizontally, at least mMinVisibleWidth pixels of the window should remain visible.
608        final int maxLeft = stackBounds.right - mMinVisibleWidth;
609        final int minLeft = stackBounds.left + mMinVisibleWidth - mWindowOriginalBounds.width();
610
611        // Vertically, the top mMinVisibleHeight of the window should remain visible.
612        // (This assumes that the window caption bar is at the top of the window).
613        final int minTop = stackBounds.top;
614        final int maxTop = stackBounds.bottom - mMinVisibleHeight;
615
616        mWindowDragBounds.offsetTo(
617                Math.min(Math.max(mWindowOriginalBounds.left + offsetX, minLeft), maxLeft),
618                Math.min(Math.max(mWindowOriginalBounds.top + offsetY, minTop), maxTop));
619
620        if (DEBUG_TASK_POSITIONING) Slog.d(TAG,
621                "updateWindowDragBounds: " + mWindowDragBounds);
622    }
623
624    private void updateDimLayerVisibility(int x) {
625        @CtrlType
626        int dimSide = getDimSide(x);
627        if (dimSide == mCurrentDimSide) {
628            return;
629        }
630
631        mCurrentDimSide = dimSide;
632
633        if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility");
634        mService.openSurfaceTransaction();
635        if (mCurrentDimSide == CTRL_NONE) {
636            mDimLayer.hide();
637        } else {
638            showDimLayer();
639        }
640        mService.closeSurfaceTransaction();
641    }
642
643    /**
644     * Returns the side of the screen the dim layer should be shown.
645     * @param x horizontal coordinate used to determine if the dim layer should be shown
646     * @return Returns {@link #CTRL_LEFT} if the dim layer should be shown on the left half of the
647     * screen, {@link #CTRL_RIGHT} if on the right side, or {@link #CTRL_NONE} if the dim layer
648     * shouldn't be shown.
649     */
650    private int getDimSide(int x) {
651        if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
652                || !mTask.mStack.fillsParent()
653                || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) {
654            return CTRL_NONE;
655        }
656
657        mTask.mStack.getDimBounds(mTmpRect);
658        if (x - mSideMargin <= mTmpRect.left) {
659            return CTRL_LEFT;
660        }
661        if (x + mSideMargin >= mTmpRect.right) {
662            return CTRL_RIGHT;
663        }
664
665        return CTRL_NONE;
666    }
667
668    private void showDimLayer() {
669        mTask.mStack.getDimBounds(mTmpRect);
670        if (mCurrentDimSide == CTRL_LEFT) {
671            mTmpRect.right = mTmpRect.centerX();
672        } else if (mCurrentDimSide == CTRL_RIGHT) {
673            mTmpRect.left = mTmpRect.centerX();
674        }
675
676        mDimLayer.setBounds(mTmpRect);
677        mDimLayer.show(mService.getDragLayerLocked(), RESIZING_HINT_ALPHA,
678                RESIZING_HINT_DURATION_MS);
679    }
680
681    @Override /** {@link DimLayer.DimLayerUser} */
682    public boolean dimFullscreen() {
683        return isFullscreen();
684    }
685
686    boolean isFullscreen() {
687        return false;
688    }
689
690    @Override /** {@link DimLayer.DimLayerUser} */
691    public DisplayInfo getDisplayInfo() {
692        return mTask.mStack.getDisplayInfo();
693    }
694
695    @Override
696    public boolean isAttachedToDisplay() {
697        return mTask != null && mTask.getDisplayContent() != null;
698    }
699
700    @Override
701    public void getDimBounds(Rect out) {
702        // This dim layer user doesn't need this.
703    }
704
705    @Override
706    public String toShortString() {
707        return TAG;
708    }
709}
710