Merge "Drag from appDrawer & pin onto secondary display homescreen" into tm-qpr-dev
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index a8d1367..75c28f9 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -299,6 +299,10 @@
public static final BooleanFlag ENABLE_TRANSIENT_TASKBAR = getDebugFlag(
"ENABLE_TRANSIENT_TASKBAR", false, "Enables transient taskbar.");
+ public static final BooleanFlag SECONDARY_DRAG_N_DROP_TO_PIN = getDebugFlag(
+ "SECONDARY_DRAG_N_DROP_TO_PIN", false,
+ "Enable dragging and dropping to pin apps within secondary display");
+
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
index a0ed77e..f03c62a 100644
--- a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
+++ b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
@@ -168,15 +168,18 @@
mPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
- private void update(ItemInfo info, Function<ComponentKey, Boolean> op) {
+ /**
+ * Pins or unpins apps from home screen
+ */
+ public void update(ItemInfo info, Function<ComponentKey, Boolean> op) {
ComponentKey key = new ComponentKey(info.getTargetComponent(), info.user);
if (op.apply(key)) {
createFilteredAppsList();
Set<ComponentKey> copy = new HashSet<>(mPinnedApps);
Executors.MODEL_EXECUTOR.submit(() ->
mPrefs.edit().putStringSet(PINNED_APPS_KEY,
- copy.stream().map(this::encode).collect(Collectors.toSet()))
- .apply());
+ copy.stream().map(this::encode).collect(Collectors.toSet()))
+ .apply());
}
}
@@ -210,6 +213,13 @@
mPinnedApps.contains(new ComponentKey(info.getTargetComponent(), info.user)));
}
+ /**
+ * Pins app to home screen
+ */
+ public void addPinnedApp(ItemInfo info) {
+ update(info, mPinnedApps::add);
+ }
+
private class PinUnPinShortcut extends SystemShortcut<SecondaryDisplayLauncher> {
private final boolean mIsPinned;
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index a55f7e3..1bea590 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -18,6 +18,9 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
@@ -26,6 +29,9 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
@@ -33,6 +39,11 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.model.data.AppInfo;
@@ -52,11 +63,11 @@
* Launcher activity for secondary displays
*/
public class SecondaryDisplayLauncher extends BaseDraggingActivity
- implements BgDataModel.Callbacks {
+ implements BgDataModel.Callbacks, DragController.DragListener {
private LauncherModel mModel;
-
private BaseDragLayer mDragLayer;
+ private SecondaryDragController mDragController;
private ActivityAllAppsContainerView<SecondaryDisplayLauncher> mAppsView;
private View mAppsButton;
@@ -69,10 +80,13 @@
private boolean mBindingItems = false;
private SecondaryDisplayPredictions mSecondaryDisplayPredictions;
+ private final int[] mTempXY = new int[2];
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mModel = LauncherAppState.getInstance(this).getModel();
+ mDragController = new SecondaryDragController(this);
mOnboardingPrefs = new OnboardingPrefs<>(this, Utilities.getPrefs(this));
mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this);
if (getWindow().getDecorView().isAttachedToWindow()) {
@@ -86,6 +100,12 @@
initUi();
}
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ this.getDragController().removeDragListener(this);
+ }
+
private void initUi() {
if (mDragLayer != null) {
return;
@@ -106,6 +126,7 @@
mAppsView = findViewById(R.id.apps_view);
mAppsButton = findViewById(R.id.all_apps_button);
+ mDragController.addDragListener(this);
mPopupDataProvider = new PopupDataProvider(
mAppsView.getAppsStore()::updateNotificationDots);
@@ -113,6 +134,12 @@
}
@Override
+ protected void onPause() {
+ super.onPause();
+ mDragController.cancelDrag();
+ }
+
+ @Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
@@ -129,12 +156,21 @@
showAppDrawer(false);
}
+ public DragController getDragController() {
+ return mDragController;
+ }
+
@Override
public void onBackPressed() {
if (finishAutoCancelActionMode()) {
return;
}
+ if (mDragController.isDragging()) {
+ mDragController.cancelDrag();
+ return;
+ }
+
// Note: There should be at most one log per method call. This is enforced implicitly
// by using if-else statements.
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
@@ -202,7 +238,7 @@
float closeR = Themes.getDialogCornerRadius(this);
float startR = mAppsButton.getWidth() / 2f;
- float[] buttonPos = new float[] { startR, startR};
+ float[] buttonPos = new float[]{startR, startR};
mDragLayer.getDescendantCoordRelativeToSelf(mAppsButton, buttonPos);
mDragLayer.mapCoordInSelfToDescendant(mAppsView, buttonPos);
final Animator animator = ViewAnimationUtils.createCircularReveal(mAppsView,
@@ -236,6 +272,7 @@
@Override
public void startBinding() {
mBindingItems = true;
+ mDragController.cancelDrag();
}
@Override
@@ -308,4 +345,101 @@
startActivitySafely(v, intent, item);
}
}
+
+ /**
+ * Core functionality for beginning a drag operation for an item that will be dropped within
+ * the secondary display grid home screen
+ */
+ public void beginDragShared(View child, DragSource source, DragOptions options) {
+ Object dragObject = child.getTag();
+ if (!(dragObject instanceof ItemInfo)) {
+ String msg = "Drag started with a view that has no tag set. This "
+ + "will cause a crash (issue 11627249) down the line. "
+ + "View: " + child + " tag: " + child.getTag();
+ throw new IllegalStateException(msg);
+ }
+ beginDragShared(child, source, (ItemInfo) dragObject,
+ new DragPreviewProvider(child), options);
+ }
+
+ private void beginDragShared(View child, DragSource source,
+ ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions options) {
+
+ float iconScale = 1f;
+ if (child instanceof BubbleTextView) {
+ FastBitmapDrawable icon = ((BubbleTextView) child).getIcon();
+ if (icon != null) {
+ iconScale = icon.getAnimatedScale();
+ }
+ }
+
+ // clear pressed state if necessary
+ child.clearFocus();
+ child.setPressed(false);
+ if (child instanceof BubbleTextView) {
+ BubbleTextView icon = (BubbleTextView) child;
+ icon.clearPressedBackground();
+ }
+
+ DraggableView draggableView = null;
+ if (child instanceof DraggableView) {
+ draggableView = (DraggableView) child;
+ }
+
+ final View contentView = previewProvider.getContentView();
+ final float scale;
+ // The draggable drawable follows the touch point around on the screen
+ final Drawable drawable;
+ if (contentView == null) {
+ drawable = previewProvider.createDrawable();
+ scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
+ } else {
+ drawable = null;
+ scale = previewProvider.getScaleAndPosition(contentView, mTempXY);
+ }
+ int halfPadding = previewProvider.previewPadding / 2;
+ int dragLayerX = mTempXY[0];
+ int dragLayerY = mTempXY[1];
+
+ Point dragVisualizeOffset = null;
+ Rect dragRect = new Rect();
+ if (draggableView != null) {
+ draggableView.getSourceVisualDragBounds(dragRect);
+ dragLayerY += dragRect.top;
+ dragVisualizeOffset = new Point(-halfPadding, halfPadding);
+ }
+ if (contentView != null) {
+ mDragController.startDrag(
+ contentView,
+ draggableView,
+ dragLayerX,
+ dragLayerY,
+ source,
+ dragObject,
+ dragVisualizeOffset,
+ dragRect,
+ scale * iconScale,
+ scale,
+ options);
+ } else {
+ mDragController.startDrag(
+ drawable,
+ draggableView,
+ dragLayerX,
+ dragLayerY,
+ source,
+ dragObject,
+ dragVisualizeOffset,
+ dragRect,
+ scale * iconScale,
+ scale,
+ options);
+ }
+ }
+
+ @Override
+ public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { }
+
+ @Override
+ public void onDragEnd() { }
}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java
new file mode 100644
index 0000000..9bf2764
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragController.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 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.secondarydisplay;
+
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+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.R;
+import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragDriver;
+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.testing.shared.TestProtocol;
+
+/**
+ * Drag controller for Secondary Launcher activity
+ */
+public class SecondaryDragController extends DragController<SecondaryDisplayLauncher> {
+
+ private static final boolean PROFILE_DRAWING_DURING_DRAG = false;
+
+ public SecondaryDragController(SecondaryDisplayLauncher secondaryLauncher) {
+ super(secondaryLauncher);
+ }
+
+ @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 (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.NO_DROP_TARGET, "5");
+ }
+
+ if (PROFILE_DRAWING_DURING_DRAG) {
+ android.os.Debug.startMethodTracing("Launcher");
+ }
+ mActivity.hideKeyboard();
+
+ 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 SecondaryDragView(
+ mActivity,
+ drawable,
+ registrationX,
+ registrationY,
+ initialDragViewScale,
+ dragViewScaleOnDrop,
+ scaleDps)
+ : new SecondaryDragView(
+ 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, ev -> {
+ });
+ 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);
+ return dragView;
+ }
+
+ @Override
+ protected void exitDrag() { }
+
+ @Override
+ protected DropTarget getDefaultDropTarget(int[] dropCoordinates) {
+ DropTarget target = new DropTarget() {
+ @Override
+ public boolean isDropEnabled() {
+ return true;
+ }
+
+ @Override
+ public void onDrop(DragObject dragObject, DragOptions options) {
+ ((SecondaryDragLayer) mActivity.getDragLayer()).getPinnedAppsAdapter().addPinnedApp(
+ dragObject.dragInfo);
+ dragObject.dragView.remove();
+ }
+
+ @Override
+ public void onDragEnter(DragObject dragObject) {
+ if (getDistanceDragged() > mActivity.getResources().getDimensionPixelSize(
+ R.dimen.drag_distanceThreshold)) {
+ mActivity.showAppDrawer(false);
+ AbstractFloatingView.closeAllOpenViews(mActivity);
+ }
+ }
+
+ @Override
+ public void onDragOver(DragObject dragObject) { }
+
+ @Override
+ public void onDragExit(DragObject dragObject) { }
+
+ @Override
+ public boolean acceptDrop(DragObject dragObject) {
+ return true;
+ }
+
+ @Override
+ public void prepareAccessibilityDrop() { }
+
+ @Override
+ public void getHitRectRelativeToDragLayer(Rect outRect) { }
+ };
+ return target;
+ }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
index c79d70d..42d5970 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -29,8 +29,12 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DropTarget;
import com.android.launcher3.R;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
@@ -59,7 +63,8 @@
@Override
public void recreateControllers() {
- mControllers = new TouchController[] {new CloseAllAppsTouchController()};
+ mControllers = new TouchController[]{new CloseAllAppsTouchController(),
+ mActivity.getDragController()};
}
/**
@@ -166,6 +171,10 @@
}
}
+ public PinnedAppsAdapter getPinnedAppsAdapter() {
+ return mPinnedAppsAdapter;
+ }
+
private boolean onIconLongClicked(View v) {
if (!(v instanceof BubbleTextView)) {
return false;
@@ -183,6 +192,7 @@
if (popupDataProvider == null) {
return false;
}
+
final PopupContainerWithArrow container =
(PopupContainerWithArrow) mActivity.getLayoutInflater().inflate(
R.layout.popup_container, mActivity.getDragLayer(), false);
@@ -192,7 +202,42 @@
Collections.emptyList(),
Arrays.asList(mPinnedAppsAdapter.getSystemShortcut(item, v),
APP_INFO.getShortcut(mActivity, item, v)));
- v.getParent().requestDisallowInterceptTouchEvent(true);
+ container.requestFocus();
+
+ if (!FeatureFlags.SECONDARY_DRAG_N_DROP_TO_PIN.get() || !mActivity.isAppDrawerShown()) {
+ return true;
+ }
+
+ DragOptions options = new DragOptions();
+ DeviceProfile grid = mActivity.getDeviceProfile();
+ options.intrinsicIconScaleFactor = (float) grid.allAppsIconSizePx / grid.iconSizePx;
+ options.preDragCondition = container.createPreDragCondition(false);
+ if (options.preDragCondition == null) {
+ options.preDragCondition = new DragOptions.PreDragCondition() {
+ private DragView<SecondaryDisplayLauncher> mDragView;
+
+ @Override
+ public boolean shouldStartDrag(double distanceDragged) {
+ return mDragView != null && mDragView.isAnimationFinished();
+ }
+
+ @Override
+ public void onPreDragStart(DropTarget.DragObject dragObject) {
+ mDragView = dragObject.dragView;
+ if (!shouldStartDrag(0)) {
+ mDragView.setOnAnimationEndCallback(() -> {
+ mActivity.beginDragShared(v, mActivity.getAppsView(), options);
+ });
+ }
+ }
+
+ @Override
+ public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
+ mDragView = null;
+ }
+ };
+ }
+ mActivity.beginDragShared(v, mActivity.getAppsView(), options);
return true;
}
}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragView.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragView.java
new file mode 100644
index 0000000..0168b8f
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragView.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.secondarydisplay;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.DragView;
+
+/**
+ * A DragView drawn/used by the Secondary Launcher activity.
+ */
+public class SecondaryDragView extends DragView<SecondaryDisplayLauncher> {
+
+ public SecondaryDragView(SecondaryDisplayLauncher launcher,
+ Drawable drawable,
+ int registrationX, int registrationY, float initialScale, float scaleOnDrop,
+ float finalScaleDps) {
+ super(launcher, drawable, registrationX, registrationY, initialScale, scaleOnDrop,
+ finalScaleDps);
+ }
+
+ public SecondaryDragView(SecondaryDisplayLauncher launcher, View content, int width, int height,
+ int registrationX, int registrationY, float initialScale, float scaleOnDrop,
+ float finalScaleDps) {
+ super(launcher, content, width, height, registrationX, registrationY, initialScale,
+ scaleOnDrop, finalScaleDps);
+ }
+
+ @Override
+ public void animateTo(int toTouchX, int toTouchY, Runnable onCompleteRunnable, int duration) {
+ Runnable onAnimationEnd = () -> {
+ if (onCompleteRunnable != null) {
+ onCompleteRunnable.run();
+ }
+ mActivity.getDragLayer().removeView(this);
+ };
+
+ duration = Math.max(duration,
+ getResources().getInteger(R.integer.config_dropAnimMinDuration));
+
+ animate()
+ .translationX(toTouchX - mRegistrationX)
+ .translationY(toTouchY - mRegistrationY)
+ .scaleX(mScaleOnDrop)
+ .scaleY(mScaleOnDrop)
+ .withEndAction(onAnimationEnd)
+ .setDuration(duration)
+ .start();
+ }
+}