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 */);
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