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