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