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