• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
20 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
21 import static android.app.ActivityManager.RESIZE_MODE_USER;
22 import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
23 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
24 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
25 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
26 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
27 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
28 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
29 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
30 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
31 import static com.android.server.wm.WindowManagerService.dipToPixel;
32 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
33 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
34 import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
35 
36 import android.annotation.IntDef;
37 import android.graphics.Point;
38 import android.graphics.Rect;
39 import android.os.Looper;
40 import android.os.Process;
41 import android.os.RemoteException;
42 import android.os.Trace;
43 import android.util.DisplayMetrics;
44 import android.util.Slog;
45 import android.view.BatchedInputEventReceiver;
46 import android.view.Choreographer;
47 import android.view.Display;
48 import android.view.DisplayInfo;
49 import android.view.InputChannel;
50 import android.view.InputDevice;
51 import android.view.InputEvent;
52 import android.view.MotionEvent;
53 import android.view.WindowManager;
54 
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.server.input.InputApplicationHandle;
57 import com.android.server.input.InputWindowHandle;
58 import com.android.server.wm.WindowManagerService.H;
59 
60 import java.lang.annotation.Retention;
61 import java.lang.annotation.RetentionPolicy;
62 
63 class TaskPositioner implements DimLayer.DimLayerUser {
64     private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
65     private static final String TAG_LOCAL = "TaskPositioner";
66     private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
67 
68     // The margin the pointer position has to be within the side of the screen to be
69     // considered at the side of the screen.
70     static final int SIDE_MARGIN_DIP = 100;
71 
72     @IntDef(flag = true,
73             value = {
74                     CTRL_NONE,
75                     CTRL_LEFT,
76                     CTRL_RIGHT,
77                     CTRL_TOP,
78                     CTRL_BOTTOM
79             })
80     @Retention(RetentionPolicy.SOURCE)
81     @interface CtrlType {}
82 
83     private static final int CTRL_NONE   = 0x0;
84     private static final int CTRL_LEFT   = 0x1;
85     private static final int CTRL_RIGHT  = 0x2;
86     private static final int CTRL_TOP    = 0x4;
87     private static final int CTRL_BOTTOM = 0x8;
88 
89     public static final float RESIZING_HINT_ALPHA = 0.5f;
90 
91     public static final int RESIZING_HINT_DURATION_MS = 0;
92 
93     // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait).
94     // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever
95     // aspect he desires.
96     @VisibleForTesting
97     static final float MIN_ASPECT = 1.2f;
98 
99     private final WindowManagerService mService;
100     private WindowPositionerEventReceiver mInputEventReceiver;
101     private Display mDisplay;
102     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
103     private DimLayer mDimLayer;
104     @CtrlType
105     private int mCurrentDimSide;
106     private Rect mTmpRect = new Rect();
107     private int mSideMargin;
108     private int mMinVisibleWidth;
109     private int mMinVisibleHeight;
110 
111     private Task mTask;
112     private boolean mResizing;
113     private boolean mPreserveOrientation;
114     private boolean mStartOrientationWasLandscape;
115     private final Rect mWindowOriginalBounds = new Rect();
116     private final Rect mWindowDragBounds = new Rect();
117     private final Point mMaxVisibleSize = new Point();
118     private float mStartDragX;
119     private float mStartDragY;
120     @CtrlType
121     private int mCtrlType = CTRL_NONE;
122     private boolean mDragEnded = false;
123 
124     InputChannel mServerChannel;
125     InputChannel mClientChannel;
126     InputApplicationHandle mDragApplicationHandle;
127     InputWindowHandle mDragWindowHandle;
128 
129     private final class WindowPositionerEventReceiver extends BatchedInputEventReceiver {
WindowPositionerEventReceiver( InputChannel inputChannel, Looper looper, Choreographer choreographer)130         public WindowPositionerEventReceiver(
131                 InputChannel inputChannel, Looper looper, Choreographer choreographer) {
132             super(inputChannel, looper, choreographer);
133         }
134 
135         @Override
onInputEvent(InputEvent event, int displayId)136         public void onInputEvent(InputEvent event, int displayId) {
137             if (!(event instanceof MotionEvent)
138                     || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
139                 return;
140             }
141             final MotionEvent motionEvent = (MotionEvent) event;
142             boolean handled = false;
143 
144             try {
145                 if (mDragEnded) {
146                     // The drag has ended but the clean-up message has not been processed by
147                     // window manager. Drop events that occur after this until window manager
148                     // has a chance to clean-up the input handle.
149                     handled = true;
150                     return;
151                 }
152 
153                 final float newX = motionEvent.getRawX();
154                 final float newY = motionEvent.getRawY();
155 
156                 switch (motionEvent.getAction()) {
157                     case MotionEvent.ACTION_DOWN: {
158                         if (DEBUG_TASK_POSITIONING) {
159                             Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}");
160                         }
161                     } break;
162 
163                     case MotionEvent.ACTION_MOVE: {
164                         if (DEBUG_TASK_POSITIONING){
165                             Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}");
166                         }
167                         synchronized (mService.mWindowMap) {
168                             mDragEnded = notifyMoveLocked(newX, newY);
169                             mTask.getDimBounds(mTmpRect);
170                         }
171                         if (!mTmpRect.equals(mWindowDragBounds)) {
172                             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
173                                     "wm.TaskPositioner.resizeTask");
174                             try {
175                                 mService.mActivityManager.resizeTask(
176                                         mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER);
177                             } catch (RemoteException e) {
178                             }
179                             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
180                         }
181                     } break;
182 
183                     case MotionEvent.ACTION_UP: {
184                         if (DEBUG_TASK_POSITIONING) {
185                             Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}");
186                         }
187                         mDragEnded = true;
188                     } break;
189 
190                     case MotionEvent.ACTION_CANCEL: {
191                         if (DEBUG_TASK_POSITIONING) {
192                             Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}");
193                         }
194                         mDragEnded = true;
195                     } break;
196                 }
197 
198                 if (mDragEnded) {
199                     final boolean wasResizing = mResizing;
200                     synchronized (mService.mWindowMap) {
201                         endDragLocked();
202                         mTask.getDimBounds(mTmpRect);
203                     }
204                     try {
205                         if (wasResizing && !mTmpRect.equals(mWindowDragBounds)) {
206                             // We were using fullscreen surface during resizing. Request
207                             // resizeTask() one last time to restore surface to window size.
208                             mService.mActivityManager.resizeTask(
209                                     mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
210                         }
211 
212                         if (mCurrentDimSide != CTRL_NONE) {
213                             final int createMode = mCurrentDimSide == CTRL_LEFT
214                                     ? DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT
215                                     : DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
216                             mService.mActivityManager.moveTaskToDockedStack(
217                                     mTask.mTaskId, createMode, true /*toTop*/, true /* animate */,
218                                     null /* initialBounds */);
219                         }
220                     } catch(RemoteException e) {}
221 
222                     // Post back to WM to handle clean-ups. We still need the input
223                     // event handler for the last finishInputEvent()!
224                     mService.mH.sendEmptyMessage(H.FINISH_TASK_POSITIONING);
225                 }
226                 handled = true;
227             } catch (Exception e) {
228                 Slog.e(TAG, "Exception caught by drag handleMotion", e);
229             } finally {
230                 finishInputEvent(event, handled);
231             }
232         }
233     }
234 
TaskPositioner(WindowManagerService service)235     TaskPositioner(WindowManagerService service) {
236         mService = service;
237     }
238 
239     @VisibleForTesting
getWindowDragBounds()240     Rect getWindowDragBounds() {
241         return mWindowDragBounds;
242     }
243 
244     /**
245      * @param display The Display that the window being dragged is on.
246      */
register(Display display)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 
unregister()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 
startDrag(WindowState win, boolean resize, boolean preserveOrientation, float startX, float startY)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
startDrag(boolean resize, boolean preserveOrientation, float startX, float startY, Rect startBounds)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 
endDragLocked()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. */
notifyMoveLocked(float x, float y)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
resizeDrag(float x, float y)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      */
updateDraggedBounds(int left, int top, int right, int bottom, int newWidth, int newHeight)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      */
checkBoundsForOrientationViolations(Rect bounds)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 
updateWindowDragBounds(int x, int y, Rect stackBounds)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 
updateDimLayerVisibility(int x)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      */
getDimSide(int x)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 
showDimLayer()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} */
dimFullscreen()683     public boolean dimFullscreen() {
684         return isFullscreen();
685     }
686 
isFullscreen()687     boolean isFullscreen() {
688         return false;
689     }
690 
691     @Override /** {@link DimLayer.DimLayerUser} */
getDisplayInfo()692     public DisplayInfo getDisplayInfo() {
693         return mTask.mStack.getDisplayInfo();
694     }
695 
696     @Override
isAttachedToDisplay()697     public boolean isAttachedToDisplay() {
698         return mTask != null && mTask.getDisplayContent() != null;
699     }
700 
701     @Override
getDimBounds(Rect out)702     public void getDimBounds(Rect out) {
703         // This dim layer user doesn't need this.
704     }
705 
706     @Override
toShortString()707     public String toShortString() {
708         return TAG;
709     }
710 }
711