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