1 /* 2 * Copyright (C) 2018 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.launcher3.views; 18 19 import static android.view.MotionEvent.ACTION_CANCEL; 20 import static android.view.MotionEvent.ACTION_DOWN; 21 import static android.view.MotionEvent.ACTION_UP; 22 23 import static com.android.launcher3.util.DisplayController.getSingleFrameMs; 24 25 import android.annotation.TargetApi; 26 import android.app.WallpaperManager; 27 import android.content.Context; 28 import android.graphics.Insets; 29 import android.graphics.Rect; 30 import android.graphics.RectF; 31 import android.os.Build; 32 import android.util.AttributeSet; 33 import android.util.Property; 34 import android.view.MotionEvent; 35 import android.view.View; 36 import android.view.ViewDebug; 37 import android.view.ViewGroup; 38 import android.view.WindowInsets; 39 import android.view.accessibility.AccessibilityEvent; 40 import android.widget.FrameLayout; 41 42 import com.android.launcher3.AbstractFloatingView; 43 import com.android.launcher3.InsettableFrameLayout; 44 import com.android.launcher3.Utilities; 45 import com.android.launcher3.util.MultiValueAlpha; 46 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; 47 import com.android.launcher3.util.TouchController; 48 49 import java.io.PrintWriter; 50 import java.util.ArrayList; 51 52 /** 53 * A viewgroup with utility methods for drag-n-drop and touch interception 54 */ 55 public abstract class BaseDragLayer<T extends Context & ActivityContext> 56 extends InsettableFrameLayout { 57 58 public static final Property<LayoutParams, Integer> LAYOUT_X = 59 new Property<LayoutParams, Integer>(Integer.TYPE, "x") { 60 @Override 61 public Integer get(LayoutParams lp) { 62 return lp.x; 63 } 64 65 @Override 66 public void set(LayoutParams lp, Integer x) { 67 lp.x = x; 68 } 69 }; 70 71 public static final Property<LayoutParams, Integer> LAYOUT_Y = 72 new Property<LayoutParams, Integer>(Integer.TYPE, "y") { 73 @Override 74 public Integer get(LayoutParams lp) { 75 return lp.y; 76 } 77 78 @Override 79 public void set(LayoutParams lp, Integer y) { 80 lp.y = y; 81 } 82 }; 83 84 // Touch coming from normal view system is being dispatched. 85 private static final int TOUCH_DISPATCHING_FROM_VIEW = 1 << 0; 86 // Touch is being dispatched through the normal view dispatch system, and started at the 87 // system gesture region. In this case we prevent internal gesture handling and only allow 88 // normal view event handling. 89 private static final int TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION = 1 << 1; 90 // Touch coming from InputMonitor proxy is being dispatched 'only to gestures'. Note that both 91 // this and view-system can be active at the same time where view-system would go to the views, 92 // and this would go to the gestures. 93 // Note that this is not set when events are coming from proxy, but going through full dispatch 94 // process (both views and gestures) to allow view-system to easily take over in case it 95 // comes later. 96 private static final int TOUCH_DISPATCHING_FROM_PROXY = 1 << 2; 97 // ACTION_DOWN has been dispatched to child views and ACTION_UP or ACTION_CANCEL is pending. 98 // Note that the event source can either be view-dispatching or proxy-dispatching based on if 99 // TOUCH_DISPATCHING_VIEW is present or not. 100 private static final int TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS = 1 << 3; 101 102 protected final float[] mTmpXY = new float[2]; 103 protected final float[] mTmpRectPoints = new float[4]; 104 protected final Rect mHitRect = new Rect(); 105 106 @ViewDebug.ExportedProperty(category = "launcher") 107 private final RectF mSystemGestureRegion = new RectF(); 108 private int mTouchDispatchState = 0; 109 110 protected final T mActivity; 111 private final MultiValueAlpha mMultiValueAlpha; 112 private final WallpaperManager mWallpaperManager; 113 114 // All the touch controllers for the view 115 protected TouchController[] mControllers; 116 // Touch controller which is currently active for the normal view dispatch 117 protected TouchController mActiveController; 118 // Touch controller which is being used for the proxy events 119 protected TouchController mProxyTouchController; 120 121 private TouchCompleteListener mTouchCompleteListener; 122 BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount)123 public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) { 124 super(context, attrs); 125 mActivity = (T) ActivityContext.lookupContext(context); 126 mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount); 127 mWallpaperManager = context.getSystemService(WallpaperManager.class); 128 } 129 130 /** 131 * Called to reinitialize touch controllers. 132 */ recreateControllers()133 public abstract void recreateControllers(); 134 135 /** 136 * Same as {@link #isEventOverView(View, MotionEvent, View)} where evView == this drag layer. 137 */ isEventOverView(View view, MotionEvent ev)138 public boolean isEventOverView(View view, MotionEvent ev) { 139 getDescendantRectRelativeToSelf(view, mHitRect); 140 return mHitRect.contains((int) ev.getX(), (int) ev.getY()); 141 } 142 143 /** 144 * Given a motion event in evView's coordinates, return whether the event is within another 145 * view's bounds. 146 */ isEventOverView(View view, MotionEvent ev, View evView)147 public boolean isEventOverView(View view, MotionEvent ev, View evView) { 148 int[] xy = new int[] {(int) ev.getX(), (int) ev.getY()}; 149 getDescendantCoordRelativeToSelf(evView, xy); 150 getDescendantRectRelativeToSelf(view, mHitRect); 151 return mHitRect.contains(xy[0], xy[1]); 152 } 153 154 @Override onInterceptTouchEvent(MotionEvent ev)155 public boolean onInterceptTouchEvent(MotionEvent ev) { 156 int action = ev.getAction(); 157 158 if (action == ACTION_UP || action == ACTION_CANCEL) { 159 if (mTouchCompleteListener != null) { 160 mTouchCompleteListener.onTouchComplete(); 161 } 162 mTouchCompleteListener = null; 163 } else if (action == MotionEvent.ACTION_DOWN) { 164 mActivity.finishAutoCancelActionMode(); 165 } 166 return findActiveController(ev); 167 } 168 isEventInLauncher(MotionEvent ev)169 private boolean isEventInLauncher(MotionEvent ev) { 170 final float x = ev.getX(); 171 final float y = ev.getY(); 172 173 return x >= mSystemGestureRegion.left && x < getWidth() - mSystemGestureRegion.right 174 && y >= mSystemGestureRegion.top && y < getHeight() - mSystemGestureRegion.bottom; 175 } 176 findControllerToHandleTouch(MotionEvent ev)177 private TouchController findControllerToHandleTouch(MotionEvent ev) { 178 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity); 179 if (topView != null 180 && (isEventInLauncher(ev) || topView.canInterceptEventsInSystemGestureRegion()) 181 && topView.onControllerInterceptTouchEvent(ev)) { 182 return topView; 183 } 184 185 for (TouchController controller : mControllers) { 186 if (controller.onControllerInterceptTouchEvent(ev)) { 187 return controller; 188 } 189 } 190 return null; 191 } 192 findActiveController(MotionEvent ev)193 protected boolean findActiveController(MotionEvent ev) { 194 mActiveController = null; 195 if (canFindActiveController()) { 196 mActiveController = findControllerToHandleTouch(ev); 197 } 198 return mActiveController != null; 199 } 200 canFindActiveController()201 protected boolean canFindActiveController() { 202 // Only look for controllers if we are not dispatching from gesture area and proxy is 203 // not active 204 return (mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION 205 | TOUCH_DISPATCHING_FROM_PROXY)) == 0; 206 } 207 208 @Override onRequestSendAccessibilityEvent(View child, AccessibilityEvent event)209 public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { 210 // Shortcuts can appear above folder 211 View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity, 212 AbstractFloatingView.TYPE_ACCESSIBLE); 213 if (topView != null) { 214 if (child == topView) { 215 return super.onRequestSendAccessibilityEvent(child, event); 216 } 217 // Skip propagating onRequestSendAccessibilityEvent for all other children 218 // which are not topView 219 return false; 220 } 221 return super.onRequestSendAccessibilityEvent(child, event); 222 } 223 224 @Override addChildrenForAccessibility(ArrayList<View> childrenForAccessibility)225 public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) { 226 View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity, 227 AbstractFloatingView.TYPE_ACCESSIBLE); 228 if (topView != null) { 229 // Only add the top view as a child for accessibility when it is open 230 addAccessibleChildToList(topView, childrenForAccessibility); 231 } else { 232 super.addChildrenForAccessibility(childrenForAccessibility); 233 } 234 } 235 addAccessibleChildToList(View child, ArrayList<View> outList)236 protected void addAccessibleChildToList(View child, ArrayList<View> outList) { 237 if (child.isImportantForAccessibility()) { 238 outList.add(child); 239 } else { 240 child.addChildrenForAccessibility(outList); 241 } 242 } 243 244 @Override onViewRemoved(View child)245 public void onViewRemoved(View child) { 246 super.onViewRemoved(child); 247 if (child instanceof AbstractFloatingView) { 248 // Handles the case where the view is removed without being properly closed. 249 // This can happen if something goes wrong during a state change/transition. 250 AbstractFloatingView floatingView = (AbstractFloatingView) child; 251 if (floatingView.isOpen()) { 252 postDelayed(() -> floatingView.close(false), getSingleFrameMs(getContext())); 253 } 254 } 255 } 256 257 @Override onTouchEvent(MotionEvent ev)258 public boolean onTouchEvent(MotionEvent ev) { 259 int action = ev.getAction(); 260 if (action == ACTION_UP || action == ACTION_CANCEL) { 261 if (mTouchCompleteListener != null) { 262 mTouchCompleteListener.onTouchComplete(); 263 } 264 mTouchCompleteListener = null; 265 } 266 267 if (mActiveController != null) { 268 return mActiveController.onControllerTouchEvent(ev); 269 } else { 270 // In case no child view handled the touch event, we may not get onIntercept anymore 271 return findActiveController(ev); 272 } 273 } 274 275 @Override dispatchTouchEvent(MotionEvent ev)276 public boolean dispatchTouchEvent(MotionEvent ev) { 277 switch (ev.getAction()) { 278 case ACTION_DOWN: { 279 if ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0) { 280 // Cancel the previous touch 281 int action = ev.getAction(); 282 ev.setAction(ACTION_CANCEL); 283 super.dispatchTouchEvent(ev); 284 ev.setAction(action); 285 } 286 mTouchDispatchState |= TOUCH_DISPATCHING_FROM_VIEW 287 | TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS; 288 289 if (isEventInLauncher(ev)) { 290 mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION; 291 } else { 292 mTouchDispatchState |= TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION; 293 } 294 break; 295 } 296 case ACTION_CANCEL: 297 case ACTION_UP: 298 mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION; 299 mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW; 300 mTouchDispatchState &= ~TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS; 301 break; 302 } 303 super.dispatchTouchEvent(ev); 304 305 // We want to get all events so that mTouchDispatchSource is maintained properly 306 return true; 307 } 308 309 /** 310 * Proxies the touch events to the gesture handlers 311 */ proxyTouchEvent(MotionEvent ev, boolean allowViewDispatch)312 public boolean proxyTouchEvent(MotionEvent ev, boolean allowViewDispatch) { 313 int actionMasked = ev.getActionMasked(); 314 boolean isViewDispatching = (mTouchDispatchState & TOUCH_DISPATCHING_FROM_VIEW) != 0; 315 316 // Only do view dispatch if another view-dispatching is not running, or we already started 317 // proxy-dispatching before. Note that view-dispatching can always take over the proxy 318 // dispatching at anytime, but not vice-versa. 319 allowViewDispatch = allowViewDispatch && !isViewDispatching 320 && (actionMasked == ACTION_DOWN 321 || ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0)); 322 323 if (allowViewDispatch) { 324 mTouchDispatchState |= TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS; 325 super.dispatchTouchEvent(ev); 326 327 if (actionMasked == ACTION_UP || actionMasked == ACTION_CANCEL) { 328 mTouchDispatchState &= ~TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS; 329 mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY; 330 } 331 return true; 332 } else { 333 boolean handled; 334 if (mProxyTouchController != null) { 335 handled = mProxyTouchController.onControllerTouchEvent(ev); 336 } else { 337 if (actionMasked == ACTION_DOWN) { 338 if (isViewDispatching && mActiveController != null) { 339 // A controller is already active, we can't initiate our own controller 340 mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY; 341 } else { 342 // We will control the handler via proxy 343 mTouchDispatchState |= TOUCH_DISPATCHING_FROM_PROXY; 344 } 345 } 346 if ((mTouchDispatchState & TOUCH_DISPATCHING_FROM_PROXY) != 0) { 347 mProxyTouchController = findControllerToHandleTouch(ev); 348 } 349 handled = mProxyTouchController != null; 350 } 351 if (actionMasked == ACTION_UP || actionMasked == ACTION_CANCEL) { 352 mProxyTouchController = null; 353 mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY; 354 } 355 return handled; 356 } 357 } 358 359 /** 360 * Determine the rect of the descendant in this DragLayer's coordinates 361 * 362 * @param descendant The descendant whose coordinates we want to find. 363 * @param r The rect into which to place the results. 364 * @return The factor by which this descendant is scaled relative to this DragLayer. 365 */ getDescendantRectRelativeToSelf(View descendant, Rect r)366 public float getDescendantRectRelativeToSelf(View descendant, Rect r) { 367 mTmpRectPoints[0] = 0; 368 mTmpRectPoints[1] = 0; 369 mTmpRectPoints[2] = descendant.getWidth(); 370 mTmpRectPoints[3] = descendant.getHeight(); 371 float s = getDescendantCoordRelativeToSelf(descendant, mTmpRectPoints); 372 r.left = Math.round(Math.min(mTmpRectPoints[0], mTmpRectPoints[2])); 373 r.top = Math.round(Math.min(mTmpRectPoints[1], mTmpRectPoints[3])); 374 r.right = Math.round(Math.max(mTmpRectPoints[0], mTmpRectPoints[2])); 375 r.bottom = Math.round(Math.max(mTmpRectPoints[1], mTmpRectPoints[3])); 376 return s; 377 } 378 getLocationInDragLayer(View child, int[] loc)379 public float getLocationInDragLayer(View child, int[] loc) { 380 loc[0] = 0; 381 loc[1] = 0; 382 return getDescendantCoordRelativeToSelf(child, loc); 383 } 384 getDescendantCoordRelativeToSelf(View descendant, int[] coord)385 public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) { 386 mTmpXY[0] = coord[0]; 387 mTmpXY[1] = coord[1]; 388 float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY); 389 Utilities.roundArray(mTmpXY, coord); 390 return scale; 391 } 392 getDescendantCoordRelativeToSelf(View descendant, float[] coord)393 public float getDescendantCoordRelativeToSelf(View descendant, float[] coord) { 394 return getDescendantCoordRelativeToSelf(descendant, coord, false); 395 } 396 397 /** 398 * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's 399 * coordinates. 400 * 401 * @param descendant The descendant to which the passed coordinate is relative. 402 * @param coord The coordinate that we want mapped. 403 * @param includeRootScroll Whether or not to account for the scroll of the root descendant: 404 * sometimes this is relevant as in a child's coordinates within the root descendant. 405 * @return The factor by which this descendant is scaled relative to this DragLayer. Caution 406 * this scale factor is assumed to be equal in X and Y, and so if at any point this 407 * assumption fails, we will need to return a pair of scale factors. 408 */ getDescendantCoordRelativeToSelf(View descendant, float[] coord, boolean includeRootScroll)409 public float getDescendantCoordRelativeToSelf(View descendant, float[] coord, 410 boolean includeRootScroll) { 411 return Utilities.getDescendantCoordRelativeToAncestor(descendant, this, 412 coord, includeRootScroll); 413 } 414 415 /** 416 * Inverse of {@link #getDescendantCoordRelativeToSelf(View, float[])}. 417 */ mapCoordInSelfToDescendant(View descendant, float[] coord)418 public void mapCoordInSelfToDescendant(View descendant, float[] coord) { 419 Utilities.mapCoordInSelfToDescendant(descendant, this, coord); 420 } 421 422 /** 423 * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}. 424 */ mapCoordInSelfToDescendant(View descendant, int[] coord)425 public void mapCoordInSelfToDescendant(View descendant, int[] coord) { 426 mTmpXY[0] = coord[0]; 427 mTmpXY[1] = coord[1]; 428 Utilities.mapCoordInSelfToDescendant(descendant, this, mTmpXY); 429 Utilities.roundArray(mTmpXY, coord); 430 } 431 getViewRectRelativeToSelf(View v, Rect r)432 public void getViewRectRelativeToSelf(View v, Rect r) { 433 int[] loc = new int[2]; 434 getLocationInWindow(loc); 435 int x = loc[0]; 436 int y = loc[1]; 437 438 v.getLocationInWindow(loc); 439 int vX = loc[0]; 440 int vY = loc[1]; 441 442 int left = vX - x; 443 int top = vY - y; 444 r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight()); 445 } 446 447 @Override onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)448 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 449 View topView = AbstractFloatingView.getTopOpenView(mActivity); 450 if (topView != null) { 451 return topView.requestFocus(direction, previouslyFocusedRect); 452 } else { 453 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 454 } 455 } 456 457 @Override addFocusables(ArrayList<View> views, int direction, int focusableMode)458 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 459 View topView = AbstractFloatingView.getTopOpenView(mActivity); 460 if (topView != null) { 461 topView.addFocusables(views, direction); 462 } else { 463 super.addFocusables(views, direction, focusableMode); 464 } 465 } 466 setTouchCompleteListener(TouchCompleteListener listener)467 public void setTouchCompleteListener(TouchCompleteListener listener) { 468 mTouchCompleteListener = listener; 469 } 470 471 public interface TouchCompleteListener { onTouchComplete()472 void onTouchComplete(); 473 } 474 475 @Override generateLayoutParams(AttributeSet attrs)476 public LayoutParams generateLayoutParams(AttributeSet attrs) { 477 return new LayoutParams(getContext(), attrs); 478 } 479 480 @Override generateDefaultLayoutParams()481 protected LayoutParams generateDefaultLayoutParams() { 482 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 483 } 484 485 // Override to allow type-checking of LayoutParams. 486 @Override checkLayoutParams(ViewGroup.LayoutParams p)487 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 488 return p instanceof LayoutParams; 489 } 490 491 @Override generateLayoutParams(ViewGroup.LayoutParams p)492 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 493 return new LayoutParams(p); 494 } 495 getAlphaProperty(int index)496 public AlphaProperty getAlphaProperty(int index) { 497 return mMultiValueAlpha.getProperty(index); 498 } 499 dump(String prefix, PrintWriter writer)500 public void dump(String prefix, PrintWriter writer) { 501 writer.println(prefix + "DragLayer:"); 502 if (mActiveController != null) { 503 writer.println(prefix + "\tactiveController: " + mActiveController); 504 mActiveController.dump(prefix + "\t", writer); 505 } 506 writer.println(prefix + "\tdragLayerAlpha : " + mMultiValueAlpha ); 507 } 508 509 public static class LayoutParams extends InsettableFrameLayout.LayoutParams { 510 public int x, y; 511 public boolean customPosition = false; 512 LayoutParams(Context c, AttributeSet attrs)513 public LayoutParams(Context c, AttributeSet attrs) { 514 super(c, attrs); 515 } 516 LayoutParams(int width, int height)517 public LayoutParams(int width, int height) { 518 super(width, height); 519 } 520 LayoutParams(ViewGroup.LayoutParams lp)521 public LayoutParams(ViewGroup.LayoutParams lp) { 522 super(lp); 523 } 524 } 525 onLayout(boolean changed, int l, int t, int r, int b)526 protected void onLayout(boolean changed, int l, int t, int r, int b) { 527 super.onLayout(changed, l, t, r, b); 528 int count = getChildCount(); 529 for (int i = 0; i < count; i++) { 530 View child = getChildAt(i); 531 final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams(); 532 if (flp instanceof LayoutParams) { 533 final LayoutParams lp = (LayoutParams) flp; 534 if (lp.customPosition) { 535 child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height); 536 } 537 } 538 } 539 } 540 541 @Override 542 @TargetApi(Build.VERSION_CODES.Q) dispatchApplyWindowInsets(WindowInsets insets)543 public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { 544 if (Utilities.ATLEAST_Q) { 545 Insets gestureInsets = insets.getMandatorySystemGestureInsets(); 546 mSystemGestureRegion.set(gestureInsets.left, gestureInsets.top, 547 gestureInsets.right, gestureInsets.bottom); 548 } 549 return super.dispatchApplyWindowInsets(insets); 550 } 551 } 552