Snap for 12190349 from 9164534fb9 to 24Q4-release
Change-Id: I04f4e09f968b4c9af7c8ea4822f0ad0c67fd6441
This commit is contained in:
@@ -138,6 +138,8 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
List<RemovedBubble> removedBubbles;
|
||||
List<String> bubbleKeysInOrder;
|
||||
Point expandedViewDropTargetSize;
|
||||
boolean showOverflow;
|
||||
boolean showOverflowChanged;
|
||||
|
||||
// These need to be loaded in the background
|
||||
BubbleBarBubble addedBubble;
|
||||
@@ -156,6 +158,8 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
removedBubbles = update.removedBubbles;
|
||||
bubbleKeysInOrder = update.bubbleKeysInOrder;
|
||||
expandedViewDropTargetSize = update.expandedViewDropTargetSize;
|
||||
showOverflow = update.showOverflow;
|
||||
showOverflowChanged = update.showOverflowChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +275,13 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
|
||||
BubbleBarBubble bubbleToSelect = null;
|
||||
|
||||
if (update.addedBubble != null && update.removedBubbles.size() == 1) {
|
||||
if (Flags.enableOptionalBubbleOverflow()
|
||||
&& update.showOverflowChanged && !update.showOverflow && update.addedBubble != null
|
||||
&& update.removedBubbles.isEmpty()) {
|
||||
// A bubble was added from the overflow (& now it's empty / not showing)
|
||||
mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
|
||||
mBubbleBarViewController.removeOverflowAndAddBubble(update.addedBubble);
|
||||
} else if (update.addedBubble != null && update.removedBubbles.size() == 1) {
|
||||
// we're adding and removing a bubble at the same time. handle this as a single update.
|
||||
RemovedBubble removedBubble = update.removedBubbles.get(0);
|
||||
BubbleBarBubble bubbleToRemove = mBubbles.remove(removedBubble.getKey());
|
||||
@@ -285,11 +295,17 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
Log.w(TAG, "trying to remove bubble that doesn't exist: " + removedBubble.getKey());
|
||||
}
|
||||
} else {
|
||||
boolean overflowNeedsToBeAdded = Flags.enableOptionalBubbleOverflow()
|
||||
&& update.showOverflowChanged && update.showOverflow;
|
||||
if (!update.removedBubbles.isEmpty()) {
|
||||
for (int i = 0; i < update.removedBubbles.size(); i++) {
|
||||
RemovedBubble removedBubble = update.removedBubbles.get(i);
|
||||
BubbleBarBubble bubble = mBubbles.remove(removedBubble.getKey());
|
||||
if (bubble != null) {
|
||||
if (bubble != null && overflowNeedsToBeAdded) {
|
||||
// First removal, show the overflow
|
||||
overflowNeedsToBeAdded = false;
|
||||
mBubbleBarViewController.addOverflowAndRemoveBubble(bubble);
|
||||
} else if (bubble != null) {
|
||||
mBubbleBarViewController.removeBubble(bubble);
|
||||
} else {
|
||||
Log.w(TAG, "trying to remove bubble that doesn't exist: "
|
||||
@@ -302,6 +318,11 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
mBubbleBarViewController.addBubble(update.addedBubble, isExpanding,
|
||||
suppressAnimation);
|
||||
}
|
||||
if (Flags.enableOptionalBubbleOverflow()
|
||||
&& update.showOverflowChanged
|
||||
&& update.showOverflow != mBubbleBarViewController.isOverflowAdded()) {
|
||||
mBubbleBarViewController.showOverflow(update.showOverflow);
|
||||
}
|
||||
}
|
||||
|
||||
// if a bubble was updated upstream, but removed before the update was received, add it back
|
||||
@@ -333,6 +354,9 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Flags.enableOptionalBubbleOverflow() && update.initialState && update.showOverflow) {
|
||||
mBubbleBarViewController.showOverflow(true);
|
||||
}
|
||||
|
||||
// Adds and removals have happened, update visibility before any other visual changes.
|
||||
mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
|
||||
|
||||
@@ -715,11 +715,13 @@ public class BubbleBarView extends FrameLayout {
|
||||
public void addBubble(BubbleView bubble) {
|
||||
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) mIconSize, (int) mIconSize,
|
||||
Gravity.LEFT);
|
||||
final int index = bubble.isOverflow() ? getChildCount() : 0;
|
||||
|
||||
if (isExpanded()) {
|
||||
// if we're expanded scale the new bubble in
|
||||
bubble.setScaleX(0f);
|
||||
bubble.setScaleY(0f);
|
||||
addView(bubble, 0, lp);
|
||||
addView(bubble, index, lp);
|
||||
bubble.showDotIfNeeded(/* animate= */ false);
|
||||
|
||||
mBubbleAnimator = new BubbleAnimator(mIconSize, mExpandedBarIconsSpacing,
|
||||
@@ -748,23 +750,33 @@ public class BubbleBarView extends FrameLayout {
|
||||
};
|
||||
mBubbleAnimator.animateNewBubble(indexOfChild(mSelectedBubbleView), listener);
|
||||
} else {
|
||||
addView(bubble, 0, lp);
|
||||
addView(bubble, index, lp);
|
||||
}
|
||||
}
|
||||
|
||||
/** Add a new bubble and remove an old bubble from the bubble bar. */
|
||||
public void addBubbleAndRemoveBubble(View addedBubble, View removedBubble) {
|
||||
public void addBubbleAndRemoveBubble(BubbleView addedBubble, BubbleView removedBubble) {
|
||||
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) mIconSize, (int) mIconSize,
|
||||
Gravity.LEFT);
|
||||
boolean isOverflowSelected = mSelectedBubbleView.isOverflow();
|
||||
boolean removingOverflow = removedBubble.isOverflow();
|
||||
boolean addingOverflow = addedBubble.isOverflow();
|
||||
|
||||
if (!isExpanded()) {
|
||||
removeView(removedBubble);
|
||||
addView(addedBubble, 0, lp);
|
||||
int index = addingOverflow ? getChildCount() : 0;
|
||||
addView(addedBubble, index, lp);
|
||||
return;
|
||||
}
|
||||
int index = addingOverflow ? getChildCount() : 0;
|
||||
addedBubble.setScaleX(0f);
|
||||
addedBubble.setScaleY(0f);
|
||||
addView(addedBubble, 0, lp);
|
||||
addView(addedBubble, index, lp);
|
||||
|
||||
if (isOverflowSelected && removingOverflow) {
|
||||
// The added bubble will be selected
|
||||
mSelectedBubbleView = addedBubble;
|
||||
}
|
||||
int indexOfSelectedBubble = indexOfChild(mSelectedBubbleView);
|
||||
int indexOfBubbleToRemove = indexOfChild(removedBubble);
|
||||
|
||||
@@ -924,7 +936,7 @@ public class BubbleBarView extends FrameLayout {
|
||||
final float currentWidth = getWidth();
|
||||
final float expandedWidth = expandedWidth();
|
||||
final float collapsedWidth = collapsedWidth();
|
||||
int bubbleCount = getChildCount();
|
||||
int childCount = getChildCount();
|
||||
float viewBottom = mBubbleBarBounds.height() + (isExpanded() ? mPointerSize : 0);
|
||||
float bubbleBarAnimatedTop = viewBottom - getBubbleBarHeight();
|
||||
// When translating X & Y the scale is ignored, so need to deduct it from the translations
|
||||
@@ -932,7 +944,7 @@ public class BubbleBarView extends FrameLayout {
|
||||
final boolean onLeft = bubbleBarLocation.isOnLeft(isLayoutRtl());
|
||||
// elevation state is opposite to widthState - when expanded all icons are flat
|
||||
float elevationState = (1 - widthState);
|
||||
for (int i = 0; i < bubbleCount; i++) {
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
BubbleView bv = (BubbleView) getChildAt(i);
|
||||
if (bv == mDraggedBubbleView || bv == mDismissedByDragBubbleView) {
|
||||
// Skip the dragged bubble. Its translation is managed by the drag controller.
|
||||
@@ -951,9 +963,9 @@ public class BubbleBarView extends FrameLayout {
|
||||
bv.setTranslationY(ty);
|
||||
|
||||
// the position of the bubble when the bar is fully expanded
|
||||
final float expandedX = getExpandedBubbleTranslationX(i, bubbleCount, onLeft);
|
||||
final float expandedX = getExpandedBubbleTranslationX(i, childCount, onLeft);
|
||||
// the position of the bubble when the bar is fully collapsed
|
||||
final float collapsedX = getCollapsedBubbleTranslationX(i, bubbleCount, onLeft);
|
||||
final float collapsedX = getCollapsedBubbleTranslationX(i, childCount, onLeft);
|
||||
|
||||
// slowly animate elevation while keeping correct Z ordering
|
||||
float fullElevationForChild = (MAX_BUBBLES * mBubbleElevation) - i;
|
||||
@@ -981,13 +993,10 @@ public class BubbleBarView extends FrameLayout {
|
||||
final float collapsedBarShift = onLeft ? 0 : currentWidth - collapsedWidth;
|
||||
final float targetX = collapsedX + collapsedBarShift;
|
||||
bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
|
||||
// If we're fully collapsed, hide all bubbles except for the first 2. If there are
|
||||
// only 2 bubbles, hide the second bubble as well because it's the overflow.
|
||||
// If we're fully collapsed, hide all bubbles except for the first 2, excluding
|
||||
// the overflow.
|
||||
if (widthState == 0) {
|
||||
if (i > MAX_VISIBLE_BUBBLES_COLLAPSED - 1) {
|
||||
bv.setAlpha(0);
|
||||
} else if (i == MAX_VISIBLE_BUBBLES_COLLAPSED - 1
|
||||
&& bubbleCount == MAX_VISIBLE_BUBBLES_COLLAPSED) {
|
||||
if (bv.isOverflow() || i > MAX_VISIBLE_BUBBLES_COLLAPSED - 1) {
|
||||
bv.setAlpha(0);
|
||||
} else {
|
||||
bv.setAlpha(1);
|
||||
@@ -1043,22 +1052,26 @@ public class BubbleBarView extends FrameLayout {
|
||||
return translationX - getScaleIconShift();
|
||||
}
|
||||
|
||||
private float getCollapsedBubbleTranslationX(int bubbleIndex, int bubbleCount, boolean onLeft) {
|
||||
if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
|
||||
private float getCollapsedBubbleTranslationX(int bubbleIndex, int childCount, boolean onLeft) {
|
||||
if (bubbleIndex < 0 || bubbleIndex >= childCount) {
|
||||
return 0;
|
||||
}
|
||||
float translationX;
|
||||
if (onLeft) {
|
||||
// Shift the first bubble only if there are more bubbles in addition to overflow
|
||||
translationX = mBubbleBarPadding + (
|
||||
bubbleIndex == 0 && bubbleCount > MAX_VISIBLE_BUBBLES_COLLAPSED
|
||||
? mIconOverlapAmount : 0);
|
||||
// Shift the first bubble only if there are more bubbles
|
||||
if (bubbleIndex == 0 && getBubbleChildCount() >= MAX_VISIBLE_BUBBLES_COLLAPSED) {
|
||||
translationX = mIconOverlapAmount;
|
||||
} else {
|
||||
translationX = 0f;
|
||||
}
|
||||
} else {
|
||||
translationX = mBubbleBarPadding + (
|
||||
bubbleIndex == 0 || bubbleCount <= MAX_VISIBLE_BUBBLES_COLLAPSED
|
||||
? 0 : mIconOverlapAmount);
|
||||
if (bubbleIndex == 1 && getBubbleChildCount() >= MAX_VISIBLE_BUBBLES_COLLAPSED) {
|
||||
translationX = mIconOverlapAmount;
|
||||
} else {
|
||||
translationX = 0f;
|
||||
}
|
||||
}
|
||||
return translationX - getScaleIconShift();
|
||||
return mBubbleBarPadding + translationX - getScaleIconShift();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1256,15 +1269,20 @@ public class BubbleBarView extends FrameLayout {
|
||||
}
|
||||
|
||||
private float collapsedWidth() {
|
||||
final int childCount = getChildCount();
|
||||
final int bubbleChildCount = getBubbleChildCount();
|
||||
final float horizontalPadding = 2 * mBubbleBarPadding;
|
||||
// If there are more than 2 bubbles, the first 2 should be visible when collapsed.
|
||||
// Otherwise just the first bubble should be visible because we don't show the overflow.
|
||||
return childCount > MAX_VISIBLE_BUBBLES_COLLAPSED
|
||||
// If there are more than 2 bubbles, the first 2 should be visible when collapsed,
|
||||
// excluding the overflow.
|
||||
return bubbleChildCount >= MAX_VISIBLE_BUBBLES_COLLAPSED
|
||||
? getScaledIconSize() + mIconOverlapAmount + horizontalPadding
|
||||
: getScaledIconSize() + horizontalPadding;
|
||||
}
|
||||
|
||||
/** Returns the child count excluding the overflow if it's present. */
|
||||
private int getBubbleChildCount() {
|
||||
return hasOverflow() ? getChildCount() - 1 : getChildCount();
|
||||
}
|
||||
|
||||
private float getBubbleBarExpandedHeight() {
|
||||
return getBubbleBarCollapsedHeight() + mPointerSize;
|
||||
}
|
||||
@@ -1303,8 +1321,8 @@ public class BubbleBarView extends FrameLayout {
|
||||
return mIsAnimatingNewBubble;
|
||||
}
|
||||
|
||||
private boolean hasOverview() {
|
||||
// Overview is always the last bubble
|
||||
private boolean hasOverflow() {
|
||||
// Overflow is always the last bubble
|
||||
View lastChild = getChildAt(getChildCount() - 1);
|
||||
if (lastChild instanceof BubbleView bubbleView) {
|
||||
return bubbleView.getBubble() instanceof BubbleBarOverflow;
|
||||
@@ -1336,7 +1354,7 @@ public class BubbleBarView extends FrameLayout {
|
||||
CharSequence contentDesc = firstChild != null ? firstChild.getContentDescription() : "";
|
||||
|
||||
// Don't count overflow if it exists
|
||||
int bubbleCount = getChildCount() - (hasOverview() ? 1 : 0);
|
||||
int bubbleCount = getChildCount() - (hasOverflow() ? 1 : 0);
|
||||
if (bubbleCount > 1) {
|
||||
contentDesc = getResources().getString(R.string.bubble_bar_description_multiple_bubbles,
|
||||
contentDesc, bubbleCount - 1);
|
||||
|
||||
@@ -92,6 +92,8 @@ public class BubbleBarViewController {
|
||||
private boolean mHiddenForNoBubbles = true;
|
||||
private boolean mShouldShowEducation;
|
||||
|
||||
public boolean mOverflowAdded;
|
||||
|
||||
private BubbleBarViewAnimator mBubbleBarViewAnimator;
|
||||
|
||||
private final TimeSource mTimeSource = System::currentTimeMillis;
|
||||
@@ -123,7 +125,6 @@ public class BubbleBarViewController {
|
||||
mBubbleBarClickListener = v -> expandBubbleBar();
|
||||
mBubbleDragController.setupBubbleBarView(mBarView);
|
||||
mOverflowBubble = bubbleControllers.bubbleCreator.createOverflow(mBarView);
|
||||
addOverflow();
|
||||
mBarView.setOnClickListener(mBubbleBarClickListener);
|
||||
mBarView.addOnLayoutChangeListener(
|
||||
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
||||
@@ -494,13 +495,44 @@ public class BubbleBarViewController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the overflow view to the bubble bar.
|
||||
*/
|
||||
public void addOverflow() {
|
||||
mBarView.addBubble(mOverflowBubble.getView());
|
||||
/** Whether the overflow view is added to the bubble bar. */
|
||||
public boolean isOverflowAdded() {
|
||||
return mOverflowAdded;
|
||||
}
|
||||
|
||||
/** Shows or hides the overflow view. */
|
||||
public void showOverflow(boolean showOverflow) {
|
||||
if (mOverflowAdded == showOverflow) return;
|
||||
mOverflowAdded = showOverflow;
|
||||
if (mOverflowAdded) {
|
||||
mBarView.addBubble(mOverflowBubble.getView());
|
||||
mOverflowBubble.getView().setOnClickListener(mBubbleClickListener);
|
||||
mOverflowBubble.getView().setController(mBubbleViewController);
|
||||
} else {
|
||||
mBarView.removeBubble(mOverflowBubble.getView());
|
||||
mOverflowBubble.getView().setOnClickListener(null);
|
||||
mOverflowBubble.getView().setController(null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds the overflow view to the bubble bar while animating a view away. */
|
||||
public void addOverflowAndRemoveBubble(BubbleBarBubble removedBubble) {
|
||||
if (mOverflowAdded) return;
|
||||
mOverflowAdded = true;
|
||||
mBarView.addBubbleAndRemoveBubble(mOverflowBubble.getView(), removedBubble.getView());
|
||||
mOverflowBubble.getView().setOnClickListener(mBubbleClickListener);
|
||||
mOverflowBubble.getView().setController(mBubbleViewController);
|
||||
removedBubble.getView().setController(null);
|
||||
}
|
||||
|
||||
/** Removes the overflow view to the bubble bar while animating a view in. */
|
||||
public void removeOverflowAndAddBubble(BubbleBarBubble addedBubble) {
|
||||
if (!mOverflowAdded) return;
|
||||
mOverflowAdded = false;
|
||||
mBarView.addBubbleAndRemoveBubble(addedBubble.getView(), mOverflowBubble.getView());
|
||||
addedBubble.getView().setOnClickListener(mBubbleClickListener);
|
||||
addedBubble.getView().setController(mBubbleViewController);
|
||||
mOverflowBubble.getView().setController(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,6 +74,7 @@ public class BubbleView extends ConstraintLayout {
|
||||
private boolean mOnLeft = false;
|
||||
|
||||
private BubbleBarItem mBubble;
|
||||
private boolean mIsOverflow;
|
||||
|
||||
private Bitmap mIcon;
|
||||
|
||||
@@ -271,12 +272,18 @@ public class BubbleView extends ConstraintLayout {
|
||||
*/
|
||||
public void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
|
||||
mBubble = overflow;
|
||||
mIsOverflow = true;
|
||||
mIcon = bitmap;
|
||||
updateBubbleIcon();
|
||||
mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
|
||||
setContentDescription(getResources().getString(R.string.bubble_bar_overflow_description));
|
||||
}
|
||||
|
||||
/** Whether this view represents the overflow button. */
|
||||
public boolean isOverflow() {
|
||||
return mIsOverflow;
|
||||
}
|
||||
|
||||
/** Returns the bubble being rendered in this view. */
|
||||
@Nullable
|
||||
public BubbleBarItem getBubble() {
|
||||
|
||||
@@ -989,7 +989,6 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
|
||||
dp = dp.copy(mContext);
|
||||
}
|
||||
dp.updateInsets(targets.homeContentInsets);
|
||||
dp.updateIsSeascape(mContext);
|
||||
initTransitionEndpoints(dp);
|
||||
orientationState.setMultiWindowMode(dp.isMultiWindowMode);
|
||||
}
|
||||
|
||||
@@ -782,7 +782,6 @@ abstract class TutorialController implements BackGestureAttemptCallback,
|
||||
(RelativeLayout.LayoutParams) mFakeHotseatView.getLayoutParams();
|
||||
if (!mTutorialFragment.isLargeScreen()) {
|
||||
DeviceProfile dp = mTutorialFragment.getDeviceProfile();
|
||||
dp.updateIsSeascape(mContext);
|
||||
|
||||
hotseatLayoutParams.addRule(dp.isLandscape
|
||||
? (dp.isSeascape()
|
||||
|
||||
@@ -140,15 +140,11 @@ public abstract class BaseDraggingActivity extends BaseActivity
|
||||
}
|
||||
|
||||
protected void onDeviceProfileInitiated() {
|
||||
if (mDeviceProfile.isVerticalBarLayout()) {
|
||||
mDeviceProfile.updateIsSeascape(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayInfoChanged(Context context, Info info, int flags) {
|
||||
if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.isVerticalBarLayout()) {
|
||||
mDeviceProfile.updateIsSeascape(this);
|
||||
reapplyUi();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@ import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecT
|
||||
import com.android.launcher3.responsive.ResponsiveSpec.DimensionType;
|
||||
import com.android.launcher3.responsive.ResponsiveSpecsProvider;
|
||||
import com.android.launcher3.util.CellContentDimensions;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.DisplayController.Info;
|
||||
import com.android.launcher3.util.IconSizeSteps;
|
||||
import com.android.launcher3.util.ResourceHelper;
|
||||
@@ -298,9 +297,6 @@ public class DeviceProfile {
|
||||
// the widgetView, such that the actual view size is same as the widget size.
|
||||
public final Rect widgetPadding = new Rect();
|
||||
|
||||
// When true, nav bar is on the left side of the screen.
|
||||
private boolean mIsSeascape;
|
||||
|
||||
// Notification dots
|
||||
public final DotRenderer mDotRendererWorkSpace;
|
||||
public final DotRenderer mDotRendererAllApps;
|
||||
@@ -2007,25 +2003,8 @@ public class DeviceProfile {
|
||||
return isLandscape && transposeLayoutWithOrientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates orientation information and returns true if it has changed from the previous value.
|
||||
*/
|
||||
public boolean updateIsSeascape(Context context) {
|
||||
if (isVerticalBarLayout()) {
|
||||
boolean isSeascape = DisplayController.INSTANCE.get(context)
|
||||
.getInfo().rotation == Surface.ROTATION_270;
|
||||
if (mIsSeascape != isSeascape) {
|
||||
mIsSeascape = isSeascape;
|
||||
// Hotseat changing sides requires updating workspace left/right paddings
|
||||
updateWorkspacePadding();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSeascape() {
|
||||
return isVerticalBarLayout() && mIsSeascape;
|
||||
return rotationHint == Surface.ROTATION_270 && isVerticalBarLayout();
|
||||
}
|
||||
|
||||
public boolean shouldFadeAdjacentWorkspaceScreens() {
|
||||
|
||||
@@ -62,6 +62,10 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() where T : Context, T : Act
|
||||
fun preInflateAllAppsViewHolders(context: T) {
|
||||
val appsView = context.appsView ?: return
|
||||
val activeRv: RecyclerView = appsView.activeRecyclerView ?: return
|
||||
val preInflateCount = getPreinflateCount(context)
|
||||
if (preInflateCount <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (activeRv.layoutManager == null) {
|
||||
if (BuildConfig.IS_STUDIO_BUILD) {
|
||||
@@ -99,7 +103,12 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() where T : Context, T : Act
|
||||
override fun getLayoutManager(): RecyclerView.LayoutManager? = null
|
||||
}
|
||||
|
||||
preInflateAllAppsViewHolders(adapter, BaseAllAppsAdapter.VIEW_TYPE_ICON, activeRv) {
|
||||
preInflateAllAppsViewHolders(
|
||||
adapter,
|
||||
BaseAllAppsAdapter.VIEW_TYPE_ICON,
|
||||
activeRv,
|
||||
preInflateCount
|
||||
) {
|
||||
getPreinflateCount(context)
|
||||
}
|
||||
}
|
||||
@@ -109,10 +118,10 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() where T : Context, T : Act
|
||||
adapter: RecyclerView.Adapter<*>,
|
||||
viewType: Int,
|
||||
parent: ViewGroup,
|
||||
preInflationCount: Int,
|
||||
preInflationCountProvider: () -> Int
|
||||
) {
|
||||
val preinflationCount = preInflationCountProvider.invoke()
|
||||
if (preinflationCount <= 0) {
|
||||
if (preInflationCount <= 0) {
|
||||
return
|
||||
}
|
||||
mCancellableTask?.cancel()
|
||||
@@ -121,7 +130,7 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() where T : Context, T : Act
|
||||
CancellableTask(
|
||||
{
|
||||
val list: ArrayList<ViewHolder> = ArrayList()
|
||||
for (i in 0 until preinflationCount) {
|
||||
for (i in 0 until preInflationCount) {
|
||||
if (task?.canceled == true) {
|
||||
break
|
||||
}
|
||||
@@ -132,8 +141,8 @@ class AllAppsRecyclerViewPool<T> : RecycledViewPool() where T : Context, T : Act
|
||||
MAIN_EXECUTOR,
|
||||
{ viewHolders ->
|
||||
// Run preInflationCountProvider again as the needed VH might have changed
|
||||
val newPreinflationCount = preInflationCountProvider.invoke()
|
||||
for (i in 0 until minOf(viewHolders.size, newPreinflationCount)) {
|
||||
val newPreInflationCount = preInflationCountProvider.invoke()
|
||||
for (i in 0 until minOf(viewHolders.size, newPreInflationCount)) {
|
||||
putRecycledView(viewHolders[i])
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -104,7 +104,7 @@ class HotseatReorderUnitTest {
|
||||
val cl = cellLayoutBuilder.createCellLayout(board.width, board.height, false)
|
||||
// The views have to be sorted or the result can vary
|
||||
board.icons
|
||||
.map(IconPoint::getCoord)
|
||||
.map(IconPoint::coord)
|
||||
.sortedWith(
|
||||
Comparator.comparing { p: Any -> (p as Point).x }
|
||||
.thenComparing { p: Any -> (p as Point).y }
|
||||
@@ -120,9 +120,7 @@ class HotseatReorderUnitTest {
|
||||
)
|
||||
}
|
||||
board.widgets
|
||||
.sortedWith(
|
||||
Comparator.comparing(WidgetRect::getCellX).thenComparing(WidgetRect::getCellY)
|
||||
)
|
||||
.sortedWith(Comparator.comparing(WidgetRect::cellX).thenComparing(WidgetRect::cellY))
|
||||
.forEach { widget ->
|
||||
addViewInCellLayout(
|
||||
cl,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.celllayout.board
|
||||
|
||||
import android.graphics.Point
|
||||
import android.graphics.Rect
|
||||
|
||||
/** Represents a widget in a CellLayoutBoard */
|
||||
data class WidgetRect(
|
||||
val type: Char,
|
||||
val bounds: Rect,
|
||||
) {
|
||||
val spanX: Int = bounds.right - bounds.left + 1
|
||||
val spanY: Int = bounds.top - bounds.bottom + 1
|
||||
val cellY: Int = bounds.bottom
|
||||
val cellX: Int = bounds.left
|
||||
|
||||
fun shouldIgnore() = type == CellType.IGNORE
|
||||
|
||||
fun contains(x: Int, y: Int) = bounds.contains(x, y)
|
||||
}
|
||||
|
||||
/**
|
||||
* [A-Z]: Represents a folder and number of icons in the folder is represented by the order of
|
||||
* letter in the alphabet, A=2, B=3, C=4 ... etc.
|
||||
*/
|
||||
data class FolderPoint(val coord: Point, val type: Char) {
|
||||
val numberIconsInside: Int = type.code - 'A'.code + 2
|
||||
}
|
||||
|
||||
/** Represents an icon in a CellLayoutBoard */
|
||||
data class IconPoint(val coord: Point, val type: Char = CellType.ICON)
|
||||
|
||||
object CellType {
|
||||
// The cells marked by this will be filled by 1x1 widgets and will be ignored when
|
||||
// validating
|
||||
const val IGNORE = 'x'
|
||||
|
||||
// The cells marked by this will be filled by app icons
|
||||
const val ICON = 'i'
|
||||
|
||||
// The cells marked by FOLDER will be filled by folders with 27 app icons inside
|
||||
const val FOLDER = 'Z'
|
||||
|
||||
// Empty space
|
||||
const val EMPTY = '-'
|
||||
|
||||
// Widget that will be saved as "main widget" for easier retrieval
|
||||
const val MAIN_WIDGET = 'm' // Everything else will be consider a widget
|
||||
}
|
||||
+16
-16
@@ -88,7 +88,7 @@ public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
|
||||
|
||||
public WidgetRect getWidgetOfType(char type) {
|
||||
return mWidgetsRects.stream()
|
||||
.filter(widgetRect -> widgetRect.mType == type).findFirst().orElse(null);
|
||||
.filter(widgetRect -> widgetRect.getType() == type).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public WidgetRect getWidgetAt(int x, int y) {
|
||||
@@ -117,8 +117,8 @@ public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
|
||||
}
|
||||
|
||||
private void removeWidgetFromBoard(WidgetRect widget) {
|
||||
for (int xi = widget.mBounds.left; xi <= widget.mBounds.right; xi++) {
|
||||
for (int yi = widget.mBounds.bottom; yi <= widget.mBounds.top; yi++) {
|
||||
for (int xi = widget.getBounds().left; xi <= widget.getBounds().right; xi++) {
|
||||
for (int yi = widget.getBounds().bottom; yi <= widget.getBounds().top; yi++) {
|
||||
mWidget[xi][yi] = '-';
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,7 @@ public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
|
||||
private void removeOverlappingItems(Rect rect) {
|
||||
// Remove overlapping widgets and remove them from the board
|
||||
mWidgetsRects = mWidgetsRects.stream().filter(widget -> {
|
||||
if (rect.intersect(widget.mBounds)) {
|
||||
if (rect.intersect(widget.getBounds())) {
|
||||
removeWidgetFromBoard(widget);
|
||||
return false;
|
||||
}
|
||||
@@ -135,8 +135,8 @@ public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
|
||||
}).collect(Collectors.toList());
|
||||
// Remove overlapping icons and remove them from the board
|
||||
mIconPoints = mIconPoints.stream().filter(iconPoint -> {
|
||||
int x = iconPoint.coord.x;
|
||||
int y = iconPoint.coord.y;
|
||||
int x = iconPoint.getCoord().x;
|
||||
int y = iconPoint.getCoord().y;
|
||||
if (rect.contains(x, y)) {
|
||||
mWidget[x][y] = '-';
|
||||
return false;
|
||||
@@ -146,8 +146,8 @@ public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
|
||||
|
||||
// Remove overlapping folders and remove them from the board
|
||||
mFolderPoints = mFolderPoints.stream().filter(folderPoint -> {
|
||||
int x = folderPoint.coord.x;
|
||||
int y = folderPoint.coord.y;
|
||||
int x = folderPoint.getCoord().x;
|
||||
int y = folderPoint.getCoord().y;
|
||||
if (rect.contains(x, y)) {
|
||||
mWidget[x][y] = '-';
|
||||
return false;
|
||||
@@ -159,7 +159,7 @@ public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
|
||||
private void removeOverlappingItems(Point p) {
|
||||
// Remove overlapping widgets and remove them from the board
|
||||
mWidgetsRects = mWidgetsRects.stream().filter(widget -> {
|
||||
if (IdenticalBoardComparator.Companion.touchesPoint(widget.mBounds, p)) {
|
||||
if (IdenticalBoardComparator.Companion.touchesPoint(widget.getBounds(), p)) {
|
||||
removeWidgetFromBoard(widget);
|
||||
return false;
|
||||
}
|
||||
@@ -167,8 +167,8 @@ public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
|
||||
}).collect(Collectors.toList());
|
||||
// Remove overlapping icons and remove them from the board
|
||||
mIconPoints = mIconPoints.stream().filter(iconPoint -> {
|
||||
int x = iconPoint.coord.x;
|
||||
int y = iconPoint.coord.y;
|
||||
int x = iconPoint.getCoord().x;
|
||||
int y = iconPoint.getCoord().y;
|
||||
if (p.x == x && p.y == y) {
|
||||
mWidget[x][y] = '-';
|
||||
return false;
|
||||
@@ -178,8 +178,8 @@ public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
|
||||
|
||||
// Remove overlapping folders and remove them from the board
|
||||
mFolderPoints = mFolderPoints.stream().filter(folderPoint -> {
|
||||
int x = folderPoint.coord.x;
|
||||
int y = folderPoint.coord.y;
|
||||
int x = folderPoint.getCoord().x;
|
||||
int y = folderPoint.getCoord().y;
|
||||
if (p.x == x && p.y == y) {
|
||||
mWidget[x][y] = '-';
|
||||
return false;
|
||||
@@ -226,7 +226,7 @@ public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
|
||||
|
||||
public void removeItem(char type) {
|
||||
mWidgetsRects.stream()
|
||||
.filter(widgetRect -> widgetRect.mType == type)
|
||||
.filter(widgetRect -> widgetRect.getType() == type)
|
||||
.forEach(widgetRect -> removeOverlappingItems(
|
||||
new Point(widgetRect.getCellX(), widgetRect.getCellY())));
|
||||
}
|
||||
@@ -365,10 +365,10 @@ public class CellLayoutBoard implements Comparable<CellLayoutBoard> {
|
||||
board.mWidth = lines[0].length();
|
||||
board.mWidgetsRects = getRects(board.mWidget);
|
||||
board.mWidgetsRects.forEach(widgetRect -> {
|
||||
if (widgetRect.mType == CellType.MAIN_WIDGET) {
|
||||
if (widgetRect.getType() == CellType.MAIN_WIDGET) {
|
||||
board.mMain = widgetRect;
|
||||
}
|
||||
board.mWidgetsMap.put(widgetRect.mType, widgetRect);
|
||||
board.mWidgetsMap.put(widgetRect.getType(), widgetRect);
|
||||
});
|
||||
board.mIconPoints = getIconPoints(board.mWidget);
|
||||
board.mFolderPoints = getFolderPoints(board.mWidget);
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.celllayout.board;
|
||||
|
||||
public class CellType {
|
||||
// The cells marked by this will be filled by 1x1 widgets and will be ignored when
|
||||
// validating
|
||||
public static final char IGNORE = 'x';
|
||||
// The cells marked by this will be filled by app icons
|
||||
public static final char ICON = 'i';
|
||||
// The cells marked by FOLDER will be filled by folders with 27 app icons inside
|
||||
public static final char FOLDER = 'Z';
|
||||
// Empty space
|
||||
public static final char EMPTY = '-';
|
||||
// Widget that will be saved as "main widget" for easier retrieval
|
||||
public static final char MAIN_WIDGET = 'm';
|
||||
// Everything else will be consider a widget
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.celllayout.board;
|
||||
|
||||
import android.graphics.Point;
|
||||
|
||||
public class FolderPoint {
|
||||
public Point coord;
|
||||
public char mType;
|
||||
|
||||
public FolderPoint(Point coord, char type) {
|
||||
this.coord = coord;
|
||||
mType = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* [A-Z]: Represents a folder and number of icons in the folder is represented by
|
||||
* the order of letter in the alphabet, A=2, B=3, C=4 ... etc.
|
||||
*/
|
||||
public int getNumberIconsInside() {
|
||||
return (mType - 'A') + 2;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.celllayout.board;
|
||||
|
||||
import android.graphics.Point;
|
||||
|
||||
public class IconPoint {
|
||||
public Point coord;
|
||||
public char mType;
|
||||
|
||||
public IconPoint(Point coord, char type) {
|
||||
this.coord = coord;
|
||||
mType = type;
|
||||
}
|
||||
|
||||
public char getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
public void setType(char type) {
|
||||
mType = type;
|
||||
}
|
||||
|
||||
public Point getCoord() {
|
||||
return coord;
|
||||
}
|
||||
|
||||
public void setCoord(Point coord) {
|
||||
this.coord = coord;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -26,11 +26,11 @@ class IdenticalBoardComparator : Comparator<CellLayoutBoard> {
|
||||
|
||||
/** Converts a list of WidgetRect into a map of the count of different widget.bounds */
|
||||
private fun widgetsToBoundsMap(widgets: List<WidgetRect>) =
|
||||
widgets.groupingBy { it.mBounds }.eachCount()
|
||||
widgets.groupingBy { it.bounds }.eachCount()
|
||||
|
||||
/** Converts a list of IconPoint into a map of the count of different icon.coord */
|
||||
private fun iconsToPosCountMap(widgets: List<IconPoint>) =
|
||||
widgets.groupingBy { it.getCoord() }.eachCount()
|
||||
widgets.groupingBy { it.coord }.eachCount()
|
||||
|
||||
override fun compare(
|
||||
cellLayoutBoard: CellLayoutBoard,
|
||||
@@ -47,7 +47,7 @@ class IdenticalBoardComparator : Comparator<CellLayoutBoard> {
|
||||
widgetsToBoundsMap(
|
||||
otherCellLayoutBoard.widgets
|
||||
.filter { !it.shouldIgnore() }
|
||||
.filter { !overlapsWithIgnored(ignoredRectangles, it.mBounds) }
|
||||
.filter { !overlapsWithIgnored(ignoredRectangles, it.bounds) }
|
||||
)
|
||||
|
||||
if (widgetsMap != otherWidgetMap) {
|
||||
|
||||
-192
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.celllayout.board;
|
||||
|
||||
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.launcher3.ui.TestViewHelpers.findWidgetProvider;
|
||||
import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.model.data.FolderInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class TestWorkspaceBuilder {
|
||||
|
||||
private static final String TAG = "CellLayoutBoardBuilder";
|
||||
private static final String TEST_ACTIVITY_PACKAGE_PREFIX = "com.android.launcher3.tests.";
|
||||
private ComponentName mAppComponentName = new ComponentName(
|
||||
"com.google.android.calculator", "com.android.calculator2.Calculator");
|
||||
private UserHandle mMyUser;
|
||||
|
||||
private Context mContext;
|
||||
|
||||
public TestWorkspaceBuilder(Context context) {
|
||||
mMyUser = Process.myUserHandle();
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the given rect in WidgetRect with 1x1 widgets. This is useful to equalize cases.
|
||||
*/
|
||||
private FavoriteItemsTransaction fillWithWidgets(WidgetRect widgetRect,
|
||||
FavoriteItemsTransaction transaction, int screenId) {
|
||||
int initX = widgetRect.getCellX();
|
||||
int initY = widgetRect.getCellY();
|
||||
for (int x = initX; x < initX + widgetRect.getSpanX(); x++) {
|
||||
for (int y = initY; y < initY + widgetRect.getSpanY(); y++) {
|
||||
try {
|
||||
// this widgets are filling, we don't care if we can't place them
|
||||
transaction.addItem(createWidgetInCell(
|
||||
new WidgetRect(CellType.IGNORE,
|
||||
new Rect(x, y, x, y)), screenId));
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Unable to place filling widget at " + x + "," + y);
|
||||
}
|
||||
}
|
||||
}
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private AppInfo getApp() {
|
||||
return new AppInfo(mAppComponentName, "test icon", mMyUser,
|
||||
AppInfo.makeLaunchIntent(mAppComponentName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to set the app to use for the test workspace,
|
||||
* using activity-alias from AndroidManifest-common.
|
||||
* @param testAppName the android:name field of the test app activity-alias to use
|
||||
*/
|
||||
public void setTestAppActivityAlias(String testAppName) {
|
||||
this.mAppComponentName = new ComponentName(
|
||||
getInstrumentation().getContext().getPackageName(),
|
||||
TEST_ACTIVITY_PACKAGE_PREFIX + testAppName
|
||||
);
|
||||
}
|
||||
|
||||
private void addCorrespondingWidgetRect(WidgetRect widgetRect,
|
||||
FavoriteItemsTransaction transaction, int screenId) {
|
||||
if (widgetRect.mType == 'x') {
|
||||
fillWithWidgets(widgetRect, transaction, screenId);
|
||||
} else {
|
||||
transaction.addItem(createWidgetInCell(widgetRect, screenId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the given board into the transaction
|
||||
*/
|
||||
public FavoriteItemsTransaction buildFromBoard(CellLayoutBoard board,
|
||||
FavoriteItemsTransaction transaction, final int screenId) {
|
||||
board.getWidgets().forEach(
|
||||
(widgetRect) -> addCorrespondingWidgetRect(widgetRect, transaction, screenId));
|
||||
board.getIcons().forEach((iconPoint) ->
|
||||
transaction.addItem(() -> createIconInCell(iconPoint, screenId))
|
||||
);
|
||||
board.getFolders().forEach((folderPoint) ->
|
||||
transaction.addItem(() -> createFolderInCell(folderPoint, screenId))
|
||||
);
|
||||
return transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the hotseat row with apps instead of suggestions, for this to work the workspace should
|
||||
* be clean otherwise this doesn't overrides the existing icons.
|
||||
*/
|
||||
public FavoriteItemsTransaction fillHotseatIcons(FavoriteItemsTransaction transaction) {
|
||||
IntStream.range(0, InvariantDeviceProfile.INSTANCE.get(mContext).numDatabaseHotseatIcons)
|
||||
.forEach(i -> transaction.addItem(() -> getHotseatValues(i)));
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private Supplier<ItemInfo> createWidgetInCell(
|
||||
WidgetRect widgetRect, int screenId) {
|
||||
// Create the widget lazily since the appWidgetId can get lost during setup
|
||||
return () -> {
|
||||
LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
|
||||
LauncherAppWidgetInfo item = createWidgetInfo(info, getApplicationContext(), true);
|
||||
item.cellX = widgetRect.getCellX();
|
||||
item.cellY = widgetRect.getCellY();
|
||||
item.spanX = widgetRect.getSpanX();
|
||||
item.spanY = widgetRect.getSpanY();
|
||||
item.screenId = screenId;
|
||||
return item;
|
||||
};
|
||||
}
|
||||
|
||||
public FolderInfo createFolderInCell(FolderPoint folderPoint, int screenId) {
|
||||
FolderInfo folderInfo = new FolderInfo();
|
||||
folderInfo.screenId = screenId;
|
||||
folderInfo.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
||||
folderInfo.cellX = folderPoint.coord.x;
|
||||
folderInfo.cellY = folderPoint.coord.y;
|
||||
folderInfo.minSpanY = folderInfo.minSpanX = folderInfo.spanX = folderInfo.spanY = 1;
|
||||
folderInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, null);
|
||||
|
||||
for (int i = 0; i < folderPoint.getNumberIconsInside(); i++) {
|
||||
folderInfo.add(getDefaultWorkspaceItem(screenId), false);
|
||||
}
|
||||
|
||||
return folderInfo;
|
||||
}
|
||||
|
||||
private WorkspaceItemInfo getDefaultWorkspaceItem(int screenId) {
|
||||
WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
|
||||
item.screenId = screenId;
|
||||
item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
|
||||
item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
||||
return item;
|
||||
}
|
||||
|
||||
private ItemInfo createIconInCell(IconPoint iconPoint, int screenId) {
|
||||
WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
|
||||
item.screenId = screenId;
|
||||
item.cellX = iconPoint.getCoord().x;
|
||||
item.cellY = iconPoint.getCoord().y;
|
||||
item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
|
||||
item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
||||
return item;
|
||||
}
|
||||
|
||||
private ItemInfo getHotseatValues(int x) {
|
||||
WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
|
||||
item.cellX = x;
|
||||
item.cellY = 0;
|
||||
item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
|
||||
item.rank = x;
|
||||
item.screenId = x;
|
||||
item.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.celllayout.board
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.os.Process
|
||||
import android.os.UserHandle
|
||||
import android.util.Log
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.android.launcher3.InvariantDeviceProfile
|
||||
import com.android.launcher3.LauncherSettings
|
||||
import com.android.launcher3.celllayout.FavoriteItemsTransaction
|
||||
import com.android.launcher3.model.data.AppInfo
|
||||
import com.android.launcher3.model.data.FolderInfo
|
||||
import com.android.launcher3.model.data.ItemInfo
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo
|
||||
import com.android.launcher3.ui.TestViewHelpers
|
||||
import com.android.launcher3.util.WidgetUtils
|
||||
import java.util.function.Supplier
|
||||
|
||||
class TestWorkspaceBuilder(private val mContext: Context) {
|
||||
|
||||
private var appComponentName =
|
||||
ComponentName("com.google.android.calculator", "com.android.calculator2.Calculator")
|
||||
private val myUser: UserHandle = Process.myUserHandle()
|
||||
|
||||
/** Fills the given rect in WidgetRect with 1x1 widgets. This is useful to equalize cases. */
|
||||
private fun fillWithWidgets(
|
||||
widgetRect: WidgetRect,
|
||||
transaction: FavoriteItemsTransaction,
|
||||
screenId: Int
|
||||
): FavoriteItemsTransaction {
|
||||
val initX = widgetRect.cellX
|
||||
val initY = widgetRect.cellY
|
||||
for (x in initX until initX + widgetRect.spanX) {
|
||||
for (y in initY until initY + widgetRect.spanY) {
|
||||
try {
|
||||
// this widgets are filling, we don't care if we can't place them
|
||||
transaction.addItem(
|
||||
createWidgetInCell(WidgetRect(CellType.IGNORE, Rect(x, y, x, y)), screenId)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Unable to place filling widget at $x,$y")
|
||||
}
|
||||
}
|
||||
}
|
||||
return transaction
|
||||
}
|
||||
|
||||
private fun app() =
|
||||
AppInfo(appComponentName, "test icon", myUser, AppInfo.makeLaunchIntent(appComponentName))
|
||||
|
||||
/**
|
||||
* Helper to set the app to use for the test workspace, using activity-alias from
|
||||
* AndroidManifest-common.
|
||||
*
|
||||
* @param testAppName the android:name field of the test app activity-alias to use
|
||||
*/
|
||||
fun setTestAppActivityAlias(testAppName: String) {
|
||||
appComponentName =
|
||||
ComponentName(
|
||||
InstrumentationRegistry.getInstrumentation().context.packageName,
|
||||
TEST_ACTIVITY_PACKAGE_PREFIX + testAppName
|
||||
)
|
||||
}
|
||||
|
||||
private fun addCorrespondingWidgetRect(
|
||||
widgetRect: WidgetRect,
|
||||
transaction: FavoriteItemsTransaction,
|
||||
screenId: Int
|
||||
) {
|
||||
if (widgetRect.type == 'x') {
|
||||
fillWithWidgets(widgetRect, transaction, screenId)
|
||||
} else {
|
||||
transaction.addItem(createWidgetInCell(widgetRect, screenId))
|
||||
}
|
||||
}
|
||||
|
||||
/** Builds the given board into the transaction */
|
||||
fun buildFromBoard(
|
||||
board: CellLayoutBoard,
|
||||
transaction: FavoriteItemsTransaction,
|
||||
screenId: Int
|
||||
): FavoriteItemsTransaction {
|
||||
board.widgets.forEach { addCorrespondingWidgetRect(it, transaction, screenId) }
|
||||
board.icons.forEach { transaction.addItem { createIconInCell(it, screenId) } }
|
||||
board.folders.forEach { transaction.addItem { createFolderInCell(it, screenId) } }
|
||||
return transaction
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the hotseat row with apps instead of suggestions, for this to work the workspace should
|
||||
* be clean otherwise this doesn't overrides the existing icons.
|
||||
*/
|
||||
fun fillHotseatIcons(transaction: FavoriteItemsTransaction): FavoriteItemsTransaction {
|
||||
for (i in 0..<InvariantDeviceProfile.INSTANCE[mContext].numDatabaseHotseatIcons) {
|
||||
transaction.addItem { getHotseatValues(i) }
|
||||
}
|
||||
return transaction
|
||||
}
|
||||
|
||||
private fun createWidgetInCell(widgetRect: WidgetRect, paramScreenId: Int): Supplier<ItemInfo> {
|
||||
// Create the widget lazily since the appWidgetId can get lost during setup
|
||||
return Supplier<ItemInfo> {
|
||||
WidgetUtils.createWidgetInfo(
|
||||
TestViewHelpers.findWidgetProvider(false),
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
true
|
||||
)
|
||||
.apply {
|
||||
cellX = widgetRect.cellX
|
||||
cellY = widgetRect.cellY
|
||||
spanX = widgetRect.spanX
|
||||
spanY = widgetRect.spanY
|
||||
screenId = paramScreenId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createFolderInCell(folderPoint: FolderPoint, paramScreenId: Int): FolderInfo =
|
||||
FolderInfo().apply {
|
||||
screenId = paramScreenId
|
||||
container = LauncherSettings.Favorites.CONTAINER_DESKTOP
|
||||
cellX = folderPoint.coord.x
|
||||
cellY = folderPoint.coord.y
|
||||
spanY = 1
|
||||
spanX = 1
|
||||
minSpanX = 1
|
||||
minSpanY = 1
|
||||
setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, null)
|
||||
for (i in 0 until folderPoint.numberIconsInside) {
|
||||
add(getDefaultWorkspaceItem(paramScreenId), false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDefaultWorkspaceItem(paramScreenId: Int): WorkspaceItemInfo =
|
||||
WorkspaceItemInfo(app()).apply {
|
||||
screenId = paramScreenId
|
||||
spanY = 1
|
||||
spanX = 1
|
||||
minSpanX = 1
|
||||
minSpanY = 1
|
||||
container = LauncherSettings.Favorites.CONTAINER_DESKTOP
|
||||
}
|
||||
|
||||
private fun createIconInCell(iconPoint: IconPoint, paramScreenId: Int) =
|
||||
WorkspaceItemInfo(app()).apply {
|
||||
screenId = paramScreenId
|
||||
cellX = iconPoint.coord.x
|
||||
cellY = iconPoint.coord.y
|
||||
spanY = 1
|
||||
spanX = 1
|
||||
minSpanX = 1
|
||||
minSpanY = 1
|
||||
container = LauncherSettings.Favorites.CONTAINER_DESKTOP
|
||||
}
|
||||
|
||||
private fun getHotseatValues(x: Int) =
|
||||
WorkspaceItemInfo(app()).apply {
|
||||
cellX = x
|
||||
cellY = 0
|
||||
spanY = 1
|
||||
spanX = 1
|
||||
minSpanX = 1
|
||||
minSpanY = 1
|
||||
rank = x
|
||||
screenId = x
|
||||
container = LauncherSettings.Favorites.CONTAINER_HOTSEAT
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "CellLayoutBoardBuilder"
|
||||
private const val TEST_ACTIVITY_PACKAGE_PREFIX = "com.android.launcher3.tests."
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.celllayout.board;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
public class WidgetRect {
|
||||
public char mType;
|
||||
public Rect mBounds;
|
||||
|
||||
public WidgetRect(char type, Rect bounds) {
|
||||
this.mType = type;
|
||||
this.mBounds = bounds;
|
||||
}
|
||||
|
||||
public int getSpanX() {
|
||||
return mBounds.right - mBounds.left + 1;
|
||||
}
|
||||
|
||||
public int getSpanY() {
|
||||
return mBounds.top - mBounds.bottom + 1;
|
||||
}
|
||||
|
||||
public int getCellX() {
|
||||
return mBounds.left;
|
||||
}
|
||||
|
||||
public int getCellY() {
|
||||
return mBounds.bottom;
|
||||
}
|
||||
|
||||
boolean shouldIgnore() {
|
||||
return this.mType == CellType.IGNORE;
|
||||
}
|
||||
|
||||
boolean contains(int x, int y) {
|
||||
return mBounds.contains(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WidgetRect type = " + mType + " x = " + getCellX() + " | y " + getCellY()
|
||||
+ " xs = " + getSpanX() + " ys = " + getSpanY();
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -65,7 +65,7 @@ class AllAppsRecyclerViewPoolTest<T> where T : Context, T : ActivityContext {
|
||||
|
||||
@Test
|
||||
fun preinflate_success() {
|
||||
underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
|
||||
underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 10) { 10 }
|
||||
|
||||
awaitTasksCompleted()
|
||||
assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(10)
|
||||
@@ -73,7 +73,7 @@ class AllAppsRecyclerViewPoolTest<T> where T : Context, T : ActivityContext {
|
||||
|
||||
@Test
|
||||
fun preinflate_not_triggered() {
|
||||
underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 0 }
|
||||
underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 0) { 0 }
|
||||
|
||||
awaitTasksCompleted()
|
||||
assertThat(underTest.getRecycledViewCount(VIEW_TYPE)).isEqualTo(0)
|
||||
@@ -81,7 +81,7 @@ class AllAppsRecyclerViewPoolTest<T> where T : Context, T : ActivityContext {
|
||||
|
||||
@Test
|
||||
fun preinflate_cancel_before_runOnMainThread() {
|
||||
underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
|
||||
underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 10) { 10 }
|
||||
assertThat(underTest.mCancellableTask!!.canceled).isFalse()
|
||||
|
||||
underTest.clear()
|
||||
@@ -94,7 +94,7 @@ class AllAppsRecyclerViewPoolTest<T> where T : Context, T : ActivityContext {
|
||||
|
||||
@Test
|
||||
fun preinflate_cancel_after_run() {
|
||||
underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent) { 10 }
|
||||
underTest.preInflateAllAppsViewHolders(adapter, VIEW_TYPE, parent, 10) { 10 }
|
||||
assertThat(underTest.mCancellableTask!!.canceled).isFalse()
|
||||
awaitTasksCompleted()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user