1 /* 2 * Copyright (C) 2021 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 package com.android.launcher3.taskbar; 17 18 import android.content.ClipData; 19 import android.content.ClipDescription; 20 import android.content.Intent; 21 import android.content.pm.LauncherApps; 22 import android.content.res.Resources; 23 import android.graphics.Canvas; 24 import android.graphics.Point; 25 import android.graphics.Rect; 26 import android.graphics.drawable.Drawable; 27 import android.os.UserHandle; 28 import android.view.DragEvent; 29 import android.view.MotionEvent; 30 import android.view.View; 31 32 import androidx.annotation.Nullable; 33 34 import com.android.internal.logging.InstanceId; 35 import com.android.internal.logging.InstanceIdSequence; 36 import com.android.launcher3.AbstractFloatingView; 37 import com.android.launcher3.BubbleTextView; 38 import com.android.launcher3.DragSource; 39 import com.android.launcher3.DropTarget; 40 import com.android.launcher3.LauncherSettings; 41 import com.android.launcher3.R; 42 import com.android.launcher3.accessibility.DragViewStateAnnouncer; 43 import com.android.launcher3.config.FeatureFlags; 44 import com.android.launcher3.dragndrop.DragController; 45 import com.android.launcher3.dragndrop.DragDriver; 46 import com.android.launcher3.dragndrop.DragOptions; 47 import com.android.launcher3.dragndrop.DragView; 48 import com.android.launcher3.dragndrop.DraggableView; 49 import com.android.launcher3.graphics.DragPreviewProvider; 50 import com.android.launcher3.logging.StatsLogManager; 51 import com.android.launcher3.model.data.ItemInfo; 52 import com.android.launcher3.model.data.WorkspaceItemInfo; 53 import com.android.launcher3.popup.PopupContainerWithArrow; 54 import com.android.systemui.shared.recents.model.Task; 55 import com.android.systemui.shared.system.ClipDescriptionCompat; 56 import com.android.systemui.shared.system.LauncherAppsCompat; 57 58 /** 59 * Handles long click on Taskbar items to start a system drag and drop operation. 60 */ 61 public class TaskbarDragController extends DragController<TaskbarActivityContext> { 62 63 private final int mDragIconSize; 64 private final int[] mTempXY = new int[2]; 65 66 // Initialized in init. 67 TaskbarControllers mControllers; 68 69 // Where the initial touch was relative to the dragged icon. 70 private int mRegistrationX; 71 private int mRegistrationY; 72 73 private boolean mIsSystemDragInProgress; 74 TaskbarDragController(TaskbarActivityContext activity)75 public TaskbarDragController(TaskbarActivityContext activity) { 76 super(activity); 77 Resources resources = mActivity.getResources(); 78 mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size); 79 } 80 init(TaskbarControllers controllers)81 public void init(TaskbarControllers controllers) { 82 mControllers = controllers; 83 } 84 85 /** 86 * Attempts to start a system drag and drop operation for the given View, using its tag to 87 * generate the ClipDescription and Intent. 88 * @return Whether {@link View#startDragAndDrop} started successfully. 89 */ startDragOnLongClick(View view)90 protected boolean startDragOnLongClick(View view) { 91 if (!(view instanceof BubbleTextView)) { 92 return false; 93 } 94 95 BubbleTextView btv = (BubbleTextView) view; 96 97 mActivity.setTaskbarWindowFullscreen(true); 98 btv.post(() -> { 99 startInternalDrag(btv); 100 btv.getIcon().setIsDisabled(true); 101 mControllers.taskbarAutohideSuspendController.updateFlag( 102 TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, true); 103 }); 104 return true; 105 } 106 startInternalDrag(BubbleTextView btv)107 private void startInternalDrag(BubbleTextView btv) { 108 float iconScale = btv.getIcon().getAnimatedScale(); 109 110 // Clear the pressed state if necessary 111 btv.clearFocus(); 112 btv.setPressed(false); 113 btv.clearPressedBackground(); 114 115 final DragPreviewProvider previewProvider = new DragPreviewProvider(btv); 116 final Drawable drawable = previewProvider.createDrawable(); 117 final float scale = previewProvider.getScaleAndPosition(drawable, mTempXY); 118 int dragLayerX = mTempXY[0]; 119 int dragLayerY = mTempXY[1]; 120 121 Rect dragRect = new Rect(); 122 btv.getSourceVisualDragBounds(dragRect); 123 dragLayerY += dragRect.top; 124 125 DragOptions dragOptions = new DragOptions(); 126 dragOptions.preDragCondition = new DragOptions.PreDragCondition() { 127 private DragView mDragView; 128 129 @Override 130 public boolean shouldStartDrag(double distanceDragged) { 131 return mDragView != null && mDragView.isAnimationFinished(); 132 } 133 134 @Override 135 public void onPreDragStart(DropTarget.DragObject dragObject) { 136 mDragView = dragObject.dragView; 137 } 138 139 @Override 140 public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) { 141 mDragView = null; 142 } 143 }; 144 if (FeatureFlags.ENABLE_TASKBAR_POPUP_MENU.get()) { 145 PopupContainerWithArrow<TaskbarActivityContext> popupContainer = 146 mControllers.taskbarPopupController.showForIcon(btv); 147 if (popupContainer != null) { 148 dragOptions.preDragCondition = popupContainer.createPreDragCondition(); 149 } 150 } 151 152 startDrag( 153 drawable, 154 /* view = */ null, 155 /* originalView = */ btv, 156 dragLayerX, 157 dragLayerY, 158 (View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */, 159 (WorkspaceItemInfo) btv.getTag(), 160 /* dragVisualizeOffset = */ null, 161 dragRect, 162 scale * iconScale, 163 scale, 164 dragOptions); 165 } 166 167 @Override startDrag(@ullable Drawable drawable, @Nullable View view, DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options)168 protected DragView startDrag(@Nullable Drawable drawable, @Nullable View view, 169 DraggableView originalView, int dragLayerX, int dragLayerY, DragSource source, 170 ItemInfo dragInfo, Point dragOffset, Rect dragRegion, float initialDragViewScale, 171 float dragViewScaleOnDrop, DragOptions options) { 172 mOptions = options; 173 174 mRegistrationX = mMotionDown.x - dragLayerX; 175 mRegistrationY = mMotionDown.y - dragLayerY; 176 177 final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; 178 final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; 179 180 mLastDropTarget = null; 181 182 mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext()); 183 mDragObject.originalView = originalView; 184 mDragObject.deferDragViewCleanupPostAnimation = false; 185 186 mIsInPreDrag = mOptions.preDragCondition != null 187 && !mOptions.preDragCondition.shouldStartDrag(0); 188 189 float scalePx = mDragIconSize - dragRegion.width(); 190 final DragView dragView = mDragObject.dragView = new TaskbarDragView( 191 mActivity, 192 drawable, 193 mRegistrationX, 194 mRegistrationY, 195 initialDragViewScale, 196 dragViewScaleOnDrop, 197 scalePx); 198 dragView.setItemInfo(dragInfo); 199 mDragObject.dragComplete = false; 200 201 mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft); 202 mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop); 203 204 mDragDriver = DragDriver.create(this, mOptions, /* secondaryEventConsumer = */ ev -> {}); 205 if (!mOptions.isAccessibleDrag) { 206 mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView); 207 } 208 209 mDragObject.dragSource = source; 210 mDragObject.dragInfo = dragInfo; 211 mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy(); 212 213 if (dragRegion != null) { 214 dragView.setDragRegion(new Rect(dragRegion)); 215 } 216 217 dragView.show(mLastTouch.x, mLastTouch.y); 218 mDistanceSinceScroll = 0; 219 220 if (!mIsInPreDrag) { 221 callOnDragStart(); 222 } else if (mOptions.preDragCondition != null) { 223 mOptions.preDragCondition.onPreDragStart(mDragObject); 224 } 225 226 handleMoveEvent(mLastTouch.x, mLastTouch.y); 227 228 return dragView; 229 } 230 231 @Override callOnDragStart()232 protected void callOnDragStart() { 233 super.callOnDragStart(); 234 // Pre-drag has ended, start the global system drag. 235 AbstractFloatingView.closeAllOpenViews(mActivity); 236 startSystemDrag((BubbleTextView) mDragObject.originalView); 237 } 238 startSystemDrag(BubbleTextView btv)239 private void startSystemDrag(BubbleTextView btv) { 240 View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(btv) { 241 242 @Override 243 public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) { 244 shadowSize.set(mDragIconSize, mDragIconSize); 245 // The registration point was taken before the icon scaled to mDragIconSize, so 246 // offset the registration to where the touch is on the new size. 247 int offsetX = (mDragIconSize - mDragObject.dragView.getDragRegionWidth()) / 2; 248 int offsetY = (mDragIconSize - mDragObject.dragView.getDragRegionHeight()) / 2; 249 shadowTouchPoint.set(mRegistrationX + offsetX, mRegistrationY + offsetY); 250 } 251 252 @Override 253 public void onDrawShadow(Canvas canvas) { 254 canvas.save(); 255 float scale = mDragObject.dragView.getScaleX(); 256 canvas.scale(scale, scale); 257 mDragObject.dragView.draw(canvas); 258 canvas.restore(); 259 } 260 }; 261 262 Object tag = btv.getTag(); 263 ClipDescription clipDescription = null; 264 Intent intent = null; 265 if (tag instanceof WorkspaceItemInfo) { 266 WorkspaceItemInfo item = (WorkspaceItemInfo) tag; 267 LauncherApps launcherApps = mActivity.getSystemService(LauncherApps.class); 268 clipDescription = new ClipDescription(item.title, 269 new String[] { 270 item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT 271 ? ClipDescriptionCompat.MIMETYPE_APPLICATION_SHORTCUT 272 : ClipDescriptionCompat.MIMETYPE_APPLICATION_ACTIVITY 273 }); 274 intent = new Intent(); 275 if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 276 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage()); 277 intent.putExtra(Intent.EXTRA_SHORTCUT_ID, item.getDeepShortcutId()); 278 } else { 279 intent.putExtra(ClipDescriptionCompat.EXTRA_PENDING_INTENT, 280 LauncherAppsCompat.getMainActivityLaunchIntent(launcherApps, 281 item.getIntent().getComponent(), null, item.user)); 282 } 283 intent.putExtra(Intent.EXTRA_USER, item.user); 284 } else if (tag instanceof Task) { 285 Task task = (Task) tag; 286 clipDescription = new ClipDescription(task.titleDescription, 287 new String[] { 288 ClipDescriptionCompat.MIMETYPE_APPLICATION_TASK 289 }); 290 intent = new Intent(); 291 intent.putExtra(ClipDescriptionCompat.EXTRA_TASK_ID, task.key.id); 292 intent.putExtra(Intent.EXTRA_USER, UserHandle.of(task.key.userId)); 293 } 294 295 if (clipDescription != null && intent != null) { 296 // Need to share the same InstanceId between launcher3 and WM Shell (internal). 297 InstanceId internalInstanceId = new InstanceIdSequence( 298 com.android.launcher3.logging.InstanceId.INSTANCE_ID_MAX).newInstanceId(); 299 com.android.launcher3.logging.InstanceId launcherInstanceId = 300 new com.android.launcher3.logging.InstanceId(internalInstanceId.getId()); 301 302 intent.putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, internalInstanceId); 303 304 ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent)); 305 if (btv.startDragAndDrop(clipData, shadowBuilder, null /* localState */, 306 View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE)) { 307 onSystemDragStarted(); 308 309 mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo) 310 .withInstanceId(launcherInstanceId) 311 .log(StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED); 312 } 313 } 314 } 315 onSystemDragStarted()316 private void onSystemDragStarted() { 317 mIsSystemDragInProgress = true; 318 mActivity.getDragLayer().setOnDragListener((view, dragEvent) -> { 319 switch (dragEvent.getAction()) { 320 case DragEvent.ACTION_DRAG_STARTED: 321 // Return true to tell system we are interested in events, so we get DRAG_ENDED. 322 return true; 323 case DragEvent.ACTION_DRAG_ENDED: 324 mIsSystemDragInProgress = false; 325 maybeOnDragEnd(); 326 return true; 327 } 328 return false; 329 }); 330 } 331 332 @Override isDragging()333 public boolean isDragging() { 334 return super.isDragging() || mIsSystemDragInProgress; 335 } 336 maybeOnDragEnd()337 private void maybeOnDragEnd() { 338 if (!isDragging()) { 339 ((BubbleTextView) mDragObject.originalView).getIcon().setIsDisabled(false); 340 mControllers.taskbarAutohideSuspendController.updateFlag( 341 TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, false); 342 } 343 } 344 345 @Override callOnDragEnd()346 protected void callOnDragEnd() { 347 super.callOnDragEnd(); 348 maybeOnDragEnd(); 349 } 350 351 @Override getX(MotionEvent ev)352 protected float getX(MotionEvent ev) { 353 // We will resize to fill the screen while dragging, so use screen coordinates. This ensures 354 // we start at the correct position even though touch down is on the smaller DragLayer size. 355 return ev.getRawX(); 356 } 357 358 @Override getY(MotionEvent ev)359 protected float getY(MotionEvent ev) { 360 // We will resize to fill the screen while dragging, so use screen coordinates. This ensures 361 // we start at the correct position even though touch down is on the smaller DragLayer size. 362 return ev.getRawY(); 363 } 364 365 @Override getClampedDragLayerPos(float x, float y)366 protected Point getClampedDragLayerPos(float x, float y) { 367 // No need to clamp, as we will take up the entire screen. 368 mTmpPoint.set(Math.round(x), Math.round(y)); 369 return mTmpPoint; 370 } 371 372 @Override exitDrag()373 protected void exitDrag() { 374 if (mDragObject != null) { 375 mActivity.getDragLayer().removeView(mDragObject.dragView); 376 } 377 } 378 379 @Override addDropTarget(DropTarget target)380 public void addDropTarget(DropTarget target) { 381 // No-op as Taskbar currently doesn't support any drop targets internally. 382 // Note: if we do add internal DropTargets, we'll still need to ignore Folder. 383 } 384 385 @Override getDefaultDropTarget(int[] dropCoordinates)386 protected DropTarget getDefaultDropTarget(int[] dropCoordinates) { 387 return null; 388 } 389 } 390