diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java index a6844e48b7..63e7390d34 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java @@ -15,12 +15,15 @@ */ package com.android.launcher3.hybridhotseat; -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent - .LAUNCHER_HOTSEAT_EDU_ONLY_TIP; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_ONLY_TIP; import android.content.Intent; +import android.graphics.Rect; +import android.util.Log; +import android.view.Gravity; import android.view.View; +import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.Hotseat; import com.android.launcher3.InvariantDeviceProfile; @@ -47,6 +50,8 @@ import java.util.stream.IntStream; */ public class HotseatEduController { + private static final String TAG = "HotseatEduController"; + public static final String SETTINGS_ACTION = "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS"; @@ -188,8 +193,12 @@ public class HotseatEduController { .getInt(LauncherSettings.Settings.EXTRA_VALUE); mNewScreens = IntArray.wrap(pageId); } - for (int i = 0; i < mLauncher.getDeviceProfile().numShownHotseatIcons; i++) { - View child = mHotseat.getChildAt(i, 0); + boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout(); + int hotseatItemsNum = mLauncher.getDeviceProfile().numShownHotseatIcons; + for (int i = 0; i < hotseatItemsNum; i++) { + int x = isPortrait ? i : 0; + int y = isPortrait ? 0 : hotseatItemsNum - i - 1; + View child = mHotseat.getChildAt(x, y); if (child == null || child.getTag() == null) continue; ItemInfo tag = (ItemInfo) child.getTag(); if (tag.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) continue; @@ -229,8 +238,7 @@ public class HotseatEduController { R.string.hotseat_prediction_settings, null, () -> mLauncher.startActivity(getSettingsIntent())); } else { - new ArrowTipView(mLauncher).show( - mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop()); + showHotseatArrowTip(true, mLauncher.getString(R.string.hotseat_tip_no_empty_slots)); } } @@ -251,15 +259,50 @@ public class HotseatEduController { if (requiresMigration && canMigrateToFirstPage) { showDialog(); } else { - new ArrowTipView(mLauncher).show(mLauncher.getString( + if (showHotseatArrowTip(requiresMigration, mLauncher.getString( requiresMigration ? R.string.hotseat_tip_no_empty_slots - : R.string.hotseat_auto_enrolled), - mHotseat.getTop()); - mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_ONLY_TIP); + : R.string.hotseat_auto_enrolled))) { + mLauncher.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_ONLY_TIP); + } finishOnboarding(); } } + /** + * Finds a child suitable child in hotseat and shows arrow tip pointing at it. + * + * @param usePinned used to determine target view. If true, will use the first matching pinned + * item. Otherwise, will use the first predicted child + * @param message String to be shown inside the arrowView + * @return whether suitable child was found and tip was shown + */ + private boolean showHotseatArrowTip(boolean usePinned, String message) { + int childCount = mHotseat.getShortcutsAndWidgets().getChildCount(); + boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout(); + + BubbleTextView tipTargetView = null; + for (int i = childCount - 1; i > -1; i--) { + int x = isPortrait ? i : 0; + int y = isPortrait ? 0 : i; + View v = mHotseat.getShortcutsAndWidgets().getChildAt(x, y); + if (v instanceof BubbleTextView && v.getTag() instanceof WorkspaceItemInfo) { + ItemInfo info = (ItemInfo) v.getTag(); + boolean isPinned = info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT; + if (isPinned == usePinned) { + tipTargetView = (BubbleTextView) v; + break; + } + } + } + if (tipTargetView == null) { + Log.e(TAG, "Unable to find suitable view for ArrowTip"); + return false; + } + Rect bounds = mLauncher.getViewBounds(tipTargetView); + new ArrowTipView(mLauncher).show(message, Gravity.END, bounds.centerX(), bounds.top); + return true; + } + void showDialog() { if (mPredictedApps == null || mPredictedApps.isEmpty()) { return; diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java index c7c25670ce..c41f2ceb14 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java @@ -77,6 +77,11 @@ public class HotseatEduDialog extends AbstractSlideInView implements I mContent = this; } + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + setTranslationShift(TRANSLATION_SHIFT_CLOSED); + } @Override protected void onFinishInflate() { @@ -200,9 +205,9 @@ public class HotseatEduDialog extends AbstractSlideInView implements I } AbstractFloatingView.closeAllOpenViews(mActivityContext); attachToContainer(); - mActivityContext.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_SEEN); animateOpen(); populatePreview(predictions); + mActivityContext.getStatsLogManager().logger().log(LAUNCHER_HOTSEAT_EDU_SEEN); } /** diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index 86562c4374..85d9f01735 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -57,7 +57,6 @@ import com.android.launcher3.uioverrides.PredictedAppIcon; import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.OnboardingPrefs; -import com.android.launcher3.views.ArrowTipView; import com.android.launcher3.views.Snackbar; import java.util.ArrayList; @@ -153,37 +152,14 @@ public class HotseatPredictionController implements DragController.DragListener, */ public void showEdu() { mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> { - if (mPredictedItems.isEmpty()) { - // launcher has empty predictions set - Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled, - R.string.hotseat_prediction_settings, null, - () -> mLauncher.startActivity(getSettingsIntent())); - } else if (getPredictedIcons().size() >= (mHotSeatItemsCount + 1) / 2) { - showDiscoveryTip(); - } else { - HotseatEduController eduController = new HotseatEduController(mLauncher); - eduController.setPredictedApps(mPredictedItems.stream() - .map(i -> (WorkspaceItemInfo) i) - .collect(Collectors.toList())); - eduController.showEdu(); - } + HotseatEduController eduController = new HotseatEduController(mLauncher); + eduController.setPredictedApps(mPredictedItems.stream() + .map(i -> (WorkspaceItemInfo) i) + .collect(Collectors.toList())); + eduController.showEdu(); })); } - /** - * Shows educational tip for hotseat if user does not go through Tips app. - */ - private void showDiscoveryTip() { - if (getPredictedIcons().isEmpty()) { - new ArrowTipView(mLauncher).show( - mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop()); - } else { - Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled, - R.string.hotseat_prediction_settings, null, - () -> mLauncher.startActivity(getSettingsIntent())); - } - } - /** * Returns if hotseat client has predictions */ @@ -510,6 +486,16 @@ public class HotseatPredictionController implements DragController.DragListener, } } + /** + * Called when user completes adding item requiring a config activity to the hotseat + */ + public void onDeferredDrop(int cellX, int cellY) { + View child = mHotseat.getChildAt(cellX, cellY); + if (child instanceof PredictedAppIcon) { + removeIconWithoutNotify((PredictedAppIcon) child); + } + } + private class PinPrediction extends SystemShortcut { private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) { diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 9e93829ff3..9050ddc511 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -17,6 +17,7 @@ package com.android.launcher3.uioverrides; import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; @@ -69,6 +70,7 @@ import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchT import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.OnboardingPrefs; +import com.android.launcher3.util.PendingRequestArgs; import com.android.launcher3.util.TouchController; import com.android.launcher3.util.UiThreadHelper; import com.android.launcher3.util.UiThreadHelper.AsyncCommand; @@ -138,6 +140,15 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId); } + @Override + protected void completeAddShortcut(Intent data, int container, int screenId, int cellX, + int cellY, PendingRequestArgs args) { + if (container == CONTAINER_HOTSEAT) { + mHotseatPredictionController.onDeferredDrop(cellX, cellY); + } + super.completeAddShortcut(data, container, screenId, cellX, cellY, args); + } + @Override protected LauncherAccessibilityDelegate createAccessibilityDelegate() { return new QuickstepAccessibilityDelegate(this); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 8ec600ded4..cb8e7f75aa 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -2725,7 +2725,7 @@ public abstract class RecentsView 0 && mTopRowIdSet.size() >= (taskCount - 1) / 2f; // Pick the next focused task from the preferred row. @@ -2908,7 +2908,8 @@ public abstract class RecentsView13dp 24dp 22dp + 24dp 14dp diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java index ee711463d9..fef3f8fe7d 100644 --- a/src/com/android/launcher3/AppWidgetResizeFrame.java +++ b/src/com/android/launcher3/AppWidgetResizeFrame.java @@ -2,6 +2,7 @@ package com.android.launcher3; import static android.appwidget.AppWidgetHostView.getDefaultPaddingForWidget; +import static com.android.launcher3.CellLayout.SPRING_LOADED_PROGRESS; import static com.android.launcher3.LauncherAnimUtils.LAYOUT_HEIGHT; import static com.android.launcher3.LauncherAnimUtils.LAYOUT_WIDTH; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_RESIZE_COMPLETED; @@ -9,6 +10,8 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.views.BaseDragLayer.LAYOUT_X; import static com.android.launcher3.views.BaseDragLayer.LAYOUT_Y; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; @@ -29,6 +32,7 @@ import androidx.annotation.Px; import com.android.launcher3.accessibility.DragViewStateAnnouncer; import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.model.data.ItemInfo; @@ -49,12 +53,14 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O private static final String KEY_RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN = "launcher.reconfigurable_widget_education_tip_seen"; private static final Rect sTmpRect = new Rect(); + private static final Rect sTmpRect2 = new Rect(); private static final int HANDLE_COUNT = 4; private static final int INDEX_LEFT = 0; private static final int INDEX_TOP = 1; private static final int INDEX_RIGHT = 2; private static final int INDEX_BOTTOM = 3; + private static final float MIN_OPACITY_FOR_CELL_LAYOUT_DURING_INVALID_RESIZE = 0.5f; private final Launcher mLauncher; private final DragViewStateAnnouncer mStateAnnouncer; @@ -103,6 +109,16 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O private final InstanceId logInstanceId = new InstanceIdSequence().newInstanceId(); + private final ViewGroupFocusHelper mDragLayerRelativeCoordinateHelper; + + /** + * In the two panel UI, it is not possible to resize a widget to cross its host + * {@link CellLayout}'s sibling. When this happens, we gradually reduce the opacity of the + * sibling {@link CellLayout} from 1f to + * {@link #MIN_OPACITY_FOR_CELL_LAYOUT_DURING_INVALID_RESIZE}. + */ + private final float mDragAcrossTwoPanelOpacityMargin; + private boolean mLeftBorderActive; private boolean mRightBorderActive; private boolean mTopBorderActive; @@ -149,6 +165,10 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O for (int i = 0; i < HANDLE_COUNT; i++) { mSystemGestureExclusionRects.add(new Rect()); } + + mDragAcrossTwoPanelOpacityMargin = mLauncher.getResources().getDimensionPixelSize( + R.dimen.resize_frame_invalid_drag_across_two_panel_opacity_margin); + mDragLayerRelativeCoordinateHelper = new ViewGroupFocusHelper(mLauncher.getDragLayer()); } @Override @@ -359,6 +379,37 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O lp.y = sTmpRect.top; } + // Handle invalid resize across CellLayouts in the two panel UI. + if (mCellLayout.getParent() instanceof Workspace) { + Workspace workspace = (Workspace) mCellLayout.getParent(); + CellLayout pairedCellLayout = workspace.getScreenPair(mCellLayout); + if (pairedCellLayout != null) { + Rect focusedCellLayoutBound = sTmpRect; + mDragLayerRelativeCoordinateHelper.viewToRect(mCellLayout, focusedCellLayoutBound); + Rect resizeFrameBound = sTmpRect2; + findViewById(R.id.widget_resize_frame).getGlobalVisibleRect(resizeFrameBound); + float progress = 1f; + if (workspace.indexOfChild(pairedCellLayout) < workspace.indexOfChild(mCellLayout) + && mDeltaX < 0 + && resizeFrameBound.left < focusedCellLayoutBound.left) { + // Resize from right to left. + progress = (mDragAcrossTwoPanelOpacityMargin + mDeltaX) + / mDragAcrossTwoPanelOpacityMargin; + } else if (workspace.indexOfChild(pairedCellLayout) + > workspace.indexOfChild(mCellLayout) + && mDeltaX > 0 + && resizeFrameBound.right > focusedCellLayoutBound.right) { + // Resize from left to right. + progress = (mDragAcrossTwoPanelOpacityMargin - mDeltaX) + / mDragAcrossTwoPanelOpacityMargin; + } + float alpha = Math.max(MIN_OPACITY_FOR_CELL_LAYOUT_DURING_INVALID_RESIZE, progress); + float springLoadedProgress = Math.min(1f, 1f - progress); + updateInvalidResizeEffect(mCellLayout, pairedCellLayout, alpha, + springLoadedProgress); + } + } + requestLayout(); } @@ -515,13 +566,24 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); + final CellLayout pairedCellLayout; + if (mCellLayout.getParent() instanceof Workspace) { + Workspace workspace = (Workspace) mCellLayout.getParent(); + pairedCellLayout = workspace.getScreenPair(mCellLayout); + } else { + pairedCellLayout = null; + } if (!animate) { lp.width = newWidth; lp.height = newHeight; lp.x = newX; lp.y = newY; for (int i = 0; i < HANDLE_COUNT; i++) { - mDragHandles[i].setAlpha(1.0f); + mDragHandles[i].setAlpha(1f); + } + if (pairedCellLayout != null) { + updateInvalidResizeEffect(mCellLayout, pairedCellLayout, /* alpha= */ 1f, + /* springLoadedProgress= */ 0f); } requestLayout(); } else { @@ -538,6 +600,10 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O set.play(mFirstFrameAnimatorHelper.addTo( ObjectAnimator.ofFloat(mDragHandles[i], ALPHA, 1f))); } + if (pairedCellLayout != null) { + updateInvalidResizeEffect(mCellLayout, pairedCellLayout, /* alpha= */ 1f, + /* springLoadedProgress= */ 0f, /* animatorSet= */ set); + } set.setDuration(SNAP_DURATION); set.start(); } @@ -624,6 +690,52 @@ public class AppWidgetResizeFrame extends AbstractFloatingView implements View.O } } + private void updateInvalidResizeEffect(CellLayout cellLayout, CellLayout pairedCellLayout, + float alpha, float springLoadedProgress) { + updateInvalidResizeEffect(cellLayout, pairedCellLayout, alpha, + springLoadedProgress, /* animatorSet= */ null); + } + + private void updateInvalidResizeEffect(CellLayout cellLayout, CellLayout pairedCellLayout, + float alpha, float springLoadedProgress, @Nullable AnimatorSet animatorSet) { + int childCount = pairedCellLayout.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = pairedCellLayout.getChildAt(i); + if (animatorSet != null) { + animatorSet.play( + mFirstFrameAnimatorHelper.addTo( + ObjectAnimator.ofFloat(child, ALPHA, alpha))); + } else { + child.setAlpha(alpha); + } + } + if (animatorSet != null) { + animatorSet.play(mFirstFrameAnimatorHelper.addTo( + ObjectAnimator.ofFloat(cellLayout, SPRING_LOADED_PROGRESS, + springLoadedProgress))); + animatorSet.play(mFirstFrameAnimatorHelper.addTo( + ObjectAnimator.ofFloat(pairedCellLayout, SPRING_LOADED_PROGRESS, + springLoadedProgress))); + } else { + cellLayout.setSpringLoadedProgress(springLoadedProgress); + pairedCellLayout.setSpringLoadedProgress(springLoadedProgress); + } + + boolean shouldShowCellLayoutBorder = springLoadedProgress > 0f; + if (animatorSet != null) { + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + cellLayout.setIsDragOverlapping(shouldShowCellLayoutBorder); + pairedCellLayout.setIsDragOverlapping(shouldShowCellLayoutBorder); + } + }); + } else { + cellLayout.setIsDragOverlapping(shouldShowCellLayoutBorder); + pairedCellLayout.setIsDragOverlapping(shouldShowCellLayoutBorder); + } + } + @Override protected boolean isOfType(int type) { return (type & TYPE_WIDGET_RESIZE_FRAME) != 0; diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 85dd3b3cc6..8a3518561f 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1264,7 +1264,7 @@ public class Launcher extends StatefulActivity implements Launche * * @param data The intent describing the shortcut. */ - private void completeAddShortcut(Intent data, int container, int screenId, int cellX, + protected void completeAddShortcut(Intent data, int container, int screenId, int cellX, int cellY, PendingRequestArgs args) { if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT || args.getPendingIntent().getComponent() == null) { @@ -2128,7 +2128,7 @@ public class Launcher extends StatefulActivity implements Launche actualIds.add(id); } int firstId = visibleIds.getArray().get(0); - int pairId = mWorkspace.getPagePair(firstId); + int pairId = mWorkspace.getScreenPair(firstId); // Double check that actual screenIds contains the visibleId, as empty screens are hidden // in single panel. if (actualIds.contains(firstId)) { @@ -2212,7 +2212,7 @@ public class Launcher extends StatefulActivity implements Launche // Some empty pages might have been removed while the phone was in a single panel // mode, so we want to add those empty pages back. IntSet screenIds = IntSet.wrap(orderedScreenIds); - orderedScreenIds.forEach(screenId -> screenIds.add(mWorkspace.getPagePair(screenId))); + orderedScreenIds.forEach(screenId -> screenIds.add(mWorkspace.getScreenPair(screenId))); orderedScreenIds = screenIds.getArray(); } diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index a7198a80cd..d534c5d85d 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -208,7 +208,7 @@ public abstract class PagedView extends ViewGrou public void initParentViews(View parent) { if (mPageIndicatorViewId > -1) { mPageIndicator = parent.findViewById(mPageIndicatorViewId); - mPageIndicator.setMarkersCount(getChildCount()); + mPageIndicator.setMarkersCount(getChildCount() / getPanelCount()); } } @@ -830,7 +830,7 @@ public abstract class PagedView extends ViewGrou private void dispatchPageCountChanged() { if (mPageIndicator != null) { - mPageIndicator.setMarkersCount(getChildCount()); + mPageIndicator.setMarkersCount(getChildCount() / getPanelCount()); } // This ensures that when children are added, they get the correct transforms / alphas // in accordance with any scroll effects. diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index bd2a14fd2d..3faa3d0970 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -651,7 +651,7 @@ public class Workspace extends PagedView // If the icon was dragged from Hotseat, there is no page pair if (isTwoPanelEnabled() && !(mDragSourceInternal.getParent() instanceof Hotseat)) { - int pagePairScreenId = getPagePair(dragObject.dragInfo.screenId); + int pagePairScreenId = getScreenPair(dragObject.dragInfo.screenId); CellLayout pagePair = mWorkspaceScreens.get(pagePairScreenId); if (pagePair == null) { // TODO: after http://b/198820019 is fixed, remove this @@ -917,16 +917,33 @@ public class Workspace extends PagedView } /** - * Returns the page that is shown together with the given page when two panel is enabled. + * Returns the screen ID of a page that is shown together with the given page screen ID when the + * two panel UI is enabled. */ - public int getPagePair(int page) { - if (page % 2 == 0) { - return page + 1; + public int getScreenPair(int screenId) { + if (screenId % 2 == 0) { + return screenId + 1; } else { - return page - 1; + return screenId - 1; } } + /** + * Returns {@link CellLayout} that is shown together with the given {@link CellLayout} when the + * two panel UI is enabled. + */ + @Nullable + public CellLayout getScreenPair(CellLayout cellLayout) { + if (!isTwoPanelEnabled()) { + return null; + } + int screenId = getIdForScreen(cellLayout); + if (screenId == -1) { + return null; + } + return getScreenWithId(getScreenPair(screenId)); + } + public void stripEmptyScreens() { if (mLauncher.isWorkspaceLoading()) { // Don't strip empty screens if the workspace is still loading. @@ -959,7 +976,7 @@ public class Workspace extends PagedView Iterator removeScreensIterator = removeScreens.iterator(); while (removeScreensIterator.hasNext()) { int pageToRemove = removeScreensIterator.next(); - int pagePair = getPagePair(pageToRemove); + int pagePair = getScreenPair(pageToRemove); if (!removeScreens.contains(pagePair)) { // The page pair isn't empty so we want to remove the current page from the // removable pages' collection diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java index 239b5ff08f..8d16a8d982 100644 --- a/src/com/android/launcher3/views/ArrowTipView.java +++ b/src/com/android/launcher3/views/ArrowTipView.java @@ -37,6 +37,7 @@ import androidx.core.content.ContextCompat; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.dragndrop.DragLayer; @@ -56,6 +57,7 @@ public class ArrowTipView extends AbstractFloatingView { protected final BaseDraggingActivity mActivity; private final Handler mHandler = new Handler(); private final int mArrowWidth; + private final int mArrowMinOffset; private boolean mIsPointingUp; private Runnable mOnClosed; private View mArrowView; @@ -69,6 +71,8 @@ public class ArrowTipView extends AbstractFloatingView { mActivity = BaseDraggingActivity.fromContext(context); mIsPointingUp = isPointingUp; mArrowWidth = context.getResources().getDimensionPixelSize(R.dimen.arrow_toast_arrow_width); + mArrowMinOffset = context.getResources().getDimensionPixelSize( + R.dimen.dynamic_grid_cell_border_spacing); init(context); } @@ -126,10 +130,10 @@ public class ArrowTipView extends AbstractFloatingView { /** * Show the ArrowTipView (tooltip) center, start, or end aligned. * - * @param text The text to be shown in the tooltip. - * @param gravity The gravity aligns the tooltip center, start, or end. + * @param text The text to be shown in the tooltip. + * @param gravity The gravity aligns the tooltip center, start, or end. * @param arrowMarginStart The margin from start to place arrow (ignored if center) - * @param top The Y coordinate of the bottom of tooltip. + * @param top The Y coordinate of the bottom of tooltip. * @return The tooltip. */ public ArrowTipView show(String text, int gravity, int arrowMarginStart, int top) { @@ -152,23 +156,28 @@ public class ArrowTipView extends AbstractFloatingView { ViewGroup parent = mActivity.getDragLayer(); parent.addView(this); + DeviceProfile grid = mActivity.getDeviceProfile(); + DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams(); params.gravity = gravity; + params.leftMargin = mArrowMinOffset + grid.getInsets().left; + params.rightMargin = mArrowMinOffset + grid.getInsets().right; LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mArrowView.getLayoutParams(); + lp.gravity = gravity; if (parent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) { arrowMarginStart = parent.getMeasuredWidth() - arrowMarginStart; } if (gravity == Gravity.END) { - lp.setMarginEnd(parent.getMeasuredWidth() - arrowMarginStart - mArrowWidth); + lp.setMarginEnd(Math.max(mArrowMinOffset, + parent.getMeasuredWidth() - params.rightMargin - arrowMarginStart + - mArrowWidth / 2)); } else if (gravity == Gravity.START) { - lp.setMarginStart(arrowMarginStart - mArrowWidth / 2); + lp.setMarginStart(Math.max(mArrowMinOffset, + arrowMarginStart - params.leftMargin - mArrowWidth / 2)); } requestLayout(); - - params.leftMargin = mActivity.getDeviceProfile().workspacePadding.left; - params.rightMargin = mActivity.getDeviceProfile().workspacePadding.right; post(() -> setY(top - (mIsPointingUp ? 0 : getHeight()))); mIsOpen = true;