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