diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 14382d6f09..1b54211beb 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -491,7 +491,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener List viewsToAnimate = new ArrayList<>(); Workspace workspace = mLauncher.getWorkspace(); - workspace.getVisiblePages().forEach( + workspace.forEachVisiblePage( view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets())); viewsToAnimate.add(mLauncher.getHotseat()); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 5513c1658c..3af51d575d 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -16,12 +16,24 @@ package com.android.launcher3.taskbar; import android.content.ContextWrapper; +import android.graphics.Point; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.view.LayoutInflater; +import android.view.View; + +import androidx.annotation.Nullable; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.DragSource; +import com.android.launcher3.DropTarget; import com.android.launcher3.R; +import com.android.launcher3.dragndrop.DragController; +import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.dragndrop.DragView; +import com.android.launcher3.dragndrop.DraggableView; +import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; @@ -35,6 +47,7 @@ public class TaskbarActivityContext extends ContextWrapper implements ActivityCo private final DeviceProfile mDeviceProfile; private final LayoutInflater mLayoutInflater; private final TaskbarContainerView mTaskbarContainerView; + private final MyDragController mDragController; public TaskbarActivityContext(BaseQuickstepLauncher launcher) { super(launcher); @@ -47,6 +60,7 @@ public class TaskbarActivityContext extends ContextWrapper implements ActivityCo mTaskbarContainerView = (TaskbarContainerView) mLayoutInflater .inflate(R.layout.taskbar, null, false); + mDragController = new MyDragController(this); } public TaskbarContainerView getTaskbarContainerView() { @@ -72,4 +86,31 @@ public class TaskbarActivityContext extends ContextWrapper implements ActivityCo public Rect getFolderBoundingBox() { return mTaskbarContainerView.getFolderBoundingBox(); } + + @Override + public DragController getDragController() { + return mDragController; + } + + private static class MyDragController extends DragController { + MyDragController(TaskbarActivityContext activity) { + super(activity); + } + + @Override + protected DragView startDrag(@Nullable 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) { + return null; + } + + @Override + protected void exitDrag() { } + + @Override + protected DropTarget getDefaultDropTarget(int[] dropCoordinates) { + return null; + } + } } diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index ab951385b3..0ea1fcac76 100644 --- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -89,8 +89,7 @@ public class StaggeredWorkspaceAnim { int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2); // Add animation for all the visible workspace pages - workspace.getVisiblePages() - .forEach(page -> addAnimationForPage((CellLayout) page, totalRows)); + workspace.forEachVisiblePage(page -> addAnimationForPage((CellLayout) page, totalRows)); boolean workspaceClipChildren = workspace.getClipChildren(); boolean workspaceClipToPadding = workspace.getClipToPadding(); diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java index f64ce5c753..9778b61854 100644 --- a/src/com/android/launcher3/BaseActivity.java +++ b/src/com/android/launcher3/BaseActivity.java @@ -132,6 +132,7 @@ public abstract class BaseActivity extends Activity implements ActivityContext { private final ViewCache mViewCache = new ViewCache(); + @Override public ViewCache getViewCache() { return mViewCache; } diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java index 4312939321..20c69380cc 100644 --- a/src/com/android/launcher3/ExtendedEditText.java +++ b/src/com/android/launcher3/ExtendedEditText.java @@ -15,6 +15,8 @@ */ package com.android.launcher3; +import static com.android.launcher3.util.UiThreadHelper.hideKeyboardAsync; + import android.content.Context; import android.text.TextUtils; import android.util.AttributeSet; @@ -25,7 +27,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.util.UiThreadHelper; +import com.android.launcher3.views.ActivityContext; /** @@ -99,7 +101,7 @@ public class ExtendedEditText extends EditText { } public void hideKeyboard() { - UiThreadHelper.hideKeyboardAsync(Launcher.getLauncher(getContext()), getWindowToken()); + hideKeyboardAsync(ActivityContext.lookupContext(getContext()), getWindowToken()); } private boolean showSoftInput() { diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 92d1b11ded..ece0eb4e38 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -124,6 +124,7 @@ import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragView; +import com.android.launcher3.dragndrop.LauncherDragController; import com.android.launcher3.folder.FolderGridOrganizer; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.icons.BitmapRenderer; @@ -428,7 +429,7 @@ public class Launcher extends StatefulActivity implements Launche mIconCache = app.getIconCache(); mAccessibilityDelegate = createAccessibilityDelegate(); - mDragController = new DragController(this); + mDragController = new LauncherDragController(this); mAllAppsController = new AllAppsTransitionController(this); mStateManager = new StateManager<>(this, NORMAL); diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index fb216987c9..52a6cef497 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -59,7 +59,7 @@ import com.android.launcher3.util.Thunk; import com.android.launcher3.views.ActivityContext; import java.util.ArrayList; -import java.util.List; +import java.util.function.Consumer; /** * An abstraction of the original Workspace which supports browsing through a @@ -295,18 +295,16 @@ public abstract class PagedView extends ViewGrou } /** - * Returns the currently visible pages. + * Executes the callback against each visible page */ - public Iterable getVisiblePages() { + public void forEachVisiblePage(Consumer callback) { int panelCount = getPanelCount(); - List visiblePages = new ArrayList<>(panelCount); for (int i = mCurrentPage; i < mCurrentPage + panelCount; i++) { View page = getPageAt(i); if (page != null) { - visiblePages.add(page); + callback.accept(page); } } - return visiblePages; } /** @@ -1034,7 +1032,7 @@ public abstract class PagedView extends ViewGrou // Try canceling the long press. It could also have been scheduled // by a distant descendant, so use the mAllowLongPress flag to block // everything - getVisiblePages().forEach(View::cancelLongPress); + forEachVisiblePage(View::cancelLongPress); } protected float getScrollProgress(int screenCenter, View v, int page) { diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 7c5f99e07b..f846d14ff9 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -2974,7 +2974,7 @@ public class Workspace extends PagedView List cellLayouts = new ArrayList<>(getPanelCount() + 1); cellLayouts.add(getHotseat()); - getVisiblePages().forEach(page -> cellLayouts.add((CellLayout) page)); + forEachVisiblePage(page -> cellLayouts.add((CellLayout) page)); // Order: App icons, app in folder. Items in hotseat get returned first. if (ADAPTIVE_ICON_WINDOW_ANIM.get()) { diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index 575b8fdd99..5731db4cbb 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -16,45 +16,37 @@ package com.android.launcher3.dragndrop; -import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE; -import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; -import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.Utilities.ATLEAST_Q; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.content.ComponentName; -import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.DragEvent; -import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import androidx.annotation.Nullable; -import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; -import com.android.launcher3.Launcher; -import com.android.launcher3.R; -import com.android.launcher3.accessibility.DragViewStateAnnouncer; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.TouchController; +import com.android.launcher3.views.ActivityContext; import java.util.ArrayList; import java.util.Optional; /** * Class for initiating a drag within a view or across multiple views. + * @param */ -public class DragController implements DragDriver.EventListener, TouchController { - private static final boolean PROFILE_DRAWING_DURING_DRAG = false; +public abstract class DragController + implements DragDriver.EventListener, TouchController { /** * When a drag is started from a deep press, you need to drag this much farther than normal to @@ -62,8 +54,7 @@ public class DragController implements DragDriver.EventListener, TouchController */ private static final int DEEP_PRESS_DISTANCE_FACTOR = 3; - private final Launcher mLauncher; - private final FlingToDeleteHelper mFlingToDeleteHelper; + protected final T mActivity; // temporaries to avoid gc thrash private final Rect mRectTemp = new Rect(); @@ -73,30 +64,30 @@ public class DragController implements DragDriver.EventListener, TouchController * Drag driver for the current drag/drop operation, or null if there is no active DND operation. * It's null during accessible drag operations. */ - private DragDriver mDragDriver = null; + protected DragDriver mDragDriver = null; /** Options controlling the drag behavior. */ - private DragOptions mOptions; + protected DragOptions mOptions; /** Coordinate for motion down event */ - private final Point mMotionDown = new Point(); + protected final Point mMotionDown = new Point(); /** Coordinate for last touch event **/ - private final Point mLastTouch = new Point(); + protected final Point mLastTouch = new Point(); private final Point mTmpPoint = new Point(); - private DropTarget.DragObject mDragObject; + protected DropTarget.DragObject mDragObject; /** Who can receive drop events */ private final ArrayList mDropTargets = new ArrayList<>(); private final ArrayList mListeners = new ArrayList<>(); - private DropTarget mLastDropTarget; + protected DropTarget mLastDropTarget; private int mLastTouchClassification; - private int mDistanceSinceScroll = 0; + protected int mDistanceSinceScroll = 0; - private boolean mIsInPreDrag; + protected boolean mIsInPreDrag; /** * Interface to receive notifications when a drag starts or stops @@ -119,9 +110,8 @@ public class DragController implements DragDriver.EventListener, TouchController /** * Used to create a new DragLayer from XML. */ - public DragController(Launcher launcher) { - mLauncher = launcher; - mFlingToDeleteHelper = new FlingToDeleteHelper(launcher); + public DragController(T activity) { + mActivity = activity; } /** @@ -198,7 +188,7 @@ public class DragController implements DragDriver.EventListener, TouchController options); } - private DragView startDrag( + protected abstract DragView startDrag( @Nullable Drawable drawable, @Nullable View view, DraggableView originalView, @@ -210,98 +200,9 @@ public class DragController implements DragDriver.EventListener, TouchController Rect dragRegion, float initialDragViewScale, float dragViewScaleOnDrop, - DragOptions options) { - if (PROFILE_DRAWING_DURING_DRAG) { - android.os.Debug.startMethodTracing("Launcher"); - } + DragOptions options); - mLauncher.hideKeyboard(); - AbstractFloatingView.closeOpenViews(mLauncher, false, TYPE_DISCOVERY_BOUNCE); - - mOptions = options; - if (mOptions.simulatedDndStartPoint != null) { - mLastTouch.x = mMotionDown.x = mOptions.simulatedDndStartPoint.x; - mLastTouch.y = mMotionDown.y = mOptions.simulatedDndStartPoint.y; - } - - final int registrationX = mMotionDown.x - dragLayerX; - final int registrationY = mMotionDown.y - dragLayerY; - - final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; - final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; - - mLastDropTarget = null; - - mDragObject = new DropTarget.DragObject(mLauncher.getApplicationContext()); - mDragObject.originalView = originalView; - - mIsInPreDrag = mOptions.preDragCondition != null - && !mOptions.preDragCondition.shouldStartDrag(0); - - final Resources res = mLauncher.getResources(); - final float scaleDps = mIsInPreDrag - ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f; - final DragView dragView = mDragObject.dragView = drawable != null - ? new DragView( - mLauncher, - drawable, - registrationX, - registrationY, - initialDragViewScale, - dragViewScaleOnDrop, - scaleDps) - : new DragView( - mLauncher, - view, - view.getMeasuredWidth(), - view.getMeasuredHeight(), - registrationX, - registrationY, - initialDragViewScale, - dragViewScaleOnDrop, - scaleDps); - dragView.setItemInfo(dragInfo); - mDragObject.dragComplete = false; - - mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft); - mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop); - - mDragDriver = DragDriver.create(this, mOptions, mFlingToDeleteHelper::recordMotionEvent); - if (!mOptions.isAccessibleDrag) { - mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView); - } - - mDragObject.dragSource = source; - mDragObject.dragInfo = dragInfo; - mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy(); - - if (dragOffset != null) { - dragView.setDragVisualizeOffset(new Point(dragOffset)); - } - if (dragRegion != null) { - dragView.setDragRegion(new Rect(dragRegion)); - } - - mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - dragView.show(mLastTouch.x, mLastTouch.y); - mDistanceSinceScroll = 0; - - if (!mIsInPreDrag) { - callOnDragStart(); - } else if (mOptions.preDragCondition != null) { - mOptions.preDragCondition.onPreDragStart(mDragObject); - } - - handleMoveEvent(mLastTouch.x, mLastTouch.y); - - if (!mLauncher.isTouchInProgress() && options.simulatedDndStartPoint == null) { - // If it is an internal drag and the touch is already complete, cancel immediately - MAIN_EXECUTOR.submit(this::cancelDrag); - } - return dragView; - } - - private void callOnDragStart() { + protected void callOnDragStart() { if (mOptions.preDragCondition != null) { mOptions.preDragCondition.onPreDragEnd(mDragObject, true /* dragStarted*/); } @@ -357,13 +258,15 @@ public class DragController implements DragDriver.EventListener, TouchController if (!accepted) { // If it was not accepted, cleanup the state. If it was accepted, it is the // responsibility of the drop target to cleanup the state. - mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); + exitDrag(); mDragObject.deferDragViewCleanupPostAnimation = false; } mDragObject.dragSource.onDropCompleted(dropTarget, mDragObject, accepted); } + protected abstract void exitDrag(); + public void onAppsRemoved(ItemInfoMatcher matcher) { // Cancel the current drag if we are removing an app that we are dragging if (mDragObject != null) { @@ -377,7 +280,7 @@ public class DragController implements DragDriver.EventListener, TouchController } } - private void endDrag() { + protected void endDrag() { if (isDragging()) { mDragDriver = null; boolean isDeferred = false; @@ -396,8 +299,6 @@ public class DragController implements DragDriver.EventListener, TouchController callOnDragEnd(); } } - - mFlingToDeleteHelper.releaseVelocityTracker(); } public void animateDragViewToOriginalPosition(final Runnable onComplete, @@ -443,7 +344,7 @@ public class DragController implements DragDriver.EventListener, TouchController * Clamps the position to the drag layer bounds. */ private Point getClampedDragLayerPos(float x, float y) { - mLauncher.getDragLayer().getLocalVisibleRect(mRectTemp); + mActivity.getDragLayer().getLocalVisibleRect(mRectTemp); mTmpPoint.x = (int) Math.max(mRectTemp.left, Math.min(x, mRectTemp.right - 1)); mTmpPoint.y = (int) Math.max(mRectTemp.top, Math.min(y, mRectTemp.bottom - 1)); return mTmpPoint; @@ -465,19 +366,16 @@ public class DragController implements DragDriver.EventListener, TouchController @Override public void onDriverDragEnd(float x, float y) { - DropTarget dropTarget; - Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject, mOptions); - if (flingAnimation != null) { - dropTarget = mFlingToDeleteHelper.getDropTarget(); - } else { - dropTarget = findDropTarget((int) x, (int) y, mCoordinatesTemp); + if (!endWithFlingAnimation()) { + drop(findDropTarget((int) x, (int) y, mCoordinatesTemp), null); } - - drop(dropTarget, flingAnimation); - endDrag(); } + protected boolean endWithFlingAnimation() { + return false; + } + @Override public void onDriverDragCancel() { cancelDrag(); @@ -520,7 +418,7 @@ public class DragController implements DragDriver.EventListener, TouchController return mDragDriver != null && mDragDriver.onDragEvent(event); } - private void handleMoveEvent(int x, int y) { + protected void handleMoveEvent(int x, int y) { mDragObject.dragView.move(x, y); // Drop on someone? @@ -592,7 +490,7 @@ public class DragController implements DragDriver.EventListener, TouchController endDrag(); } - private void drop(DropTarget dropTarget, Runnable flingAnimation) { + protected void drop(DropTarget dropTarget, Runnable flingAnimation) { final int[] coordinates = mCoordinatesTemp; mDragObject.x = coordinates[0]; mDragObject.y = coordinates[1]; @@ -649,7 +547,7 @@ public class DragController implements DragDriver.EventListener, TouchController if (r.contains(x, y)) { dropCoordinates[0] = x; dropCoordinates[1] = y; - mLauncher.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates); + mActivity.getDragLayer().mapCoordInSelfToDescendant((View) target, dropCoordinates); return target; } } @@ -657,11 +555,11 @@ public class DragController implements DragDriver.EventListener, TouchController // cell layout to drop to in the existing drag/drop logic. dropCoordinates[0] = x; dropCoordinates[1] = y; - mLauncher.getDragLayer().mapCoordInSelfToDescendant(mLauncher.getWorkspace(), - dropCoordinates); - return mLauncher.getWorkspace(); + return getDefaultDropTarget(dropCoordinates); } + protected abstract DropTarget getDefaultDropTarget(int[] dropCoordinates); + /** * Sets the drag listener which will be notified when a drag starts or ends. */ diff --git a/src/com/android/launcher3/dragndrop/LauncherDragController.java b/src/com/android/launcher3/dragndrop/LauncherDragController.java new file mode 100644 index 0000000000..a98d70ce71 --- /dev/null +++ b/src/com/android/launcher3/dragndrop/LauncherDragController.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.dragndrop; + +import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE; +import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; +import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.view.HapticFeedbackConstants; +import android.view.View; + +import androidx.annotation.Nullable; + +import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.DragSource; +import com.android.launcher3.DropTarget; +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.accessibility.DragViewStateAnnouncer; +import com.android.launcher3.model.data.ItemInfo; + +/** + * Drag controller for Launcher activity + */ +public class LauncherDragController extends DragController { + + private static final boolean PROFILE_DRAWING_DURING_DRAG = false; + + private final FlingToDeleteHelper mFlingToDeleteHelper; + + public LauncherDragController(Launcher launcher) { + super(launcher); + mFlingToDeleteHelper = new FlingToDeleteHelper(launcher); + } + + @Override + protected DragView startDrag( + @Nullable 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) { + if (PROFILE_DRAWING_DURING_DRAG) { + android.os.Debug.startMethodTracing("Launcher"); + } + + mActivity.hideKeyboard(); + AbstractFloatingView.closeOpenViews(mActivity, false, TYPE_DISCOVERY_BOUNCE); + + mOptions = options; + if (mOptions.simulatedDndStartPoint != null) { + mLastTouch.x = mMotionDown.x = mOptions.simulatedDndStartPoint.x; + mLastTouch.y = mMotionDown.y = mOptions.simulatedDndStartPoint.y; + } + + final int registrationX = mMotionDown.x - dragLayerX; + final int registrationY = mMotionDown.y - dragLayerY; + + final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left; + final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; + + mLastDropTarget = null; + + mDragObject = new DropTarget.DragObject(mActivity.getApplicationContext()); + mDragObject.originalView = originalView; + + mIsInPreDrag = mOptions.preDragCondition != null + && !mOptions.preDragCondition.shouldStartDrag(0); + + final Resources res = mActivity.getResources(); + final float scaleDps = mIsInPreDrag + ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f; + final DragView dragView = mDragObject.dragView = drawable != null + ? new DragView( + mActivity, + drawable, + registrationX, + registrationY, + initialDragViewScale, + dragViewScaleOnDrop, + scaleDps) + : new DragView( + mActivity, + view, + view.getMeasuredWidth(), + view.getMeasuredHeight(), + registrationX, + registrationY, + initialDragViewScale, + dragViewScaleOnDrop, + scaleDps); + dragView.setItemInfo(dragInfo); + mDragObject.dragComplete = false; + + mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft); + mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop); + + mDragDriver = DragDriver.create(this, mOptions, mFlingToDeleteHelper::recordMotionEvent); + if (!mOptions.isAccessibleDrag) { + mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView); + } + + mDragObject.dragSource = source; + mDragObject.dragInfo = dragInfo; + mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy(); + + if (dragOffset != null) { + dragView.setDragVisualizeOffset(new Point(dragOffset)); + } + if (dragRegion != null) { + dragView.setDragRegion(new Rect(dragRegion)); + } + + mActivity.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + dragView.show(mLastTouch.x, mLastTouch.y); + mDistanceSinceScroll = 0; + + if (!mIsInPreDrag) { + callOnDragStart(); + } else if (mOptions.preDragCondition != null) { + mOptions.preDragCondition.onPreDragStart(mDragObject); + } + + handleMoveEvent(mLastTouch.x, mLastTouch.y); + + if (!mActivity.isTouchInProgress() && options.simulatedDndStartPoint == null) { + // If it is an internal drag and the touch is already complete, cancel immediately + MAIN_EXECUTOR.submit(this::cancelDrag); + } + return dragView; + } + + @Override + protected void exitDrag() { + mActivity.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); + } + + @Override + protected boolean endWithFlingAnimation() { + Runnable flingAnimation = mFlingToDeleteHelper.getFlingAnimation(mDragObject, mOptions); + if (flingAnimation != null) { + drop(mFlingToDeleteHelper.getDropTarget(), flingAnimation); + return true; + } + return super.endWithFlingAnimation(); + } + + @Override + protected void endDrag() { + super.endDrag(); + mFlingToDeleteHelper.releaseVelocityTracker(); + } + + @Override + protected DropTarget getDefaultDropTarget(int[] dropCoordinates) { + mActivity.getDragLayer().mapCoordInSelfToDescendant(mActivity.getWorkspace(), + dropCoordinates); + return mActivity.getWorkspace(); + } +} diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 92d891f774..22d1b1c48a 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -23,7 +23,6 @@ import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED; import static com.android.launcher3.util.DisplayController.getSingleFrameMs; @@ -109,7 +108,6 @@ import com.android.launcher3.widget.LocalColorExtractor; import com.android.launcher3.widget.PendingAddShortcutInfo; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -177,8 +175,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo private boolean mIsAnimatingClosed = false; // Folder can be displayed in Launcher's activity or a separate window (e.g. Taskbar). - // Anything specific to Launcher should use mLauncher, otherwise should use mActivityContext. - protected final Launcher mLauncher; + // Anything specific to Launcher should use mLauncherDelegate, otherwise should + // use mActivityContext. + protected final LauncherDelegate mLauncherDelegate; protected final ActivityContext mActivityContext; protected DragController mDragController; @@ -235,8 +234,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Wallpaper local color extraction @Nullable private LocalColorExtractor mColorExtractor; @Nullable private LocalColorExtractor.Listener mColorListener; - private final Rect mTempRect = new Rect(); - private final RectF mTempRectF = new RectF(); // For simplicity, we start the color change only after the open animation has started. private Runnable mColorChangeRunnable; @@ -255,8 +252,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo super(context, attrs); setAlwaysDrawnWithCacheEnabled(false); - mLauncher = Launcher.getLauncher(context); mActivityContext = ActivityContext.lookupContext(context); + mLauncherDelegate = LauncherDelegate.from(mActivityContext); + mStatsLogManager = StatsLogManager.newInstance(context); // We need this view to be focusable in touch mode so that when text editing of the folder // name is complete, we have something to focus on, thus hiding the cursor and giving @@ -267,7 +265,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Override protected void onFinishInflate() { super.onFinishInflate(); - final DeviceProfile dp = mLauncher.getDeviceProfile(); + final DeviceProfile dp = mActivityContext.getDeviceProfile(); final int paddingLeftRight = dp.folderContentPaddingLeftRight; mContent = findViewById(R.id.folder_content); @@ -298,7 +296,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } if (Utilities.ATLEAST_S) { - mColorExtractor = LocalColorExtractor.newInstance(mLauncher); + mColorExtractor = LocalColorExtractor.newInstance(getContext()); mColorListener = (RectF rect, SparseIntArray extractedColors) -> { mColorChangeRunnable = () -> { mColorChangeRunnable = null; @@ -339,7 +337,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo public boolean onLongClick(View v) { // Return if global dragging is not enabled - if (!mLauncher.isDraggingEnabled()) return true; + if (!mLauncherDelegate.isDraggingEnabled()) return true; return startDrag(v, new DragOptions()); } @@ -365,7 +363,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo }); } - mLauncher.getWorkspace().beginDragShared(v, this, options); + mLauncherDelegate.beginDragShared(v, this, options); } return true; } @@ -421,7 +419,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (DEBUG) { Log.d(TAG, "onBackKey newTitle=" + newTitle); } - mInfo.setTitle(newTitle, mLauncher.getModelWriter()); + mInfo.setTitle(newTitle, mLauncherDelegate.getModelWriter()); mFolderIcon.onTitleChanged(newTitle); if (TextUtils.isEmpty(mInfo.title)) { @@ -486,6 +484,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo public void setFolderIcon(FolderIcon icon) { mFolderIcon = icon; + mLauncherDelegate.init(this, icon); } @Override @@ -592,8 +591,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } private void startAnimation(final AnimatorSet a) { - mLauncher.getWorkspace().getVisiblePages() - .forEach(visiblePage -> addAnimatorListenerForPage(a, (CellLayout) visiblePage)); + mLauncherDelegate.forEachVisibleWorkspacePage( + visiblePage -> addAnimatorListenerForPage(a, (CellLayout) visiblePage)); a.addListener(new AnimatorListenerAdapter() { @Override @@ -754,12 +753,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mFolderName.animate().setDuration(FOLDER_NAME_ANIMATION_DURATION) .translationX(0) .setInterpolator(AnimationUtils.loadInterpolator( - mLauncher, android.R.interpolator.fast_out_slow_in)); + getContext(), android.R.interpolator.fast_out_slow_in)); mPageIndicator.playEntryAnimation(); if (updateAnimationFlag) { mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, - mLauncher.getModelWriter()); + mLauncherDelegate.getModelWriter()); } } }); @@ -1115,7 +1114,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (getItemCount() <= mContent.itemsPerPage()) { // Show the animation, next time something is added to the folder. mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, false, - mLauncher.getModelWriter()); + mLauncherDelegate.getModelWriter()); } } @@ -1133,7 +1132,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } if (!items.isEmpty()) { - mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0); + mLauncherDelegate.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0); } if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && !isBind && total > 1 /* no need to update if there's one icon */) { @@ -1190,12 +1189,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (mColorExtractor != null) { mColorExtractor.removeLocations(); mColorExtractor.setListener(mColorListener); - mTempRect.set(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height); - mColorExtractor.getExtractedRectForViewRect(mLauncher, - mLauncher.getWorkspace().getCurrentPage(), mTempRect, mTempRectF); - if (!mTempRectF.isEmpty()) { - mColorExtractor.addLocation(Arrays.asList(mTempRectF)); - } + mLauncherDelegate.addRectForColorExtraction(lp, mColorExtractor); } } @@ -1236,7 +1230,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (mContent.getChildCount() > 0) { int cellIconGap = (mContent.getPageAt(0).getCellWidth() - - mLauncher.getDeviceProfile().iconSizePx) / 2; + - mActivityContext.getDeviceProfile().iconSizePx) / 2; mFooter.setPadding(mContent.getPaddingLeft() + cellIconGap, mFooter.getPaddingTop(), mContent.getPaddingRight() + cellIconGap, @@ -1266,56 +1260,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } @Thunk void replaceFolderWithFinalItem() { - // Add the last remaining child to the workspace in place of the folder - Runnable onCompleteRunnable = new Runnable() { - @Override - public void run() { - int itemCount = getItemCount(); - if (itemCount <= 1) { - View newIcon = null; - WorkspaceItemInfo finalItem = null; - - if (itemCount == 1) { - // Move the item from the folder to the workspace, in the position of the - // folder - CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, - mInfo.screenId); - finalItem = mInfo.contents.remove(0); - newIcon = mLauncher.createShortcut(cellLayout, finalItem); - mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem, - mInfo.container, mInfo.screenId, mInfo.cellX, mInfo.cellY); - } - - // Remove the folder - mLauncher.removeItem(mFolderIcon, mInfo, true /* deleteFromDb */); - if (mFolderIcon instanceof DropTarget) { - mDragController.removeDropTarget((DropTarget) mFolderIcon); - } - - if (newIcon != null) { - // We add the child after removing the folder to prevent both from existing - // at the same time in the CellLayout. We need to add the new item with - // addInScreenFromBind() to ensure that hotseat items are placed correctly. - mLauncher.getWorkspace().addInScreenFromBind(newIcon, mInfo); - - // Focus the newly created child - newIcon.requestFocus(); - } - if (finalItem != null) { - StatsLogger logger = mStatsLogManager.logger().withItemInfo(finalItem); - mDragController.getLogInstanceId().map(logger::withInstanceId) - .orElse(logger) - .log(LAUNCHER_FOLDER_CONVERTED_TO_ICON); - } - } - } - }; - View finalChild = mContent.getLastItem(); - if (finalChild != null) { - mFolderIcon.performDestroyAnimation(onCompleteRunnable); - } else { - onCompleteRunnable.run(); - } + mLauncherDelegate.replaceFolderWithFinalItem(this); mDestroyed = true; } @@ -1374,6 +1319,10 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mScrollPauseAlarm.cancelAlarm(); } mContent.completePendingPageChanges(); + Launcher launcher = mLauncherDelegate.getLauncher(); + if (launcher == null) { + return; + } PendingAddShortcutInfo pasi = d.dragInfo instanceof PendingAddShortcutInfo ? (PendingAddShortcutInfo) d.dragInfo : null; @@ -1383,7 +1332,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo pasi.container = mInfo.id; pasi.rank = mEmptyCellRank; - mLauncher.addPendingItem(pasi, pasi.container, pasi.screenId, null, pasi.spanX, + launcher.addPendingItem(pasi, pasi.container, pasi.screenId, null, pasi.spanX, pasi.spanY); d.deferDragViewCleanupPostAnimation = false; mRearrangeOnClose = true; @@ -1405,7 +1354,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Actually move the item in the database if it was an external drag. Call this // before creating the view, so that WorkspaceItemInfo is updated appropriately. - mLauncher.getModelWriter().addOrMoveItemInDatabase( + mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase( si, mInfo.id, 0, si.cellX, si.cellY); mIsExternalDrag = false; } else { @@ -1420,7 +1369,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo float scaleY = getScaleY(); setScaleX(1.0f); setScaleY(1.0f); - mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView, null); + launcher.getDragLayer().animateViewIntoPosition(d.dragView, currentDragView, null); setScaleX(scaleX); setScaleY(scaleY); } else { @@ -1448,10 +1397,11 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (mContent.getPageCount() > 1) { // The animation has already been shown while opening the folder. - mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, mLauncher.getModelWriter()); + mInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, + mLauncherDelegate.getModelWriter()); } - mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); + launcher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); if (d.stateAnnouncer != null) { d.stateAnnouncer.completeAction(R.string.item_moved); } @@ -1480,7 +1430,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo FolderGridOrganizer verifier = new FolderGridOrganizer( mActivityContext.getDeviceProfile().inv).setFolderInfo(mInfo); verifier.updateRankAndPos(item, rank); - mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX, + mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX, item.cellY); updateItemLocationsInDatabaseBatch(false); @@ -1713,17 +1663,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return true; } return false; - } else if (!dl.isEventOverView(this, ev)) { - if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) { - // Do not close the container if in drag and drop. - if (!dl.isEventOverView(mLauncher.getDropTargetBar(), ev)) { - return true; - } - } else { - // TODO: add ww log if need to gather tap outside to close folder - close(true); - return true; - } + } else if (!dl.isEventOverView(this, ev) + && mLauncherDelegate.interceptOutsideTouch(ev, dl, this)) { + return true; } } return false; diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 6b02021ed3..279c4457d3 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -169,14 +169,11 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel public static FolderIcon inflateFolderAndIcon(int resId, T activityContext, ViewGroup group, FolderInfo folderInfo) { Folder folder = Folder.fromXml(activityContext); - folder.setDragController(folder.mLauncher.getDragController()); FolderIcon icon = inflateIcon(resId, activityContext, group, folderInfo); folder.setFolderIcon(icon); folder.bind(folderInfo); icon.setFolder(folder); - - icon.setOnFocusChangeListener(folder.mLauncher.getFocusHandler()); icon.mBackground.paddingY = icon.isInHotseat() ? 0 : activityContext.getDeviceProfile().cellYPaddingPx; return icon; @@ -467,7 +464,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel CharSequence newTitle = nameInfos.getLabels()[0]; FromState fromState = mInfo.getFromLabelState(); - mInfo.setTitle(newTitle, mFolder.mLauncher.getModelWriter()); + mInfo.setTitle(newTitle, mFolder.mLauncherDelegate.getModelWriter()); onTitleChanged(mInfo.title); mFolder.mFolderName.setText(mInfo.title); diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index a4e8be6974..7fc3740106 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -31,7 +31,6 @@ import android.view.View; import android.view.ViewDebug; import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.BaseActivity; import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; @@ -49,6 +48,7 @@ import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.util.Thunk; import com.android.launcher3.util.ViewCache; +import com.android.launcher3.views.ActivityContext; import java.util.ArrayList; import java.util.Iterator; @@ -102,7 +102,7 @@ public class FolderPagedView extends PagedView { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); mFocusIndicatorHelper = new ViewGroupFocusHelper(this); - mViewCache = BaseActivity.fromContext(context).getViewCache(); + mViewCache = ActivityContext.lookupContext(context).getViewCache(); } public void setFolder(Folder folder) { diff --git a/src/com/android/launcher3/folder/LauncherDelegate.java b/src/com/android/launcher3/folder/LauncherDelegate.java new file mode 100644 index 0000000000..f7d8e8c0d1 --- /dev/null +++ b/src/com/android/launcher3/folder/LauncherDelegate.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.folder; + +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON; + +import android.content.Context; +import android.graphics.Rect; +import android.graphics.RectF; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.Nullable; + +import com.android.launcher3.CellLayout; +import com.android.launcher3.DragSource; +import com.android.launcher3.DropTarget; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.logging.InstanceId; +import com.android.launcher3.logging.StatsLogManager.StatsLogger; +import com.android.launcher3.model.ModelWriter; +import com.android.launcher3.model.data.FolderInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.views.ActivityContext; +import com.android.launcher3.views.BaseDragLayer; +import com.android.launcher3.views.BaseDragLayer.LayoutParams; +import com.android.launcher3.widget.LocalColorExtractor; + +import java.util.Arrays; +import java.util.Optional; +import java.util.function.Consumer; + +/** + * Wrapper around Launcher methods to allow folders in non-launcher context + */ +public class LauncherDelegate { + + private final Launcher mLauncher; + private final Rect mTempRect = new Rect(); + private final RectF mTempRectF = new RectF(); + + private LauncherDelegate(Launcher launcher) { + mLauncher = launcher; + } + + void init(Folder folder, FolderIcon icon) { + folder.setDragController(mLauncher.getDragController()); + icon.setOnFocusChangeListener(mLauncher.getFocusHandler()); + } + + boolean isDraggingEnabled() { + return mLauncher.isDraggingEnabled(); + } + + void beginDragShared(View child, DragSource source, DragOptions options) { + mLauncher.getWorkspace().beginDragShared(child, source, options); + } + + ModelWriter getModelWriter() { + return mLauncher.getModelWriter(); + } + + void forEachVisibleWorkspacePage(Consumer callback) { + mLauncher.getWorkspace().forEachVisiblePage(callback); + } + + @Nullable + Launcher getLauncher() { + return mLauncher; + } + + void addRectForColorExtraction(BaseDragLayer.LayoutParams lp, LocalColorExtractor target) { + mTempRect.set(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height); + target.getExtractedRectForViewRect(mLauncher, + mLauncher.getWorkspace().getCurrentPage(), mTempRect, mTempRectF); + if (!mTempRectF.isEmpty()) { + target.addLocation(Arrays.asList(mTempRectF)); + } + } + + void replaceFolderWithFinalItem(Folder folder) { + // Add the last remaining child to the workspace in place of the folder + Runnable onCompleteRunnable = new Runnable() { + @Override + public void run() { + int itemCount = folder.getItemCount(); + FolderInfo info = folder.mInfo; + if (itemCount <= 1) { + View newIcon = null; + WorkspaceItemInfo finalItem = null; + + if (itemCount == 1) { + // Move the item from the folder to the workspace, in the position of the + // folder + CellLayout cellLayout = mLauncher.getCellLayout(info.container, + info.screenId); + finalItem = info.contents.remove(0); + newIcon = mLauncher.createShortcut(cellLayout, finalItem); + mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem, + info.container, info.screenId, info.cellX, info.cellY); + } + + // Remove the folder + mLauncher.removeItem(folder.mFolderIcon, info, true /* deleteFromDb */); + if (folder.mFolderIcon instanceof DropTarget) { + folder.mDragController.removeDropTarget((DropTarget) folder.mFolderIcon); + } + + if (newIcon != null) { + // We add the child after removing the folder to prevent both from existing + // at the same time in the CellLayout. We need to add the new item with + // addInScreenFromBind() to ensure that hotseat items are placed correctly. + mLauncher.getWorkspace().addInScreenFromBind(newIcon, info); + + // Focus the newly created child + newIcon.requestFocus(); + } + if (finalItem != null) { + StatsLogger logger = mLauncher.getStatsLogManager().logger() + .withItemInfo(finalItem); + ((Optional) folder.mDragController.getLogInstanceId()) + .map(logger::withInstanceId) + .orElse(logger) + .log(LAUNCHER_FOLDER_CONVERTED_TO_ICON); + } + } + } + }; + View finalChild = folder.mContent.getLastItem(); + if (finalChild != null) { + folder.mFolderIcon.performDestroyAnimation(onCompleteRunnable); + } else { + onCompleteRunnable.run(); + } + } + + + boolean interceptOutsideTouch(MotionEvent ev, BaseDragLayer dl, Folder folder) { + if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) { + // Do not close the container if in drag and drop. + if (!dl.isEventOverView(mLauncher.getDropTargetBar(), ev)) { + return true; + } + } else { + // TODO: add ww log if need to gather tap outside to close folder + folder.close(true); + return true; + } + return false; + } + + private static class FallbackDelegate extends LauncherDelegate { + + private final ActivityContext mContext; + private ModelWriter mWriter; + + FallbackDelegate(ActivityContext context) { + super(null); + mContext = context; + } + + @Override + void init(Folder folder, FolderIcon icon) { + folder.setDragController(mContext.getDragController()); + } + + @Override + boolean isDraggingEnabled() { + return false; + } + + @Override + void beginDragShared(View child, DragSource source, DragOptions options) { } + + @Override + ModelWriter getModelWriter() { + if (mWriter == null) { + mWriter = LauncherAppState.getInstance((Context) mContext).getModel() + .getWriter(false, false); + } + return mWriter; + } + + @Override + void forEachVisibleWorkspacePage(Consumer callback) { } + + @Override + Launcher getLauncher() { + return null; + } + + @Override + void replaceFolderWithFinalItem(Folder folder) { } + + @Override + boolean interceptOutsideTouch(MotionEvent ev, BaseDragLayer dl, Folder folder) { + folder.close(true); + return true; + } + + @Override + void addRectForColorExtraction(LayoutParams lp, LocalColorExtractor target) { } + } + + static LauncherDelegate from(ActivityContext context) { + return context instanceof Launcher + ? new LauncherDelegate((Launcher) context) + : new FallbackDelegate(context); + } +} diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java index f5e1234a28..523f3d6d13 100644 --- a/src/com/android/launcher3/util/UiThreadHelper.java +++ b/src/com/android/launcher3/util/UiThreadHelper.java @@ -23,11 +23,12 @@ import android.content.Context; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.view.View; import android.view.WindowInsets; import android.view.inputmethod.InputMethodManager; -import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; +import com.android.launcher3.views.ActivityContext; /** * Utility class for offloading some class from UI thread @@ -43,21 +44,21 @@ public class UiThreadHelper { private static final int MSG_RUN_COMMAND = 3; @SuppressLint("NewApi") - public static void hideKeyboardAsync(Launcher launcher, IBinder token) { + public static void hideKeyboardAsync(ActivityContext activityContext, IBinder token) { + View root = activityContext.getDragLayer(); if (Utilities.ATLEAST_R) { - WindowInsets rootInsets = launcher.getRootView().getRootWindowInsets(); + WindowInsets rootInsets = root.getRootWindowInsets(); boolean isImeShown = rootInsets != null && rootInsets.isVisible( WindowInsets.Type.ime()); if (isImeShown) { // this call is already asynchronous - launcher.getAppsView().getWindowInsetsController().hide( - WindowInsets.Type.ime() - ); + root.getWindowInsetsController().hide(WindowInsets.Type.ime()); } return; } - Message.obtain(HANDLER.get(launcher), MSG_HIDE_KEYBOARD, token).sendToTarget(); + Message.obtain(HANDLER.get(root.getContext()), + MSG_HIDE_KEYBOARD, token).sendToTarget(); } public static void setOrientationAsync(Activity activity, int orientation) { diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java index 71aa4ac445..646b66942c 100644 --- a/src/com/android/launcher3/views/ActivityContext.java +++ b/src/com/android/launcher3/views/ActivityContext.java @@ -23,7 +23,9 @@ import android.view.View.AccessibilityDelegate; import com.android.launcher3.DeviceProfile; import com.android.launcher3.dot.DotInfo; +import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.util.ViewCache; /** * An interface to be used along with a context for various activities in Launcher. This allows a @@ -86,6 +88,17 @@ public interface ActivityContext { DeviceProfile getDeviceProfile(); + default ViewCache getViewCache() { + return new ViewCache(); + } + + /** + * Controller for supporting item drag-and-drop + */ + default T getDragController() { + return null; + } + /** * Returns the ActivityContext associated with the given Context. */