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