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