Merge "Notify recents controller when no-longer finishing-to-home" into tm-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index d65de51..d348cc3 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -517,6 +517,7 @@
appsView.setAlpha(startAlpha);
SCALE_PROPERTY.set(appsView, startScale);
appsView.setLayerType(View.LAYER_TYPE_NONE, null);
+ mLauncher.resumeExpensiveViewUpdates();
};
} else if (mLauncher.isInState(OVERVIEW)) {
endListener = composeViewContentAnimator(launcherAnimator, alphas, scales);
@@ -634,6 +635,7 @@
overview.setFreezeViewVisibility(false);
SCALE_PROPERTY.set(overview, 1f);
mLauncher.getStateManager().reapplyState();
+ mLauncher.resumeExpensiveViewUpdates();
};
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 872e64a..cb1da38 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -188,6 +188,9 @@
AllAppsSwipeController.applyAllAppsToNormalConfig(mActivity, config);
} else if (fromState == NORMAL && toState == ALL_APPS) {
AllAppsSwipeController.applyNormalToAllAppsAnimConfig(mActivity, config);
+ } else if (fromState == OVERVIEW && toState == OVERVIEW_SPLIT_SELECT) {
+ config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE,
+ clampToProgress(LINEAR, 0, 0.167f));
}
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index e79d56b..cb08ac8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -16,6 +16,8 @@
package com.android.launcher3.uioverrides.states;
+import android.content.Context;
+
import com.android.launcher3.Launcher;
import com.android.quickstep.views.RecentsView;
@@ -24,6 +26,8 @@
* pinned and user is selecting the second one
*/
public class SplitScreenSelectState extends OverviewState {
+ private static final int OVERVIEW_SPLIT_SELECT_SLIDE_IN_DURATION = 500;
+
public SplitScreenSelectState(int id) {
super(id);
}
@@ -38,4 +42,9 @@
RecentsView recentsView = launcher.getOverviewPanel();
return recentsView.getSplitSelectTranslation();
}
+
+ @Override
+ public int getTransitionDuration(Context context, boolean isToState) {
+ return OVERVIEW_SPLIT_SELECT_SLIDE_IN_DURATION;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index 7a66ea0..d93f015 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -2,8 +2,9 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.anim.Interpolators.FASTER_OUT_SLOWER_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.clampToProgress;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -200,10 +201,16 @@
RectF floatingTaskViewBounds = new RectF();
if (fadeWithThumbnail) {
- animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT,
- 0, 1, ACCEL);
+ // FloatingTaskThumbnailView: thumbnail fades out to transparent
animation.addFloat(mThumbnailView, LauncherAnimUtils.VIEW_ALPHA,
- 1, 0, DEACCEL_3);
+ 1, 0, clampToProgress(LINEAR, 0, 0.267f));
+
+ // SplitPlaceholderView: gray background fades in at the same time, then new icon fades
+ // in
+ animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT,
+ 0, 1, clampToProgress(LINEAR, 0, 0.267f));
+ animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ICON_ALPHA,
+ 0, 1, clampToProgress(LINEAR, 0.333f, 0.5f));
} else if (isStagedTask) {
// Fade in the placeholder view when split is initiated from homescreen / all apps
// icons.
@@ -214,12 +221,15 @@
}
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
- final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration, LINEAR);
- final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration, LINEAR);
+ // SplitPlaceholderView: rectangle translates and stretches to new position
+ final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration,
+ clampToProgress(FASTER_OUT_SLOWER_IN, 0, 0.833f));
+ final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration,
+ clampToProgress(FASTER_OUT_SLOWER_IN, 0, 0.833f));
final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX, 0,
- animDuration, LINEAR);
+ animDuration, clampToProgress(FASTER_OUT_SLOWER_IN, 0, 0.833f));
final FloatProp mTaskViewScaleY = new FloatProp(1f, prop.finalTaskViewScaleY, 0,
- animDuration, LINEAR);
+ animDuration, clampToProgress(FASTER_OUT_SLOWER_IN, 0, 0.833f));
@Override
public void onUpdate(float percent, boolean initOnly) {
// Calculate the icon position.
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 64068ad..a153f26 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -35,6 +35,7 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -113,6 +114,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.core.graphics.ColorUtils;
+import androidx.dynamicanimation.animation.SpringForce;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener;
@@ -125,6 +127,7 @@
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.anim.SpringProperty;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
@@ -442,6 +445,9 @@
private static final float ANIMATION_DISMISS_PROGRESS_MIDPOINT = 0.5f;
private static final float END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.75f;
+ private static final float INITIAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.133f;
+ private static final float ADDITIONAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.033f;
+
private static final float SIGNIFICANT_MOVE_SCREEN_WIDTH_PERCENTAGE = 0.15f;
protected final RecentsOrientedState mOrientationState;
@@ -2825,7 +2831,20 @@
RectF startingTaskRect = new RectF();
if (mSplitHiddenTaskView != null) {
- mSplitHiddenTaskView.setVisibility(INVISIBLE);
+ // Split staging is initiated, hide the original TaskView except for the icon (which
+ // needs to animate out over time)
+ // If needed, visibility should be toggled back on in resetFromSplitSelectionState().
+ for (int i = 0; i < mSplitHiddenTaskView.getChildCount(); i++) {
+ View child = mSplitHiddenTaskView.getChildAt(i);
+ if (child != mSplitHiddenTaskView.mIconView) {
+ child.setVisibility(INVISIBLE);
+ }
+ }
+
+ // Icon animates out over time
+ anim.addFloat(mSplitHiddenTaskView, TaskView.ICON_ALPHA, 1, 0,
+ clampToProgress(LINEAR, 0, 0.167f));
+
mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
mSplitHiddenTaskView.getThumbnail(),
mSplitHiddenTaskView.getThumbnail().getThumbnail(),
@@ -2842,9 +2861,15 @@
false /* fadeWithThumbnail */, true /* isStagedTask */);
}
+ // SplitInstructionsView: animate in
mSplitInstructionsView = SplitInstructionsView.getSplitInstructionsView(mActivity);
mSplitInstructionsView.setAlpha(0);
- anim.addFloat(mSplitInstructionsView, SplitInstructionsView.ALPHA_FLOAT, 0, 1, ACCEL);
+ anim.addFloat(mSplitInstructionsView, SplitInstructionsView.CONTAINER_ALPHA, 0, 1,
+ clampToProgress(LINEAR, 0, 0.167f));
+ anim.addFloat(mSplitInstructionsView, SplitInstructionsView.TEXT_ALPHA, 0, 1,
+ clampToProgress(LINEAR, 0.1f, 0.267f));
+ anim.addFloat(mSplitInstructionsView, mSplitInstructionsView.UNFOLD, 0.1f, 1,
+ clampToProgress(EMPHASIZED_DECELERATE, 0, 0.667f));
InteractionJankMonitorWrapper.begin(this,
InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER, "First tile selected");
@@ -3124,11 +3149,22 @@
// Animate task with index >= dismissed index and in the same row as the
// dismissed index or next focused index. Offset successive task dismissal
// durations for a staggered effect.
- float animationStartProgress = Utilities.boundToRange(
- INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
- + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
- * ++distanceFromDismissedTask, 0f,
- dismissTranslationInterpolationEnd);
+ distanceFromDismissedTask++;
+ // If user is initiating splitscreen from the focused (large) task, we use a
+ // spring-based animation and timings. For other, smaller, repositions, we currently
+ // fall back on a less complicated linear animation and timings.
+ float animationStartProgress = isFocusedTaskDismissed && nextFocusedTaskView == null
+ ? Utilities.boundToRange(
+ INITIAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+ + ADDITIONAL_SPRING_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+ * (int) Math.ceil(distanceFromDismissedTask / 2f), 0f,
+ dismissTranslationInterpolationEnd)
+ : Utilities.boundToRange(
+ INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+ + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET
+ * distanceFromDismissedTask, 0f,
+ dismissTranslationInterpolationEnd);
+
if (taskView == nextFocusedTaskView) {
// Enlarge the task to be focused next, and translate into focus position.
float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width();
@@ -3163,12 +3199,36 @@
primaryTranslation +=
mIsRtl ? -mSplitPlaceholderSize : mSplitPlaceholderSize;
}
- }
- anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
- mIsRtl ? primaryTranslation : -primaryTranslation,
- clampToProgress(LINEAR, animationStartProgress,
- dismissTranslationInterpolationEnd));
+ // Transitioning to split select -- set up staggered spring animation for
+ // other TaskViews.
+ Animator taskSlideIn = new SpringAnimationBuilder(taskView.mActivity)
+ .setDampingRatio(0.85f)
+ .setStiffness(SpringForce.STIFFNESS_LOW)
+ .setEndValue(mIsRtl ? primaryTranslation : -primaryTranslation)
+ .setStartValue(
+ taskView.getPrimaryDismissTranslationProperty()
+ .get(taskView)
+ )
+ .build(taskView, taskView.getPrimaryDismissTranslationProperty());
+ long taskSlideInDuration = taskSlideIn.getDuration();
+ anim.add(taskSlideIn);
+ taskSlideIn
+ .setDuration(taskSlideInDuration)
+ .setStartDelay(
+ Math.round(animationStartProgress * anim.getDuration()));
+ } else {
+ // Task was dismissed individually -- translate other TaskViews to fill the
+ // vacant space.
+
+ // TODO (b/242075836): This dismiss animation uses a linear transition.
+ // When the above bug is fixed, it can use the same (nicer) spring
+ // transition as the focused task split case above.
+ anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
+ mIsRtl ? primaryTranslation : -primaryTranslation,
+ clampToProgress(LINEAR, animationStartProgress,
+ dismissTranslationInterpolationEnd));
+ }
}
}
}
@@ -3189,7 +3249,8 @@
final boolean finalCloseGapBetweenClearAll = closeGapBetweenClearAll;
final boolean finalSnapToLastTask = snapToLastTask;
final boolean finalIsFocusedTaskDismissed = isFocusedTaskDismissed;
- mPendingAnimation.addEndListener(new Consumer<Boolean>() {
+
+ Consumer endConsumer = new Consumer<Boolean>() {
@Override
public void accept(Boolean success) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
@@ -3399,7 +3460,9 @@
onDismissAnimationEnds();
mPendingAnimation = null;
}
- });
+ };
+
+ mPendingAnimation.addListener(AnimatorListeners.forEndCallback(endConsumer));
return anim;
}
@@ -4220,7 +4283,6 @@
resetTaskVisuals();
mSplitHiddenTaskViewIndex = -1;
if (mSplitHiddenTaskView != null) {
- mSplitHiddenTaskView.setVisibility(VISIBLE);
mSplitHiddenTaskView = null;
}
}
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index 7d94505..d0d715f 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -25,6 +25,7 @@
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
+import androidx.appcompat.widget.AppCompatTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
@@ -40,9 +41,10 @@
*/
public class SplitInstructionsView extends FrameLayout {
private final StatefulActivity mLauncher;
+ private AppCompatTextView mTextView;
- public static final FloatProperty<SplitInstructionsView> ALPHA_FLOAT =
- new FloatProperty<SplitInstructionsView>("SplitInstructionsAlpha") {
+ public static final FloatProperty<SplitInstructionsView> CONTAINER_ALPHA =
+ new FloatProperty<SplitInstructionsView>("SplitInstructionsContainerAlpha") {
@Override
public void setValue(SplitInstructionsView splitInstructionsView, float v) {
splitInstructionsView.setVisibility(v != 0 ? VISIBLE : GONE);
@@ -55,6 +57,32 @@
}
};
+ public static final FloatProperty<SplitInstructionsView> UNFOLD =
+ new FloatProperty<SplitInstructionsView>("SplitInstructionsUnfold") {
+ @Override
+ public void setValue(SplitInstructionsView splitInstructionsView, float v) {
+ splitInstructionsView.setScaleY(v);
+ }
+
+ @Override
+ public Float get(SplitInstructionsView splitInstructionsView) {
+ return splitInstructionsView.getScaleY();
+ }
+ };
+
+ public static final FloatProperty<SplitInstructionsView> TEXT_ALPHA =
+ new FloatProperty<SplitInstructionsView>("SplitInstructionsTextAlpha") {
+ @Override
+ public void setValue(SplitInstructionsView splitInstructionsView, float v) {
+ splitInstructionsView.mTextView.setAlpha(v);
+ }
+
+ @Override
+ public Float get(SplitInstructionsView splitInstructionsView) {
+ return splitInstructionsView.mTextView.getAlpha();
+ }
+ };
+
public SplitInstructionsView(Context context) {
this(context, null);
}
@@ -77,6 +105,9 @@
false
);
+ splitInstructionsView.mTextView = splitInstructionsView.findViewById(
+ R.id.split_instructions_text);
+
dragLayer.addView(splitInstructionsView);
return splitInstructionsView;
}
diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
index 28080d4..ae6aae1 100644
--- a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
@@ -47,6 +47,19 @@
}
};
+ public static final FloatProperty<SplitPlaceholderView> ICON_ALPHA =
+ new FloatProperty<SplitPlaceholderView>("SplitViewIconAlpha") {
+ @Override
+ public void setValue(SplitPlaceholderView splitPlaceholderView, float v) {
+ splitPlaceholderView.mIconView.setAlpha(v);
+ }
+
+ @Override
+ public Float get(SplitPlaceholderView splitPlaceholderView) {
+ return splitPlaceholderView.mIconView.getAlpha();
+ }
+ };
+
@Nullable
private IconView mIconView;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index b089155..d2c2988 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -323,6 +323,19 @@
}
};
+ public static final FloatProperty<TaskView> ICON_ALPHA =
+ new FloatProperty<TaskView>("iconAlpha") {
+ @Override
+ public void setValue(TaskView taskView, float v) {
+ taskView.mIconView.setAlpha(v);
+ }
+
+ @Override
+ public Float get(TaskView taskView) {
+ return taskView.mIconView.getAlpha();
+ }
+ };
+
@Nullable
protected Task mTask;
protected TaskThumbnailView mSnapshotView;
diff --git a/quickstep/tests/src/com/android/quickstep/DeviceProfileTest.kt b/quickstep/tests/src/com/android/quickstep/DeviceProfileTest.kt
index 45a342a..9977207 100644
--- a/quickstep/tests/src/com/android/quickstep/DeviceProfileTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DeviceProfileTest.kt
@@ -73,13 +73,14 @@
"\ticonTextSizePx: 36.0px (13.714286dp)\n" +
"\ticonDrawablePaddingPx: 17.0px (6.4761906dp)\n" +
"\tfolderCellWidthPx: 210.0px (80.0dp)\n" +
- "\tfolderCellHeightPx: 272.0px (103.61905dp)\n" +
+ "\tfolderCellHeightPx: 247.0px (94.09524dp)\n" +
"\tfolderChildIconSizePx: 158.0px (60.190475dp)\n" +
"\tfolderChildTextSizePx: 37.0px (14.095238dp)\n" +
- "\tfolderChildDrawablePaddingPx: 21.0px (8.0dp)\n" +
- "\tfolderCellLayoutBorderSpaceOriginalPx: 42.0px (16.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tfolderCellLayoutBorderSpacePx Horizontal: 42.0px (16.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx Vertical: 42.0px (16.0dp)\n" +
+ "\tfolderContentPaddingLeftRight: 42.0px (16.0dp)\n" +
+ "\tfolderTopPadding: 63.0px (24.0dp)\n" +
"\tbottomSheetTopPadding: 146.0px (55.61905dp)\n" +
"\tallAppsShiftRange: 788.0px (300.1905dp)\n" +
"\tallAppsTopPadding: 0.0px (0.0dp)\n" +
@@ -192,13 +193,14 @@
"\ticonTextSizePx: 36.0px (13.714286dp)\n" +
"\ticonDrawablePaddingPx: 17.0px (6.4761906dp)\n" +
"\tfolderCellWidthPx: 210.0px (80.0dp)\n" +
- "\tfolderCellHeightPx: 272.0px (103.61905dp)\n" +
+ "\tfolderCellHeightPx: 247.0px (94.09524dp)\n" +
"\tfolderChildIconSizePx: 158.0px (60.190475dp)\n" +
"\tfolderChildTextSizePx: 37.0px (14.095238dp)\n" +
- "\tfolderChildDrawablePaddingPx: 21.0px (8.0dp)\n" +
- "\tfolderCellLayoutBorderSpaceOriginalPx: 42.0px (16.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tfolderCellLayoutBorderSpacePx Horizontal: 42.0px (16.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx Vertical: 42.0px (16.0dp)\n" +
+ "\tfolderContentPaddingLeftRight: 42.0px (16.0dp)\n" +
+ "\tfolderTopPadding: 63.0px (24.0dp)\n" +
"\tbottomSheetTopPadding: 146.0px (55.61905dp)\n" +
"\tallAppsShiftRange: 788.0px (300.1905dp)\n" +
"\tallAppsTopPadding: 0.0px (0.0dp)\n" +
@@ -316,9 +318,10 @@
"\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
"\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
"\tfolderChildDrawablePaddingPx: 16.0px (8.0dp)\n" +
- "\tfolderCellLayoutBorderSpaceOriginalPx: 0.0px (0.0dp)\n" +
- "\tfolderCellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
- "\tfolderCellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx Horizontal: 32.0px (16.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx Vertical: 32.0px (16.0dp)\n" +
+ "\tfolderContentPaddingLeftRight: 32.0px (16.0dp)\n" +
+ "\tfolderTopPadding: 48.0px (24.0dp)\n" +
"\tbottomSheetTopPadding: 104.0px (52.0dp)\n" +
"\tallAppsShiftRange: 1496.0px (748.0dp)\n" +
"\tallAppsTopPadding: 104.0px (52.0dp)\n" +
@@ -436,9 +439,10 @@
"\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
"\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
"\tfolderChildDrawablePaddingPx: 16.0px (8.0dp)\n" +
- "\tfolderCellLayoutBorderSpaceOriginalPx: 0.0px (0.0dp)\n" +
- "\tfolderCellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
- "\tfolderCellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx Horizontal: 32.0px (16.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx Vertical: 32.0px (16.0dp)\n" +
+ "\tfolderContentPaddingLeftRight: 32.0px (16.0dp)\n" +
+ "\tfolderTopPadding: 48.0px (24.0dp)\n" +
"\tbottomSheetTopPadding: 104.0px (52.0dp)\n" +
"\tallAppsShiftRange: 1496.0px (748.0dp)\n" +
"\tallAppsTopPadding: 104.0px (52.0dp)\n" +
@@ -551,14 +555,15 @@
"\ticonSizePx: 120.0px (60.0dp)\n" +
"\ticonTextSizePx: 28.0px (14.0dp)\n" +
"\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
- "\tfolderCellWidthPx: 204.0px (102.0dp)\n" +
- "\tfolderCellHeightPx: 240.0px (120.0dp)\n" +
+ "\tfolderCellWidthPx: 240.0px (120.0dp)\n" +
+ "\tfolderCellHeightPx: 208.0px (104.0dp)\n" +
"\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
"\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
- "\tfolderChildDrawablePaddingPx: 27.0px (13.5dp)\n" +
- "\tfolderCellLayoutBorderSpaceOriginalPx: 0.0px (0.0dp)\n" +
- "\tfolderCellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
- "\tfolderCellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 16.0px (8.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx Horizontal: 32.0px (16.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx Vertical: 32.0px (16.0dp)\n" +
+ "\tfolderContentPaddingLeftRight: 32.0px (16.0dp)\n" +
+ "\tfolderTopPadding: 48.0px (24.0dp)\n" +
"\tbottomSheetTopPadding: 704.0px (352.0dp)\n" +
"\tallAppsShiftRange: 1936.0px (968.0dp)\n" +
"\tallAppsTopPadding: 624.0px (312.0dp)\n" +
@@ -671,14 +676,15 @@
"\ticonSizePx: 120.0px (60.0dp)\n" +
"\ticonTextSizePx: 28.0px (14.0dp)\n" +
"\ticonDrawablePaddingPx: 14.0px (7.0dp)\n" +
- "\tfolderCellWidthPx: 204.0px (102.0dp)\n" +
- "\tfolderCellHeightPx: 240.0px (120.0dp)\n" +
+ "\tfolderCellWidthPx: 240.0px (120.0dp)\n" +
+ "\tfolderCellHeightPx: 208.0px (104.0dp)\n" +
"\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
"\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
- "\tfolderChildDrawablePaddingPx: 27.0px (13.5dp)\n" +
- "\tfolderCellLayoutBorderSpaceOriginalPx: 0.0px (0.0dp)\n" +
- "\tfolderCellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)\n" +
- "\tfolderCellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 16.0px (8.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx Horizontal: 32.0px (16.0dp)\n" +
+ "\tfolderCellLayoutBorderSpacePx Vertical: 32.0px (16.0dp)\n" +
+ "\tfolderContentPaddingLeftRight: 32.0px (16.0dp)\n" +
+ "\tfolderTopPadding: 48.0px (24.0dp)\n" +
"\tbottomSheetTopPadding: 704.0px (352.0dp)\n" +
"\tallAppsShiftRange: 1936.0px (968.0dp)\n" +
"\tallAppsTopPadding: 624.0px (312.0dp)\n" +
@@ -792,13 +798,14 @@
"\ticonTextSizePx: 36.0px (13.714286dp)\n" +
"\ticonDrawablePaddingPx: 17.0px (6.4761906dp)\n" +
"\tfolderCellWidthPx: 210.0px (80.0dp)\n" +
- "\tfolderCellHeightPx: 267.0px (101.71429dp)\n" +
+ "\tfolderCellHeightPx: 247.0px (94.09524dp)\n" +
"\tfolderChildIconSizePx: 158.0px (60.190475dp)\n" +
"\tfolderChildTextSizePx: 37.0px (14.095238dp)\n" +
- "\tfolderChildDrawablePaddingPx: 19.0px (7.2380953dp)\n" +
- "\tfolderCellLayoutBorderSpaceOriginalPx: 42.0px (16.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tfolderCellLayoutBorderSpacePx Horizontal: 42.0px (16.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx Vertical: 42.0px (16.0dp)\n" +
+ "\tfolderContentPaddingLeftRight: 42.0px (16.0dp)\n" +
+ "\tfolderTopPadding: 63.0px (24.0dp)\n" +
"\tbottomSheetTopPadding: 110.0px (41.904762dp)\n" +
"\tallAppsShiftRange: 1730.0px (659.0476dp)\n" +
"\tallAppsTopPadding: 110.0px (41.904762dp)\n" +
@@ -912,13 +919,14 @@
"\ticonTextSizePx: 36.0px (13.714286dp)\n" +
"\ticonDrawablePaddingPx: 17.0px (6.4761906dp)\n" +
"\tfolderCellWidthPx: 210.0px (80.0dp)\n" +
- "\tfolderCellHeightPx: 267.0px (101.71429dp)\n" +
+ "\tfolderCellHeightPx: 247.0px (94.09524dp)\n" +
"\tfolderChildIconSizePx: 158.0px (60.190475dp)\n" +
"\tfolderChildTextSizePx: 37.0px (14.095238dp)\n" +
- "\tfolderChildDrawablePaddingPx: 19.0px (7.2380953dp)\n" +
- "\tfolderCellLayoutBorderSpaceOriginalPx: 42.0px (16.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tfolderCellLayoutBorderSpacePx Horizontal: 42.0px (16.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx Vertical: 42.0px (16.0dp)\n" +
+ "\tfolderContentPaddingLeftRight: 42.0px (16.0dp)\n" +
+ "\tfolderTopPadding: 63.0px (24.0dp)\n" +
"\tbottomSheetTopPadding: 110.0px (41.904762dp)\n" +
"\tallAppsShiftRange: 1730.0px (659.0476dp)\n" +
"\tallAppsTopPadding: 110.0px (41.904762dp)\n" +
@@ -1031,14 +1039,15 @@
"\ticonSizePx: 136.0px (51.809525dp)\n" +
"\ticonTextSizePx: 31.0px (11.809524dp)\n" +
"\ticonDrawablePaddingPx: 17.0px (6.4761906dp)\n" +
- "\tfolderCellWidthPx: 192.0px (73.14286dp)\n" +
- "\tfolderCellHeightPx: 304.0px (115.809525dp)\n" +
+ "\tfolderCellWidthPx: 210.0px (80.0dp)\n" +
+ "\tfolderCellHeightPx: 247.0px (94.09524dp)\n" +
"\tfolderChildIconSizePx: 158.0px (60.190475dp)\n" +
"\tfolderChildTextSizePx: 37.0px (14.095238dp)\n" +
- "\tfolderChildDrawablePaddingPx: 32.0px (12.190476dp)\n" +
- "\tfolderCellLayoutBorderSpaceOriginalPx: 42.0px (16.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tfolderCellLayoutBorderSpacePx Horizontal: 42.0px (16.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx Vertical: 42.0px (16.0dp)\n" +
+ "\tfolderContentPaddingLeftRight: 42.0px (16.0dp)\n" +
+ "\tfolderTopPadding: 63.0px (24.0dp)\n" +
"\tbottomSheetTopPadding: 110.0px (41.904762dp)\n" +
"\tallAppsShiftRange: 2098.0px (799.2381dp)\n" +
"\tallAppsTopPadding: 110.0px (41.904762dp)\n" +
@@ -1151,14 +1160,15 @@
"\ticonSizePx: 136.0px (51.809525dp)\n" +
"\ticonTextSizePx: 31.0px (11.809524dp)\n" +
"\ticonDrawablePaddingPx: 17.0px (6.4761906dp)\n" +
- "\tfolderCellWidthPx: 192.0px (73.14286dp)\n" +
- "\tfolderCellHeightPx: 304.0px (115.809525dp)\n" +
+ "\tfolderCellWidthPx: 210.0px (80.0dp)\n" +
+ "\tfolderCellHeightPx: 247.0px (94.09524dp)\n" +
"\tfolderChildIconSizePx: 158.0px (60.190475dp)\n" +
"\tfolderChildTextSizePx: 37.0px (14.095238dp)\n" +
- "\tfolderChildDrawablePaddingPx: 32.0px (12.190476dp)\n" +
- "\tfolderCellLayoutBorderSpaceOriginalPx: 42.0px (16.0dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 13.0px (4.952381dp)\n" +
"\tfolderCellLayoutBorderSpacePx Horizontal: 42.0px (16.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx Vertical: 42.0px (16.0dp)\n" +
+ "\tfolderContentPaddingLeftRight: 42.0px (16.0dp)\n" +
+ "\tfolderTopPadding: 63.0px (24.0dp)\n" +
"\tbottomSheetTopPadding: 110.0px (41.904762dp)\n" +
"\tallAppsShiftRange: 2098.0px (799.2381dp)\n" +
"\tallAppsTopPadding: 110.0px (41.904762dp)\n" +
@@ -1270,14 +1280,15 @@
"\ticonSizePx: 142.0px (54.095238dp)\n" +
"\ticonTextSizePx: 0.0px (0.0dp)\n" +
"\ticonDrawablePaddingPx: 0.0px (0.0dp)\n" +
- "\tfolderCellWidthPx: 179.0px (68.190475dp)\n" +
- "\tfolderCellHeightPx: 212.0px (80.7619dp)\n" +
- "\tfolderChildIconSizePx: 135.0px (51.42857dp)\n" +
- "\tfolderChildTextSizePx: 35.0px (13.333333dp)\n" +
- "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
- "\tfolderCellLayoutBorderSpaceOriginalPx: 42.0px (16.0dp)\n" +
+ "\tfolderCellWidthPx: 175.0px (66.666664dp)\n" +
+ "\tfolderCellHeightPx: 205.0px (78.09524dp)\n" +
+ "\tfolderChildIconSizePx: 131.0px (49.904762dp)\n" +
+ "\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
+ "\tfolderChildDrawablePaddingPx: 9.0px (3.4285715dp)\n" +
"\tfolderCellLayoutBorderSpacePx Horizontal: 42.0px (16.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx Vertical: 42.0px (16.0dp)\n" +
+ "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
+ "\tfolderTopPadding: 42.0px (16.0dp)\n" +
"\tbottomSheetTopPadding: 114.0px (43.42857dp)\n" +
"\tallAppsShiftRange: 788.0px (300.1905dp)\n" +
"\tallAppsTopPadding: 0.0px (0.0dp)\n" +
@@ -1389,14 +1400,15 @@
"\ticonSizePx: 142.0px (54.095238dp)\n" +
"\ticonTextSizePx: 0.0px (0.0dp)\n" +
"\ticonDrawablePaddingPx: 0.0px (0.0dp)\n" +
- "\tfolderCellWidthPx: 163.0px (62.095238dp)\n" +
- "\tfolderCellHeightPx: 192.0px (73.14286dp)\n" +
- "\tfolderChildIconSizePx: 123.0px (46.857143dp)\n" +
- "\tfolderChildTextSizePx: 32.0px (12.190476dp)\n" +
+ "\tfolderCellWidthPx: 159.0px (60.57143dp)\n" +
+ "\tfolderCellHeightPx: 187.0px (71.2381dp)\n" +
+ "\tfolderChildIconSizePx: 119.0px (45.333332dp)\n" +
+ "\tfolderChildTextSizePx: 31.0px (11.809524dp)\n" +
"\tfolderChildDrawablePaddingPx: 8.0px (3.047619dp)\n" +
- "\tfolderCellLayoutBorderSpaceOriginalPx: 42.0px (16.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx Horizontal: 42.0px (16.0dp)\n" +
"\tfolderCellLayoutBorderSpacePx Vertical: 42.0px (16.0dp)\n" +
+ "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
+ "\tfolderTopPadding: 42.0px (16.0dp)\n" +
"\tbottomSheetTopPadding: 114.0px (43.42857dp)\n" +
"\tallAppsShiftRange: 788.0px (300.1905dp)\n" +
"\tallAppsTopPadding: 0.0px (0.0dp)\n" +
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 11eea60..337014a 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -30,7 +30,7 @@
<LinearLayout
android:id="@+id/folder_footer"
android:layout_width="match_parent"
- android:layout_height="48dp"
+ android:layout_height="@dimen/folder_label_height"
android:clipChildren="false"
android:orientation="horizontal"
android:paddingLeft="12dp"
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index df1ca14..f41c288 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -327,6 +327,19 @@
if not specified -->
<attr name="allAppsBorderSpaceTwoPanelLandscapeVertical" format="float" />
+ <!-- defaults to minCellHeight if not specified
+ when GridDisplayOption#isScalable is true. -->
+ <attr name="folderCellHeight" format="float" />
+ <!-- defaults to minCellWidth, if not specified -->
+ <attr name="folderCellWidth" format="float" />
+
+ <!-- defaults to borderSpace, if not specified -->
+ <!-- space to be used horizontally and vertically -->
+ <attr name="folderBorderSpace" format="float" />
+
+ <!-- defaults to folderBorderSpace vertical, if not specified -->
+ <attr name="folderTopPadding" format="float" />
+
<!-- defaults to res.hotseat_bar_bottom_space_default, if not specified -->
<attr name="hotseatBarBottomSpace" format="float" />
<!-- defaults to hotseatBarBottomSpace, if not specified -->
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 546ee35..a9d1127 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -251,7 +251,7 @@
<dimen name="folder_cell_y_padding">6dp</dimen>
<!-- label text size = workspace text size multiplied by this scale -->
<dimen name="folder_label_text_scale">1.14</dimen>
- <dimen name="folder_label_height">48dp</dimen>
+ <dimen name="folder_label_height">56dp</dimen>
<dimen name="folder_content_padding_left_right">8dp</dimen>
<dimen name="folder_content_padding_top">16dp</dimen>
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 418df37..14a467a 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -38,11 +38,13 @@
import android.view.Surface;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.DevicePaddings.DevicePadding;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
@@ -57,6 +59,9 @@
private static final int DEFAULT_DOT_SIZE = 100;
private static final float ALL_APPS_TABLET_MAX_ROWS = 5.5f;
+ public static final PointF DEFAULT_SCALE = new PointF(1.0f, 1.0f);
+ public static final ViewScaleProvider DEFAULT_PROVIDER = itemInfo -> DEFAULT_SCALE;
+
// Ratio of empty space, qsb should take up to appear visually centered.
private final float mQsbCenterFactor;
@@ -144,7 +149,6 @@
// Folder content
public Point folderCellLayoutBorderSpacePx;
- public int folderCellLayoutBorderSpaceOriginalPx;
public int folderContentPaddingLeftRight;
public int folderContentPaddingTop;
@@ -204,7 +208,7 @@
public int overviewGridSideMargin;
// Widgets
- public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
+ private final ViewScaleProvider mViewScaleProvider;
// Drop Target
public int dropTargetBarSizePx;
@@ -240,7 +244,8 @@
/** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */
DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
SparseArray<DotRenderer> dotRendererCache, boolean isMultiWindowMode,
- boolean transposeLayoutWithOrientation, boolean useTwoPanels, boolean isGestureMode) {
+ boolean transposeLayoutWithOrientation, boolean useTwoPanels, boolean isGestureMode,
+ @NonNull final ViewScaleProvider viewScaleProvider) {
this.inv = inv;
this.isLandscape = windowBounds.isLandscape();
@@ -321,9 +326,8 @@
pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics),
pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics));
cellLayoutBorderSpaceOriginalPx = new Point(cellLayoutBorderSpacePx);
- folderCellLayoutBorderSpaceOriginalPx = pxFromDp(inv.folderBorderSpace, mMetrics);
- folderCellLayoutBorderSpacePx = new Point(folderCellLayoutBorderSpaceOriginalPx,
- folderCellLayoutBorderSpaceOriginalPx);
+ folderCellLayoutBorderSpacePx = new Point(pxFromDp(inv.folderBorderSpaces.x, mMetrics),
+ pxFromDp(inv.folderBorderSpaces.y, mMetrics));
workspacePageIndicatorHeight = res.getDimensionPixelSize(
R.dimen.workspace_page_indicator_height);
@@ -473,6 +477,8 @@
flingToDeleteThresholdVelocity = res.getDimensionPixelSize(
R.dimen.drag_flingToDeleteMinVelocity);
+ mViewScaleProvider = viewScaleProvider;
+
// This is done last, after iconSizePx is calculated above.
mDotRendererWorkSpace = createDotRenderer(iconSizePx, dotRendererCache);
mDotRendererAllApps = createDotRenderer(allAppsIconSizePx, dotRendererCache);
@@ -669,13 +675,18 @@
.setMultiWindowMode(true)
.build();
- profile.hideWorkspaceLabelsIfNotEnoughSpace();
-
// We use these scales to measure and layout the widgets using their full invariant profile
// sizes and then draw them scaled and centered to fit in their multi-window mode cellspans.
float appWidgetScaleX = (float) profile.getCellSize().x / getCellSize().x;
float appWidgetScaleY = (float) profile.getCellSize().y / getCellSize().y;
- profile.appWidgetScale.set(appWidgetScaleX, appWidgetScaleY);
+ if (appWidgetScaleX != 1 || appWidgetScaleY != 1) {
+ final PointF p = new PointF(appWidgetScaleX, appWidgetScaleY);
+ profile = profile.toBuilder(context)
+ .setViewScaleProvider(i -> p)
+ .build();
+ }
+
+ profile.hideWorkspaceLabelsIfNotEnoughSpace();
return profile;
}
@@ -919,16 +930,14 @@
int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
if (isScalableGrid) {
- int minWidth = folderChildIconSizePx + iconDrawablePaddingPx * 2;
- int minHeight = folderChildIconSizePx + iconDrawablePaddingPx * 2 + textHeight;
+ folderCellWidthPx = pxFromDp(inv.folderCellSize.x, mMetrics, scale);
+ folderCellHeightPx = pxFromDp(inv.folderCellSize.y, mMetrics, scale);
- folderCellWidthPx = (int) Math.max(minWidth, cellWidthPx * scale);
- folderCellHeightPx = (int) Math.max(minHeight, cellHeightPx * scale);
-
- int scaledSpace = (int) (folderCellLayoutBorderSpaceOriginalPx * scale);
- folderCellLayoutBorderSpacePx = new Point(scaledSpace, scaledSpace);
- folderContentPaddingLeftRight = scaledSpace;
- folderContentPaddingTop = scaledSpace;
+ folderCellLayoutBorderSpacePx = new Point(
+ pxFromDp(inv.folderBorderSpaces.x, mMetrics, scale),
+ pxFromDp(inv.folderBorderSpaces.y, mMetrics, scale));
+ folderContentPaddingLeftRight = folderCellLayoutBorderSpacePx.x;
+ folderContentPaddingTop = pxFromDp(inv.folderTopPadding, mMetrics, scale);
} else {
int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding)
* scale);
@@ -1243,6 +1252,19 @@
}
/**
+ * Takes the View and return the scales of width and height depending on the DeviceProfile
+ * specifications
+ *
+ * @param itemInfo The tag of the widget view
+ * @return A PointF instance with the x set to be the scale of width, and y being the scale of
+ * height
+ */
+ @NonNull
+ public PointF getAppWidgetScale(@Nullable final ItemInfo itemInfo) {
+ return mViewScaleProvider.getScaleFromItemInfo(itemInfo);
+ }
+
+ /**
* @return the bounds for which the open folders should be contained within
*/
public Rect getAbsoluteOpenFolderBounds() {
@@ -1391,12 +1413,13 @@
writer.println(prefix + pxToDpStr("folderChildTextSizePx", folderChildTextSizePx));
writer.println(prefix + pxToDpStr("folderChildDrawablePaddingPx",
folderChildDrawablePaddingPx));
- writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpaceOriginalPx",
- folderCellLayoutBorderSpaceOriginalPx));
writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx Horizontal",
folderCellLayoutBorderSpacePx.x));
writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx Vertical",
folderCellLayoutBorderSpacePx.y));
+ writer.println(prefix + pxToDpStr("folderContentPaddingLeftRight",
+ folderContentPaddingLeftRight));
+ writer.println(prefix + pxToDpStr("folderTopPadding", folderContentPaddingTop));
writer.println(prefix + pxToDpStr("bottomSheetTopPadding", bottomSheetTopPadding));
@@ -1551,6 +1574,22 @@
}
}
+ /**
+ * Handler that deals with ItemInfo of the views for the DeviceProfile
+ */
+ @FunctionalInterface
+ public interface ViewScaleProvider {
+ /**
+ * Get the scales from the view
+ *
+ * @param itemInfo The tag of the widget view
+ * @return PointF instance containing the scale information, or null if using the default
+ * app widget scale of this device profile.
+ */
+ @NonNull
+ PointF getScaleFromItemInfo(@Nullable ItemInfo itemInfo);
+ }
+
public static class Builder {
private Context mContext;
private InvariantDeviceProfile mInv;
@@ -1562,6 +1601,7 @@
private boolean mIsMultiWindowMode = false;
private Boolean mTransposeLayoutWithOrientation;
private Boolean mIsGestureMode;
+ private ViewScaleProvider mViewScaleProvider = null;
private SparseArray<DotRenderer> mDotRendererCache;
@@ -1601,6 +1641,19 @@
return this;
}
+ /**
+ * Set the viewScaleProvider for the builder
+ *
+ * @param viewScaleProvider The viewScaleProvider to be set for the
+ * DeviceProfile
+ * @return This builder
+ */
+ @NonNull
+ public Builder setViewScaleProvider(@Nullable ViewScaleProvider viewScaleProvider) {
+ mViewScaleProvider = viewScaleProvider;
+ return this;
+ }
+
public DeviceProfile build() {
if (mWindowBounds == null) {
throw new IllegalArgumentException("Window bounds not set");
@@ -1614,9 +1667,12 @@
if (mDotRendererCache == null) {
mDotRendererCache = new SparseArray<>();
}
+ if (mViewScaleProvider == null) {
+ mViewScaleProvider = DEFAULT_PROVIDER;
+ }
return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, mDotRendererCache,
mIsMultiWindowMode, mTransposeLayoutWithOrientation, mUseTwoPanels,
- mIsGestureMode);
+ mIsGestureMode, mViewScaleProvider);
}
}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index cf2a3f8..bb7c0a4 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -127,9 +127,12 @@
public PointF[] minCellSize;
public PointF[] borderSpaces;
- public float folderBorderSpace;
public int inlineNavButtonsEndSpacing;
+ public PointF folderBorderSpaces;
+ public PointF folderCellSize;
+ public float folderTopPadding;
+
public float[] horizontalMargin;
public PointF[] allAppsCellSize;
@@ -353,7 +356,10 @@
minCellSize = displayOption.minCellSize;
borderSpaces = displayOption.borderSpaces;
- folderBorderSpace = displayOption.folderBorderSpace;
+
+ folderBorderSpaces = displayOption.folderBorderSpaces;
+ folderCellSize = displayOption.folderCellSize;
+ folderTopPadding = displayOption.folderTopPadding;
horizontalMargin = displayOption.horizontalMargin;
@@ -828,7 +834,10 @@
private final PointF[] minCellSize = new PointF[COUNT_SIZES];
- private float folderBorderSpace;
+ private final PointF folderCellSize;
+ private final PointF folderBorderSpaces;
+ private float folderTopPadding;
+
private final PointF[] borderSpaces = new PointF[COUNT_SIZES];
private final float[] horizontalMargin = new float[COUNT_SIZES];
private final float[] hotseatBarBottomSpace = new float[COUNT_SIZES];
@@ -924,7 +933,20 @@
borderSpaceTwoPanelLandscape);
borderSpaces[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y);
- folderBorderSpace = borderSpace;
+ x = a.getFloat(R.styleable.ProfileDisplayOption_folderCellWidth,
+ minCellSize[INDEX_DEFAULT].x);
+ y = a.getFloat(R.styleable.ProfileDisplayOption_folderCellHeight,
+ minCellSize[INDEX_DEFAULT].y);
+ folderCellSize = new PointF(x, y);
+
+ float folderBorderSpace = a.getFloat(R.styleable.ProfileDisplayOption_folderBorderSpace,
+ borderSpace);
+
+ x = y = folderBorderSpace;
+ folderBorderSpaces = new PointF(x, y);
+
+ folderTopPadding = a.getFloat(R.styleable.ProfileDisplayOption_folderTopPadding,
+ folderBorderSpaces.y);
x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidth,
minCellSize[INDEX_DEFAULT].x);
@@ -1099,6 +1121,9 @@
allAppsBorderSpaces[i] = new PointF();
inlineQsb[i] = false;
}
+ folderBorderSpaces = new PointF();
+ folderCellSize = new PointF();
+ folderTopPadding = 0f;
}
private DisplayOption multiply(float w) {
@@ -1119,8 +1144,11 @@
allAppsBorderSpaces[i].x *= w;
allAppsBorderSpaces[i].y *= w;
}
-
- folderBorderSpace *= w;
+ folderBorderSpaces.x *= w;
+ folderBorderSpaces.y *= w;
+ folderCellSize.x *= w;
+ folderCellSize.y *= w;
+ folderTopPadding *= w;
return this;
}
@@ -1144,8 +1172,11 @@
allAppsBorderSpaces[i].y += p.allAppsBorderSpaces[i].y;
inlineQsb[i] |= p.inlineQsb[i];
}
-
- folderBorderSpace += p.folderBorderSpace;
+ folderBorderSpaces.x += p.folderBorderSpaces.x;
+ folderBorderSpaces.y += p.folderBorderSpaces.y;
+ folderCellSize.x += p.folderCellSize.x;
+ folderCellSize.y += p.folderCellSize.y;
+ folderTopPadding += p.folderTopPadding;
return this;
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e55321b..761f198 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1494,7 +1494,7 @@
root.getViewTreeObserver().removeOnDrawListener(mViewCapture);
}
mViewCapture = new ViewCapture(root);
- root.getViewTreeObserver().addOnDrawListener(mViewCapture);
+ mViewCapture.attach();
}
}
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 5583eae..486a68f 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -25,6 +25,7 @@
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
@@ -32,6 +33,7 @@
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.NavigableAppWidgetHostView;
@@ -109,8 +111,9 @@
if (child instanceof NavigableAppWidgetHostView) {
DeviceProfile profile = mActivity.getDeviceProfile();
((NavigableAppWidgetHostView) child).getWidgetInset(profile, mTempRect);
+ final PointF appWidgetScale = profile.getAppWidgetScale((ItemInfo) child.getTag());
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
- profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpace, mTempRect);
+ appWidgetScale.x, appWidgetScale.y, mBorderSpace, mTempRect);
} else {
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
mBorderSpace, null);
@@ -133,8 +136,9 @@
if (child instanceof NavigableAppWidgetHostView) {
((NavigableAppWidgetHostView) child).getWidgetInset(dp, mTempRect);
+ final PointF appWidgetScale = dp.getAppWidgetScale((ItemInfo) child.getTag());
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
- dp.appWidgetScale.x, dp.appWidgetScale.y, mBorderSpace, mTempRect);
+ appWidgetScale.x, appWidgetScale.y, mBorderSpace, mTempRect);
} else {
lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
mBorderSpace, null);
@@ -187,8 +191,9 @@
// Scale and center the widget to fit within its cells.
DeviceProfile profile = mActivity.getDeviceProfile();
- float scaleX = profile.appWidgetScale.x;
- float scaleY = profile.appWidgetScale.y;
+ final PointF appWidgetScale = profile.getAppWidgetScale((ItemInfo) child.getTag());
+ float scaleX = appWidgetScale.x;
+ float scaleY = appWidgetScale.y;
nahv.setScaleToFit(Math.min(scaleX, scaleY));
nahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 4c9542d..f4a8ce5 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -44,6 +44,7 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
@@ -369,7 +370,8 @@
float scale = 1;
if (isWidget) {
DeviceProfile profile = mLauncher.getDeviceProfile();
- scale = Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
+ final PointF appWidgetScale = profile.getAppWidgetScale(null);
+ scale = Utilities.shrinkRect(r, appWidgetScale.x, appWidgetScale.y);
}
size[0] = r.width();
size[1] = r.height();
@@ -2061,20 +2063,11 @@
if (container != LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
cell instanceof LauncherAppWidgetHostView) {
- final CellLayout cellLayout = dropTargetLayout;
+
// We post this call so that the widget has a chance to be placed
// in its final location
-
- final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
- AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
- if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
- && !options.isAccessibleDrag) {
- onCompleteRunnable = () -> {
- if (!isPageInTransition()) {
- AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
- }
- };
- }
+ onCompleteRunnable = getWidgetResizeFrameRunnable(options,
+ (LauncherAppWidgetHostView) cell, dropTargetLayout);
}
mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
lp.cellX, lp.cellY, item.spanX, item.spanY);
@@ -2095,8 +2088,16 @@
}
} else {
// When drag is cancelled, reattach content view back to its original parent.
- if (mDragInfo.cell instanceof LauncherAppWidgetHostView) {
+ if (cell instanceof LauncherAppWidgetHostView) {
d.dragView.detachContentView(/* reattachToPreviousParent= */ true);
+
+ final CellLayout cellLayout = getParentCellLayoutForView(cell);
+ boolean pageIsVisible = isVisible(cellLayout);
+
+ if (pageIsVisible) {
+ onCompleteRunnable = getWidgetResizeFrameRunnable(options,
+ (LauncherAppWidgetHostView) cell, cellLayout);
+ }
}
}
@@ -2149,6 +2150,21 @@
}
}
+ @Nullable
+ private Runnable getWidgetResizeFrameRunnable(DragOptions options,
+ LauncherAppWidgetHostView hostView, CellLayout cellLayout) {
+ AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
+ if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
+ && !options.isAccessibleDrag) {
+ return () -> {
+ if (!isPageInTransition()) {
+ AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
+ }
+ };
+ }
+ return null;
+ }
+
public void onNoCellFound(
View dropTargetLayout, ItemInfo itemInfo, @Nullable InstanceId logInstanceId) {
int strId = mLauncher.isHotseatLayout(dropTargetLayout)
@@ -2884,7 +2900,8 @@
r.top -= widgetPadding.top;
r.bottom += widgetPadding.bottom;
}
- Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
+ PointF appWidgetScale = profile.getAppWidgetScale(null);
+ Utilities.shrinkRect(r, appWidgetScale.x, appWidgetScale.y);
}
mTempFXY[0] = r.left;
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 0a77aa7..464b3ed 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -50,6 +50,8 @@
public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator();
public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+ public static final Interpolator FASTER_OUT_SLOWER_IN =
+ new PathInterpolator(0.3f, 0f, 0.1f, 1f);
public static final Interpolator AGGRESSIVE_EASE = new PathInterpolator(0.2f, 0f, 0f, 1f);
public static final Interpolator AGGRESSIVE_EASE_IN_OUT = new PathInterpolator(0.6f,0, 0.4f, 1);
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index a8546e8..c1bab54 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -20,6 +20,7 @@
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static android.view.View.VISIBLE;
+import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
@@ -36,6 +37,7 @@
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -55,6 +57,7 @@
import android.view.WindowManager;
import android.widget.TextClock;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BubbleTextView;
@@ -100,6 +103,7 @@
import com.android.launcher3.widget.LocalColorExtractor;
import com.android.launcher3.widget.NavigableAppWidgetHostView;
import com.android.launcher3.widget.custom.CustomWidgetManager;
+import com.android.launcher3.widget.util.WidgetSizes;
import java.util.ArrayList;
import java.util.Collections;
@@ -173,6 +177,7 @@
private final Context mContext;
private final InvariantDeviceProfile mIdp;
private final DeviceProfile mDp;
+ private final DeviceProfile mDpOrig;
private final Rect mInsets;
private final WorkspaceItemInfo mWorkspaceItemInfo;
private final LayoutInflater mHomeElementInflater;
@@ -192,7 +197,16 @@
mUiHandler = new Handler(Looper.getMainLooper());
mContext = context;
mIdp = idp;
- mDp = idp.getDeviceProfile(context).copy(context);
+ mDp = idp.getDeviceProfile(context).toBuilder(context).setViewScaleProvider(
+ this::getAppWidgetScale).build();
+ if (context instanceof PreviewContext) {
+ Context tempContext = ((PreviewContext) context).getBaseContext();
+ mDpOrig = new InvariantDeviceProfile(tempContext, InvariantDeviceProfile
+ .getCurrentGridName(tempContext)).getDeviceProfile(tempContext)
+ .copy(tempContext);
+ } else {
+ mDpOrig = mDp;
+ }
WindowInsets currentWindowInsets = context.getSystemService(WindowManager.class)
.getCurrentWindowMetrics().getWindowInsets();
@@ -390,6 +404,41 @@
addInScreenFromBind(view, info);
}
+ @NonNull
+ private PointF getAppWidgetScale(@Nullable ItemInfo itemInfo) {
+ if (!(itemInfo instanceof LauncherAppWidgetInfo)) {
+ return DEFAULT_SCALE;
+ }
+ LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) itemInfo;
+ final Size launcherWidgetSize = mLauncherWidgetSpanInfo.get(info.appWidgetId);
+ if (launcherWidgetSize == null) {
+ return DEFAULT_SCALE;
+ }
+ final Size origSize = WidgetSizes.getWidgetSizePx(mDpOrig,
+ launcherWidgetSize.getWidth(), launcherWidgetSize.getHeight());
+ final Size newSize = WidgetSizes.getWidgetSizePx(mDp, info.spanX, info.spanY);
+ final Rect previewInset = new Rect();
+ final Rect origInset = new Rect();
+ // When the setup() is called for the LayoutParams, insets are added to the width
+ // and height of the view. This is not accounted for in WidgetSizes and is handled
+ // here.
+ if (mDp.shouldInsetWidgets()) {
+ previewInset.set(mDp.inv.defaultWidgetPadding);
+ } else {
+ previewInset.setEmpty();
+ }
+ if (mDpOrig.shouldInsetWidgets()) {
+ origInset.set(mDpOrig.inv.defaultWidgetPadding);
+ } else {
+ origInset.setEmpty();
+ }
+
+ return new PointF((float) newSize.getWidth() / (origSize.getWidth()
+ + origInset.left + origInset.right),
+ (float) newSize.getHeight() / (origSize.getHeight()
+ + origInset.top + origInset.bottom));
+ }
+
private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) {
CellLayout screen = mWorkspaceScreens.get(info.screenId);
View view = PredictedAppIconInflater.inflate(mHomeElementInflater, screen, info);
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 9ac1c0e..2a0fe3a 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -440,7 +440,7 @@
public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
int splitInstructionsWidth, int threeButtonNavShift) {
out.setPivotX(0);
- out.setPivotY(0);
+ out.setPivotY(splitInstructionsHeight);
out.setRotation(getDegreesRotated());
int distanceToEdge = out.getResources().getDimensionPixelSize(
R.dimen.split_instructions_bottom_margin_phone_landscape);
@@ -448,8 +448,8 @@
int insetCorrectionX = dp.getInsets().left;
// Center the view in case of unbalanced insets on top or bottom of screen
int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
- out.setTranslationX(splitInstructionsHeight + distanceToEdge - insetCorrectionX);
- out.setTranslationY(((splitInstructionsHeight - splitInstructionsWidth) / 2f)
+ out.setTranslationX(distanceToEdge - insetCorrectionX);
+ out.setTranslationY(((-splitInstructionsHeight - splitInstructionsWidth) / 2f)
+ insetCorrectionY);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
// Setting gravity to LEFT instead of the lint-recommended START because we always want this
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index dd9f642..f89c0e5 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -500,7 +500,7 @@
public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
int splitInstructionsWidth, int threeButtonNavShift) {
out.setPivotX(0);
- out.setPivotY(0);
+ out.setPivotY(splitInstructionsHeight);
out.setRotation(getDegreesRotated());
int distanceToEdge;
if ((DisplayController.getNavigationMode(out.getContext()) == THREE_BUTTONS)
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index 387e980..55bb5e8 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -190,7 +190,7 @@
public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
int splitInstructionsWidth, int threeButtonNavShift) {
out.setPivotX(0);
- out.setPivotY(0);
+ out.setPivotY(splitInstructionsHeight);
out.setRotation(getDegreesRotated());
int distanceToEdge = out.getResources().getDimensionPixelSize(
R.dimen.split_instructions_bottom_margin_phone_landscape);
@@ -198,9 +198,8 @@
int insetCorrectionX = dp.getInsets().right;
// Center the view in case of unbalanced insets on top or bottom of screen
int insetCorrectionY = (dp.getInsets().bottom - dp.getInsets().top) / 2;
- out.setTranslationX(splitInstructionsWidth - splitInstructionsHeight - distanceToEdge
- + insetCorrectionX);
- out.setTranslationY(((splitInstructionsHeight + splitInstructionsWidth) / 2f)
+ out.setTranslationX(splitInstructionsWidth - distanceToEdge + insetCorrectionX);
+ out.setTranslationY(((-splitInstructionsHeight + splitInstructionsWidth) / 2f)
+ insetCorrectionY);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
// Setting gravity to RIGHT instead of the lint-recommended END because we always want this
diff --git a/src/com/android/launcher3/util/ViewCapture.java b/src/com/android/launcher3/util/ViewCapture.java
index cf9ea69..cf4e84a 100644
--- a/src/com/android/launcher3/util/ViewCapture.java
+++ b/src/com/android/launcher3/util/ViewCapture.java
@@ -15,10 +15,11 @@
*/
package com.android.launcher3.util;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
import android.content.res.Resources;
import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
+import android.os.Message;
import android.os.Trace;
import android.util.Base64;
import android.util.Base64OutputStream;
@@ -28,6 +29,7 @@
import android.view.ViewTreeObserver.OnDrawListener;
import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
import com.android.launcher3.view.ViewCaptureData.ExportedData;
import com.android.launcher3.view.ViewCaptureData.FrameData;
@@ -36,7 +38,7 @@
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.OutputStream;
-import java.util.concurrent.FutureTask;
+import java.util.concurrent.Future;
/**
* Utility class for capturing view data every frame
@@ -45,49 +47,132 @@
private static final String TAG = "ViewCapture";
+ // Number of frames to keep in memory
private static final int MEMORY_SIZE = 2000;
+ // Initial size of the reference pool. This is at least be 5 * total number of views in
+ // Launcher. This allows the first free frames avoid object allocation during view capture.
+ private static final int INIT_POOL_SIZE = 300;
private final View mRoot;
- private final long[] mFrameTimes = new long[MEMORY_SIZE];
- private final Node[] mNodes = new Node[MEMORY_SIZE];
+ private final Resources mResources;
- private int mFrameIndex = -1;
+ private final Handler mHandler;
+ private final ViewRef mViewRef = new ViewRef();
+
+ private int mFrameIndexBg = -1;
+ private final long[] mFrameTimesBg = new long[MEMORY_SIZE];
+ private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[MEMORY_SIZE];
+
+ // Pool used for capturing view tree on the UI thread.
+ private ViewRef mPool = new ViewRef();
/**
* @param root the root view for the capture data
*/
public ViewCapture(View root) {
mRoot = root;
+ mResources = root.getResources();
+ mHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::captureViewPropertiesBg);
+ }
+
+ /**
+ * Attaches the ViewCapture to the root
+ */
+ public void attach() {
+ mHandler.post(this::initPool);
}
@Override
public void onDraw() {
Trace.beginSection("view_capture");
- long now = SystemClock.elapsedRealtimeNanos();
-
- mFrameIndex++;
- if (mFrameIndex >= MEMORY_SIZE) {
- mFrameIndex = 0;
- }
- mFrameTimes[mFrameIndex] = now;
- mNodes[mFrameIndex] = captureView(mRoot, mNodes[mFrameIndex]);
+ captureViewTree(mRoot, mViewRef);
+ Message m = Message.obtain(mHandler);
+ m.obj = mViewRef.next;
+ mHandler.sendMessage(m);
Trace.endSection();
}
/**
+ * Captures the View property on the background thread, and transfer all the ViewRef objects
+ * back to the pool
+ */
+ @WorkerThread
+ private boolean captureViewPropertiesBg(Message msg) {
+ ViewRef start = (ViewRef) msg.obj;
+ long time = msg.getWhen();
+ if (start == null) {
+ return false;
+ }
+ mFrameIndexBg++;
+ if (mFrameIndexBg >= MEMORY_SIZE) {
+ mFrameIndexBg = 0;
+ }
+ mFrameTimesBg[mFrameIndexBg] = time;
+
+ ViewPropertyRef recycle = mNodesBg[mFrameIndexBg];
+
+ ViewPropertyRef result = null;
+ ViewPropertyRef resultEnd = null;
+
+ ViewRef current = start;
+ ViewRef last = start;
+ while (current != null) {
+ ViewPropertyRef propertyRef = recycle;
+ if (propertyRef == null) {
+ propertyRef = new ViewPropertyRef();
+ } else {
+ recycle = recycle.next;
+ propertyRef.next = null;
+ }
+
+ propertyRef.transfer(current);
+ last = current;
+ current = current.next;
+
+ if (result == null) {
+ result = propertyRef;
+ resultEnd = result;
+ } else {
+ resultEnd.next = propertyRef;
+ resultEnd = propertyRef;
+ }
+ }
+ mNodesBg[mFrameIndexBg] = result;
+ ViewRef end = last;
+ Executors.MAIN_EXECUTOR.execute(() -> addToPool(start, end));
+ return true;
+ }
+
+ @UiThread
+ private void addToPool(ViewRef start, ViewRef end) {
+ end.next = mPool;
+ mPool = start;
+ }
+
+ @WorkerThread
+ private void initPool() {
+ ViewRef start = new ViewRef();
+ ViewRef current = start;
+
+ for (int i = 0; i < INIT_POOL_SIZE; i++) {
+ current.next = new ViewRef();
+ current = current.next;
+ }
+
+ ViewRef end = current;
+ Executors.MAIN_EXECUTOR.execute(() -> {
+ addToPool(start, end);
+ if (mRoot.isAttachedToWindow()) {
+ mRoot.getViewTreeObserver().addOnDrawListener(this);
+ }
+ });
+ }
+
+ /**
* Creates a proto of all the data captured so far.
*/
public void dump(FileDescriptor out) {
- Handler handler = mRoot.getHandler();
- if (handler == null) {
- handler = Executors.MAIN_EXECUTOR.getHandler();
- }
- FutureTask<ExportedData> task = new FutureTask<>(this::dumpToProtoUI);
- if (Looper.myLooper() == handler.getLooper()) {
- task.run();
- } else {
- handler.post(task);
- }
+ Future<ExportedData> task = UI_HELPER_EXECUTOR.submit(this::dumpToProto);
try (OutputStream os = new FileOutputStream(out)) {
ExportedData data = task.get();
Base64OutputStream encodedOS = new Base64OutputStream(os,
@@ -100,70 +185,53 @@
}
}
- @UiThread
- private ExportedData dumpToProtoUI() {
+ @WorkerThread
+ private ExportedData dumpToProto() {
ExportedData.Builder dataBuilder = ExportedData.newBuilder();
- Resources res = mRoot.getResources();
+ Resources res = mResources;
- int size = (mNodes[MEMORY_SIZE - 1] == null) ? mFrameIndex + 1 : MEMORY_SIZE;
+ int size = (mNodesBg[MEMORY_SIZE - 1] == null) ? mFrameIndexBg + 1 : MEMORY_SIZE;
for (int i = size - 1; i >= 0; i--) {
- int index = (MEMORY_SIZE + mFrameIndex - i) % MEMORY_SIZE;
+ int index = (MEMORY_SIZE + mFrameIndexBg - i) % MEMORY_SIZE;
+ ViewNode.Builder nodeBuilder = ViewNode.newBuilder();
+ mNodesBg[index].toProto(res, nodeBuilder);
dataBuilder.addFrameData(FrameData.newBuilder()
- .setNode(mNodes[index].toProto(res))
- .setTimestamp(mFrameTimes[index]));
+ .setNode(nodeBuilder)
+ .setTimestamp(mFrameTimesBg[index]));
}
return dataBuilder.build();
}
- private Node captureView(View view, Node recycle) {
- Node result = recycle == null ? new Node() : recycle;
-
- result.clazz = view.getClass();
- result.hashCode = view.hashCode();
- result.id = view.getId();
- result.left = view.getLeft();
- result.top = view.getTop();
- result.right = view.getRight();
- result.bottom = view.getBottom();
- result.scrollX = view.getScrollX();
- result.scrollY = view.getScrollY();
-
- result.translateX = view.getTranslationX();
- result.translateY = view.getTranslationY();
- result.scaleX = view.getScaleX();
- result.scaleY = view.getScaleY();
- result.alpha = view.getAlpha();
-
- result.visibility = view.getVisibility();
- result.willNotDraw = view.willNotDraw();
-
- if (view instanceof ViewGroup) {
- ViewGroup parent = (ViewGroup) view;
- result.clipChildren = parent.getClipChildren();
- int childCount = parent.getChildCount();
- if (childCount == 0) {
- result.children = null;
- } else {
- result.children = captureView(parent.getChildAt(0), result.children);
- Node lastChild = result.children;
- for (int i = 1; i < childCount; i++) {
- lastChild.sibling = captureView(parent.getChildAt(i), lastChild.sibling);
- lastChild = lastChild.sibling;
- }
- lastChild.sibling = null;
- }
+ private ViewRef captureViewTree(View view, ViewRef start) {
+ ViewRef ref;
+ if (mPool != null) {
+ ref = mPool;
+ mPool = mPool.next;
+ ref.next = null;
} else {
- result.clipChildren = false;
- result.children = null;
+ ref = new ViewRef();
}
- return result;
+ ref.view = view;
+ start.next = ref;
+ if (view instanceof ViewGroup) {
+ ViewRef result = ref;
+ ViewGroup parent = (ViewGroup) view;
+ int childCount = ref.childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ result = captureViewTree(parent.getChildAt(i), result);
+ }
+ return result;
+ } else {
+ ref.childCount = 0;
+ return ref;
+ }
}
- private static class Node {
-
+ private static class ViewPropertyRef {
// We store reference in memory to avoid generating and storing too many strings
public Class clazz;
public int hashCode;
+ public int childCount = 0;
public int id;
public int left, top, right, bottom;
@@ -177,10 +245,41 @@
public boolean willNotDraw;
public boolean clipChildren;
- public Node sibling;
- public Node children;
+ public ViewPropertyRef next;
- public ViewNode toProto(Resources res) {
+ public void transfer(ViewRef viewRef) {
+ childCount = viewRef.childCount;
+
+ View view = viewRef.view;
+ viewRef.view = null;
+
+ clazz = view.getClass();
+ hashCode = view.hashCode();
+ id = view.getId();
+ left = view.getLeft();
+ top = view.getTop();
+ right = view.getRight();
+ bottom = view.getBottom();
+ scrollX = view.getScrollX();
+ scrollY = view.getScrollY();
+
+ translateX = view.getTranslationX();
+ translateY = view.getTranslationY();
+ scaleX = view.getScaleX();
+ scaleY = view.getScaleY();
+ alpha = view.getAlpha();
+
+ visibility = view.getVisibility();
+ willNotDraw = view.willNotDraw();
+ }
+
+ /**
+ * Converts the data to the proto representation and returns the next property ref
+ * at the end of the iteration.
+ * @param res
+ * @return
+ */
+ public ViewPropertyRef toProto(Resources res, ViewNode.Builder outBuilder) {
String resolvedId;
if (id >= 0) {
try {
@@ -191,9 +290,7 @@
} else {
resolvedId = "NO_ID";
}
-
- ViewNode.Builder result = ViewNode.newBuilder()
- .setClassname(clazz.getName() + "@" + hashCode)
+ outBuilder.setClassname(clazz.getName() + "@" + hashCode)
.setId(resolvedId)
.setLeft(left)
.setTop(top)
@@ -207,13 +304,20 @@
.setVisibility(visibility)
.setWillNotDraw(willNotDraw)
.setClipChildren(clipChildren);
- Node child = children;
- while (child != null) {
- result.addChildren(child.toProto(res));
- child = child.sibling;
- }
- return result.build();
- }
+ ViewPropertyRef result = next;
+ for (int i = 0; (i < childCount) && (result != null); i++) {
+ ViewNode.Builder childBuilder = ViewNode.newBuilder();
+ result = result.toProto(res, childBuilder);
+ outBuilder.addChildren(childBuilder);
+ }
+ return result;
+ }
+ }
+
+ private static class ViewRef {
+ public View view;
+ public int childCount = 0;
+ public ViewRef next;
}
}
diff --git a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
index cf6be7f..2c1cbdf 100644
--- a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
+++ b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
@@ -20,6 +20,7 @@
import android.graphics.Rect
import android.util.SparseArray
import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.DeviceProfile.DEFAULT_PROVIDER;
import com.android.launcher3.util.DisplayController.Info
import com.android.launcher3.util.WindowBounds
import org.junit.Before
@@ -61,7 +62,8 @@
isMultiWindowMode,
transposeLayoutWithOrientation,
useTwoPanels,
- isGestureMode
+ isGestureMode,
+ DEFAULT_PROVIDER
)
protected fun initializeVarsForPhone(isGestureMode: Boolean = true,
@@ -94,8 +96,6 @@
numColumns = 4
numSearchContainerColumns = 4
- numFolderRows = 3
- numFolderColumns = 3
iconSize = floatArrayOf(60f, 54f, 60f, 60f)
iconTextSize = FloatArray(4) { 14f }
deviceType = InvariantDeviceProfile.TYPE_PHONE
@@ -113,7 +113,14 @@
PointF(16f, 16f),
PointF(16f, 16f)
).toTypedArray()
- folderBorderSpace = 16f
+
+ numFolderRows = 3
+ numFolderColumns = 3
+ folderBorderSpaces = PointF(16f, 16f)
+ folderTopPadding = 24f
+ folderCellSize = PointF(80f, 94f)
+
+
inlineNavButtonsEndSpacing = R.dimen.taskbar_button_margin_4_5
horizontalMargin = FloatArray(4) { 22f }
@@ -197,6 +204,13 @@
PointF(16f, 64f),
PointF(16f, 64f)
).toTypedArray()
+
+ numFolderRows = 3
+ numFolderColumns = 3
+ folderBorderSpaces = PointF(16f, 16f)
+ folderTopPadding = 24f
+ folderCellSize = PointF(120f, 104f)
+
inlineNavButtonsEndSpacing = R.dimen.taskbar_button_margin_6_5
horizontalMargin = floatArrayOf(54f, 120f, 54f, 54f)
@@ -271,8 +285,6 @@
numColumns = 4
numSearchContainerColumns = 4
- numFolderRows = 3
- numFolderColumns = 4
iconSize = floatArrayOf(60f, 52f, 52f, 60f)
iconTextSize = floatArrayOf(14f, 14f, 12f, 14f)
deviceType = InvariantDeviceProfile.TYPE_MULTI_DISPLAY
@@ -290,7 +302,13 @@
PointF(16f, 20f),
PointF(20f, 20f)
).toTypedArray()
- folderBorderSpace = 16f
+
+ numFolderRows = 3
+ numFolderColumns = 3
+ folderBorderSpaces = PointF(16f, 16f)
+ folderTopPadding = 24f
+ folderCellSize = PointF(80f, 94f)
+
inlineNavButtonsEndSpacing = R.dimen.taskbar_button_margin_4_4
horizontalMargin = floatArrayOf(21.5f, 21.5f, 22.5f, 30.5f)
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 96622ca..fa7e8e9 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1124,6 +1124,16 @@
}
@Nullable
+ UiObject2 findObjectInContainer(UiObject2 container, String resName) {
+ try {
+ return container.findObject(getLauncherObjectSelector(resName));
+ } catch (StaleObjectException e) {
+ fail("The container disappeared from screen");
+ return null;
+ }
+ }
+
+ @Nullable
UiObject2 findObjectInContainer(UiObject2 container, BySelector selector) {
try {
return container.findObject(selector);
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 0752e1e..adc993d 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -182,6 +182,6 @@
}
boolean isTaskSplit() {
- return mTask.hasObject(By.res("bottomright_snapshot"));
+ return mLauncher.findObjectInContainer(mTask.getParent(), "bottomright_snapshot") != null;
}
}