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 }