Merge "Disable Saving App Pairs for Private Space Apps" into main
diff --git a/quickstep/Android.bp b/quickstep/Android.bp
index ec4f6fc..a290e84 100644
--- a/quickstep/Android.bp
+++ b/quickstep/Android.bp
@@ -23,15 +23,23 @@
}
filegroup {
- name: "launcher3-quickstep-robolectric-src",
- path: "robolectric_tests",
- srcs: ["robolectric_tests/src/**/*.java"],
+ name: "launcher3-quickstep-robo-src",
+ path: "tests/multivalentTests",
+ srcs: [
+ "tests/multivalentTests/src/**/*.java",
+ "tests/multivalentTests/src/**/*.kt",
+ ],
}
filegroup {
name: "launcher3-quickstep-tests-src",
path: "tests",
- srcs: ["tests/src/**/*.java", "tests/src/**/*.kt"],
+ srcs: [
+ "tests/multivalentTests/src/**/*.java",
+ "tests/multivalentTests/src/**/*.kt",
+ "tests/src/**/*.java",
+ "tests/src/**/*.kt",
+ ],
}
filegroup {
@@ -44,5 +52,5 @@
"tests/src/com/android/quickstep/TaplOverviewIconTest.java",
"tests/src/com/android/quickstep/TaplTestsQuickstep.java",
"tests/src/com/android/quickstep/TaplTestsSplitscreen.java",
- ]
+ ],
}
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 75b8796c..66e20d7 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -64,6 +64,7 @@
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
+import static com.android.quickstep.util.AnimUtils.clampToDuration;
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
@@ -748,34 +749,35 @@
final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius;
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
- FloatProp mDx = new FloatProp(0, prop.dX, 0, APP_LAUNCH_DURATION,
- mOpeningXInterpolator);
- FloatProp mDy = new FloatProp(0, prop.dY, 0, APP_LAUNCH_DURATION,
- mOpeningInterpolator);
+ FloatProp mDx = new FloatProp(0, prop.dX, mOpeningXInterpolator);
+ FloatProp mDy = new FloatProp(0, prop.dY, mOpeningInterpolator);
FloatProp mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale,
- prop.finalAppIconScale, 0, APP_LAUNCH_DURATION, mOpeningInterpolator);
+ prop.finalAppIconScale, mOpeningInterpolator);
FloatProp mIconAlpha = new FloatProp(prop.iconAlphaStart, 0f,
- APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION, LINEAR);
+ clampToDuration(LINEAR, APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION,
+ APP_LAUNCH_DURATION));
- FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 0,
- APP_LAUNCH_DURATION, mOpeningInterpolator);
- FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, 0,
- APP_LAUNCH_DURATION, mOpeningInterpolator);
+ FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius,
+ mOpeningInterpolator);
+ FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius,
+ mOpeningInterpolator);
FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd,
- 0, APP_LAUNCH_DURATION, mOpeningInterpolator);
+ mOpeningInterpolator);
FloatProp mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd,
- 0, APP_LAUNCH_DURATION, mOpeningInterpolator);
- FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd, 0,
- APP_LAUNCH_DURATION, mOpeningInterpolator);
- FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, 0,
- APP_LAUNCH_DURATION, mOpeningInterpolator);
+ mOpeningInterpolator);
+ FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd,
+ mOpeningInterpolator);
+ FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd,
+ mOpeningInterpolator);
- FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION,
- NAV_FADE_OUT_INTERPOLATOR);
- FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN,
- ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR);
+ FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration(
+ NAV_FADE_OUT_INTERPOLATOR, 0, ANIMATION_NAV_FADE_OUT_DURATION,
+ APP_LAUNCH_DURATION));
+ FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration(
+ NAV_FADE_IN_INTERPOLATOR, ANIMATION_DELAY_NAV_FADE_IN,
+ ANIMATION_NAV_FADE_IN_DURATION, APP_LAUNCH_DURATION));
@Override
public void onUpdate(float percent, boolean initOnly) {
@@ -968,37 +970,36 @@
appAnimator.addUpdateListener(new MultiValueUpdateListener() {
float mAppWindowScale = 1;
- final FloatProp mWidgetForegroundAlpha = new FloatProp(1 /* start */,
- 0 /* end */, 0 /* delay */,
- WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR);
- final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0 /* start */,
- 1 /* end */, 0 /* delay */, 75 /* duration */, LINEAR);
- final FloatProp mPreviewAlpha = new FloatProp(0 /* start */, 1 /* end */,
+ final FloatProp mWidgetForegroundAlpha = new FloatProp(1, 0, clampToDuration(
+ LINEAR, 0, WIDGET_CROSSFADE_DURATION_MILLIS / 2, APP_LAUNCH_DURATION));
+
+ final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0, 1,
+ clampToDuration(LINEAR, 0, 75, APP_LAUNCH_DURATION));
+ final FloatProp mPreviewAlpha = new FloatProp(0, 1, clampToDuration(
+ LINEAR,
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */,
- WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR);
+ WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */,
+ APP_LAUNCH_DURATION));
final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius,
- 0 /* start */, APP_LAUNCH_DURATION, mOpeningInterpolator);
- final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, APP_LAUNCH_DURATION,
mOpeningInterpolator);
+ final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, mOpeningInterpolator);
// Window & widget background positioning bounds
final FloatProp mDx = new FloatProp(widgetBackgroundBounds.centerX(),
- windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_DURATION,
- mOpeningXInterpolator);
+ windowTargetBounds.centerX(), mOpeningXInterpolator);
final FloatProp mDy = new FloatProp(widgetBackgroundBounds.centerY(),
- windowTargetBounds.centerY(), 0 /* delay */, APP_LAUNCH_DURATION,
- mOpeningInterpolator);
+ windowTargetBounds.centerY(), mOpeningInterpolator);
final FloatProp mWidth = new FloatProp(widgetBackgroundBounds.width(),
- windowTargetBounds.width(), 0 /* delay */, APP_LAUNCH_DURATION,
- mOpeningInterpolator);
+ windowTargetBounds.width(), mOpeningInterpolator);
final FloatProp mHeight = new FloatProp(widgetBackgroundBounds.height(),
- windowTargetBounds.height(), 0 /* delay */, APP_LAUNCH_DURATION,
- mOpeningInterpolator);
+ windowTargetBounds.height(), mOpeningInterpolator);
- final FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION,
- NAV_FADE_OUT_INTERPOLATOR);
- final FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN,
- ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR);
+ final FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration(
+ NAV_FADE_OUT_INTERPOLATOR, 0, ANIMATION_NAV_FADE_OUT_DURATION,
+ APP_LAUNCH_DURATION));
+ final FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration(
+ NAV_FADE_IN_INTERPOLATOR, ANIMATION_DELAY_NAV_FADE_IN,
+ ANIMATION_NAV_FADE_IN_DURATION, APP_LAUNCH_DURATION));
@Override
public void onUpdate(float percent, boolean initOnly) {
@@ -1508,11 +1509,10 @@
float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius;
closingAnimator.setDuration(duration);
closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
- FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DECELERATE_1_7);
- FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DECELERATE_1_7);
- FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR);
- FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, 0, duration,
- DECELERATE_1_7);
+ FloatProp mDy = new FloatProp(0, mClosingWindowTransY, DECELERATE_1_7);
+ FloatProp mScale = new FloatProp(1f, 1f, DECELERATE_1_7);
+ FloatProp mAlpha = new FloatProp(1f, 0f, clampToDuration(LINEAR, 25, 125, duration));
+ FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, DECELERATE_1_7);
@Override
public void onUpdate(float percent, boolean initOnly) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 87662e6..27a895c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -1087,7 +1087,6 @@
} else if (tag instanceof FolderInfo fi && fi.itemType == Favorites.ITEM_TYPE_APP_PAIR) {
// Tapping an app pair icon on Taskbar
if (recents != null && recents.isSplitSelectionActive()) {
- // TODO (b/274835596): Implement "can't split with this" bounce animation
Toast.makeText(this, "Unable to split with an app pair. Select another app.",
Toast.LENGTH_SHORT).show();
} else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index faa67be..189b687 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -686,15 +686,10 @@
float toScale = iconSize / mDragIconSize;
float toAlpha = (target == originalView) ? 1f : 0f;
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
- final FloatProp mDx = new FloatProp(fromX, toPosition[0], 0,
- ANIM_DURATION_RETURN_ICON_TO_TASKBAR, Interpolators.FAST_OUT_SLOW_IN);
- final FloatProp mDy = new FloatProp(fromY, toPosition[1], 0,
- ANIM_DURATION_RETURN_ICON_TO_TASKBAR,
- FAST_OUT_SLOW_IN);
- final FloatProp mScale = new FloatProp(1f, toScale, 0,
- ANIM_DURATION_RETURN_ICON_TO_TASKBAR, FAST_OUT_SLOW_IN);
- final FloatProp mAlpha = new FloatProp(1f, toAlpha, 0,
- ANIM_DURATION_RETURN_ICON_TO_TASKBAR, Interpolators.ACCELERATE_2);
+ final FloatProp mDx = new FloatProp(fromX, toPosition[0], FAST_OUT_SLOW_IN);
+ final FloatProp mDy = new FloatProp(fromY, toPosition[1], FAST_OUT_SLOW_IN);
+ final FloatProp mScale = new FloatProp(1f, toScale, FAST_OUT_SLOW_IN);
+ final FloatProp mAlpha = new FloatProp(1f, toAlpha, Interpolators.ACCELERATE_2);
@Override
public void onUpdate(float percent, boolean initOnly) {
animListener.updateDragShadow(mDx.value, mDy.value, mScale.value, mAlpha.value);
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 5424fcf..e0e78f9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -35,6 +35,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsRecyclerView;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
@@ -196,7 +197,13 @@
@Override
protected void dispatchDraw(Canvas canvas) {
- mAppsView.drawOnScrimWithScale(canvas, mSlideInViewScale.value);
+ // We should call drawOnScrimWithBottomOffset() rather than drawOnScrimWithScale(). Because
+ // for taskbar all apps, the scrim view is a child view of AbstractSlideInView. Thus scaling
+ // down in AbstractSlideInView#onScaleProgressChanged() with SCALE_PROPERTY has already
+ // done the job - there is no need to re-apply scale effect here. But it also means we need
+ // to pass extra bottom offset to background scrim to fill the bottom gap during predictive
+ // back swipe.
+ mAppsView.drawOnScrimWithBottomOffset(canvas, getBottomOffsetPx());
super.dispatchDraw(canvas);
}
@@ -205,6 +212,11 @@
super.onScaleProgressChanged();
mAppsView.setClipChildren(!mIsBackProgressing);
mAppsView.getAppsRecyclerViewContainer().setClipChildren(!mIsBackProgressing);
+ AllAppsRecyclerView rv = mAppsView.getActiveRecyclerView();
+ if (rv != null && rv.getScrollbar() != null) {
+ rv.getScrollbar().setVisibility(
+ mIsBackProgressing ? INVISIBLE : VISIBLE);
+ }
}
@Override
@@ -255,7 +267,11 @@
@Override
public void onBackInvoked() {
- if (!mAllAppsCallbacks.handleSearchBackInvoked()) {
+ if (mAllAppsCallbacks.handleSearchBackInvoked()) {
+ // We need to scale back taskbar all apps if we navigate back within search inside all
+ // apps
+ animateSlideInViewToNoScale();
+ } else {
super.onBackInvoked();
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index e30ea7a..8d4255c 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -37,6 +37,7 @@
import static com.android.launcher3.QuickstepTransitionManager.SPLIT_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
+import static com.android.quickstep.util.AnimUtils.clampToDuration;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import android.animation.Animator;
@@ -267,10 +268,16 @@
if (navBarTarget != null) {
final Rect cropRect = new Rect();
out.addOnFrameListener(new MultiValueUpdateListener() {
- FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0,
- ANIMATION_NAV_FADE_OUT_DURATION, NAV_FADE_OUT_INTERPOLATOR);
- FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN,
- ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR);
+ FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration(
+ NAV_FADE_OUT_INTERPOLATOR,
+ 0,
+ ANIMATION_NAV_FADE_OUT_DURATION,
+ out.getDuration()));
+ FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration(
+ NAV_FADE_IN_INTERPOLATOR,
+ ANIMATION_DELAY_NAV_FADE_IN,
+ ANIMATION_NAV_FADE_IN_DURATION,
+ out.getDuration()));
@Override
public void onUpdate(float percent, boolean initOnly) {
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 545a94d..f89888a 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -41,6 +41,7 @@
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -87,7 +88,7 @@
private static final int FEEDBACK_ANIMATION_MS = 133;
private static final int RIPPLE_VISIBLE_MS = 300;
private static final int GESTURE_ANIMATION_DELAY_MS = 1500;
- private static final int ADVANCE_TUTORIAL_TIMEOUT_MS = 2000;
+ private static final int ADVANCE_TUTORIAL_TIMEOUT_MS = 3000;
private static final long GESTURE_ANIMATION_PAUSE_DURATION_MILLIS = 1000;
protected float mExitingAppEndingCornerRadius;
protected float mExitingAppStartingCornerRadius;
@@ -209,8 +210,12 @@
mFeedbackView.removeCallbacks(mFeedbackViewCallback);
}
mFeedbackViewCallback = mTutorialFragment::continueTutorial;
- mFeedbackView.postDelayed(mFeedbackViewCallback,
- ADVANCE_TUTORIAL_TIMEOUT_MS);
+ mFeedbackView.postDelayed(
+ mFeedbackViewCallback,
+ AccessibilityManager.getInstance(mContext)
+ .getRecommendedTimeoutMillis(
+ ADVANCE_TUTORIAL_TIMEOUT_MS,
+ AccessibilityManager.FLAG_CONTENT_TEXT));
}
})
.start();
diff --git a/quickstep/src/com/android/quickstep/util/AnimUtils.java b/quickstep/src/com/android/quickstep/util/AnimUtils.java
index 1f2a02c..8e3d44f 100644
--- a/quickstep/src/com/android/quickstep/util/AnimUtils.java
+++ b/quickstep/src/com/android/quickstep/util/AnimUtils.java
@@ -16,10 +16,12 @@
package com.android.quickstep.util;
+import static com.android.app.animation.Interpolators.clampToProgress;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.os.Bundle;
import android.os.IRemoteCallback;
+import android.view.animation.Interpolator;
import com.android.launcher3.util.RunnableList;
@@ -67,4 +69,15 @@
}
};
}
+
+ /**
+ * Returns a function that runs the given interpolator such that the entire progress is set
+ * between the given duration. That is, we set the interpolation to 0 until startDelay and reach
+ * 1 by (startDelay + duration).
+ */
+ public static Interpolator clampToDuration(Interpolator interpolator, float startDelay,
+ float duration, float totalDuration) {
+ return clampToProgress(interpolator, startDelay / totalDuration,
+ (startDelay + duration) / totalDuration);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/AppCloseConfig.java b/quickstep/src/com/android/quickstep/util/AppCloseConfig.java
deleted file mode 100644
index bec3379..0000000
--- a/quickstep/src/com/android/quickstep/util/AppCloseConfig.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2021 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.quickstep.util;
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-
-/*
- * Adds getter methods to {@link MultiValueUpdateListener} specific to app close animation,
- * so that the entire animation can be defined in one place.
- */
-public abstract class AppCloseConfig extends MultiValueUpdateListener {
-
- /**
- * Returns the translation y of the workspace contents.
- */
- public abstract float getWorkspaceTransY();
-
- /*
- * Returns the scale of the workspace contents.
- */
- public abstract float getWorkspaceScale();
-
- /*
- * Returns the alpha of the window.
- */
- public abstract @FloatRange(from = 0, to = 1) float getWindowAlpha();
-
- /*
- * Returns the alpha of the foreground layer of an adaptive icon.
- */
- public abstract @IntRange(from = 0, to = 255) int getFgAlpha();
-
- /*
- * Returns the corner radius of the window and icon.
- */
- public abstract float getCornerRadius();
-
- /*
- * Returns the interpolated progress of the animation.
- */
- public abstract float getInterpolatedProgress();
-
-}
diff --git a/quickstep/src/com/android/quickstep/util/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
index f7437eb..e9a06f7 100644
--- a/quickstep/src/com/android/quickstep/util/AssistStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
@@ -22,6 +22,7 @@
import com.android.launcher3.util.ResourceBasedOverride;
import java.io.PrintWriter;
+import java.util.Optional;
/** Class to manage Assistant states. */
public class AssistStateManager implements ResourceBasedOverride {
@@ -41,21 +42,21 @@
return false;
}
+ /** Whether CsHelper CtS invocation path is available. */
+ public Optional<Boolean> isCsHelperAvailable() {
+ return Optional.empty();
+ }
+
+ /** Whether VIS CtS invocation path is available. */
+ public Optional<Boolean> isVisAvailable() {
+ return Optional.empty();
+ }
+
/** Whether search recovery is available. */
public boolean isVisRecoveryEnabled() {
return false;
}
- /** Whether search recovery is available. */
- public boolean isOseRecoveryEnabled() {
- return false;
- }
-
- /** Whether search recovery is available. */
- public boolean isOseShowSessionEnabled() {
- return false;
- }
-
/** Return {@code true} if the Settings toggle is enabled. */
public boolean isSettingsNavHandleEnabled() {
return false;
diff --git a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
index 1c3c9c2..72fc2a6 100644
--- a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
+++ b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
@@ -18,6 +18,8 @@
import android.animation.ValueAnimator;
import android.view.animation.Interpolator;
+import com.android.launcher3.Utilities;
+
import java.util.ArrayList;
/**
@@ -31,14 +33,11 @@
@Override
public final void onAnimationUpdate(ValueAnimator animator) {
final float percent = animator.getAnimatedFraction();
- final float currentPlayTime = percent * animator.getDuration();
for (int i = mAllProperties.size() - 1; i >= 0; i--) {
FloatProp prop = mAllProperties.get(i);
- float time = Math.max(0, currentPlayTime - prop.mDelay);
- float newPercent = Math.min(1f, time / prop.mDuration);
- newPercent = prop.mInterpolator.getInterpolation(newPercent);
- prop.value = prop.mEnd * newPercent + prop.mStart * (1 - newPercent);
+ float interpolatedPercent = prop.mInterpolator.getInterpolation(percent);
+ prop.value = Utilities.mapRange(interpolatedPercent, prop.mStart, prop.mEnd);
}
onUpdate(percent, false /* initOnly */);
}
@@ -55,17 +54,12 @@
private final float mStart;
private final float mEnd;
- private final float mDelay;
- private final float mDuration;
private final Interpolator mInterpolator;
- public FloatProp(float start, float end, float delay, float duration, Interpolator i) {
+ public FloatProp(float start, float end, Interpolator i) {
value = mStart = start;
mEnd = end;
- mDelay = delay;
- mDuration = duration;
mInterpolator = i;
-
mAllProperties.add(this);
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index b7b1d8f..8f5c9c1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -70,69 +70,84 @@
import java.util.function.Supplier
/**
- * Utils class to help run animations for initiating split screen from launcher.
- * Will be expanded with future refactors. Works in conjunction with the state stored in
- * [SplitSelectStateController]
+ * Utils class to help run animations for initiating split screen from launcher. Will be expanded
+ * with future refactors. Works in conjunction with the state stored in [SplitSelectStateController]
*/
class SplitAnimationController(val splitSelectStateController: SplitSelectStateController) {
companion object {
// Break this out into maybe enums? Abstractions into its own classes? Tbd.
data class SplitAnimInitProps(
- val originalView: View,
- val originalBitmap: Bitmap?,
- val iconDrawable: Drawable,
- val fadeWithThumbnail: Boolean,
- val isStagedTask: Boolean,
- val iconView: View?
+ val originalView: View,
+ val originalBitmap: Bitmap?,
+ val iconDrawable: Drawable,
+ val fadeWithThumbnail: Boolean,
+ val isStagedTask: Boolean,
+ val iconView: View?
)
}
/**
- * Returns different elements to animate for the initial split selection animation
- * depending on the state of the surface from which the split was initiated
+ * Returns different elements to animate for the initial split selection animation depending on
+ * the state of the surface from which the split was initiated
*/
- fun getFirstAnimInitViews(taskViewSupplier: Supplier<TaskView>,
- splitSelectSourceSupplier: Supplier<SplitSelectSource?>)
- : SplitAnimInitProps {
+ fun getFirstAnimInitViews(
+ taskViewSupplier: Supplier<TaskView>,
+ splitSelectSourceSupplier: Supplier<SplitSelectSource?>
+ ): SplitAnimInitProps {
val splitSelectSource = splitSelectSourceSupplier.get()
if (!splitSelectStateController.isAnimateCurrentTaskDismissal) {
// Initiating from home
- return SplitAnimInitProps(splitSelectSource!!.view, originalBitmap = null,
- splitSelectSource.drawable, fadeWithThumbnail = false, isStagedTask = true,
- iconView = null)
+ return SplitAnimInitProps(
+ splitSelectSource!!.view,
+ originalBitmap = null,
+ splitSelectSource.drawable,
+ fadeWithThumbnail = false,
+ isStagedTask = true,
+ iconView = null
+ )
} else if (splitSelectStateController.isDismissingFromSplitPair) {
// Initiating split from overview, but on a split pair
val taskView = taskViewSupplier.get()
- for (container : TaskIdAttributeContainer in taskView.taskIdAttributeContainers) {
+ for (container: TaskIdAttributeContainer in taskView.taskIdAttributeContainers) {
if (container.task.getKey().getId() == splitSelectStateController.initialTaskId) {
val drawable = getDrawable(container.iconView, splitSelectSource)
- return SplitAnimInitProps(container.thumbnailView,
- container.thumbnailView.thumbnail, drawable!!,
- fadeWithThumbnail = true, isStagedTask = true,
- iconView = container.iconView.asView()
+ return SplitAnimInitProps(
+ container.thumbnailView,
+ container.thumbnailView.thumbnail,
+ drawable!!,
+ fadeWithThumbnail = true,
+ isStagedTask = true,
+ iconView = container.iconView.asView()
)
}
}
- throw IllegalStateException("Attempting to init split from existing split pair " +
- "without a valid taskIdAttributeContainer")
+ throw IllegalStateException(
+ "Attempting to init split from existing split pair " +
+ "without a valid taskIdAttributeContainer"
+ )
} else {
// Initiating split from overview on fullscreen task TaskView
val taskView = taskViewSupplier.get()
val drawable = getDrawable(taskView.iconView, splitSelectSource)
- return SplitAnimInitProps(taskView.thumbnail, taskView.thumbnail.thumbnail,
- drawable!!, fadeWithThumbnail = true, isStagedTask = true,
- taskView.iconView.asView()
+ return SplitAnimInitProps(
+ taskView.thumbnail,
+ taskView.thumbnail.thumbnail,
+ drawable!!,
+ fadeWithThumbnail = true,
+ isStagedTask = true,
+ taskView.iconView.asView()
)
}
}
/**
- * Returns the drawable that's provided in iconView, however if that
- * is null it falls back to the drawable that's in splitSelectSource.
- * TaskView's icon drawable can be null if the TaskView is scrolled far enough off screen
+ * Returns the drawable that's provided in iconView, however if that is null it falls back to
+ * the drawable that's in splitSelectSource. TaskView's icon drawable can be null if the
+ * TaskView is scrolled far enough off screen
+ *
* @return [Drawable]
*/
- fun getDrawable(iconView: TaskViewIcon, splitSelectSource: SplitSelectSource?) : Drawable? {
+ fun getDrawable(iconView: TaskViewIcon, splitSelectSource: SplitSelectSource?): Drawable? {
if (iconView.drawable == null && splitSelectSource != null) {
return splitSelectSource.drawable
}
@@ -140,21 +155,25 @@
}
/**
- * When selecting first app from split pair, second app's thumbnail remains. This animates
- * the second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying
- * it with [TaskThumbnailView]'s splashView. Adds animations to the provided builder.
- * Note: The app that **was not** selected as the first split app should be the container that's
- * passed through.
+ * When selecting first app from split pair, second app's thumbnail remains. This animates the
+ * second thumbnail by expanding it to take up the full taskViewWidth/Height and overlaying it
+ * with [TaskThumbnailView]'s splashView. Adds animations to the provided builder. Note: The app
+ * that **was not** selected as the first split app should be the container that's passed
+ * through.
*
* @param builder Adds animation to this
* @param taskIdAttributeContainer container of the app that **was not** selected
* @param isPrimaryTaskSplitting if true, task that was split would be top/left in the pair
- * (opposite of that representing [taskIdAttributeContainer])
+ * (opposite of that representing [taskIdAttributeContainer])
*/
- fun addInitialSplitFromPair(taskIdAttributeContainer: TaskIdAttributeContainer,
- builder: PendingAnimation, deviceProfile: DeviceProfile,
- taskViewWidth: Int, taskViewHeight: Int,
- isPrimaryTaskSplitting: Boolean) {
+ fun addInitialSplitFromPair(
+ taskIdAttributeContainer: TaskIdAttributeContainer,
+ builder: PendingAnimation,
+ deviceProfile: DeviceProfile,
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ isPrimaryTaskSplitting: Boolean
+ ) {
val thumbnail = taskIdAttributeContainer.thumbnailView
val iconView: View = taskIdAttributeContainer.iconView.asView()
builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f))
@@ -170,35 +189,42 @@
)
)
builder.add(
- ObjectAnimator.ofFloat(
- iconView.splitTranslationY,
- MULTI_PROPERTY_VALUE,
- 0f
- )
+ ObjectAnimator.ofFloat(iconView.splitTranslationY, MULTI_PROPERTY_VALUE, 0f)
)
}
if (deviceProfile.isLeftRightSplit) {
// Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
val centerThumbnailTranslationX: Float = (taskViewWidth - thumbnail.width) / 2f
val finalScaleX: Float = taskViewWidth.toFloat() / thumbnail.width
- builder.add(ObjectAnimator.ofFloat(thumbnail,
- TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, centerThumbnailTranslationX))
+ builder.add(
+ ObjectAnimator.ofFloat(
+ thumbnail,
+ TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X,
+ centerThumbnailTranslationX
+ )
+ )
if (!enableOverviewIconMenu()) {
// icons are anchored from Gravity.END, so need to use negative translation
val centerIconTranslationX: Float = (taskViewWidth - iconView.width) / 2f
- builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
- -centerIconTranslationX))
+ builder.add(
+ ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, -centerIconTranslationX)
+ )
}
builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_X, finalScaleX))
// Reset other dimensions
// TODO(b/271468547), can't set Y translate to 0, need to account for top space
thumbnail.scaleY = 1f
- val translateYResetVal: Float = if (!isPrimaryTaskSplitting) 0f else
- deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
- builder.add(ObjectAnimator.ofFloat(thumbnail,
+ val translateYResetVal: Float =
+ if (!isPrimaryTaskSplitting) 0f
+ else deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
+ builder.add(
+ ObjectAnimator.ofFloat(
+ thumbnail,
TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y,
- translateYResetVal))
+ translateYResetVal
+ )
+ )
} else {
val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
// Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
@@ -214,16 +240,21 @@
// translations otherwise this asymmetry causes problems..
if (isPrimaryTaskSplitting) {
centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
- centerThumbnailTranslationY += deviceProfile.overviewTaskThumbnailTopMarginPx
- .toFloat()
+ centerThumbnailTranslationY +=
+ deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
} else {
centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
}
val finalScaleY: Float = thumbnailSize.toFloat() / thumbnail.height
- builder.add(ObjectAnimator.ofFloat(thumbnail,
- TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, centerThumbnailTranslationY))
+ builder.add(
+ ObjectAnimator.ofFloat(
+ thumbnail,
+ TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y,
+ centerThumbnailTranslationY
+ )
+ )
- if (!enableOverviewIconMenu()) {
+ if (!enableOverviewIconMenu()) {
// icons are anchored from Gravity.END, so need to use negative translation
builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, 0f))
}
@@ -231,8 +262,9 @@
// Reset other dimensions
thumbnail.scaleX = 1f
- builder.add(ObjectAnimator.ofFloat(thumbnail,
- TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f))
+ builder.add(
+ ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, 0f)
+ )
}
}
@@ -250,69 +282,94 @@
* Returns [AnimatorSet] which slides initial split placeholder view offscreen and logs an event
* for why split is being dismissed
*/
- fun createPlaceholderDismissAnim(launcher: StatefulActivity<*>,
- splitDismissEvent: EventEnum,
- duration: Long?) : AnimatorSet {
+ fun createPlaceholderDismissAnim(
+ launcher: StatefulActivity<*>,
+ splitDismissEvent: EventEnum,
+ duration: Long?
+ ): AnimatorSet {
val animatorSet = AnimatorSet()
duration?.let { animatorSet.duration = it }
- val recentsView : RecentsView<*, *> = launcher.getOverviewPanel()
- val floatingTask: FloatingTaskView = splitSelectStateController.firstFloatingTaskView
- ?: return animatorSet
+ val recentsView: RecentsView<*, *> = launcher.getOverviewPanel()
+ val floatingTask: FloatingTaskView =
+ splitSelectStateController.firstFloatingTaskView ?: return animatorSet
// We are in split selection state currently, transitioning to another state
val dragLayer: BaseDragLayer<*> = launcher.dragLayer
val onScreenRectF = RectF()
- Utilities.getBoundsForViewInDragLayer(dragLayer, floatingTask,
- Rect(0, 0, floatingTask.width, floatingTask.height),
- false, null, onScreenRectF)
+ Utilities.getBoundsForViewInDragLayer(
+ dragLayer,
+ floatingTask,
+ Rect(0, 0, floatingTask.width, floatingTask.height),
+ false,
+ null,
+ onScreenRectF
+ )
// Get the part of the floatingTask that intersects with the DragLayer (i.e. the
// on-screen portion)
onScreenRectF.intersect(
- dragLayer.left.toFloat(),
- dragLayer.top.toFloat(),
- dragLayer.right.toFloat(),
- dragLayer.bottom
- .toFloat()
+ dragLayer.left.toFloat(),
+ dragLayer.top.toFloat(),
+ dragLayer.right.toFloat(),
+ dragLayer.bottom.toFloat()
)
- animatorSet.play(ObjectAnimator.ofFloat(floatingTask,
+ animatorSet.play(
+ ObjectAnimator.ofFloat(
+ floatingTask,
FloatingTaskView.PRIMARY_TRANSLATE_OFFSCREEN,
- recentsView.pagedOrientationHandler
- .getFloatingTaskOffscreenTranslationTarget(
- floatingTask,
- onScreenRectF,
- floatingTask.stagePosition,
- launcher.deviceProfile
- )))
- animatorSet.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- splitSelectStateController.resetState()
- safeRemoveViewFromDragLayer(launcher,
- splitSelectStateController.splitInstructionsView)
+ recentsView.pagedOrientationHandler.getFloatingTaskOffscreenTranslationTarget(
+ floatingTask,
+ onScreenRectF,
+ floatingTask.stagePosition,
+ launcher.deviceProfile
+ )
+ )
+ )
+ animatorSet.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ splitSelectStateController.resetState()
+ safeRemoveViewFromDragLayer(
+ launcher,
+ splitSelectStateController.splitInstructionsView
+ )
+ }
}
- })
+ )
splitSelectStateController.logExitReason(splitDismissEvent)
return animatorSet
}
/**
- * Returns a [PendingAnimation] to animate in the chip to instruct a user to select a second
- * app for splitscreen
+ * Returns a [PendingAnimation] to animate in the chip to instruct a user to select a second app
+ * for splitscreen
*/
- fun getShowSplitInstructionsAnim(launcher: StatefulActivity<*>) : PendingAnimation {
+ fun getShowSplitInstructionsAnim(launcher: StatefulActivity<*>): PendingAnimation {
safeRemoveViewFromDragLayer(launcher, splitSelectStateController.splitInstructionsView)
val splitInstructionsView = SplitInstructionsView.getSplitInstructionsView(launcher)
splitSelectStateController.splitInstructionsView = splitInstructionsView
val timings = AnimUtils.getDeviceOverviewToSplitTimings(launcher.deviceProfile.isTablet)
val anim = PendingAnimation(100 /*duration */)
splitInstructionsView.alpha = 0f
- anim.setViewAlpha(splitInstructionsView, 1f,
- Interpolators.clampToProgress(Interpolators.LINEAR,
- timings.instructionsContainerFadeInStartOffset,
- timings.instructionsContainerFadeInEndOffset))
- anim.addFloat(splitInstructionsView, SplitInstructionsView.UNFOLD, 0.1f, 1f,
- Interpolators.clampToProgress(Interpolators.EMPHASIZED_DECELERATE,
- timings.instructionsUnfoldStartOffset,
- timings.instructionsUnfoldEndOffset))
+ anim.setViewAlpha(
+ splitInstructionsView,
+ 1f,
+ Interpolators.clampToProgress(
+ Interpolators.LINEAR,
+ timings.instructionsContainerFadeInStartOffset,
+ timings.instructionsContainerFadeInEndOffset
+ )
+ )
+ anim.addFloat(
+ splitInstructionsView,
+ SplitInstructionsView.UNFOLD,
+ 0.1f,
+ 1f,
+ Interpolators.clampToProgress(
+ Interpolators.EMPHASIZED_DECELERATE,
+ timings.instructionsUnfoldStartOffset,
+ timings.instructionsUnfoldEndOffset
+ )
+ )
return anim
}
@@ -323,15 +380,20 @@
/**
* Animates the first placeholder view to fullscreen and launches its task.
+ *
* TODO(b/276361926): Remove the [resetCallback] option once contextual launches
*/
- fun playAnimPlaceholderToFullscreen(launcher: StatefulActivity<*>, view: View,
- resetCallback: Optional<Runnable>) {
+ fun playAnimPlaceholderToFullscreen(
+ launcher: StatefulActivity<*>,
+ view: View,
+ resetCallback: Optional<Runnable>
+ ) {
val stagedTaskView = view as FloatingTaskView
val isTablet: Boolean = launcher.deviceProfile.isTablet
- val duration = if (isTablet) SplitAnimationTimings.TABLET_CONFIRM_DURATION else
- SplitAnimationTimings.PHONE_CONFIRM_DURATION
+ val duration =
+ if (isTablet) SplitAnimationTimings.TABLET_CONFIRM_DURATION
+ else SplitAnimationTimings.PHONE_CONFIRM_DURATION
val pendingAnimation = PendingAnimation(duration.toLong())
val firstTaskStartingBounds = Rect()
val firstTaskEndingBounds = Rect()
@@ -341,11 +403,12 @@
splitSelectStateController.setLaunchingFirstAppFullscreen()
stagedTaskView.addConfirmAnimation(
- pendingAnimation,
- RectF(firstTaskStartingBounds),
- firstTaskEndingBounds,
- false /* fadeWithThumbnail */,
- true /* isStagedTask */)
+ pendingAnimation,
+ RectF(firstTaskStartingBounds),
+ firstTaskEndingBounds,
+ false /* fadeWithThumbnail */,
+ true /* isStagedTask */
+ )
pendingAnimation.addEndListener {
splitSelectStateController.launchInitialAppFullscreen {
@@ -490,8 +553,8 @@
* When the user taps an app pair icon to launch split, this will play the tasks' launch
* animation from the position of the icon.
*
- * To find the root shell leash that we want to fade in, we do the following:
- * The Changes we receive in transitionInfo are structured like this
+ * To find the root shell leash that we want to fade in, we do the following: The Changes we
+ * receive in transitionInfo are structured like this
*
* Root (grandparent)
* |
@@ -503,9 +566,9 @@
* |
* --> App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW)
*
- * We want to animate the Root (grandparent) so that it affects both apps and the divider.
- * To do this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the
- * left-side ones, for simplicity) and traverse the tree until we find the grandparent.
+ * We want to animate the Root (grandparent) so that it affects both apps and the divider. To do
+ * this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the left-side ones,
+ * for simplicity) and traverse the tree until we find the grandparent.
*
* This function is only called when we are animating the app pair in from scratch. It is NOT
* called when we are animating in from an existing visible TaskView tile or an app that is
@@ -544,8 +607,10 @@
// TODO (b/316490565): Replace this logic when SplitBounds is available to
// startAnimation() and we can know the precise taskIds of launching tasks.
// Find a change that has WINDOWING_MODE_MULTI_WINDOW.
- if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW &&
- (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)) {
+ if (
+ taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW &&
+ (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)
+ ) {
// Check if it is a left/top app.
val isLeftTopApp =
(dp.isLeftRightSplit && change.endAbsBounds.left == 0) ||
@@ -614,8 +679,6 @@
FloatProp(
floatingView.startingPosition.left,
dp.widthPx / 2f - floatingView.startingPosition.width() / 2f,
- 0f /* delay */,
- timings.getDuration().toFloat(),
Interpolators.clampToProgress(
timings.getStagedRectXInterpolator(),
timings.stagedRectSlideStartOffset,
@@ -626,8 +689,6 @@
FloatProp(
floatingView.startingPosition.top,
dp.heightPx / 2f - floatingView.startingPosition.height() / 2f,
- 0f /* delay */,
- timings.getDuration().toFloat(),
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
@@ -638,8 +699,6 @@
FloatProp(
1f /* start */,
dp.widthPx / floatingView.startingPosition.width(),
- 0f /* delay */,
- timings.getDuration().toFloat(),
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
@@ -650,8 +709,6 @@
FloatProp(
1f /* start */,
dp.heightPx / floatingView.startingPosition.height(),
- 0f /* delay */,
- timings.getDuration().toFloat(),
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
diff --git a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
index fdc8f1f..121d8ed 100644
--- a/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
+++ b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
@@ -22,6 +22,7 @@
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_EDU_SHOWN;
+import static com.android.quickstep.util.AnimUtils.clampToDuration;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -187,10 +188,14 @@
intro.setInterpolator(LINEAR);
intro.setDuration(introDuration);
intro.addUpdateListener((new MultiValueUpdateListener() {
- FloatProp mCircleAlpha = new FloatProp(0, 255, 0, firstPart, LINEAR);
- FloatProp mCircleScale = new FloatProp(2f, 1f, 0, firstPart, OVERSHOOT_1_7);
- FloatProp mDeltaY = new FloatProp(0, transY, firstPart, secondPart, FAST_OUT_SLOW_IN);
- FloatProp mGradientAlpha = new FloatProp(0, 255, firstPart, secondPart * 0.3f, LINEAR);
+ FloatProp mCircleAlpha = new FloatProp(0, 255,
+ clampToDuration(LINEAR, 0, firstPart, introDuration));
+ FloatProp mCircleScale = new FloatProp(2f, 1f,
+ clampToDuration(OVERSHOOT_1_7, 0, firstPart, introDuration));
+ FloatProp mDeltaY = new FloatProp(0, transY,
+ clampToDuration(FAST_OUT_SLOW_IN, firstPart, secondPart, introDuration));
+ FloatProp mGradientAlpha = new FloatProp(0, 255,
+ clampToDuration(LINEAR, firstPart, secondPart * 0.3f, introDuration));
@Override
public void onUpdate(float progress, boolean initOnly) {
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
index 12a073f..18922a6 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -328,20 +328,20 @@
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
// SplitPlaceholderView: rectangle translates and stretches to new position
- final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration,
+ final FloatProp mDx = new FloatProp(0, prop.dX,
clampToProgress(timings.getStagedRectXInterpolator(),
timings.getStagedRectSlideStartOffset(),
timings.getStagedRectSlideEndOffset()));
- final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration,
+ final FloatProp mDy = new FloatProp(0, prop.dY,
clampToProgress(timings.getStagedRectYInterpolator(),
timings.getStagedRectSlideStartOffset(),
timings.getStagedRectSlideEndOffset()));
- final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX, 0,
- animDuration, clampToProgress(timings.getStagedRectScaleXInterpolator(),
+ final FloatProp mTaskViewScaleX = new FloatProp(1f, prop.finalTaskViewScaleX,
+ clampToProgress(timings.getStagedRectScaleXInterpolator(),
timings.getStagedRectSlideStartOffset(),
timings.getStagedRectSlideEndOffset()));
- final FloatProp mTaskViewScaleY = new FloatProp(1f, prop.finalTaskViewScaleY, 0,
- animDuration, clampToProgress(timings.getStagedRectScaleYInterpolator(),
+ final FloatProp mTaskViewScaleY = new FloatProp(1f, prop.finalTaskViewScaleY,
+ clampToProgress(timings.getStagedRectScaleYInterpolator(),
timings.getStagedRectSlideStartOffset(),
timings.getStagedRectSlideEndOffset()));
@Override
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RobolectricTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/RobolectricTest.kt
new file mode 100644
index 0000000..0694aec
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RobolectricTest.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.quickstep
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RobolectricTest {
+ @Test
+ fun test1() {
+ val actual = 1 + 1
+ assertThat(actual).isEqualTo(2)
+ }
+}
diff --git a/quickstep/tests/multivalentTestsForDevice b/quickstep/tests/multivalentTestsForDevice
new file mode 120000
index 0000000..fa0fabf
--- /dev/null
+++ b/quickstep/tests/multivalentTestsForDevice
@@ -0,0 +1 @@
+./multivalentTests
\ No newline at end of file
diff --git a/quickstep/tests/multivalentTestsForDeviceless b/quickstep/tests/multivalentTestsForDeviceless
new file mode 120000
index 0000000..fa0fabf
--- /dev/null
+++ b/quickstep/tests/multivalentTestsForDeviceless
@@ -0,0 +1 @@
+./multivalentTests
\ No newline at end of file
diff --git a/res/drawable/ic_private_space_with_background.xml b/res/drawable/ic_private_space_with_background.xml
index 59a33dd..da199f0 100644
--- a/res/drawable/ic_private_space_with_background.xml
+++ b/res/drawable/ic_private_space_with_background.xml
@@ -12,14 +12,15 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:viewportWidth="48"
android:viewportHeight="48"
android:width="48dp"
android:height="48dp">
<path
android:pathData="M48 24A24 24 0 0 1 0 24A24 24 0 0 1 48 24Z"
- android:fillColor="?attr/materialColorOutlineVariant" />
+ android:fillColor="?androidprv:attr/materialColorSurfaceContainerLowest" />
<path
android:pathData="M33.3333 14.6667V33.3333H14.6667V14.6667H33.3333ZM33.3333 12H14.6667C13.2 12 12 13.2 12 14.6667V33.3333C12 34.8 13.2 36 14.6667 36H33.3333C34.8 36 36 34.8 36 33.3333V14.6667C36 13.2 34.8 12 33.3333 12Z"
android:fillColor="?attr/materialColorOnSurface" />
diff --git a/res/layout/private_space_header.xml b/res/layout/private_space_header.xml
index 0b0af87..2b5db48 100644
--- a/res/layout/private_space_header.xml
+++ b/res/layout/private_space_header.xml
@@ -87,7 +87,8 @@
<TextView
android:id="@+id/ps_container_header"
android:layout_width="wrap_content"
- android:layout_height="@dimen/ps_header_text_height"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/ps_header_text_height"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/settingsAndLockGroup"
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c1ebbe5..7267e63 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1483,11 +1483,10 @@
if (showPendingWidget) {
launcherInfo.restoreStatus = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
PendingAppWidgetHostView pendingAppWidgetHostView = new PendingAppWidgetHostView(
- this, mAppWidgetHolder, launcherInfo, appWidgetInfo);
- pendingAppWidgetHostView.setPreviewBitmap(widgetPreviewBitmap);
+ this, mAppWidgetHolder, launcherInfo, appWidgetInfo, widgetPreviewBitmap);
hostView = pendingAppWidgetHostView;
} else if (hostView instanceof PendingAppWidgetHostView) {
- ((PendingAppWidgetHostView) hostView).setPreviewBitmap(null);
+ ((PendingAppWidgetHostView) hostView).setPreviewBitmapAndUpdateBackground(null);
// User has selected a widget config and exited the config activity, we can trigger
// re-inflation of PendingAppWidgetHostView to replace it with
// LauncherAppWidgetHostView in workspace.
@@ -1822,7 +1821,9 @@
if (isActivityStarted) {
DragView dropView = getDragLayer().clearAnimatedView();
if (dropView != null && dropView.containsAppWidgetHostView()) {
- widgetPreviewBitmap = getBitmapFromView(dropView.getContentView());
+ // Extracting Bitmap from dropView instead of its content view produces the correct
+ // bitmap.
+ widgetPreviewBitmap = getBitmapFromView(dropView);
}
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 1fede56..1c23644 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -826,7 +826,9 @@
// or right edge for RTL.
final int pageScroll =
mIsRtl ? childPrimaryEnd - scrollOffsetEnd : childStart - scrollOffsetStart;
- if (outPageScrolls[i] != pageScroll) {
+ // If there's more than one panel, only update scroll on leftmost panel.
+ if (outPageScrolls[i] != pageScroll
+ && (panelCount <= 1 || i == getLeftmostVisiblePageForIndex(i))) {
pageScrollChanged = true;
outPageScrolls[i] = pageScroll;
}
@@ -842,7 +844,7 @@
if (panelCount > 1) {
for (int i = 0; i < childCount; i++) {
- // In case we have multiple panels, always use left most panel's page scroll for all
+ // In case we have multiple panels, always use leftmost panel's page scroll for all
// panels on the screen.
int adjustedScroll = outPageScrolls[getLeftmostVisiblePageForIndex(i)];
if (outPageScrolls[i] != adjustedScroll) {
diff --git a/src/com/android/launcher3/RectUtils.kt b/src/com/android/launcher3/RectUtils.kt
new file mode 100644
index 0000000..68d2eaf
--- /dev/null
+++ b/src/com/android/launcher3/RectUtils.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.graphics.Rect
+
+/**
+ * Fit [this] into [targetRect] with letter boxing. After calling this method, [this] will be
+ * modified to be letter boxed.
+ *
+ * @param targetRect target [Rect] that [this] should be fitted into
+ */
+fun Rect.letterBox(targetRect: Rect) {
+ letterBox(targetRect, this)
+}
+
+/**
+ * Fit [this] into [targetRect] with letter boxing. After calling this method, [resultRect] will be
+ * modified to be letter boxed.
+ *
+ * @param targetRect target [Rect] that [this] should be fitted into
+ * @param resultRect the letter boxed [Rect]
+ */
+fun Rect.letterBox(targetRect: Rect, resultRect: Rect) {
+ val widthRatio: Float = 1f * targetRect.width() / width()
+ val heightRatio: Float = 1f * targetRect.height() / height()
+ if (widthRatio < heightRatio) {
+ val scaledHeight: Int = (widthRatio * height()).toInt()
+ val verticalPadding: Int = (targetRect.height() - scaledHeight) / 2
+ resultRect.set(
+ targetRect.left,
+ targetRect.top + verticalPadding,
+ targetRect.right,
+ targetRect.bottom - verticalPadding
+ )
+ } else {
+ val scaledWidth: Int = (heightRatio * width()).toInt()
+ val horizontalPadding: Int = (targetRect.width() - scaledWidth) / 2
+ resultRect.set(
+ targetRect.left + horizontalPadding,
+ targetRect.top,
+ targetRect.right - horizontalPadding,
+ targetRect.bottom
+ )
+ }
+}
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 6acfcd0..01ea9fb 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -1372,7 +1372,8 @@
}
@Override
- public void drawOnScrimWithScale(Canvas canvas, float scale) {
+ public void drawOnScrimWithScaleAndBottomOffset(
+ Canvas canvas, float scale, @Px int bottomOffsetPx) {
final View panel = mBottomSheetBackground;
final boolean hasBottomSheet = panel.getVisibility() == VISIBLE;
final float translationY = ((View) panel.getParent()).getTranslationY();
@@ -1384,6 +1385,7 @@
final float topWithScale = topNoScale + verticalScaleOffset;
final float leftWithScale = panel.getLeft() + horizontalScaleOffset;
final float rightWithScale = panel.getRight() - horizontalScaleOffset;
+ final float bottomWithOffset = panel.getBottom() + bottomOffsetPx;
// Draw full background panel for tablets.
if (hasBottomSheet) {
mHeaderPaint.setColor(mBottomSheetBackgroundColor);
@@ -1393,7 +1395,7 @@
leftWithScale,
topWithScale,
rightWithScale,
- panel.getBottom());
+ bottomWithOffset);
mTmpPath.reset();
mTmpPath.addRoundRect(mTmpRectF, mBottomSheetCornerRadii, Direction.CW);
canvas.drawPath(mTmpPath, mHeaderPaint);
diff --git a/src/com/android/launcher3/apppairs/AppPairIcon.java b/src/com/android/launcher3/apppairs/AppPairIcon.java
index 48d0fbd..13fefc4 100644
--- a/src/com/android/launcher3/apppairs/AppPairIcon.java
+++ b/src/com/android/launcher3/apppairs/AppPairIcon.java
@@ -17,7 +17,6 @@
package com.android.launcher3.apppairs;
import android.content.Context;
-import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
@@ -94,16 +93,17 @@
icon.setOnClickListener(activity.getItemOnClickListener());
icon.mInfo = appPairInfo;
+ // TODO (b/326664798): Delete this check, instead check at launcher load time
if (icon.mInfo.contents.size() != 2) {
Log.wtf(TAG, "AppPair contents not 2, size: " + icon.mInfo.contents.size());
return icon;
}
- icon.checkScreenSize();
-
// Set up icon drawable area
icon.mIconGraphic = icon.findViewById(R.id.app_pair_icon_graphic);
- icon.mIconGraphic.init(activity.getDeviceProfile(), icon);
+ icon.mIconGraphic.init(activity, icon);
+
+ icon.checkDisabledState();
// Set up app pair title
icon.mAppPairName = icon.findViewById(R.id.app_pair_icon_name);
@@ -183,23 +183,20 @@
}
/**
- * Checks if the app pair is launchable in the current device configuration.
- *
+ * Updates the "disabled" state of the app pair in the current device configuration.
* App pairs can be "disabled" in two ways:
* 1) One of the member WorkspaceItemInfos is disabled (i.e. the app software itself is paused
- * by the user or can't be launched).
+ * by the user or can't be launched for some other reason).
* 2) This specific instance of an app pair can't be launched due to screen size requirements.
- *
- * This method checks and updates #2. Both #1 and #2 are checked when app pairs are drawn
- * {@link AppPairIconGraphic#dispatchDraw(Canvas)} or clicked on
- * {@link com.android.launcher3.touch.ItemClickHandler#onClickAppPairIcon(View)}
*/
- public void checkScreenSize() {
+ public void checkDisabledState() {
DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile();
// If user is on a small screen, we can't launch if either of the apps is non-resizeable
mIsLaunchableAtScreenSize =
dp.isTablet || getInfo().contents.stream().noneMatch(
wii -> wii.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE));
+ // Call applyIcons to check and update icons
+ mIconGraphic.applyIcons();
}
/**
@@ -209,7 +206,7 @@
// If either of the app pair icons return true on the predicate (i.e. in the list of
// updated apps), redraw the icon graphic (icon background and both icons).
if (getInfo().contents.stream().anyMatch(itemCheck)) {
- checkScreenSize();
+ checkDisabledState();
mIconGraphic.invalidate();
}
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIconBackground.java b/src/com/android/launcher3/apppairs/AppPairIconBackground.java
index b5011f1..187541f 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconBackground.java
+++ b/src/com/android/launcher3/apppairs/AppPairIconBackground.java
@@ -162,6 +162,6 @@
@Override
public void setColorFilter(ColorFilter colorFilter) {
- // Required by Drawable but not used.
+ mBackgroundPaint.setColorFilter(colorFilter);
}
}
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
index 365edf8..777831b 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -25,17 +25,19 @@
import android.view.Gravity
import android.widget.FrameLayout
import com.android.launcher3.DeviceProfile
+import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener
import com.android.launcher3.icons.BitmapInfo
-import com.android.launcher3.icons.PlaceHolderIconDrawable
-import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.icons.FastBitmapDrawable
+import com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter
import com.android.launcher3.util.Themes
+import com.android.launcher3.views.ActivityContext
/**
* A FrameLayout marking the area on an [AppPairIcon] where the visual icon will be drawn. One of
* two child UI elements on an [AppPairIcon], along with a BubbleTextView holding the text title.
*/
class AppPairIconGraphic @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
- FrameLayout(context, attrs) {
+ FrameLayout(context, attrs), OnDeviceProfileChangeListener {
private val TAG = "AppPairIconGraphic"
companion object {
@@ -46,9 +48,6 @@
private const val CENTER_CHANNEL_SCALE = 1 / 30f
private const val BIG_RADIUS_SCALE = 1 / 5f
private const val SMALL_RADIUS_SCALE = 1 / 15f
- // Disabled alpha is 38%, or 97/255
- private const val DISABLED_ALPHA = 97
- private const val ENABLED_ALPHA = 255
}
// App pair icons are slightly smaller than regular icons, so we pad the icon by this much on
@@ -69,14 +68,17 @@
// The app pairs icon appears differently in portrait and landscape.
var isLeftRightSplit = false
+ private lateinit var activityContext: ActivityContext
private lateinit var parentIcon: AppPairIcon
private lateinit var appPairBackground: Drawable
- private var appIcon1: Drawable? = null
- private var appIcon2: Drawable? = null
+ private lateinit var appIcon1: FastBitmapDrawable
+ private lateinit var appIcon2: FastBitmapDrawable
- fun init(grid: DeviceProfile, icon: AppPairIcon) {
+ fun init(activity: ActivityContext, icon: AppPairIcon) {
+ activityContext = activity
+
// Calculate device-specific measurements
- val defaultIconSize = grid.iconSizePx
+ val defaultIconSize = activity.deviceProfile.iconSizePx
outerPadding = OUTER_PADDING_SCALE * defaultIconSize
innerPadding = INNER_PADDING_SCALE * defaultIconSize
backgroundSize = defaultIconSize - outerPadding * 2
@@ -84,12 +86,13 @@
centerChannelSize = CENTER_CHANNEL_SCALE * defaultIconSize
bigRadius = BIG_RADIUS_SCALE * defaultIconSize
smallRadius = SMALL_RADIUS_SCALE * defaultIconSize
- isLeftRightSplit = grid.isLeftRightSplit
parentIcon = icon
+ updateOrientation()
appPairBackground = AppPairIconBackground(context, this)
appPairBackground.setBounds(0, 0, backgroundSize.toInt(), backgroundSize.toInt())
- applyIcons(parentIcon.info.contents)
+
+ applyIcons()
// Center the drawable area in the larger icon canvas
val lp: LayoutParams = layoutParams as LayoutParams
@@ -100,27 +103,52 @@
layoutParams = lp
}
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ activityContext.addOnDeviceProfileChangeListener(this)
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ activityContext.removeOnDeviceProfileChangeListener(this)
+ }
+
+ /** Checks the device orientation and updates isLeftRightSplit accordingly. */
+ private fun updateOrientation() {
+ val activity: ActivityContext = ActivityContext.lookupContext(context)
+ isLeftRightSplit = activity.deviceProfile.isLeftRightSplit
+ }
+
+ /** When device profile changes, update orientation */
+ override fun onDeviceProfileChanged(dp: DeviceProfile?) {
+ updateOrientation()
+ invalidate()
+ }
+
/** Sets up app pair member icons for drawing. */
- private fun applyIcons(contents: ArrayList<WorkspaceItemInfo>) {
- // App pair should always contain 2 members; if not 2, return to avoid a crash loop
- if (contents.size != 2) {
- Log.wtf(TAG, "AppPair contents not 2, size: " + contents.size, Throwable())
+ fun applyIcons() {
+ val apps = parentIcon.info.contents
+
+ // TODO (b/326664798): Delete this check, instead check at launcher load time
+ if (apps.size != 2) {
+ Log.wtf(TAG, "AppPair contents not 2, size: " + apps.size, Throwable())
return
}
// Generate new icons, using themed flag if needed
val flags = if (Themes.isThemedIconEnabled(context)) BitmapInfo.FLAG_THEMED else 0
- val newIcon1 = parentIcon.info.contents[0].newIcon(context, flags)
- val newIcon2 = parentIcon.info.contents[1].newIcon(context, flags)
+ appIcon1 = apps[0].newIcon(context, flags)
+ appIcon2 = apps[1].newIcon(context, flags)
+ appIcon1.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
+ appIcon2.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
- // If app icons did not draw fully last time, animate to full icon
- (appIcon1 as? PlaceHolderIconDrawable)?.animateIconUpdate(newIcon1)
- (appIcon2 as? PlaceHolderIconDrawable)?.animateIconUpdate(newIcon2)
+ // Check disabled state
+ val shouldDrawAsDisabled =
+ parentIcon.info.isDisabled || !parentIcon.isLaunchableAtScreenSize
- appIcon1 = newIcon1
- appIcon2 = newIcon2
- appIcon1?.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
- appIcon2?.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
+ appPairBackground.colorFilter = if (shouldDrawAsDisabled) getDisabledColorFilter() else null
+ appIcon1.setIsDisabled(shouldDrawAsDisabled)
+ appIcon2.setIsDisabled(shouldDrawAsDisabled)
}
/** Gets this icon graphic's bounds, with respect to the parent icon's coordinate system. */
@@ -137,17 +165,9 @@
override fun dispatchDraw(canvas: Canvas) {
super.dispatchDraw(canvas)
- val drawAlpha =
- if (!parentIcon.isLaunchableAtScreenSize || parentIcon.info.isDisabled) DISABLED_ALPHA
- else ENABLED_ALPHA
-
// Draw background
- appPairBackground.alpha = drawAlpha
appPairBackground.draw(canvas)
- // Make sure icons are loaded and fresh
- applyIcons(parentIcon.info.contents)
-
// Draw first icon
canvas.save()
// The app icons are placed differently depending on device orientation.
@@ -156,8 +176,8 @@
} else {
canvas.translate(width / 2f - memberIconSize / 2f, innerPadding)
}
- appIcon1?.alpha = drawAlpha
- appIcon1?.draw(canvas)
+
+ appIcon1.draw(canvas)
canvas.restore()
// Draw second icon
@@ -174,8 +194,8 @@
height - (innerPadding + memberIconSize)
)
}
- appIcon2?.alpha = drawAlpha
- appIcon2?.draw(canvas)
+
+ appIcon2.draw(canvas)
canvas.restore()
}
}
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 051fb6f..db27832 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -87,6 +87,7 @@
private final int mHeight;
private String mGridName;
+ private final int mDisplayId;
private final Display mDisplay;
private final WallpaperColors mWallpaperColors;
private final RunnableList mOnDestroyCallbacks = new RunnableList();
@@ -110,8 +111,12 @@
mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
mWidth = bundle.getInt(KEY_VIEW_WIDTH);
mHeight = bundle.getInt(KEY_VIEW_HEIGHT);
+ mDisplayId = bundle.getInt(KEY_DISPLAY_ID);
mDisplay = context.getSystemService(DisplayManager.class)
- .getDisplay(bundle.getInt(KEY_DISPLAY_ID));
+ .getDisplay(mDisplayId);
+ if (mDisplay == null) {
+ throw new IllegalArgumentException("Display ID does not match any displays.");
+ }
mSurfaceControlViewHost = MAIN_EXECUTOR.submit(() ->
new SurfaceControlViewHost(mContext, context.getSystemService(DisplayManager.class)
@@ -121,7 +126,7 @@
}
public int getDisplayId() {
- return mDisplay.getDisplayId();
+ return mDisplayId;
}
public IBinder getHostToken() {
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index ca80c51..f6c4984 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -26,6 +26,7 @@
import android.view.View;
import androidx.annotation.NonNull;
+import androidx.annotation.Px;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.BaseActivity;
@@ -187,9 +188,19 @@
* A Utility interface allowing for other surfaces to draw on ScrimView
*/
public interface ScrimDrawingController {
- /**
- * Called inside ScrimView#OnDraw
- */
- void drawOnScrimWithScale(Canvas canvas, float scale);
+
+ /** Draw scrim view on canvas with scale. */
+ default void drawOnScrimWithScale(Canvas canvas, float scale) {
+ drawOnScrimWithScaleAndBottomOffset(canvas, scale, 0);
+ }
+
+ /** Draw scrim view on canvas with bottomOffset. */
+ default void drawOnScrimWithBottomOffset(Canvas canvas, @Px int bottomOffsetPx) {
+ drawOnScrimWithScaleAndBottomOffset(canvas, 1f, bottomOffsetPx);
+ }
+
+ /** Draw scrim view on canvas with scale and bottomOffset. */
+ void drawOnScrimWithScaleAndBottomOffset(
+ Canvas canvas, float scale, @Px int bottomOffsetPx);
}
}
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 86400ba..7f9a1fc 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -53,6 +53,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
+import com.android.launcher3.RectUtilsKt;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -76,6 +77,10 @@
private final Rect mRect = new Rect();
+ private final Rect mPreviewBitmapRect = new Rect();
+ private final Rect mCanvasRect = new Rect();
+ private final Rect mLetterBoxedPreviewBitmapRect = new Rect();
+
private final LauncherWidgetHolder mWidgetHolder;
private final LauncherAppWidgetProviderInfo mAppwidget;
private final LauncherAppWidgetInfo mInfo;
@@ -103,9 +108,14 @@
public PendingAppWidgetHostView(Context context, LauncherWidgetHolder widgetHolder,
LauncherAppWidgetInfo info, @Nullable LauncherAppWidgetProviderInfo appWidget) {
- this(context, widgetHolder, info, appWidget,
- context.getResources().getText(R.string.gadget_complete_setup_text));
+ this(context, widgetHolder, info, appWidget, null);
+ }
+ public PendingAppWidgetHostView(Context context, LauncherWidgetHolder widgetHolder,
+ LauncherAppWidgetInfo info, @Nullable LauncherAppWidgetProviderInfo appWidget,
+ @Nullable Bitmap previewBitmap) {
+ this(context, widgetHolder, info, appWidget,
+ context.getResources().getText(R.string.gadget_complete_setup_text), previewBitmap);
super.updateAppWidget(null);
setOnClickListener(mActivityContext.getItemOnClickListener());
@@ -123,7 +133,7 @@
Context context, LauncherWidgetHolder widgetHolder,
int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
this(context, widgetHolder, new LauncherAppWidgetInfo(appWidgetId, appWidget.provider),
- appWidget, appWidget.label);
+ appWidget, appWidget.label, null);
getBackground().mutate().setAlpha(DEFERRED_ALPHA);
mCenterDrawable = new ColorDrawable(Color.TRANSPARENT);
@@ -132,8 +142,12 @@
mIsDeferredWidget = true;
}
- /** Set {@link Bitmap} of widget preview. */
- public void setPreviewBitmap(@Nullable Bitmap previewBitmap) {
+ /**
+ * Set {@link Bitmap} of widget preview and update background drawable. When showing preview
+ * bitmap, we shouldn't draw background.
+ */
+ public void setPreviewBitmapAndUpdateBackground(@Nullable Bitmap previewBitmap) {
+ setBackgroundResource(previewBitmap != null ? 0 : R.drawable.pending_widget_bg);
if (this.mPreviewBitmap == previewBitmap) {
return;
}
@@ -143,7 +157,8 @@
private PendingAppWidgetHostView(Context context,
LauncherWidgetHolder widgetHolder, LauncherAppWidgetInfo info,
- LauncherAppWidgetProviderInfo appwidget, CharSequence label) {
+ LauncherAppWidgetProviderInfo appwidget, CharSequence label,
+ @Nullable Bitmap previewBitmap) {
super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme));
mWidgetHolder = widgetHolder;
mAppwidget = appwidget;
@@ -161,7 +176,7 @@
mPreviewPaint = new Paint(ANTI_ALIAS_FLAG | DITHER_FLAG | FILTER_BITMAP_FLAG);
setWillNotDraw(false);
- setBackgroundResource(R.drawable.pending_widget_bg);
+ setPreviewBitmapAndUpdateBackground(previewBitmap);
}
@Override
@@ -440,7 +455,12 @@
protected void onDraw(Canvas canvas) {
if (mPreviewBitmap != null
&& (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0) {
- canvas.drawBitmap(mPreviewBitmap, 0, 0, mPreviewPaint);
+ mPreviewBitmapRect.set(0, 0, mPreviewBitmap.getWidth(), mPreviewBitmap.getHeight());
+ mCanvasRect.set(0, 0, getWidth(), getHeight());
+
+ RectUtilsKt.letterBox(mPreviewBitmapRect, mCanvasRect, mLetterBoxedPreviewBitmapRect);
+ canvas.drawBitmap(mPreviewBitmap, mPreviewBitmapRect, mLetterBoxedPreviewBitmapRect,
+ mPreviewPaint);
return;
}
if (mCenterDrawable == null) {
@@ -463,7 +483,6 @@
mSetupTextLayout.draw(canvas);
canvas.restore();
}
-
}
/**
diff --git a/tests/Launcher3Tests.xml b/tests/Launcher3Tests.xml
index bcbe343..0b74663 100644
--- a/tests/Launcher3Tests.xml
+++ b/tests/Launcher3Tests.xml
@@ -13,14 +13,18 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- This test config file is auto-generated. -->
+
<configuration description="Runs Launcher3 tests.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<option name="set-test-harness" value="true" />
- <option name="run-command" value="am force-stop com.android.launcher3" />
+
+ <option name="run-command" value="svc nfc disable" />
+ <option name="run-command" value="settings put global ble_scan_always_enabled 0" />
+ <option name="run-command" value="svc bluetooth disable" />
+
<option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher" />
<option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher.out_of_proc_tests" />
<option name="run-command" value="pm uninstall com.google.android.apps.nexuslauncher.tests" />
diff --git a/tests/src/com/android/launcher3/RectUtilsTest.kt b/tests/src/com/android/launcher3/RectUtilsTest.kt
new file mode 100644
index 0000000..f0d22eb
--- /dev/null
+++ b/tests/src/com/android/launcher3/RectUtilsTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 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
+
+import android.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RectUtilsTest {
+
+ private val srcRect = Rect()
+ private val destRect = Rect()
+ private val letterBoxedRect = Rect()
+
+ @Test
+ fun letterBoxSelf_toSameRect_noScale() {
+ srcRect.set(0, 0, 100, 100)
+ destRect.set(0, 0, 100, 100)
+
+ srcRect.letterBox(destRect)
+
+ assertThat(srcRect).isEqualTo(Rect(0, 0, 100, 100))
+ }
+
+ @Test
+ fun letterBox_toSameRect_noScale() {
+ srcRect.set(0, 0, 100, 100)
+ destRect.set(0, 0, 100, 100)
+
+ srcRect.letterBox(destRect, letterBoxedRect)
+
+ assertThat(letterBoxedRect).isEqualTo(Rect(0, 0, 100, 100))
+ assertThat(srcRect).isEqualTo(Rect(0, 0, 100, 100))
+ }
+
+ @Test
+ fun letterBoxSelf_toSmallHeight_scaleDownHorizontally() {
+ srcRect.set(0, 0, 2893, 2114)
+ destRect.set(0, 0, 939, 520)
+
+ srcRect.letterBox(destRect)
+
+ assertThat(srcRect).isEqualTo(Rect(114, 0, 825, 520))
+ }
+
+ @Test
+ fun letterBoxRect_toSmallHeight_scaleDownHorizontally() {
+ srcRect.set(0, 0, 2893, 2114)
+ destRect.set(0, 0, 939, 520)
+
+ srcRect.letterBox(destRect, letterBoxedRect)
+
+ assertThat(letterBoxedRect).isEqualTo(Rect(114, 0, 825, 520))
+ assertThat(srcRect).isEqualTo(Rect(0, 0, 2893, 2114))
+ }
+
+ @Test
+ fun letterBoxSelf_toSmallHeightWithOffset_scaleDownHorizontally() {
+ srcRect.set(0, 0, 2893, 2114)
+ destRect.set(10, 20, 949, 540)
+
+ srcRect.letterBox(destRect)
+
+ assertThat(srcRect).isEqualTo(Rect(124, 20, 835, 540))
+ }
+
+ @Test
+ fun letterBoxRect_toSmallHeightWithOffset_scaleDownHorizontally() {
+ srcRect.set(0, 0, 2893, 2114)
+ destRect.set(10, 20, 949, 540)
+
+ srcRect.letterBox(destRect, letterBoxedRect)
+
+ assertThat(letterBoxedRect).isEqualTo(Rect(124, 20, 835, 540))
+ assertThat(srcRect).isEqualTo(Rect(0, 0, 2893, 2114))
+ }
+
+ @Test
+ fun letterBoxSelf_toSmallWidth_scaleDownVertically() {
+ srcRect.set(0, 0, 2893, 2114)
+ destRect.set(0, 0, 520, 939)
+
+ srcRect.letterBox(destRect)
+
+ assertThat(srcRect).isEqualTo(Rect(0, 280, 520, 659))
+ }
+
+ @Test
+ fun letterBoxRect_toSmallWidth_scaleDownVertically() {
+ srcRect.set(0, 0, 2893, 2114)
+ destRect.set(0, 0, 520, 939)
+
+ srcRect.letterBox(destRect, letterBoxedRect)
+
+ assertThat(letterBoxedRect).isEqualTo(Rect(0, 280, 520, 659))
+ assertThat(srcRect).isEqualTo(Rect(0, 0, 2893, 2114))
+ }
+
+ @Test
+ fun letterBoxSelf_toSmallWidthWithOffset_scaleDownVertically() {
+ srcRect.set(0, 0, 2893, 2114)
+ destRect.set(40, 60, 560, 999)
+
+ srcRect.letterBox(destRect)
+
+ assertThat(srcRect).isEqualTo(Rect(40, 340, 560, 719))
+ }
+
+ @Test
+ fun letterBoxRect_toSmallWidthWithOffset_scaleDownVertically() {
+ srcRect.set(0, 0, 2893, 2114)
+ destRect.set(40, 60, 560, 999)
+
+ srcRect.letterBox(destRect, letterBoxedRect)
+
+ assertThat(letterBoxedRect).isEqualTo(Rect(40, 340, 560, 719))
+ assertThat(srcRect).isEqualTo(Rect(0, 0, 2893, 2114))
+ }
+}