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 com.android.launcher3.Utilities.SINGLE_FRAME_MS; 20 21 import android.content.Context; 22 import android.graphics.Rect; 23 import android.util.AttributeSet; 24 import android.view.MotionEvent; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.view.accessibility.AccessibilityEvent; 28 import android.widget.FrameLayout; 29 30 import com.android.launcher3.AbstractFloatingView; 31 import com.android.launcher3.BaseActivity; 32 import com.android.launcher3.BaseDraggingActivity; 33 import com.android.launcher3.InsettableFrameLayout; 34 import com.android.launcher3.Utilities; 35 import com.android.launcher3.util.MultiValueAlpha; 36 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; 37 import com.android.launcher3.util.TouchController; 38 39 import java.util.ArrayList; 40 41 /** 42 * A viewgroup with utility methods for drag-n-drop and touch interception 43 */ 44 public abstract class BaseDragLayer<T extends BaseDraggingActivity> extends InsettableFrameLayout { 45 46 protected final int[] mTmpXY = new int[2]; 47 protected final Rect mHitRect = new Rect(); 48 49 protected final T mActivity; 50 private final MultiValueAlpha mMultiValueAlpha; 51 52 protected TouchController[] mControllers; 53 protected TouchController mActiveController; 54 private TouchCompleteListener mTouchCompleteListener; 55 56 public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) { 57 super(context, attrs); 58 mActivity = (T) BaseActivity.fromContext(context); 59 mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount); 60 } 61 62 public boolean isEventOverView(View view, MotionEvent ev) { 63 getDescendantRectRelativeToSelf(view, mHitRect); 64 return mHitRect.contains((int) ev.getX(), (int) ev.getY()); 65 } 66 67 @Override 68 public boolean onInterceptTouchEvent(MotionEvent ev) { 69 int action = ev.getAction(); 70 71 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 72 if (mTouchCompleteListener != null) { 73 mTouchCompleteListener.onTouchComplete(); 74 } 75 mTouchCompleteListener = null; 76 } else if (action == MotionEvent.ACTION_DOWN) { 77 mActivity.finishAutoCancelActionMode(); 78 } 79 return findActiveController(ev); 80 } 81 82 protected boolean findActiveController(MotionEvent ev) { 83 mActiveController = null; 84 85 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity); 86 if (topView != null && topView.onControllerInterceptTouchEvent(ev)) { 87 mActiveController = topView; 88 return true; 89 } 90 91 for (TouchController controller : mControllers) { 92 if (controller.onControllerInterceptTouchEvent(ev)) { 93 mActiveController = controller; 94 return true; 95 } 96 } 97 return false; 98 } 99 100 @Override 101 public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { 102 // Shortcuts can appear above folder 103 View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity, 104 AbstractFloatingView.TYPE_ACCESSIBLE); 105 if (topView != null) { 106 if (child == topView) { 107 return super.onRequestSendAccessibilityEvent(child, event); 108 } 109 // Skip propagating onRequestSendAccessibilityEvent for all other children 110 // which are not topView 111 return false; 112 } 113 return super.onRequestSendAccessibilityEvent(child, event); 114 } 115 116 @Override 117 public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) { 118 View topView = AbstractFloatingView.getTopOpenViewWithType(mActivity, 119 AbstractFloatingView.TYPE_ACCESSIBLE); 120 if (topView != null) { 121 // Only add the top view as a child for accessibility when it is open 122 addAccessibleChildToList(topView, childrenForAccessibility); 123 } else { 124 super.addChildrenForAccessibility(childrenForAccessibility); 125 } 126 } 127 128 protected void addAccessibleChildToList(View child, ArrayList<View> outList) { 129 if (child.isImportantForAccessibility()) { 130 outList.add(child); 131 } else { 132 child.addChildrenForAccessibility(outList); 133 } 134 } 135 136 @Override 137 public void onViewRemoved(View child) { 138 super.onViewRemoved(child); 139 if (child instanceof AbstractFloatingView) { 140 // Handles the case where the view is removed without being properly closed. 141 // This can happen if something goes wrong during a state change/transition. 142 postDelayed(() -> { 143 AbstractFloatingView floatingView = (AbstractFloatingView) child; 144 if (floatingView.isOpen()) { 145 floatingView.close(false); 146 } 147 }, SINGLE_FRAME_MS); 148 } 149 } 150 151 @Override 152 public boolean onTouchEvent(MotionEvent ev) { 153 int action = ev.getAction(); 154 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 155 if (mTouchCompleteListener != null) { 156 mTouchCompleteListener.onTouchComplete(); 157 } 158 mTouchCompleteListener = null; 159 } 160 161 if (mActiveController != null) { 162 return mActiveController.onControllerTouchEvent(ev); 163 } else { 164 // In case no child view handled the touch event, we may not get onIntercept anymore 165 return findActiveController(ev); 166 } 167 } 168 169 /** 170 * Determine the rect of the descendant in this DragLayer's coordinates 171 * 172 * @param descendant The descendant whose coordinates we want to find. 173 * @param r The rect into which to place the results. 174 * @return The factor by which this descendant is scaled relative to this DragLayer. 175 */ 176 public float getDescendantRectRelativeToSelf(View descendant, Rect r) { 177 mTmpXY[0] = 0; 178 mTmpXY[1] = 0; 179 float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY); 180 181 r.set(mTmpXY[0], mTmpXY[1], 182 (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()), 183 (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight())); 184 return scale; 185 } 186 187 public float getLocationInDragLayer(View child, int[] loc) { 188 loc[0] = 0; 189 loc[1] = 0; 190 return getDescendantCoordRelativeToSelf(child, loc); 191 } 192 193 public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) { 194 return getDescendantCoordRelativeToSelf(descendant, coord, false); 195 } 196 197 /** 198 * Given a coordinate relative to the descendant, find the coordinate in this DragLayer's 199 * coordinates. 200 * 201 * @param descendant The descendant to which the passed coordinate is relative. 202 * @param coord The coordinate that we want mapped. 203 * @param includeRootScroll Whether or not to account for the scroll of the root descendant: 204 * sometimes this is relevant as in a child's coordinates within the root descendant. 205 * @return The factor by which this descendant is scaled relative to this DragLayer. Caution 206 * this scale factor is assumed to be equal in X and Y, and so if at any point this 207 * assumption fails, we will need to return a pair of scale factors. 208 */ 209 public float getDescendantCoordRelativeToSelf(View descendant, int[] coord, 210 boolean includeRootScroll) { 211 return Utilities.getDescendantCoordRelativeToAncestor(descendant, this, 212 coord, includeRootScroll); 213 } 214 215 /** 216 * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}. 217 */ 218 public void mapCoordInSelfToDescendant(View descendant, int[] coord) { 219 Utilities.mapCoordInSelfToDescendant(descendant, this, coord); 220 } 221 222 public void getViewRectRelativeToSelf(View v, Rect r) { 223 int[] loc = new int[2]; 224 getLocationInWindow(loc); 225 int x = loc[0]; 226 int y = loc[1]; 227 228 v.getLocationInWindow(loc); 229 int vX = loc[0]; 230 int vY = loc[1]; 231 232 int left = vX - x; 233 int top = vY - y; 234 r.set(left, top, left + v.getMeasuredWidth(), top + v.getMeasuredHeight()); 235 } 236 237 @Override 238 public boolean dispatchUnhandledMove(View focused, int direction) { 239 // Consume the unhandled move if a container is open, to avoid switching pages underneath. 240 return AbstractFloatingView.getTopOpenView(mActivity) != null; 241 } 242 243 @Override 244 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 245 View topView = AbstractFloatingView.getTopOpenView(mActivity); 246 if (topView != null) { 247 return topView.requestFocus(direction, previouslyFocusedRect); 248 } else { 249 return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); 250 } 251 } 252 253 @Override 254 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 255 View topView = AbstractFloatingView.getTopOpenView(mActivity); 256 if (topView != null) { 257 topView.addFocusables(views, direction); 258 } else { 259 super.addFocusables(views, direction, focusableMode); 260 } 261 } 262 263 public void setTouchCompleteListener(TouchCompleteListener listener) { 264 mTouchCompleteListener = listener; 265 } 266 267 public interface TouchCompleteListener { 268 void onTouchComplete(); 269 } 270 271 @Override 272 public LayoutParams generateLayoutParams(AttributeSet attrs) { 273 return new LayoutParams(getContext(), attrs); 274 } 275 276 @Override 277 protected LayoutParams generateDefaultLayoutParams() { 278 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 279 } 280 281 // Override to allow type-checking of LayoutParams. 282 @Override 283 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 284 return p instanceof LayoutParams; 285 } 286 287 @Override 288 protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 289 return new LayoutParams(p); 290 } 291 292 public AlphaProperty getAlphaProperty(int index) { 293 return mMultiValueAlpha.getProperty(index); 294 } 295 296 public static class LayoutParams extends InsettableFrameLayout.LayoutParams { 297 public int x, y; 298 public boolean customPosition = false; 299 300 public LayoutParams(Context c, AttributeSet attrs) { 301 super(c, attrs); 302 } 303 304 public LayoutParams(int width, int height) { 305 super(width, height); 306 } 307 308 public LayoutParams(ViewGroup.LayoutParams lp) { 309 super(lp); 310 } 311 312 public void setWidth(int width) { 313 this.width = width; 314 } 315 316 public int getWidth() { 317 return width; 318 } 319 320 public void setHeight(int height) { 321 this.height = height; 322 } 323 324 public int getHeight() { 325 return height; 326 } 327 328 public void setX(int x) { 329 this.x = x; 330 } 331 332 public int getX() { 333 return x; 334 } 335 336 public void setY(int y) { 337 this.y = y; 338 } 339 340 public int getY() { 341 return y; 342 } 343 } 344 345 protected void onLayout(boolean changed, int l, int t, int r, int b) { 346 super.onLayout(changed, l, t, r, b); 347 int count = getChildCount(); 348 for (int i = 0; i < count; i++) { 349 View child = getChildAt(i); 350 final FrameLayout.LayoutParams flp = (FrameLayout.LayoutParams) child.getLayoutParams(); 351 if (flp instanceof LayoutParams) { 352 final LayoutParams lp = (LayoutParams) flp; 353 if (lp.customPosition) { 354 child.layout(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height); 355 } 356 } 357 } 358 } 359 } 360