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