Home | History | Annotate | Download | only in views
      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