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