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