diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index c13225abb7..1bf36278ca 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -37,6 +37,7 @@ import android.os.Bundle; import android.os.CancellationSignal; import android.os.IBinder; import android.view.View; +import android.window.SplashScreen; import androidx.annotation.Nullable; @@ -435,6 +436,7 @@ public abstract class BaseQuickstepLauncher extends Launcher ActivityOptionsCompat.setLauncherSourceInfo( activityOptions.options, mLastTouchUpTime); } + activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); addLaunchCookie(item, activityOptions.options); return activityOptions; } diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java index d43bb24a1e..68526420ef 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/src/com/android/quickstep/RecentsActivity.java @@ -40,6 +40,7 @@ import android.os.Handler; import android.os.Looper; import android.view.SurfaceControl.Transaction; import android.view.View; +import android.window.SplashScreen; import androidx.annotation.Nullable; @@ -222,9 +223,11 @@ public final class RecentsActivity extends StatefulActivity { wrapper, RECENTS_LAUNCH_DURATION, RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION - STATUS_BAR_TRANSITION_PRE_DELAY); - return new ActivityOptionsWrapper( + final ActivityOptionsWrapper activityOptions = new ActivityOptionsWrapper( ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback); + activityOptions.options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); + return activityOptions; } /** diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java index 4ec1c15f4d..a078bf331e 100644 --- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java @@ -30,6 +30,7 @@ import android.graphics.Rect; import android.os.Handler; import android.os.Looper; import android.view.View; +import android.window.SplashScreen; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; @@ -165,6 +166,9 @@ public interface TaskShortcutFactory { dismissTaskMenuView(mTarget); ActivityOptions options = mFactory.makeLaunchOptions(mTarget); + if (options != null) { + options.setSplashscreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON); + } if (options != null && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId, options)) { diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 74906ddac0..a46bc6b72d 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -1358,7 +1358,7 @@ public abstract class RecentsView 1) { taskWidth = boxLength; diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml index 8b188577a8..15131f11d7 100644 --- a/res/layout/user_folder_icon_normalized.xml +++ b/res/layout/user_folder_icon_normalized.xml @@ -18,7 +18,6 @@ xmlns:launcher="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:background="@drawable/round_rect_folder" android:orientation="vertical" > { @@ -163,6 +235,8 @@ public final class LauncherAppWidgetProviderInfoTest { InvariantDeviceProfile idp = new InvariantDeviceProfile(); idp.supportedProfiles.add(profile); + idp.numColumns = NUM_OF_COLS; + idp.numRows = NUM_OF_ROWS; return idp; } diff --git a/src/com/android/launcher3/anim/AlphaUpdateListener.java b/src/com/android/launcher3/anim/AlphaUpdateListener.java index eabd283699..8dad1b4fa5 100644 --- a/src/com/android/launcher3/anim/AlphaUpdateListener.java +++ b/src/com/android/launcher3/anim/AlphaUpdateListener.java @@ -17,6 +17,7 @@ package com.android.launcher3.anim; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.view.View; @@ -25,7 +26,7 @@ import android.view.ViewGroup; /** * A convenience class to update a view's visibility state after an alpha animation. */ -public class AlphaUpdateListener extends AnimationSuccessListener +public class AlphaUpdateListener extends AnimatorListenerAdapter implements AnimatorUpdateListener { public static final float ALPHA_CUTOFF_THRESHOLD = 0.01f; @@ -41,7 +42,7 @@ public class AlphaUpdateListener extends AnimationSuccessListener } @Override - public void onAnimationSuccess(Animator animator) { + public void onAnimationEnd(Animator animator) { updateVisibility(mView); } diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java index c67efefa5d..68bed443c7 100644 --- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java +++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java @@ -5,10 +5,10 @@ public class ClippedFolderIconLayoutRule { public static final int MAX_NUM_ITEMS_IN_PREVIEW = 4; private static final int MIN_NUM_ITEMS_IN_PREVIEW = 2; - private static final float MIN_SCALE = 0.48f; - private static final float MAX_SCALE = 0.58f; - private static final float MAX_RADIUS_DILATION = 0.15f; - private static final float ITEM_RADIUS_SCALE_FACTOR = 1.33f; + private static final float MIN_SCALE = 0.44f; + private static final float MAX_SCALE = 0.54f; + private static final float MAX_RADIUS_DILATION = 0.10f; + private static final float ITEM_RADIUS_SCALE_FACTOR = 1.2f; public static final int EXIT_INDEX = -2; public static final int ENTER_INDEX = -3; @@ -130,10 +130,8 @@ public class ClippedFolderIconLayoutRule { public float scaleForItem(int numItems) { // Scale is determined by the number of items in the preview. final float scale; - if (numItems <= 2) { + if (numItems <= 3) { scale = MAX_SCALE; - } else if (numItems == 3) { - scale = (MAX_SCALE + MIN_SCALE) / 2; } else { scale = MIN_SCALE; } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index e3876278b2..17c1329ed9 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -41,6 +41,7 @@ import android.graphics.Insets; import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.text.InputType; @@ -67,6 +68,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import androidx.core.content.res.ResourcesCompat; import androidx.core.graphics.ColorUtils; import com.android.launcher3.AbstractFloatingView; @@ -250,6 +252,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // so that we can cancel it when starting mColorChangeAnimator. private ObjectAnimator mOpenAnimationColorChangeAnimator; + private GradientDrawable mBackground; + /** * Used to inflate the Workspace from XML. * @@ -268,6 +272,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // name is complete, we have something to focus on, thus hiding the cursor and giving // reliable behavior when clicking the text field (since it will always gain focus on click). setFocusableInTouchMode(true); + + } + + @Override + public Drawable getBackground() { + return mBackground; } @Override @@ -276,6 +286,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo final DeviceProfile dp = mActivityContext.getDeviceProfile(); final int paddingLeftRight = dp.folderContentPaddingLeftRight; + mBackground = (GradientDrawable) ResourcesCompat.getDrawable(getResources(), + R.drawable.round_rect_folder, getContext().getTheme()); + mContent = findViewById(R.id.folder_content); mContent.setPadding(paddingLeftRight, dp.folderContentPaddingTop, paddingLeftRight, 0); mContent.setFolder(this); @@ -1213,6 +1226,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo lp.x = left; lp.y = top; + mBackground.setBounds(0, 0, width, height); + if (mColorExtractor != null) { mColorExtractor.removeLocations(); mColorExtractor.setListener(mColorListener); @@ -1714,14 +1729,16 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } @Override - public void draw(Canvas canvas) { + protected void dispatchDraw(Canvas canvas) { if (mClipPath != null) { int count = canvas.save(); canvas.clipPath(mClipPath); - super.draw(canvas); + mBackground.draw(canvas); canvas.restoreToCount(count); + super.dispatchDraw(canvas); } else { - super.draw(canvas); + mBackground.draw(canvas); + super.dispatchDraw(canvas); } } diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index 7fbfb8934e..bd0dbfde08 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -37,6 +37,7 @@ import android.view.animation.AnimationUtils; import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; +import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.Utilities; @@ -80,6 +81,8 @@ public class FolderAnimationManager { private ObjectAnimator mBgColorAnimator; + private DeviceProfile mDeviceProfile; + public FolderAnimationManager(Folder folder, boolean isOpening) { mFolder = folder; mContent = folder.mContent; @@ -89,7 +92,8 @@ public class FolderAnimationManager { mPreviewBackground = mFolderIcon.mBackground; mContext = folder.getContext(); - mPreviewVerifier = new FolderGridOrganizer(folder.mActivityContext.getDeviceProfile().inv); + mDeviceProfile = folder.mActivityContext.getDeviceProfile(); + mPreviewVerifier = new FolderGridOrganizer(mDeviceProfile.inv); mIsOpening = isOpening; @@ -211,8 +215,21 @@ public class FolderAnimationManager { play(a, getAnimator(mFolder.mContent, SCALE_PROPERTY, initialScale, finalScale)); play(a, getAnimator(mFolder.mFooter, SCALE_PROPERTY, initialScale, finalScale)); play(a, mFolderIcon.mFolderName.createTextAlphaAnimator(!mIsOpening)); + + // Create reveal animator for the folder background play(a, getShape().createRevealAnimator( mFolder, startRect, endRect, finalRadius, !mIsOpening)); + + // Create reveal animator for the folder content (capture the top 4 icons 2x2) + int width = mContent.getPaddingLeft() + mDeviceProfile.folderCellLayoutBorderSpacingPx + + mDeviceProfile.folderCellWidthPx * 2; + int height = mContent.getPaddingTop() + mDeviceProfile.folderCellLayoutBorderSpacingPx + + mDeviceProfile.folderCellHeightPx * 2; + Rect startRect2 = new Rect(0, 0, width, height); + play(a, getShape().createRevealAnimator( + mFolder.getContent(), startRect2, endRect, finalRadius, !mIsOpening)); + + // Fade in the folder name, as the text can overlap the icons when grid size is small. mFolder.mFolderName.setAlpha(mIsOpening ? 0f : 1f); play(a, getAnimator(mFolder.mFolderName, View.ALPHA, 0, 1), diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 279c4457d3..6b12d86d13 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -614,10 +614,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel if (mCurrentPreviewItems.isEmpty() && !mAnimating) return; - final int saveCount = canvas.save(); - canvas.clipPath(mBackground.getClipPath()); mPreviewItemManager.draw(canvas); - canvas.restoreToCount(saveCount); if (!mBackground.drawingDelegated()) { mBackground.drawBackgroundStroke(canvas); diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index 7fc3740106..3d2884a24c 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -22,6 +22,7 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; +import android.graphics.Path; import android.graphics.drawable.Drawable; import android.util.ArrayMap; import android.util.AttributeSet; @@ -49,6 +50,7 @@ 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 com.android.launcher3.views.ClipPathView; import java.util.ArrayList; import java.util.Iterator; @@ -57,7 +59,7 @@ import java.util.Map; import java.util.function.ToIntFunction; import java.util.stream.Collectors; -public class FolderPagedView extends PagedView { +public class FolderPagedView extends PagedView implements ClipPathView { private static final String TAG = "FolderPagedView"; @@ -89,6 +91,8 @@ public class FolderPagedView extends PagedView { private Folder mFolder; + private Path mClipPath; + // If the views are attached to the folder or not. A folder should be bound when its // animating or is open. private boolean mViewsBound = false; @@ -128,8 +132,16 @@ public class FolderPagedView extends PagedView { @Override protected void dispatchDraw(Canvas canvas) { - mFocusIndicatorHelper.draw(canvas); - super.dispatchDraw(canvas); + if (mClipPath != null) { + int count = canvas.save(); + canvas.clipPath(mClipPath); + mFocusIndicatorHelper.draw(canvas); + super.dispatchDraw(canvas); + canvas.restoreToCount(count); + } else { + mFocusIndicatorHelper.draw(canvas); + super.dispatchDraw(canvas); + } } /** @@ -628,4 +640,10 @@ public class FolderPagedView extends PagedView { public int itemsPerPage() { return mOrganizer.getMaxItemsPerPage(); } + + @Override + public void setClipPath(Path clipPath) { + mClipPath = clipPath; + invalidate(); + } } diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java index 4eab63e54f..204decbee4 100644 --- a/src/com/android/launcher3/folder/PreviewBackground.java +++ b/src/com/android/launcher3/folder/PreviewBackground.java @@ -51,6 +51,7 @@ import com.android.launcher3.views.ActivityContext; public class PreviewBackground extends CellLayout.DelegatedCellDrawing { private static final boolean DRAW_SHADOW = false; + private static final boolean DRAW_STROKE = false; private static final int CONSUMPTION_ANIMATION_DURATION = 100; @@ -303,6 +304,10 @@ public class PreviewBackground extends CellLayout.DelegatedCellDrawing { } public void animateBackgroundStroke() { + if (!DRAW_STROKE) { + return; + } + if (mStrokeAlphaAnimator != null) { mStrokeAlphaAnimator.cancel(); } @@ -319,6 +324,9 @@ public class PreviewBackground extends CellLayout.DelegatedCellDrawing { } public void drawBackgroundStroke(Canvas canvas) { + if (!DRAW_STROKE) { + return; + } mPaint.setColor(setColorAlphaBound(mStrokeColor, mStrokeAlpha)); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mStrokeWidth); @@ -363,7 +371,7 @@ public class PreviewBackground extends CellLayout.DelegatedCellDrawing { } mDrawingDelegate = null; - isClipping = true; + isClipping = false; invalidate(); } diff --git a/src/com/android/launcher3/graphics/IconShape.java b/src/com/android/launcher3/graphics/IconShape.java index 2da679c422..f82b07ebfa 100644 --- a/src/com/android/launcher3/graphics/IconShape.java +++ b/src/com/android/launcher3/graphics/IconShape.java @@ -156,19 +156,43 @@ public abstract class IconShape { } } - public static final class Circle extends SimpleRectShape { + public static final class Circle extends PathShape { - @Override - public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) { - canvas.drawCircle(radius + offsetX, radius + offsetY, radius, p); + private final float[] mTempRadii = new float[8]; + + protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect, + float endRadius, Path outPath) { + float r1 = getStartRadius(startRect); + + float[] startValues = new float[] { + startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r1}; + float[] endValues = new float[] { + endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius}; + + FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]); + + return (anim) -> { + float progress = (Float) anim.getAnimatedValue(); + float[] values = evaluator.evaluate(progress, startValues, endValues); + outPath.addRoundRect( + values[0], values[1], values[2], values[3], + getRadiiArray(values[4], values[5]), Path.Direction.CW); + }; } + private float[] getRadiiArray(float r1, float r2) { + mTempRadii[0] = mTempRadii [1] = mTempRadii[2] = mTempRadii[3] = + mTempRadii[6] = mTempRadii[7] = r1; + mTempRadii[4] = mTempRadii[5] = r2; + return mTempRadii; + } + + @Override public void addToPath(Path path, float offsetX, float offsetY, float radius) { path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW); } - @Override protected float getStartRadius(Rect startRect) { return startRect.width() / 2f; } diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java index 14108f614c..5a29171361 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java @@ -32,13 +32,44 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo public static final String CLS_CUSTOM_WIDGET_PREFIX = "#custom-widget-"; + /** + * The desired number of cells that this widget occupies horizontally in + * {@link com.android.launcher3.CellLayout}. + */ public int spanX; + + /** + * The desired number of cells that this widget occupies vertically in + * {@link com.android.launcher3.CellLayout}. + */ public int spanY; + + /** + * The minimum number of cells that this widget can occupy horizontally in + * {@link com.android.launcher3.CellLayout}. + */ public int minSpanX; + + /** + * The minimum number of cells that this widget can occupy vertically in + * {@link com.android.launcher3.CellLayout}. + */ public int minSpanY; + + /** + * The maximum number of cells that this widget can occupy horizontally in + * {@link com.android.launcher3.CellLayout}. + */ public int maxSpanX; + + /** + * The maximum number of cells that this widget can occupy vertically in + * {@link com.android.launcher3.CellLayout}. + */ public int maxSpanY; + private boolean mIsMinSizeFulfilled; + public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context, AppWidgetProviderInfo info) { final LauncherAppWidgetProviderInfo launcherInfo; @@ -133,8 +164,20 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo this.minSpanY = minSpanY; this.maxSpanX = maxSpanX; this.maxSpanY = maxSpanY; - this.spanX = spanX; - this.spanY = spanY; + this.mIsMinSizeFulfilled = Math.min(spanX, minSpanX) <= idp.numColumns + && Math.min(spanY, minSpanY) <= idp.numRows; + // Ensures the default span X and span Y will not exceed the current grid size. + this.spanX = Math.min(spanX, idp.numColumns); + this.spanY = Math.min(spanY, idp.numRows); + } + + /** + * Returns {@code true} if the widget's minimum size requirement can be fulfilled in the device + * grid setting, {@link InvariantDeviceProfile}, that was passed in + * {@link #initSpans(Context, InvariantDeviceProfile)}. + */ + public boolean isMinSizeFulfilled() { + return mIsMinSizeFulfilled; } private int getSpanX(Rect widgetPadding, int widgetWidth, int cellSpacing, float cellWidth) { diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java index 826c244a92..150bd9902f 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java @@ -202,11 +202,16 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC mDiffReporter.process(mVisibleEntries, newVisibleEntries, mRowComparator); } + /** Returns whether {@code entry} matches {@link #mWidgetsContentVisiblePackageUserKey}. */ private boolean isHeaderForVisibleContent(WidgetsListBaseEntry entry) { + return isHeaderForPackageUserKey(entry, mWidgetsContentVisiblePackageUserKey); + } + + /** Returns whether {@code entry} matches {@code key}. */ + private boolean isHeaderForPackageUserKey(WidgetsListBaseEntry entry, PackageUserKey key) { return (entry instanceof WidgetsListHeaderEntry || entry instanceof WidgetsListSearchHeaderEntry) - && new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user) - .equals(mWidgetsContentVisiblePackageUserKey); + && new PackageUserKey(entry.mPkgItem.packageName, entry.mPkgItem.user).equals(key); } /** @@ -270,36 +275,68 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC @Override public void onHeaderClicked(boolean showWidgets, PackageUserKey packageUserKey) { + // Ignore invalid clicks, such as collapsing a package that isn't currently expanded. + if (!showWidgets && !packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) return; + if (showWidgets) { mWidgetsContentVisiblePackageUserKey = packageUserKey; - updateVisibleEntries(); - // Scroll the layout manager to the header position to keep it anchored to the same - // position. - scrollToPositionAndMaintainOffset(getSelectedHeaderPosition()); mLauncher.getStatsLogManager().logger().log(LAUNCHER_WIDGETSTRAY_APP_EXPANDED); - } else if (packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) { - OptionalInt previouslySelectedPosition = getSelectedHeaderPosition(); - + } else { mWidgetsContentVisiblePackageUserKey = null; - updateVisibleEntries(); - - // Scroll to the header that was just collapsed so it maintains its scroll offset. - scrollToPositionAndMaintainOffset(previouslySelectedPosition); } + + // Get the current top of the header with the matching key before adjusting the visible + // entries. + OptionalInt topForPackageUserKey = + getOffsetForPosition(getPositionForPackageUserKey(packageUserKey)); + + updateVisibleEntries(); + + // Get the position for the clicked header after adjusting the visible entries. The + // position may have changed if another header had previously been expanded. + OptionalInt positionForPackageUserKey = getPositionForPackageUserKey(packageUserKey); + scrollToPositionAndMaintainOffset(positionForPackageUserKey, topForPackageUserKey); } - private OptionalInt getSelectedHeaderPosition() { + /** + * Returns the position of {@code key} in {@link #mVisibleEntries}, or empty if it's not + * present. + */ + private OptionalInt getPositionForPackageUserKey(PackageUserKey key) { return IntStream.range(0, mVisibleEntries.size()) - .filter(index -> isHeaderForVisibleContent(mVisibleEntries.get(index))) + .filter(index -> isHeaderForPackageUserKey(mVisibleEntries.get(index), key)) .findFirst(); } /** - * Scrolls to the selected header position. LinearLayoutManager scrolls the minimum distance - * necessary, so this will keep the selected header in place during clicks, without interrupting - * the animation. + * Returns the top of {@code positionOptional} in the recycler view, or empty if its view + * can't be found for any reason, including the position not being currently visible. The + * returned value does not include the top padding of the recycler view. */ - private void scrollToPositionAndMaintainOffset(OptionalInt positionOptional) { + private OptionalInt getOffsetForPosition(OptionalInt positionOptional) { + if (!positionOptional.isPresent() || mRecyclerView == null) return OptionalInt.empty(); + + RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager(); + if (layoutManager == null) return OptionalInt.empty(); + + View view = layoutManager.findViewByPosition(positionOptional.getAsInt()); + if (view == null) return OptionalInt.empty(); + + return OptionalInt.of(layoutManager.getDecoratedTop(view)); + } + + /** + * Scrolls to the selected header position with the provided offset. LinearLayoutManager + * scrolls the minimum distance necessary, so this will keep the selected header in place during + * clicks, without interrupting the animation. + * + * @param positionOptional The position too scroll to. No scrolling will be done if empty. + * @param offsetOptional The offset from the top to maintain. If empty, then the list will + * scroll to the top of the position. + */ + private void scrollToPositionAndMaintainOffset( + OptionalInt positionOptional, + OptionalInt offsetOptional) { if (!positionOptional.isPresent() || mRecyclerView == null) return; int position = positionOptional.getAsInt(); @@ -317,12 +354,9 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC // Scroll to the header view's current offset, accounting for the recycler view's padding. // If the header view couldn't be found, then it will appear at the top of the list. - View headerView = layoutManager.findViewByPosition(position); - int targetHeaderViewTop = - headerView == null ? 0 : layoutManager.getDecoratedTop(headerView); layoutManager.scrollToPositionWithOffset( position, - targetHeaderViewTop - mRecyclerView.getPaddingTop()); + offsetOptional.orElse(0) - mRecyclerView.getPaddingTop()); } /** diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java index be18e546d2..51c5b12b6e 100644 --- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java +++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java @@ -254,13 +254,11 @@ public class WidgetsModel { } // Ensure that all widgets we show can be added on a workspace of this size - int minSpanX = Math.min(item.widgetInfo.spanX, item.widgetInfo.minSpanX); - int minSpanY = Math.min(item.widgetInfo.spanY, item.widgetInfo.minSpanY); - if (minSpanX > mIdp.numColumns || minSpanY > mIdp.numRows) { + if (!item.widgetInfo.isMinSizeFulfilled()) { if (DEBUG) { Log.d(TAG, String.format( - "Widget %s : (%d X %d) can't fit on this device", - item.componentName, minSpanX, minSpanY)); + "Widget %s : can't fit on this device with a grid size: %dx%d", + item.componentName, mIdp.numColumns, mIdp.numRows)); } return false; }