Home | History | Annotate | Download | only in views
      1 /*
      2  * Copyright (C) 2014 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.systemui.recents.views;
     18 
     19 import android.app.ActivityManager;
     20 import android.graphics.Point;
     21 import android.graphics.Rect;
     22 import android.view.InputDevice;
     23 import android.view.MotionEvent;
     24 import android.view.PointerIcon;
     25 import android.view.View;
     26 import android.view.ViewConfiguration;
     27 import android.view.ViewDebug;
     28 
     29 import com.android.internal.policy.DividerSnapAlgorithm;
     30 import com.android.systemui.recents.Recents;
     31 import com.android.systemui.recents.events.EventBus;
     32 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
     33 import com.android.systemui.recents.events.activity.HideRecentsEvent;
     34 import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
     35 import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent;
     36 import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
     37 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
     38 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
     39 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
     40 import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
     41 import com.android.systemui.recents.misc.SystemServicesProxy;
     42 import com.android.systemui.shared.recents.model.Task;
     43 
     44 import java.util.ArrayList;
     45 
     46 /**
     47  * Handles touch events for a RecentsView.
     48  */
     49 public class RecentsViewTouchHandler {
     50 
     51     private RecentsView mRv;
     52 
     53     @ViewDebug.ExportedProperty(deepExport=true, prefix="drag_task")
     54     private Task mDragTask;
     55     @ViewDebug.ExportedProperty(deepExport=true, prefix="drag_task_view_")
     56     private TaskView mTaskView;
     57 
     58     @ViewDebug.ExportedProperty(category="recents")
     59     private Point mTaskViewOffset = new Point();
     60     @ViewDebug.ExportedProperty(category="recents")
     61     private Point mDownPos = new Point();
     62     @ViewDebug.ExportedProperty(category="recents")
     63     private boolean mDragRequested;
     64     @ViewDebug.ExportedProperty(category="recents")
     65     private boolean mIsDragging;
     66     private float mDragSlop;
     67     private int mDeviceId = -1;
     68 
     69     private DropTarget mLastDropTarget;
     70     private DividerSnapAlgorithm mDividerSnapAlgorithm;
     71     private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
     72     private ArrayList<DockState> mVisibleDockStates = new ArrayList<>();
     73 
     74     public RecentsViewTouchHandler(RecentsView rv) {
     75         mRv = rv;
     76         mDragSlop = ViewConfiguration.get(rv.getContext()).getScaledTouchSlop();
     77         updateSnapAlgorithm();
     78     }
     79 
     80     private void updateSnapAlgorithm() {
     81         Rect insets = new Rect();
     82         SystemServicesProxy.getInstance(mRv.getContext()).getStableInsets(insets);
     83         mDividerSnapAlgorithm = DividerSnapAlgorithm.create(mRv.getContext(), insets);
     84     }
     85 
     86     /**
     87      * Registers a new drop target for the current drag only.
     88      */
     89     public void registerDropTargetForCurrentDrag(DropTarget target) {
     90         mDropTargets.add(target);
     91     }
     92 
     93     /**
     94      * Returns the set of visible dock states for this current drag.
     95      */
     96     public ArrayList<DockState> getVisibleDockStates() {
     97         return mVisibleDockStates;
     98     }
     99 
    100     /** Touch preprocessing for handling below */
    101     public boolean onInterceptTouchEvent(MotionEvent ev) {
    102         return handleTouchEvent(ev) || mDragRequested;
    103     }
    104 
    105     /** Handles touch events once we have intercepted them */
    106     public boolean onTouchEvent(MotionEvent ev) {
    107         handleTouchEvent(ev);
    108         if (ev.getAction() == MotionEvent.ACTION_UP && mRv.getStack().getTaskCount() == 0) {
    109             EventBus.getDefault().send(new HideRecentsEvent(false, true));
    110         }
    111         return true;
    112     }
    113 
    114     /**** Events ****/
    115 
    116     public final void onBusEvent(DragStartEvent event) {
    117         SystemServicesProxy ssp = Recents.getSystemServices();
    118         mRv.getParent().requestDisallowInterceptTouchEvent(true);
    119         mDragRequested = true;
    120         // We defer starting the actual drag handling until the user moves past the drag slop
    121         mIsDragging = false;
    122         mDragTask = event.task;
    123         mTaskView = event.taskView;
    124         mDropTargets.clear();
    125 
    126         int[] recentsViewLocation = new int[2];
    127         mRv.getLocationInWindow(recentsViewLocation);
    128         mTaskViewOffset.set(mTaskView.getLeft() - recentsViewLocation[0] + event.tlOffset.x,
    129                 mTaskView.getTop() - recentsViewLocation[1] + event.tlOffset.y);
    130 
    131         // Change space coordinates relative to the view to RecentsView when user initiates a touch
    132         if (event.isUserTouchInitiated) {
    133             float x = mDownPos.x - mTaskViewOffset.x;
    134             float y = mDownPos.y - mTaskViewOffset.y;
    135             mTaskView.setTranslationX(x);
    136             mTaskView.setTranslationY(y);
    137         }
    138 
    139         mVisibleDockStates.clear();
    140         if (ActivityManager.supportsMultiWindow(mRv.getContext()) && !ssp.hasDockedTask()
    141                 && mDividerSnapAlgorithm.isSplitScreenFeasible()) {
    142             Recents.logDockAttempt(mRv.getContext(), event.task.getTopComponent(),
    143                     event.task.resizeMode);
    144             if (!event.task.isDockable) {
    145                 EventBus.getDefault().send(new ShowIncompatibleAppOverlayEvent());
    146             } else {
    147                 // Add the dock state drop targets (these take priority)
    148                 DockState[] dockStates = Recents.getConfiguration()
    149                         .getDockStatesForCurrentOrientation();
    150                 for (DockState dockState : dockStates) {
    151                     registerDropTargetForCurrentDrag(dockState);
    152                     dockState.update(mRv.getContext());
    153                     mVisibleDockStates.add(dockState);
    154                 }
    155             }
    156         }
    157 
    158         // Request other drop targets to register themselves
    159         EventBus.getDefault().send(new DragStartInitializeDropTargetsEvent(event.task,
    160                 event.taskView, this));
    161         if (mDeviceId != -1) {
    162             InputDevice device = InputDevice.getDevice(mDeviceId);
    163             if (device != null) {
    164                 device.setPointerType(PointerIcon.TYPE_GRABBING);
    165             }
    166         }
    167     }
    168 
    169     public final void onBusEvent(DragEndEvent event) {
    170         if (!mDragTask.isDockable) {
    171             EventBus.getDefault().send(new HideIncompatibleAppOverlayEvent());
    172         }
    173         mDragRequested = false;
    174         mDragTask = null;
    175         mTaskView = null;
    176         mLastDropTarget = null;
    177     }
    178 
    179     public final void onBusEvent(ConfigurationChangedEvent event) {
    180         if (event.fromDisplayDensityChange || event.fromDeviceOrientationChange) {
    181             updateSnapAlgorithm();
    182         }
    183     }
    184 
    185     void cancelStackActionButtonClick() {
    186         mRv.getStackActionButton().setPressed(false);
    187     }
    188 
    189     private boolean isWithinStackActionButton(float x, float y) {
    190         Rect rect = mRv.getStackActionButtonBoundsFromStackLayout();
    191         return mRv.getStackActionButton().getVisibility() == View.VISIBLE &&
    192                 mRv.getStackActionButton().pointInView(x - rect.left, y - rect.top, 0 /* slop */);
    193     }
    194 
    195     private void changeStackActionButtonDrawableHotspot(float x, float y) {
    196         Rect rect = mRv.getStackActionButtonBoundsFromStackLayout();
    197         mRv.getStackActionButton().drawableHotspotChanged(x - rect.left, y - rect.top);
    198     }
    199 
    200     /**
    201      * Handles dragging touch events
    202      */
    203     private boolean handleTouchEvent(MotionEvent ev) {
    204         int action = ev.getActionMasked();
    205         boolean consumed = false;
    206         float evX = ev.getX();
    207         float evY = ev.getY();
    208         switch (action) {
    209             case MotionEvent.ACTION_DOWN:
    210                 mDownPos.set((int) evX, (int) evY);
    211                 mDeviceId = ev.getDeviceId();
    212 
    213                 if (isWithinStackActionButton(evX, evY)) {
    214                     changeStackActionButtonDrawableHotspot(evX, evY);
    215                     mRv.getStackActionButton().setPressed(true);
    216                 }
    217                 break;
    218             case MotionEvent.ACTION_MOVE: {
    219                 float x = evX - mTaskViewOffset.x;
    220                 float y = evY - mTaskViewOffset.y;
    221 
    222                 if (mRv.getStackActionButton().isPressed() && isWithinStackActionButton(evX, evY)) {
    223                     changeStackActionButtonDrawableHotspot(evX, evY);
    224                 }
    225 
    226                 if (mDragRequested) {
    227                     if (!mIsDragging) {
    228                         mIsDragging = Math.hypot(evX - mDownPos.x, evY - mDownPos.y) > mDragSlop;
    229                     }
    230                     if (mIsDragging) {
    231                         int width = mRv.getMeasuredWidth();
    232                         int height = mRv.getMeasuredHeight();
    233 
    234                         DropTarget currentDropTarget = null;
    235 
    236                         // Give priority to the current drop target to retain the touch handling
    237                         if (mLastDropTarget != null) {
    238                             if (mLastDropTarget.acceptsDrop((int) evX, (int) evY, width, height,
    239                                     mRv.mSystemInsets, true /* isCurrentTarget */)) {
    240                                 currentDropTarget = mLastDropTarget;
    241                             }
    242                         }
    243 
    244                         // Otherwise, find the next target to handle this event
    245                         if (currentDropTarget == null) {
    246                             for (DropTarget target : mDropTargets) {
    247                                 if (target.acceptsDrop((int) evX, (int) evY, width, height,
    248                                         mRv.mSystemInsets, false /* isCurrentTarget */)) {
    249                                     currentDropTarget = target;
    250                                     break;
    251                                 }
    252                             }
    253                         }
    254                         if (mLastDropTarget != currentDropTarget) {
    255                             mLastDropTarget = currentDropTarget;
    256                             EventBus.getDefault().send(new DragDropTargetChangedEvent(mDragTask,
    257                                     currentDropTarget));
    258                         }
    259                     }
    260                     mTaskView.setTranslationX(x);
    261                     mTaskView.setTranslationY(y);
    262                 }
    263                 break;
    264             }
    265             case MotionEvent.ACTION_UP:
    266             case MotionEvent.ACTION_CANCEL: {
    267                 if (mRv.getStackActionButton().isPressed() && isWithinStackActionButton(evX, evY)) {
    268                     EventBus.getDefault().send(new DismissAllTaskViewsEvent());
    269                     consumed = true;
    270                 }
    271                 cancelStackActionButtonClick();
    272                 if (mDragRequested) {
    273                     boolean cancelled = action == MotionEvent.ACTION_CANCEL;
    274                     if (cancelled) {
    275                         EventBus.getDefault().send(new DragDropTargetChangedEvent(mDragTask, null));
    276                     }
    277                     EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView,
    278                             !cancelled ? mLastDropTarget : null));
    279                     break;
    280                 }
    281                 mDeviceId = -1;
    282             }
    283         }
    284         return consumed;
    285     }
    286 }
    287