1 /*
2  * Copyright (C) 2022 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.wm.shell.windowdecor;
18 
19 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
20 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
21 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
22 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
23 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
24 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
25 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
26 
27 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
28 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
29 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
30 import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
31 
32 import android.content.Context;
33 import android.graphics.Rect;
34 import android.graphics.Region;
35 import android.hardware.input.InputManager;
36 import android.os.Binder;
37 import android.os.Handler;
38 import android.os.IBinder;
39 import android.os.RemoteException;
40 import android.view.Choreographer;
41 import android.view.IWindowSession;
42 import android.view.InputChannel;
43 import android.view.InputEvent;
44 import android.view.InputEventReceiver;
45 import android.view.MotionEvent;
46 import android.view.PointerIcon;
47 import android.view.SurfaceControl;
48 import android.view.View;
49 import android.view.ViewConfiguration;
50 import android.view.WindowManagerGlobal;
51 
52 import com.android.internal.view.BaseIWindow;
53 
54 import java.util.function.Supplier;
55 
56 /**
57  * An input event listener registered to InputDispatcher to receive input events on task edges and
58  * and corners. Converts them to drag resize requests.
59  * Task edges are for resizing with a mouse.
60  * Task corners are for resizing with touch input.
61  */
62 class DragResizeInputListener implements AutoCloseable {
63     private static final String TAG = "DragResizeInputListener";
64     private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
65     private final Handler mHandler;
66     private final Choreographer mChoreographer;
67     private final InputManager mInputManager;
68     private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
69 
70     private final int mDisplayId;
71     private final BaseIWindow mFakeWindow;
72     private final IBinder mFocusGrantToken;
73     private final SurfaceControl mDecorationSurface;
74     private final InputChannel mInputChannel;
75     private final TaskResizeInputEventReceiver mInputEventReceiver;
76     private final DragPositioningCallback mCallback;
77 
78     private final SurfaceControl mInputSinkSurface;
79     private final BaseIWindow mFakeSinkWindow;
80     private final InputChannel mSinkInputChannel;
81 
82     private int mTaskWidth;
83     private int mTaskHeight;
84     private int mResizeHandleThickness;
85     private int mCornerSize;
86     private int mTaskCornerRadius;
87 
88     private Rect mLeftTopCornerBounds;
89     private Rect mRightTopCornerBounds;
90     private Rect mLeftBottomCornerBounds;
91     private Rect mRightBottomCornerBounds;
92 
93     private int mDragPointerId = -1;
94     private DragDetector mDragDetector;
95 
DragResizeInputListener( Context context, Handler handler, Choreographer choreographer, int displayId, int taskCornerRadius, SurfaceControl decorationSurface, DragPositioningCallback callback, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier)96     DragResizeInputListener(
97             Context context,
98             Handler handler,
99             Choreographer choreographer,
100             int displayId,
101             int taskCornerRadius,
102             SurfaceControl decorationSurface,
103             DragPositioningCallback callback,
104             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
105             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier) {
106         mInputManager = context.getSystemService(InputManager.class);
107         mHandler = handler;
108         mChoreographer = choreographer;
109         mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
110         mDisplayId = displayId;
111         mTaskCornerRadius = taskCornerRadius;
112         mDecorationSurface = decorationSurface;
113         // Use a fake window as the backing surface is a container layer, and we don't want to
114         // create a buffer layer for it, so we can't use ViewRootImpl.
115         mFakeWindow = new BaseIWindow();
116         mFakeWindow.setSession(mWindowSession);
117         mFocusGrantToken = new Binder();
118         mInputChannel = new InputChannel();
119         try {
120             mWindowSession.grantInputChannel(
121                     mDisplayId,
122                     mDecorationSurface,
123                     mFakeWindow,
124                     null /* hostInputToken */,
125                     FLAG_NOT_FOCUSABLE,
126                     PRIVATE_FLAG_TRUSTED_OVERLAY,
127                     INPUT_FEATURE_SPY,
128                     TYPE_APPLICATION,
129                     null /* windowToken */,
130                     mFocusGrantToken,
131                     TAG + " of " + decorationSurface.toString(),
132                     mInputChannel);
133         } catch (RemoteException e) {
134             e.rethrowFromSystemServer();
135         }
136 
137         mInputEventReceiver = new TaskResizeInputEventReceiver(
138                 mInputChannel, mHandler, mChoreographer);
139         mCallback = callback;
140         mDragDetector = new DragDetector(mInputEventReceiver);
141         mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
142 
143         mInputSinkSurface = surfaceControlBuilderSupplier.get()
144                 .setName("TaskInputSink of " + decorationSurface)
145                 .setContainerLayer()
146                 .setParent(mDecorationSurface)
147                 .build();
148         mSurfaceControlTransactionSupplier.get()
149                 .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER)
150                 .show(mInputSinkSurface)
151                 .apply();
152         mFakeSinkWindow = new BaseIWindow();
153         mSinkInputChannel = new InputChannel();
154         try {
155             mWindowSession.grantInputChannel(
156                     mDisplayId,
157                     mInputSinkSurface,
158                     mFakeSinkWindow,
159                     null /* hostInputToken */,
160                     FLAG_NOT_FOCUSABLE,
161                     0 /* privateFlags */,
162                     INPUT_FEATURE_NO_INPUT_CHANNEL,
163                     TYPE_INPUT_CONSUMER,
164                     null /* windowToken */,
165                     mFocusGrantToken,
166                     "TaskInputSink of " + decorationSurface,
167                     mSinkInputChannel);
168         } catch (RemoteException e) {
169             e.rethrowFromSystemServer();
170         }
171     }
172 
173     /**
174      * Updates the geometry (the touch region) of this drag resize handler.
175      *
176      * @param taskWidth The width of the task.
177      * @param taskHeight The height of the task.
178      * @param resizeHandleThickness The thickness of the resize handle in pixels.
179      * @param cornerSize The size of the resize handle centered in each corner.
180      * @param touchSlop The distance in pixels user has to drag with touch for it to register as
181      *                  a resize action.
182      * @return whether the geometry has changed or not
183      */
setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize, int touchSlop)184     boolean setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
185             int touchSlop) {
186         if (mTaskWidth == taskWidth && mTaskHeight == taskHeight
187                 && mResizeHandleThickness == resizeHandleThickness
188                 && mCornerSize == cornerSize) {
189             return false;
190         }
191 
192         mTaskWidth = taskWidth;
193         mTaskHeight = taskHeight;
194         mResizeHandleThickness = resizeHandleThickness;
195         mCornerSize = cornerSize;
196         mDragDetector.setTouchSlop(touchSlop);
197 
198         Region touchRegion = new Region();
199         final Rect topInputBounds = new Rect(
200                 -mResizeHandleThickness,
201                 -mResizeHandleThickness,
202                 mTaskWidth + mResizeHandleThickness,
203                 0);
204         touchRegion.union(topInputBounds);
205 
206         final Rect leftInputBounds = new Rect(
207                 -mResizeHandleThickness,
208                 0,
209                 0,
210                 mTaskHeight);
211         touchRegion.union(leftInputBounds);
212 
213         final Rect rightInputBounds = new Rect(
214                 mTaskWidth,
215                 0,
216                 mTaskWidth + mResizeHandleThickness,
217                 mTaskHeight);
218         touchRegion.union(rightInputBounds);
219 
220         final Rect bottomInputBounds = new Rect(
221                 -mResizeHandleThickness,
222                 mTaskHeight,
223                 mTaskWidth + mResizeHandleThickness,
224                 mTaskHeight + mResizeHandleThickness);
225         touchRegion.union(bottomInputBounds);
226 
227         // Set up touch areas in each corner.
228         int cornerRadius = mCornerSize / 2;
229         mLeftTopCornerBounds = new Rect(
230                 -cornerRadius,
231                 -cornerRadius,
232                 cornerRadius,
233                 cornerRadius);
234         touchRegion.union(mLeftTopCornerBounds);
235 
236         mRightTopCornerBounds = new Rect(
237                 mTaskWidth - cornerRadius,
238                 -cornerRadius,
239                 mTaskWidth + cornerRadius,
240                 cornerRadius);
241         touchRegion.union(mRightTopCornerBounds);
242 
243         mLeftBottomCornerBounds = new Rect(
244                 -cornerRadius,
245                 mTaskHeight - cornerRadius,
246                 cornerRadius,
247                 mTaskHeight + cornerRadius);
248         touchRegion.union(mLeftBottomCornerBounds);
249 
250         mRightBottomCornerBounds = new Rect(
251                 mTaskWidth - cornerRadius,
252                 mTaskHeight - cornerRadius,
253                 mTaskWidth + cornerRadius,
254                 mTaskHeight + cornerRadius);
255         touchRegion.union(mRightBottomCornerBounds);
256 
257         try {
258             mWindowSession.updateInputChannel(
259                     mInputChannel.getToken(),
260                     mDisplayId,
261                     mDecorationSurface,
262                     FLAG_NOT_FOCUSABLE,
263                     PRIVATE_FLAG_TRUSTED_OVERLAY,
264                     INPUT_FEATURE_SPY,
265                     touchRegion);
266         } catch (RemoteException e) {
267             e.rethrowFromSystemServer();
268         }
269 
270         mSurfaceControlTransactionSupplier.get()
271                 .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight)
272                 .apply();
273         // The touch region of the TaskInputSink should be the touch region of this
274         // DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent
275         // input windows from handling down events, which will bring tasks in the back to front.
276         //
277         // Note not the entire touch region responds to both mouse and touchscreen events.
278         // Therefore, in the region that only responds to one of them, it would be a no-op to
279         // perform a gesture in the other type of events. We currently only have a mouse-only region
280         // out of the task bounds, and due to the roughness of touchscreen events, it's not a severe
281         // issue. However, were there touchscreen-only a region out of the task bounds, mouse
282         // gestures will become no-op in that region, even though the mouse gestures may appear to
283         // be performed on the input window behind the resize handle.
284         touchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
285         try {
286             mWindowSession.updateInputChannel(
287                     mSinkInputChannel.getToken(),
288                     mDisplayId,
289                     mInputSinkSurface,
290                     FLAG_NOT_FOCUSABLE,
291                     0 /* privateFlags */,
292                     INPUT_FEATURE_NO_INPUT_CHANNEL,
293                     touchRegion);
294         } catch (RemoteException e) {
295             e.rethrowFromSystemServer();
296         }
297         return true;
298     }
299 
300     /**
301      * Generate a Region that encapsulates all 4 corner handles
302      */
getCornersRegion()303     Region getCornersRegion() {
304         Region region = new Region();
305         region.union(mLeftTopCornerBounds);
306         region.union(mLeftBottomCornerBounds);
307         region.union(mRightTopCornerBounds);
308         region.union(mRightBottomCornerBounds);
309         return region;
310     }
311 
312     @Override
close()313     public void close() {
314         mInputEventReceiver.dispose();
315         mInputChannel.dispose();
316         try {
317             mWindowSession.remove(mFakeWindow);
318         } catch (RemoteException e) {
319             e.rethrowFromSystemServer();
320         }
321 
322         mSinkInputChannel.dispose();
323         try {
324             mWindowSession.remove(mFakeSinkWindow);
325         } catch (RemoteException e) {
326             e.rethrowFromSystemServer();
327         }
328         mSurfaceControlTransactionSupplier.get()
329                 .remove(mInputSinkSurface)
330                 .apply();
331     }
332 
333     private class TaskResizeInputEventReceiver extends InputEventReceiver
334             implements DragDetector.MotionEventHandler {
335         private final Choreographer mChoreographer;
336         private final Runnable mConsumeBatchEventRunnable;
337         private boolean mConsumeBatchEventScheduled;
338         private boolean mShouldHandleEvents;
339         private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
340 
TaskResizeInputEventReceiver( InputChannel inputChannel, Handler handler, Choreographer choreographer)341         private TaskResizeInputEventReceiver(
342                 InputChannel inputChannel, Handler handler, Choreographer choreographer) {
343             super(inputChannel, handler.getLooper());
344             mChoreographer = choreographer;
345 
346             mConsumeBatchEventRunnable = () -> {
347                 mConsumeBatchEventScheduled = false;
348                 if (consumeBatchedInputEvents(mChoreographer.getFrameTimeNanos())) {
349                     // If we consumed a batch here, we want to go ahead and schedule the
350                     // consumption of batched input events on the next frame. Otherwise, we would
351                     // wait until we have more input events pending and might get starved by other
352                     // things occurring in the process.
353                     scheduleConsumeBatchEvent();
354                 }
355             };
356         }
357 
358         @Override
onBatchedInputEventPending(int source)359         public void onBatchedInputEventPending(int source) {
360             scheduleConsumeBatchEvent();
361         }
362 
scheduleConsumeBatchEvent()363         private void scheduleConsumeBatchEvent() {
364             if (mConsumeBatchEventScheduled) {
365                 return;
366             }
367             mChoreographer.postCallback(
368                     Choreographer.CALLBACK_INPUT, mConsumeBatchEventRunnable, null);
369             mConsumeBatchEventScheduled = true;
370         }
371 
372         @Override
onInputEvent(InputEvent inputEvent)373         public void onInputEvent(InputEvent inputEvent) {
374             finishInputEvent(inputEvent, handleInputEvent(inputEvent));
375         }
376 
handleInputEvent(InputEvent inputEvent)377         private boolean handleInputEvent(InputEvent inputEvent) {
378             if (!(inputEvent instanceof MotionEvent)) {
379                 return false;
380             }
381             return mDragDetector.onMotionEvent((MotionEvent) inputEvent);
382         }
383 
384         @Override
handleMotionEvent(View v, MotionEvent e)385         public boolean handleMotionEvent(View v, MotionEvent e) {
386             boolean result = false;
387             // Check if this is a touch event vs mouse event.
388             // Touch events are tracked in four corners. Other events are tracked in resize edges.
389             boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
390             switch (e.getActionMasked()) {
391                 case MotionEvent.ACTION_DOWN: {
392                     float x = e.getX(0);
393                     float y = e.getY(0);
394                     if (isTouch) {
395                         mShouldHandleEvents = isInCornerBounds(x, y);
396                     } else {
397                         mShouldHandleEvents = isInResizeHandleBounds(x, y);
398                     }
399                     if (mShouldHandleEvents) {
400                         mInputManager.pilferPointers(mInputChannel.getToken());
401 
402                         mDragPointerId = e.getPointerId(0);
403                         float rawX = e.getRawX(0);
404                         float rawY = e.getRawY(0);
405                         int ctrlType = calculateCtrlType(isTouch, x, y);
406                         mCallback.onDragPositioningStart(ctrlType, rawX, rawY);
407                         result = true;
408                     }
409                     break;
410                 }
411                 case MotionEvent.ACTION_MOVE: {
412                     if (!mShouldHandleEvents) {
413                         break;
414                     }
415                     int dragPointerIndex = e.findPointerIndex(mDragPointerId);
416                     float rawX = e.getRawX(dragPointerIndex);
417                     float rawY = e.getRawY(dragPointerIndex);
418                     mCallback.onDragPositioningMove(rawX, rawY);
419                     result = true;
420                     break;
421                 }
422                 case MotionEvent.ACTION_UP:
423                 case MotionEvent.ACTION_CANCEL: {
424                     if (mShouldHandleEvents) {
425                         int dragPointerIndex = e.findPointerIndex(mDragPointerId);
426                         mCallback.onDragPositioningEnd(
427                                 e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
428                     }
429                     mShouldHandleEvents = false;
430                     mDragPointerId = -1;
431                     result = true;
432                     break;
433                 }
434                 case MotionEvent.ACTION_HOVER_ENTER:
435                 case MotionEvent.ACTION_HOVER_MOVE: {
436                     updateCursorType(e.getXCursorPosition(), e.getYCursorPosition());
437                     result = true;
438                     break;
439                 }
440                 case MotionEvent.ACTION_HOVER_EXIT:
441                     result = true;
442                     break;
443             }
444             return result;
445         }
446 
isInCornerBounds(float xf, float yf)447         private boolean isInCornerBounds(float xf, float yf) {
448             return calculateCornersCtrlType(xf, yf) != 0;
449         }
450 
isInResizeHandleBounds(float x, float y)451         private boolean isInResizeHandleBounds(float x, float y) {
452             return calculateResizeHandlesCtrlType(x, y) != 0;
453         }
454 
455         @DragPositioningCallback.CtrlType
calculateCtrlType(boolean isTouch, float x, float y)456         private int calculateCtrlType(boolean isTouch, float x, float y) {
457             if (isTouch) {
458                 return calculateCornersCtrlType(x, y);
459             }
460             return calculateResizeHandlesCtrlType(x, y);
461         }
462 
463         @DragPositioningCallback.CtrlType
calculateResizeHandlesCtrlType(float x, float y)464         private int calculateResizeHandlesCtrlType(float x, float y) {
465             int ctrlType = 0;
466             // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
467             // sides will use the bounds specified in setGeometry and not go into task bounds.
468             if (x < mTaskCornerRadius) {
469                 ctrlType |= CTRL_TYPE_LEFT;
470             }
471             if (x > mTaskWidth - mTaskCornerRadius) {
472                 ctrlType |= CTRL_TYPE_RIGHT;
473             }
474             if (y < mTaskCornerRadius) {
475                 ctrlType |= CTRL_TYPE_TOP;
476             }
477             if (y > mTaskHeight - mTaskCornerRadius) {
478                 ctrlType |= CTRL_TYPE_BOTTOM;
479             }
480             // Check distances from the center if it's in one of four corners.
481             if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
482                     && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
483                 return checkDistanceFromCenter(ctrlType, x, y);
484             }
485             // Otherwise, we should make sure we don't resize tasks inside task bounds.
486             return (x < 0 || y < 0 || x >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0;
487         }
488 
489         // If corner input is not within appropriate distance of corner radius, do not use it.
490         // If input is not on a corner or is within valid distance, return ctrlType.
491         @DragPositioningCallback.CtrlType
checkDistanceFromCenter(@ragPositioningCallback.CtrlType int ctrlType, float x, float y)492         private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType,
493                 float x, float y) {
494             int centerX;
495             int centerY;
496 
497             // Determine center of rounded corner circle; this is simply the corner if radius is 0.
498             switch (ctrlType) {
499                 case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: {
500                     centerX = mTaskCornerRadius;
501                     centerY = mTaskCornerRadius;
502                     break;
503                 }
504                 case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: {
505                     centerX = mTaskCornerRadius;
506                     centerY = mTaskHeight - mTaskCornerRadius;
507                     break;
508                 }
509                 case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: {
510                     centerX = mTaskWidth - mTaskCornerRadius;
511                     centerY = mTaskCornerRadius;
512                     break;
513                 }
514                 case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: {
515                     centerX = mTaskWidth - mTaskCornerRadius;
516                     centerY = mTaskHeight - mTaskCornerRadius;
517                     break;
518                 }
519                 default: {
520                     throw new IllegalArgumentException("ctrlType should be complex, but it's 0x"
521                             + Integer.toHexString(ctrlType));
522                 }
523             }
524             double distanceFromCenter = Math.hypot(x - centerX, y - centerY);
525 
526             if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
527                     && distanceFromCenter >= mTaskCornerRadius) {
528                 return ctrlType;
529             }
530             return 0;
531         }
532 
533         @DragPositioningCallback.CtrlType
calculateCornersCtrlType(float x, float y)534         private int calculateCornersCtrlType(float x, float y) {
535             int xi = (int) x;
536             int yi = (int) y;
537             if (mLeftTopCornerBounds.contains(xi, yi)) {
538                 return CTRL_TYPE_LEFT | CTRL_TYPE_TOP;
539             }
540             if (mLeftBottomCornerBounds.contains(xi, yi)) {
541                 return CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM;
542             }
543             if (mRightTopCornerBounds.contains(xi, yi)) {
544                 return CTRL_TYPE_RIGHT | CTRL_TYPE_TOP;
545             }
546             if (mRightBottomCornerBounds.contains(xi, yi)) {
547                 return CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM;
548             }
549             return 0;
550         }
551 
updateCursorType(float x, float y)552         private void updateCursorType(float x, float y) {
553             @DragPositioningCallback.CtrlType int ctrlType = calculateResizeHandlesCtrlType(x, y);
554 
555             int cursorType = PointerIcon.TYPE_DEFAULT;
556             switch (ctrlType) {
557                 case CTRL_TYPE_LEFT:
558                 case CTRL_TYPE_RIGHT:
559                     cursorType = PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
560                     break;
561                 case CTRL_TYPE_TOP:
562                 case CTRL_TYPE_BOTTOM:
563                     cursorType = PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
564                     break;
565                 case CTRL_TYPE_LEFT | CTRL_TYPE_TOP:
566                 case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM:
567                     cursorType = PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
568                     break;
569                 case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM:
570                 case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP:
571                     cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
572                     break;
573             }
574             // Only update the cursor type to default once so that views behind the decor container
575             // layer that aren't in the active resizing regions have chances to update the cursor
576             // type. We would like to enforce the cursor type by setting the cursor type multilple
577             // times in active regions because we shouldn't allow the views behind to change it, as
578             // we'll pilfer the gesture initiated in this area. This is necessary because 1) we
579             // should allow the views behind regions only for touches to set the cursor type; and 2)
580             // there is a small region out of each rounded corner that's inside the task bounds,
581             // where views in the task can receive input events because we can't set touch regions
582             // of input sinks to have rounded corners.
583             if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) {
584                 mInputManager.setPointerIconType(cursorType);
585                 mLastCursorType = cursorType;
586             }
587         }
588     }
589 }