Merge "Animate the dot with the bubble bar" into main
diff --git a/OWNERS b/OWNERS
index a66bf54..22efa33 100644
--- a/OWNERS
+++ b/OWNERS
@@ -30,6 +30,7 @@
jeremysim@google.com
atsjenk@google.com
brianji@google.com
+hwwang@google.com
# Overview eng team
alexchau@google.com
@@ -52,4 +53,4 @@
per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com
# Predictive Back
-per-file LauncherBackAnimationController.java = shanh@google.com, gallmann@google.com
\ No newline at end of file
+per-file LauncherBackAnimationController.java = shanh@google.com, gallmann@google.com
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index f1f9966..31a9009 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -311,8 +311,23 @@
}
flag {
+ name: "enable_container_return_animations"
+ namespace: "launcher"
+ description: "Enables the container return animation mirroring launches."
+ bug: "341017746"
+}
+
+flag {
name: "floating_search_bar"
namespace: "launcher"
description: "Search bar persists at the bottom of the screen across Launcher states"
bug: "346408388"
}
+
+flag {
+ name: "multiline_search_bar"
+ namespace: "launcher"
+ description: "Search bar can wrap to multi-line"
+ bug: "341795751"
+}
+
diff --git a/go/quickstep/res/values-fa/strings.xml b/go/quickstep/res/values-fa/strings.xml
index 47786e9..8453d4e 100644
--- a/go/quickstep/res/values-fa/strings.xml
+++ b/go/quickstep/res/values-fa/strings.xml
@@ -14,7 +14,7 @@
<string name="assistant_not_selected_text" msgid="3244613673884359276">"برای گوش کردن به نوشتار در صفحهنمایشتان یا ترجمه کردن آن، یکی از برنامههای دستیار دیجیتالی را در «تنظیمات» انتخاب کنید"</string>
<string name="assistant_not_supported_title" msgid="1675788067597484142">"برای استفاده از این ویژگی، دستیارتان را تغییر دهید"</string>
<string name="assistant_not_supported_text" msgid="1708031078549268884">"برای گوش کردن به نوشتار در صفحهنمایشتان یا ترجمه کردن آن، برنامه دستیار دیجیتالیتان را در «تنظیمات» تغییر دهید"</string>
- <string name="tooltip_listen" msgid="7634466447860989102">"برای گوش کردن به نوشتار در این صفحه، اینجا ضربه بزنید"</string>
- <string name="tooltip_translate" msgid="4184845868901542567">"برای ترجمه نوشتار در این صفحه، اینجا ضربه بزنید"</string>
+ <string name="tooltip_listen" msgid="7634466447860989102">"برای گوش کردن به نوشتار در این صفحه، اینجا تکضرب بزنید"</string>
+ <string name="tooltip_translate" msgid="4184845868901542567">"برای ترجمه نوشتار در این صفحه، اینجا تکضرب بزنید"</string>
<string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"نمیتوان این برنامه را همرسانی کرد"</string>
</resources>
diff --git a/go/quickstep/res/values-nb/strings.xml b/go/quickstep/res/values-nb/strings.xml
index 662b544..6299cc8 100644
--- a/go/quickstep/res/values-nb/strings.xml
+++ b/go/quickstep/res/values-nb/strings.xml
@@ -9,7 +9,7 @@
<string name="dialog_cancel" msgid="6464336969134856366">"AVBRYT"</string>
<string name="dialog_settings" msgid="6564397136021186148">"INNSTILLINGER"</string>
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Oversett eller lytt til tekst på skjermen"</string>
- <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informasjon som tekst på skjermen, nettadresser og skjermdumper kan deles med Google.\n\nFor å endre hvilken informasjon du deler, gå til "<b>"Innstillinger > Apper > Standardapper > Digital assistent-app"</b>"."</string>
+ <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Informasjon som tekst på skjermen, nettadresser og skjermbilder kan deles med Google.\n\nFor å endre hvilken informasjon du deler, gå til "<b>"Innstillinger > Apper > Standardapper > Digital assistent-app"</b>"."</string>
<string name="assistant_not_selected_title" msgid="5017072974603345228">"Velg en assistent for å bruke denne funksjonen"</string>
<string name="assistant_not_selected_text" msgid="3244613673884359276">"For å høre eller oversette tekst på skjermen, velg en digital assistent-app i innstillingene"</string>
<string name="assistant_not_supported_title" msgid="1675788067597484142">"Endre assistenten for å bruke denne funksjonen"</string>
diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
index 26ca06a..68558fa 100644
--- a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
+++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
@@ -56,7 +56,7 @@
import com.android.quickstep.util.AssistContentRequester;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.GoOverviewActionsView;
-import com.android.quickstep.views.TaskView.TaskContainer;
+import com.android.quickstep.views.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
diff --git a/quickstep/res/drawable-hdpi/nav_background.9.png b/quickstep/res/drawable-hdpi/nav_background.9.png
new file mode 100644
index 0000000..a09e654
--- /dev/null
+++ b/quickstep/res/drawable-hdpi/nav_background.9.png
Binary files differ
diff --git a/quickstep/res/drawable-mdpi/nav_background.9.png b/quickstep/res/drawable-mdpi/nav_background.9.png
new file mode 100644
index 0000000..aa74153
--- /dev/null
+++ b/quickstep/res/drawable-mdpi/nav_background.9.png
Binary files differ
diff --git a/quickstep/res/drawable-xhdpi/nav_background.9.png b/quickstep/res/drawable-xhdpi/nav_background.9.png
new file mode 100644
index 0000000..3b52195
--- /dev/null
+++ b/quickstep/res/drawable-xhdpi/nav_background.9.png
Binary files differ
diff --git a/quickstep/res/drawable-xxhdpi/nav_background.9.png b/quickstep/res/drawable-xxhdpi/nav_background.9.png
new file mode 100644
index 0000000..b35183c
--- /dev/null
+++ b/quickstep/res/drawable-xxhdpi/nav_background.9.png
Binary files differ
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index eedb29e..b84f646 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -87,7 +87,7 @@
<string name="gesture_tutorial_try_again" msgid="65962545858556697">"Try again"</string>
<string name="gesture_tutorial_nice" msgid="2936275692616928280">"Nice!"</string>
<string name="gesture_tutorial_step" msgid="1279786122817620968">"Tutorial <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
- <string name="allset_title" msgid="5021126669778966707">"Ready!"</string>
+ <string name="allset_title" msgid="5021126669778966707">"All set!"</string>
<string name="allset_hint" msgid="459504134589971527">"Swipe up to go home"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Tap the home button to go to your home screen"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"You’re ready to start using your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index eedb29e..b84f646 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -87,7 +87,7 @@
<string name="gesture_tutorial_try_again" msgid="65962545858556697">"Try again"</string>
<string name="gesture_tutorial_nice" msgid="2936275692616928280">"Nice!"</string>
<string name="gesture_tutorial_step" msgid="1279786122817620968">"Tutorial <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
- <string name="allset_title" msgid="5021126669778966707">"Ready!"</string>
+ <string name="allset_title" msgid="5021126669778966707">"All set!"</string>
<string name="allset_hint" msgid="459504134589971527">"Swipe up to go home"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Tap the home button to go to your home screen"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"You’re ready to start using your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index eedb29e..b84f646 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -87,7 +87,7 @@
<string name="gesture_tutorial_try_again" msgid="65962545858556697">"Try again"</string>
<string name="gesture_tutorial_nice" msgid="2936275692616928280">"Nice!"</string>
<string name="gesture_tutorial_step" msgid="1279786122817620968">"Tutorial <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
- <string name="allset_title" msgid="5021126669778966707">"Ready!"</string>
+ <string name="allset_title" msgid="5021126669778966707">"All set!"</string>
<string name="allset_hint" msgid="459504134589971527">"Swipe up to go home"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Tap the home button to go to your home screen"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"You’re ready to start using your <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index b296080..bafc2d5 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -89,7 +89,7 @@
<string name="gesture_tutorial_step" msgid="1279786122817620968">"آموزش گامبهگام <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
<string name="allset_title" msgid="5021126669778966707">"همه چیز آماده است!"</string>
<string name="allset_hint" msgid="459504134589971527">"برای رفتن به صفحه اصلی، تند بهبالا بکشید"</string>
- <string name="allset_button_hint" msgid="2395219947744706291">"برای رفتن به صفحه اصلی، روی دکمه صفحه اصلی ضربه بزنید"</string>
+ <string name="allset_button_hint" msgid="2395219947744706291">"برای رفتن به صفحه اصلی، روی دکمه صفحه اصلی تکضرب بزنید"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"آمادهاید از <xliff:g id="DEVICE">%1$s</xliff:g> خود استفاده کنید"</string>
<string name="default_device_name" msgid="6660656727127422487">"دستگاه"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"تنظیمات پیمایش سیستم"</annotation></string>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index 1f4275a..c27b7f8 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -141,7 +141,7 @@
<string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{데스크톱 앱 #개를 표시합니다.}other{데스크톱 앱 #개를 표시합니다.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> 및 <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
<string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"풍선"</string>
- <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"오버플로"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"더보기"</string>
<string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g>의 <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> 외 <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>개"</string>
</resources>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 7a4c7e9..fe2e4a4 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -141,7 +141,7 @@
<string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Компьютерын # аппыг харуулна уу.}other{Компьютерын # аппыг харуулна уу.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> болон <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
<string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Бөмбөлөг"</string>
- <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Урт цэс"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Илүү хэсэг"</string>
<string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g>-с ирсэн <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> болон бусад <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
</resources>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index 47498cc..e4d07bd 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -113,7 +113,7 @@
<string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"Arraste uma app para o lado para usar 2 apps em simultâneo"</string>
<string name="taskbar_edu_stashing" msgid="5645461372669217294">"Deslize lentamente para cima para ver a Barra de tarefas"</string>
<string name="taskbar_edu_suggestions" msgid="8215044496435527982">"Receba sugestões de apps baseadas na sua rotina"</string>
- <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Mantenha o divisor premido para fixar a Barra de tarefas"</string>
+ <string name="taskbar_edu_pinning" msgid="6708550858580071558">"Mantenha o divisor pressionado para fixar a Barra de tarefas"</string>
<string name="taskbar_edu_features" msgid="3320337287472848162">"Faça mais com a Barra de tarefas"</string>
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Mostre sempre a Barra de tarefas"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Para mostrar sempre a Barra de tarefas no fundo do ecrã, toque sem soltar no divisor"</string>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index a89227e..79ea299 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -141,7 +141,7 @@
<string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{显示 # 款桌面应用。}other{显示 # 款桌面应用。}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g>和<xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
<string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"气泡框"</string>
- <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"菜单"</string>
+ <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"溢出式气泡框"</string>
<string name="bubble_bar_bubble_description" msgid="1882466152448446446">"来自“<xliff:g id="APP_NAME">%2$s</xliff:g>”的<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
<string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>以及另外 <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> 个"</string>
</resources>
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 15180ef..d973149 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -238,5 +238,12 @@
@Override
@UiThread
default void onAnimationCancelled() {}
+
+ /**
+ * Returns whether this animation factory supports a tightly coupled return animation.
+ */
+ default boolean supportsReturnTransition() {
+ return false;
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index fae281a..5a74f4a 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -43,6 +43,7 @@
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
+import static com.android.launcher3.Flags.enableContainerReturnAnimations;
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
@@ -68,6 +69,7 @@
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.Flags.returnAnimationFrameworkLibrary;
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
@@ -181,6 +183,9 @@
*/
public class QuickstepTransitionManager implements OnDeviceProfileChangeListener {
+ private static final String TRANSITION_COOKIE_PREFIX =
+ "com.android.launcher3.QuickstepTransitionManager_activityLaunch";
+
private static final boolean ENABLE_SHELL_STARTING_SURFACE =
SystemProperties.getBoolean("persist.debug.shell_starting_surface", true);
@@ -333,17 +338,7 @@
restartedListener.register(onEndCallback::executeAllAndDestroy);
onEndCallback.add(restartedListener::unregister);
- mAppLaunchRunner = new AppLaunchAnimationRunner(v, onEndCallback);
- ItemInfo tag = (ItemInfo) v.getTag();
- if (tag != null && tag.shouldUseBackgroundAnimation()) {
- ContainerAnimationRunner containerAnimationRunner = ContainerAnimationRunner.from(
- v, mLauncher, mStartingWindowListener, onEndCallback);
- if (containerAnimationRunner != null) {
- mAppLaunchRunner = containerAnimationRunner;
- }
- }
- RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(
- mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */);
+ RemoteAnimationRunnerCompat runner = createAppLaunchRunner(v, onEndCallback);
// Note that this duration is a guess as we do not know if the animation will be a
// recents launch or not for sure until we know the opening app targets.
@@ -360,10 +355,95 @@
IRemoteCallback endCallback = completeRunnableListCallback(onEndCallback);
options.setOnAnimationAbortListener(endCallback);
options.setOnAnimationFinishedListener(endCallback);
+
+ IBinder cookie = mAppLaunchRunner.supportsReturnTransition()
+ ? ((ContainerAnimationRunner) mAppLaunchRunner).getCookie() : null;
+ addLaunchCookie(cookie, (ItemInfo) v.getTag(), options);
+
+ // Register the return animation so it can be triggered on back from the app to home.
+ maybeRegisterAppReturnTransition(v);
+
return new ActivityOptionsWrapper(options, onEndCallback);
}
/**
+ * Selects the appropriate type of launch runner for the given view, builds it, and returns it.
+ * {@link QuickstepTransitionManager#mAppLaunchRunner} is updated as a by-product of this
+ * method.
+ */
+ private RemoteAnimationRunnerCompat createAppLaunchRunner(View v, RunnableList onEndCallback) {
+ ItemInfo tag = (ItemInfo) v.getTag();
+ ContainerAnimationRunner containerRunner = null;
+ if (tag != null && tag.shouldUseBackgroundAnimation()) {
+ // The cookie should only override the default used by launcher if container return
+ // animations are enabled.
+ ActivityTransitionAnimator.TransitionCookie cookie =
+ checkReturnAnimationsFlags()
+ ? new ActivityTransitionAnimator.TransitionCookie(
+ TRANSITION_COOKIE_PREFIX + tag.id)
+ : null;
+ ContainerAnimationRunner launchAnimationRunner =
+ ContainerAnimationRunner.fromView(
+ v, cookie, true /* forLaunch */, mLauncher, mStartingWindowListener,
+ onEndCallback);
+
+ if (launchAnimationRunner != null) {
+ containerRunner = launchAnimationRunner;
+ }
+ }
+
+ mAppLaunchRunner = containerRunner != null
+ ? containerRunner : new AppLaunchAnimationRunner(v, onEndCallback);
+ return new LauncherAnimationRunner(
+ mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */);
+ }
+
+ /**
+ * If container return animations are enabled and the current launch runner is itself a
+ * {@link ContainerAnimationRunner}, registers a matching return animation that de-registers
+ * itself after it has run once or is made obsolete by the view going away.
+ */
+ private void maybeRegisterAppReturnTransition(View v) {
+ if (!checkReturnAnimationsFlags() || !mAppLaunchRunner.supportsReturnTransition()) {
+ return;
+ }
+
+ ActivityTransitionAnimator.TransitionCookie cookie =
+ ((ContainerAnimationRunner) mAppLaunchRunner).getCookie();
+ RunnableList onEndCallback = new RunnableList();
+ ContainerAnimationRunner runner =
+ ContainerAnimationRunner.fromView(
+ v, cookie, false /* forLaunch */, mLauncher, mStartingWindowListener,
+ onEndCallback);
+ RemoteTransition transition =
+ new RemoteTransition(
+ new LauncherAnimationRunner(
+ mHandler, runner, true /* startAtFrontOfQueue */
+ ).toRemoteTransition()
+ );
+
+ SystemUiProxy.INSTANCE.get(mLauncher).registerRemoteTransition(
+ transition, ContainerAnimationRunner.buildBackToHomeFilter(cookie, mLauncher));
+ ContainerAnimationRunner.setUpRemoteAnimationCleanup(
+ v, transition, onEndCallback, mLauncher);
+ }
+
+ /**
+ * Adds a new launch cookie for the activity launch if supported.
+ * Prioritizes the explicitly provided cookie, falling back on extracting one from the given
+ * {@link ItemInfo} if necessary.
+ */
+ private void addLaunchCookie(IBinder cookie, ItemInfo info, ActivityOptions options) {
+ if (cookie == null) {
+ cookie = mLauncher.getLaunchCookie(info);
+ }
+
+ if (cookie != null) {
+ options.setLaunchCookie(cookie);
+ }
+ }
+
+ /**
* Whether the launch is a recents app transition and we should do a launch animation
* from the recents view. Note that if the remote animation targets are not provided, this
* may not always be correct as we may resolve the opening app to a task when the animation
@@ -1728,6 +1808,10 @@
}
}
+ private static boolean checkReturnAnimationsFlags() {
+ return enableContainerReturnAnimations() && returnAnimationFrameworkLibrary();
+ }
+
/**
* Remote animation runner for animation from the app to Launcher, including recents.
*/
@@ -1844,38 +1928,45 @@
/** The delegate runner that handles the actual animation. */
private final RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> mDelegate;
+ @Nullable
+ private final ActivityTransitionAnimator.TransitionCookie mCookie;
+
private ContainerAnimationRunner(
- RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> delegate) {
+ RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> delegate,
+ ActivityTransitionAnimator.TransitionCookie cookie) {
mDelegate = delegate;
+ mCookie = cookie;
}
@Nullable
- private static ContainerAnimationRunner from(View v, Launcher launcher,
- StartingWindowListener startingWindowListener, RunnableList onEndCallback) {
- View viewToUse = findLaunchableViewWithBackground(v);
- if (viewToUse == null) {
- return null;
+ ActivityTransitionAnimator.TransitionCookie getCookie() {
+ return mCookie;
+ }
+
+ @Nullable
+ static ContainerAnimationRunner fromView(
+ View v,
+ ActivityTransitionAnimator.TransitionCookie cookie,
+ boolean forLaunch,
+ Launcher launcher,
+ StartingWindowListener startingWindowListener,
+ RunnableList onEndCallback) {
+ if (!forLaunch && !checkReturnAnimationsFlags()) {
+ throw new IllegalStateException(
+ "forLaunch cannot be false when the enableContainerReturnAnimations or "
+ + "returnAnimationFrameworkLibrary flag is disabled");
}
- // The CUJ is logged by the click handler, so we don't log it inside the animation
- // library.
- ActivityTransitionAnimator.Controller controllerDelegate =
- ActivityTransitionAnimator.Controller.fromView(viewToUse, null /* cujType */);
-
- if (controllerDelegate == null) {
- return null;
- }
-
- // This wrapper allows us to override the default value, telling the controller that the
- // current window is below the animating window.
+ // First the controller is created. This is used by the runner to animate the
+ // origin/target view.
ActivityTransitionAnimator.Controller controller =
- new DelegateTransitionAnimatorController(controllerDelegate) {
- @Override
- public boolean isBelowAnimatingWindow() {
- return true;
- }
- };
+ buildController(v, cookie, forLaunch);
+ if (controller == null) {
+ return null;
+ }
+ // The callback is used to make sure that we use the right color to fade between view
+ // and the window.
ActivityTransitionAnimator.Callback callback = task -> {
final int backgroundColor =
startingWindowListener.mBackgroundColor == Color.TRANSPARENT
@@ -1894,7 +1985,52 @@
return new ContainerAnimationRunner(
new ActivityTransitionAnimator.AnimationDelegate(
- MAIN_EXECUTOR, controller, callback, listener));
+ MAIN_EXECUTOR, controller, callback, listener),
+ cookie);
+ }
+
+ /**
+ * Constructs a {@link ActivityTransitionAnimator.Controller} that can be used by a
+ * {@link ContainerAnimationRunner} to animate a view into an opening window or from a
+ * closing one.
+ */
+ @Nullable
+ private static ActivityTransitionAnimator.Controller buildController(
+ View v, ActivityTransitionAnimator.TransitionCookie cookie, boolean isLaunching) {
+ View viewToUse = findLaunchableViewWithBackground(v);
+ if (viewToUse == null) {
+ return null;
+ }
+
+ // The CUJ is logged by the click handler, so we don't log it inside the animation
+ // library. TODO: figure out return CUJ.
+ ActivityTransitionAnimator.Controller controllerDelegate =
+ ActivityTransitionAnimator.Controller.fromView(viewToUse, null /* cujType */);
+
+ if (controllerDelegate == null) {
+ return null;
+ }
+
+ // This wrapper allows us to override the default value, telling the controller that the
+ // current window is below the animating window as well as information about the return
+ // animation.
+ return new DelegateTransitionAnimatorController(controllerDelegate) {
+ @Override
+ public boolean isLaunching() {
+ return isLaunching;
+ }
+
+ @Override
+ public boolean isBelowAnimatingWindow() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public ActivityTransitionAnimator.TransitionCookie getTransitionCookie() {
+ return cookie;
+ }
+ };
}
/**
@@ -1916,6 +2052,67 @@
return (T) current;
}
+ /**
+ * Builds the filter used by WM Shell to match app closing transitions (only back, no home
+ * button/gesture) to the given launch cookie.
+ */
+ static TransitionFilter buildBackToHomeFilter(
+ ActivityTransitionAnimator.TransitionCookie cookie, Launcher launcher) {
+ // Closing activity must include the cookie in its list of launch cookies.
+ TransitionFilter.Requirement appRequirement = new TransitionFilter.Requirement();
+ appRequirement.mActivityType = ACTIVITY_TYPE_STANDARD;
+ appRequirement.mLaunchCookie = cookie;
+ appRequirement.mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ // Opening activity must be Launcher.
+ TransitionFilter.Requirement launcherRequirement = new TransitionFilter.Requirement();
+ launcherRequirement.mActivityType = ACTIVITY_TYPE_HOME;
+ launcherRequirement.mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+ launcherRequirement.mTopActivity = launcher.getComponentName();
+ // Transition types CLOSE and TO_BACK match the back button/gesture but not the home
+ // button/gesture.
+ TransitionFilter filter = new TransitionFilter();
+ filter.mTypeSet = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
+ filter.mRequirements =
+ new TransitionFilter.Requirement[]{appRequirement, launcherRequirement};
+ return filter;
+ }
+
+ /**
+ * Creates various conditions to ensure that the given transition is cleaned up correctly
+ * when necessary:
+ * - if the transition has run, it is the callback that unregisters it;
+ * - if the associated view is detached before the transition has had an opportunity to run,
+ * a {@link View.OnAttachStateChangeListener} allows us to do the same (and removes
+ * itself).
+ */
+ static void setUpRemoteAnimationCleanup(
+ View v, RemoteTransition transition, RunnableList callback, Launcher launcher) {
+ View.OnAttachStateChangeListener listener = new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(@NonNull View v) {}
+
+ @Override
+ public void onViewDetachedFromWindow(@NonNull View v) {
+ SystemUiProxy.INSTANCE.get(launcher)
+ .unregisterRemoteTransition(transition);
+ v.removeOnAttachStateChangeListener(this);
+ }
+ };
+
+ // Remove the animation as soon as it has run once.
+ callback.add(() -> {
+ SystemUiProxy.INSTANCE.get(launcher).unregisterRemoteTransition(transition);
+ if (v != null) {
+ v.removeOnAttachStateChangeListener(listener);
+ }
+ });
+
+ // Remove the animation when the view is detached from the hierarchy.
+ // This is so that if back is not invoked (e.g. if we go back home through the home
+ // gesture) we don't have obsolete transitions staying registered.
+ v.addOnAttachStateChangeListener(listener);
+ }
+
@Override
public void onAnimationStart(int transit, RemoteAnimationTarget[] appTargets,
RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets,
@@ -1928,6 +2125,11 @@
public void onAnimationCancelled() {
mDelegate.onAnimationCancelled();
}
+
+ @Override
+ public boolean supportsReturnTransition() {
+ return true;
+ }
}
/**
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 7e52ea1..7cdca74 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -120,10 +120,6 @@
WindowInsetsController wc = mDragLayer.getWindowInsetsController();
wc.hide(navigationBars() + statusBars());
- BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true);
- widgetSheet.disableNavBarScrim(true);
- widgetSheet.addOnCloseListener(this::finish);
-
parseIntentExtras();
refreshAndBindWidgets();
}
@@ -224,9 +220,10 @@
};
}
- /** Updates the model with widgets and provides them after applying the provided filter. */
+ /** Updates the model with widgets, applies filters and launches the widgets sheet once
+ * widgets are available */
private void refreshAndBindWidgets() {
- MODEL_EXECUTOR.execute(() -> {
+ MODEL_EXECUTOR.getHandler().postDelayed(() -> {
LauncherAppState app = LauncherAppState.getInstance(this);
mModel.update(app, null);
final List<WidgetsListBaseEntry> allWidgets =
@@ -240,6 +237,9 @@
}
);
bindWidgets(allWidgets);
+ // Open sheet once widgets are available, so that it doesn't interrupt the open
+ // animation.
+ openWidgetsSheet();
if (mUiSurface != null) {
Map<ComponentKey, WidgetItem> allWidgetItems = allWidgets.stream()
.filter(entry -> entry instanceof WidgetsListContentEntry)
@@ -253,15 +253,26 @@
mUiSurface, allWidgetItems);
mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
}
- });
+ }, mDeviceProfile.bottomSheetOpenDuration);
}
private void bindWidgets(List<WidgetsListBaseEntry> widgets) {
MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
}
+ private void openWidgetsSheet() {
+ MAIN_EXECUTOR.execute(() -> {
+ BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true);
+ widgetSheet.disableNavBarScrim(true);
+ widgetSheet.addOnCloseListener(this::finish);
+ });
+ }
+
private void bindRecommendedWidgets(List<ItemInfo> recommendedWidgets) {
- MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets));
+ // Bind recommendations once picker has finished open animation.
+ MAIN_EXECUTOR.getHandler().postDelayed(
+ () -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets),
+ mDeviceProfile.bottomSheetOpenDuration);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 8b5ed7c..6af5a30 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -205,6 +205,7 @@
mActive = true;
}
+ @WorkerThread
@Override
public void workspaceLoadComplete() {
super.workspaceLoadComplete();
@@ -323,6 +324,7 @@
}
}
+ @WorkerThread
@Override
public void destroy() {
super.destroy();
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index a7c9652..28bc01c 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -111,6 +111,7 @@
mWorkerHandler.post(this::initializeInBackground);
}
+ @WorkerThread
private void initializeInBackground() {
if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
mContext.registerReceiver(
@@ -134,8 +135,8 @@
public void close() {
if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
mWorkerHandler.post(() -> {
- mWellbeingAppChangeReceiver.unregisterReceiverSafely(mContext);
- mAppAddRemoveReceiver.unregisterReceiverSafely(mContext);
+ mWellbeingAppChangeReceiver.unregisterReceiverSafelySync(mContext);
+ mAppAddRemoveReceiver.unregisterReceiverSafelySync(mContext);
mContext.getContentResolver().unregisterContentObserver(mContentObserver);
});
}
diff --git a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
index 5730273..41fcf61 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
@@ -65,6 +65,7 @@
private final Context mContext;
@NonNull
private final String mUiSurface;
+ private boolean mPredictionsAvailable;
@NonNull
private final Map<ComponentKey, WidgetItem> mAllWidgets;
@@ -76,8 +77,8 @@
}
/**
- * Requests predictions from the app predictions manager and registers the provided callback to
- * receive updates when predictions are available.
+ * Requests one time predictions from the app predictions manager and invokes provided callback
+ * once predictions are available.
*
* @param existingWidgets widgets that are currently added to the surface;
* @param callback consumer of prediction results to be called when predictions are
@@ -159,10 +160,14 @@
@WorkerThread
private void bindPredictions(List<AppTarget> targets, Predicate<WidgetItem> filter,
Consumer<List<ItemInfo>> callback) {
- List<WidgetItem> filteredPredictions = filterPredictions(targets, mAllWidgets, filter);
- List<ItemInfo> mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions);
+ if (!mPredictionsAvailable) {
+ mPredictionsAvailable = true;
+ List<WidgetItem> filteredPredictions = filterPredictions(targets, mAllWidgets, filter);
+ List<ItemInfo> mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions);
- MAIN_EXECUTOR.execute(() -> callback.accept(mappedPredictions));
+ MAIN_EXECUTOR.execute(() -> callback.accept(mappedPredictions));
+ MODEL_EXECUTOR.execute(this::clear);
+ }
}
/**
@@ -214,5 +219,6 @@
mAppPredictor.destroy();
mAppPredictor = null;
}
+ mPredictionsAvailable = false;
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 81581b8..b647a3e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -41,12 +41,14 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
+import static com.android.wm.shell.Flags.enableTaskbarOnPhones;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
@@ -105,6 +107,7 @@
import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.shared.rotation.RotationButton;
import com.android.systemui.shared.rotation.RotationButtonController;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
@@ -153,6 +156,8 @@
public static final int ALPHA_INDEX_SUW = 2;
private static final int NUM_ALPHA_CHANNELS = 3;
+ private static final long AUTODIM_TIMEOUT_MS = 2250;
+
private final ArrayList<StatePropertyHolder> mPropertyHolders = new ArrayList<>();
private final ArrayList<ImageView> mAllButtons = new ArrayList<>();
private int mState;
@@ -161,6 +166,7 @@
private final @Nullable Context mNavigationBarPanelContext;
private final WindowManagerProxy mWindowManagerProxy;
private final NearestTouchFrame mNavButtonsView;
+ private final Handler mHandler;
private final LinearLayout mNavButtonContainer;
// Used for IME+A11Y buttons
private final ViewGroup mEndContextualContainer;
@@ -182,7 +188,7 @@
this::updateNavButtonInAppDisplayProgressForSysui);
/** Expected nav button dark intensity communicated via the framework. */
private final AnimatedFloat mTaskbarNavButtonDarkIntensity = new AnimatedFloat(
- this::updateNavButtonColor);
+ this::onDarkIntensityChanged);
/** {@code 1} if the Taskbar background color is fully opaque. */
private final AnimatedFloat mOnTaskbarBackgroundNavButtonColorOverride = new AnimatedFloat(
this::updateNavButtonColor);
@@ -218,12 +224,19 @@
private ImageView mRecentsButton;
private Space mSpace;
+ private TaskbarTransitions mTaskbarTransitions;
+ private @BarTransitions.TransitionMode int mTransitionMode;
+
+ private final Runnable mAutoDim = () -> mTaskbarTransitions.setAutoDim(true);
+
public NavbarButtonsViewController(TaskbarActivityContext context,
- @Nullable Context navigationBarPanelContext, NearestTouchFrame navButtonsView) {
+ @Nullable Context navigationBarPanelContext, NearestTouchFrame navButtonsView,
+ Handler handler) {
mContext = context;
mNavigationBarPanelContext = navigationBarPanelContext;
mWindowManagerProxy = WindowManagerProxy.INSTANCE.get(mContext);
mNavButtonsView = navButtonsView;
+ mHandler = handler;
mNavButtonContainer = mNavButtonsView.findViewById(R.id.end_nav_buttons);
mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);
@@ -233,6 +246,8 @@
mOnBackgroundIconColor = Utilities.isDarkTheme(context)
? context.getColor(R.color.taskbar_nav_icon_light_color)
: context.getColor(R.color.taskbar_nav_icon_dark_color);
+
+ mTaskbarTransitions = new TaskbarTransitions(mContext, mNavButtonsView);
}
/**
@@ -344,6 +359,7 @@
R.bool.floating_rotation_button_position_left);
mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
mRotationButtonListener);
+ mTaskbarTransitions.init();
applyState();
mPropertyHolders.forEach(StatePropertyHolder::endAnimation);
@@ -604,6 +620,39 @@
mBackButton.setAccessibilityDelegate(accessibilityDelegate);
}
+ public void setWallpaperVisible(boolean isVisible) {
+ mTaskbarTransitions.setWallpaperVisibility(isVisible);
+ }
+
+ public void onTransitionModeUpdated(int barMode, boolean checkBarModes) {
+ mTransitionMode = barMode;
+ if (checkBarModes) {
+ checkNavBarModes();
+ }
+ }
+
+ public void checkNavBarModes() {
+ boolean isBarHidden = (mSysuiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
+ mTaskbarTransitions.transitionTo(mTransitionMode, !isBarHidden);
+ }
+
+ public void finishBarAnimations() {
+ mTaskbarTransitions.finishAnimations();
+ }
+
+ public void touchAutoDim(boolean reset) {
+ mTaskbarTransitions.setAutoDim(false);
+ mHandler.removeCallbacks(mAutoDim);
+ if (reset) {
+ mHandler.postDelayed(mAutoDim, AUTODIM_TIMEOUT_MS);
+ }
+ }
+
+ public void transitionTo(@BarTransitions.TransitionMode int barMode,
+ boolean animate) {
+ mTaskbarTransitions.transitionTo(barMode, animate);
+ }
+
/** Use to set the translationY for the all nav+contextual buttons */
public AnimatedFloat getTaskbarNavButtonTranslationY() {
return mTaskbarNavButtonTranslationY;
@@ -678,14 +727,18 @@
mLightIconColorOnHome,
mDarkIconColorOnHome);
- // Override the color from framework if nav buttons are over an opaque Taskbar surface.
- final int iconColor = (int) argbEvaluator.evaluate(
- mOnBackgroundNavButtonColorOverrideMultiplier.value
- * Math.max(
- mOnTaskbarBackgroundNavButtonColorOverride.value,
- mSlideInViewVisibleNavButtonColorOverride.value),
- sysUiNavButtonIconColorOnHome,
- mOnBackgroundIconColor);
+ final int iconColor;
+ if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && mContext.isPhoneMode()) {
+ iconColor = sysUiNavButtonIconColorOnHome;
+ } else {
+ // Override the color from framework if nav buttons are over an opaque Taskbar surface.
+ iconColor = (int) argbEvaluator.evaluate(
+ mOnBackgroundNavButtonColorOverrideMultiplier.value * Math.max(
+ mOnTaskbarBackgroundNavButtonColorOverride.value,
+ mSlideInViewVisibleNavButtonColorOverride.value),
+ sysUiNavButtonIconColorOnHome,
+ mOnBackgroundIconColor);
+ }
for (ImageView button : mAllButtons) {
button.setImageTintList(ColorStateList.valueOf(iconColor));
@@ -697,6 +750,11 @@
}
}
+ private void onDarkIntensityChanged() {
+ updateNavButtonColor();
+ mTaskbarTransitions.onDarkIntensityChanged(mTaskbarNavButtonDarkIntensity.value);
+ }
+
protected ImageView addButton(@DrawableRes int drawableId, @TaskbarButton int buttonType,
ViewGroup parent, TaskbarNavButtonController navButtonController, @IdRes int id) {
return addButton(drawableId, buttonType, parent, navButtonController, id,
@@ -1042,6 +1100,7 @@
+ mOnBackgroundNavButtonColorOverrideMultiplier.value);
mNavButtonsView.dumpLogs(prefix + "\t", pw);
+ mTaskbarTransitions.dumpLogs(prefix + "\t", pw);
}
private static String getStateString(int flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 5020206..21a8268 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -139,6 +139,7 @@
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.rotation.RotationButtonController;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.unfold.updates.RotationChangeProvider;
@@ -275,7 +276,8 @@
mControllers = new TaskbarControllers(this,
new TaskbarDragController(this),
buttonController,
- new NavbarButtonsViewController(this, mNavigationBarPanelContext, navButtonsView),
+ new NavbarButtonsViewController(this, mNavigationBarPanelContext, navButtonsView,
+ getMainThreadHandler()),
rotationButtonController,
new TaskbarDragLayerController(this, mDragLayer),
new TaskbarViewController(this, taskbarView),
@@ -799,6 +801,27 @@
mControllers.taskbarStashController.setSetupUIVisible(isVisible);
}
+ public void setWallpaperVisible(boolean isVisible) {
+ mControllers.navbarButtonsViewController.setWallpaperVisible(isVisible);
+ }
+
+ public void checkNavBarModes() {
+ mControllers.navbarButtonsViewController.checkNavBarModes();
+ }
+
+ public void finishBarAnimations() {
+ mControllers.navbarButtonsViewController.finishBarAnimations();
+ }
+
+ public void touchAutoDim(boolean reset) {
+ mControllers.navbarButtonsViewController.touchAutoDim(reset);
+ }
+
+ public void transitionTo(@BarTransitions.TransitionMode int barMode,
+ boolean animate) {
+ mControllers.navbarButtonsViewController.transitionTo(barMode, animate);
+ }
+
/**
* Called when this instance of taskbar is no longer needed
*/
@@ -876,6 +899,9 @@
mControllers.rotationButtonController.onBehaviorChanged(displayId, behavior);
}
+ public void onTransitionModeUpdated(int barMode, boolean checkBarModes) {
+ mControllers.navbarButtonsViewController.onTransitionModeUpdated(barMode, checkBarModes);
+ }
public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
mControllers.navbarButtonsViewController.getTaskbarNavButtonDarkIntensity()
.updateValue(darkIntensity);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index f703463..a9b34d2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -18,7 +18,6 @@
import static android.view.KeyEvent.ACTION_UP;
import static android.view.KeyEvent.KEYCODE_BACK;
-import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import android.content.Context;
@@ -42,7 +41,6 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.views.BaseDragLayer;
@@ -106,10 +104,6 @@
mTaskbarBackgroundAlpha = new MultiPropertyFactory<>(this, BG_ALPHA, INDEX_COUNT,
(a, b) -> a * b, 1f);
mTaskbarBackgroundAlpha.get(INDEX_ALL_OTHER_STATES).setValue(0);
- mTaskbarBackgroundAlpha.get(INDEX_STASH_ANIM).setValue(
- enableScalingRevealHomeAnimation() && DisplayController.isTransientTaskbar(context)
- ? 0
- : 1);
}
public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
index c45c667..7f9d8a3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
@@ -27,6 +27,7 @@
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.animation.Interpolator
+import android.window.OnBackInvokedDispatcher
import androidx.core.view.updateLayoutParams
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
@@ -66,11 +67,14 @@
/** Container where the tooltip's body should be inflated. */
lateinit var content: ViewGroup
private set
+
private lateinit var arrow: View
/** Callback invoked when the tooltip is being closed. */
var onCloseCallback: () -> Unit = {}
private var openCloseAnimator: AnimatorSet? = null
+ /** Used to set whether users can tap outside the current tooltip window to dismiss it */
+ var allowTouchDismissal = true
/** Animates the tooltip into view. */
fun show() {
@@ -134,14 +138,25 @@
override fun isOfType(type: Int): Boolean = type and TYPE_TASKBAR_EDUCATION_DIALOG != 0
override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
- if (ev?.action == ACTION_DOWN && !activityContext.dragLayer.isEventOverView(this, ev)) {
+ if (
+ ev?.action == ACTION_DOWN &&
+ !activityContext.dragLayer.isEventOverView(this, ev) &&
+ allowTouchDismissal
+ ) {
close(true)
}
return false
}
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ findOnBackInvokedDispatcher()
+ ?.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, this)
+ }
+
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
+ findOnBackInvokedDispatcher()?.unregisterOnBackInvokedCallback(this)
Settings.Secure.putInt(mContext.contentResolver, LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0)
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index 5cbd5c9..d57c483 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -86,10 +86,13 @@
!activityContext.isPhoneMode &&
!activityContext.isTinyTaskbar
}
+
private val isOpen: Boolean
get() = tooltip?.isOpen ?: false
+
val isBeforeTooltipFeaturesStep: Boolean
get() = isTooltipEnabled && tooltipStep <= TOOLTIP_STEP_FEATURES
+
private lateinit var controllers: TaskbarControllers
// Keep track of whether the user has seen the Search Edu
@@ -152,6 +155,7 @@
tooltipStep = TOOLTIP_STEP_NONE
inflateTooltip(R.layout.taskbar_edu_features)
tooltip?.run {
+ allowTouchDismissal = false
val splitscreenAnim = requireViewById<LottieAnimationView>(R.id.splitscreen_animation)
val suggestionsAnim = requireViewById<LottieAnimationView>(R.id.suggestions_animation)
val pinningAnim = requireViewById<LottieAnimationView>(R.id.pinning_animation)
@@ -216,6 +220,7 @@
inflateTooltip(R.layout.taskbar_edu_pinning)
tooltip?.run {
+ allowTouchDismissal = true
requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
.supportLightTheme()
@@ -260,6 +265,7 @@
userHasSeenSearchEdu = true
inflateTooltip(R.layout.taskbar_edu_search)
tooltip?.run {
+ allowTouchDismissal = true
requireViewById<LottieAnimationView>(R.id.search_edu_animation).supportLightTheme()
val eduSubtitle: TextView = requireViewById(R.id.search_edu_text)
showDisclosureText(eduSubtitle)
@@ -332,7 +338,9 @@
}
/** Closes the current [tooltip]. */
- fun hide() = tooltip?.close(true)
+ fun hide() {
+ tooltip?.close(true)
+ }
/** Initializes [tooltip] with content from [contentResId]. */
private fun inflateTooltip(@LayoutRes contentResId: Int) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 2a58db2..b294208 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -72,6 +72,7 @@
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.AssistUtils;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
@@ -304,7 +305,7 @@
.register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
mContext.registerComponentCallbacks(mComponentCallbacks);
- mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
+ mShutdownReceiver.registerAsync(mContext, Intent.ACTION_SHUTDOWN);
UI_HELPER_EXECUTOR.execute(() -> {
mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
mContext,
@@ -519,6 +520,36 @@
}
}
+ public void setWallpaperVisible(boolean isVisible) {
+ mSharedState.wallpaperVisible = isVisible;
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.setWallpaperVisible(isVisible);
+ }
+ }
+
+ public void checkNavBarModes() {
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.checkNavBarModes();
+ }
+ }
+
+ public void finishBarAnimations() {
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.finishBarAnimations();
+ }
+ }
+
+ public void touchAutoDim(boolean reset) {
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.touchAutoDim(reset);
+ }
+ }
+
+ public void transitionTo(@BarTransitions.TransitionMode int barMode,
+ boolean animate) {
+ mTaskbarActivityContext.transitionTo(barMode, animate);
+ }
+
private boolean isTaskbarEnabled(DeviceProfile deviceProfile) {
return ENABLE_TASKBAR_NAVBAR_UNIFICATION || deviceProfile.isTaskbarPresent;
}
@@ -546,6 +577,13 @@
}
}
+ public void onTransitionModeUpdated(int barMode, boolean checkBarModes) {
+ mSharedState.barMode = barMode;
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.onTransitionModeUpdated(barMode, checkBarModes);
+ }
+ }
+
public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
mSharedState.navButtonsDarkIntensity = darkIntensity;
if (mTaskbarActivityContext != null) {
@@ -582,8 +620,7 @@
public void destroy() {
debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
removeActivityCallbacksAndListeners();
- UI_HELPER_EXECUTOR.execute(
- () -> mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext));
+ mTaskbarBroadcastReceiver.unregisterReceiverSafelyAsync(mContext);
destroyExistingTaskbar();
removeTaskbarRootViewFromWindow();
if (mUserUnlocked) {
@@ -595,7 +632,7 @@
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
mContext.unregisterComponentCallbacks(mComponentCallbacks);
- mContext.unregisterReceiver(mShutdownReceiver);
+ mShutdownReceiver.unregisterReceiverSafelyAsync(mContext);
}
public @Nullable TaskbarActivityContext getCurrentActivityContext() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
index edaeb63..77bd35f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
@@ -56,12 +56,17 @@
// TaskbarManager#onNavButtonsDarkIntensityChanged()
public float navButtonsDarkIntensity;
+ // TaskbarManager#onTransitionModeUpdated()
+ public int barMode;
+
// TaskbarManager#onNavigationBarLumaSamplingEnabled()
public int mLumaSamplingDisplayId = DEFAULT_DISPLAY;
public boolean mIsLumaSamplingEnabled = true;
public boolean setupUIVisible = false;
+ public boolean wallpaperVisible = false;
+
public boolean allAppsVisible = false;
// LauncherTaskbarUIController#mTaskbarInAppDisplayProgressMultiProp
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 6279903..fa2d907 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -338,7 +338,16 @@
// For now, assume we're in an app, since LauncherTaskbarUIController won't be able to tell
// us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
updateStateForFlag(FLAG_IN_APP, true);
+
applyState(/* duration = */ 0);
+
+ // Hide the background while stashed so it doesn't show on fast swipes home
+ boolean shouldHideTaskbarBackground = enableScalingRevealHomeAnimation()
+ && DisplayController.isTransientTaskbar(mActivity)
+ && isStashed();
+
+ mTaskbarBackgroundAlphaForStash.setValue(shouldHideTaskbarBackground ? 0 : 1);
+
if (mTaskbarSharedState.getTaskbarWasPinned()
|| !mTaskbarSharedState.taskbarWasStashedAuto) {
tryStartTaskbarTimeout();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarTransitions.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarTransitions.java
new file mode 100644
index 0000000..615db01
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarTransitions.java
@@ -0,0 +1,135 @@
+/*
+ * 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.taskbar;
+
+import android.view.View;
+
+import com.android.launcher3.R;
+import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
+
+import java.io.PrintWriter;
+
+/** Manages task bar transitions */
+public class TaskbarTransitions extends BarTransitions implements
+ TaskbarControllers.LoggableTaskbarController {
+
+ private final TaskbarActivityContext mContext;
+
+ private boolean mWallpaperVisible;
+
+ private boolean mLightsOut;
+ private boolean mAutoDim;
+ private View mNavButtons;
+ private float mDarkIntensity;
+
+ private final NearestTouchFrame mView;
+
+ public TaskbarTransitions(TaskbarActivityContext context, NearestTouchFrame view) {
+ super(view, R.drawable.nav_background);
+
+ mContext = context;
+ mView = view;
+ }
+
+ void init() {
+ mView.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ mNavButtons = mView.findViewById(R.id.end_nav_buttons);
+ applyLightsOut(false, true);
+ });
+ mNavButtons = mView.findViewById(R.id.end_nav_buttons);
+
+ applyModeBackground(-1, getMode(), false /*animate*/);
+ applyLightsOut(false /*animate*/, true /*force*/);
+ if (mContext.isPhoneButtonNavMode()) {
+ mBarBackground.setOverrideAlpha(1);
+ }
+ }
+
+ void setWallpaperVisibility(boolean visible) {
+ mWallpaperVisible = visible;
+ applyLightsOut(true, false);
+ }
+
+ @Override
+ public void setAutoDim(boolean autoDim) {
+ // Ensure we aren't in gestural nav if we are triggering auto dim
+ if (autoDim && !mContext.isPhoneButtonNavMode()) {
+ return;
+ }
+ if (mAutoDim == autoDim) return;
+ mAutoDim = autoDim;
+ applyLightsOut(true, false);
+ }
+
+ @Override
+ protected void onTransition(int oldMode, int newMode, boolean animate) {
+ super.onTransition(oldMode, newMode, animate);
+ applyLightsOut(animate, false /*force*/);
+ }
+
+ private void applyLightsOut(boolean animate, boolean force) {
+ // apply to lights out
+ applyLightsOut(isLightsOut(getMode()), animate, force);
+ }
+
+ private void applyLightsOut(boolean lightsOut, boolean animate, boolean force) {
+ if (!force && lightsOut == mLightsOut) return;
+
+ mLightsOut = lightsOut;
+ if (mNavButtons == null) return;
+
+ // ok, everyone, stop it right there
+ mNavButtons.animate().cancel();
+
+ // Bump percentage by 10% if dark.
+ float darkBump = mDarkIntensity / 10;
+ final float navButtonsAlpha = lightsOut ? 0.6f + darkBump : 1f;
+
+ if (!animate) {
+ mNavButtons.setAlpha(navButtonsAlpha);
+ } else {
+ final int duration = lightsOut ? LIGHTS_OUT_DURATION : LIGHTS_IN_DURATION;
+ mNavButtons.animate()
+ .alpha(navButtonsAlpha)
+ .setDuration(duration)
+ .start();
+ }
+ }
+
+ void onDarkIntensityChanged(float darkIntensity) {
+ mDarkIntensity = darkIntensity;
+ if (mAutoDim) {
+ applyLightsOut(false, true);
+ }
+ }
+
+ @Override
+ public void dumpLogs(String prefix, PrintWriter pw) {
+ pw.println(prefix + "TaskbarTransitions:");
+
+ pw.println(prefix + "\tmMode=" + getMode());
+ pw.println(prefix + "\tmAlwaysOpaque: " + isAlwaysOpaque());
+ pw.println(prefix + "\tmWallpaperVisible: " + mWallpaperVisible);
+ pw.println(prefix + "\tmLightsOut: " + mLightsOut);
+ pw.println(prefix + "\tmAutoDim: " + mAutoDim);
+ pw.println(prefix + "\tbg overrideAlpha: " + mBarBackground.getOverrideAlpha());
+ pw.println(prefix + "\tbg color: " + mBarBackground.getColor());
+ pw.println(prefix + "\tbg frame: " + mBarBackground.getFrame());
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index ce281c3..170e018 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -43,8 +43,8 @@
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
-import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
@@ -269,8 +269,8 @@
foundTaskView,
foundTask,
taskContainer.getIconView().getDrawable(),
- taskContainer.getThumbnailViewDeprecated(),
- taskContainer.getThumbnailViewDeprecated().getThumbnail(),
+ taskContainer.getSnapshotView(),
+ taskContainer.getThumbnail(),
null /* intent */,
null /* user */,
info);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 15e4578..f6b1328 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -322,27 +322,45 @@
|| mImeVisibilityChecker.isImeVisible();
BubbleBarBubble bubbleToSelect = null;
- if (!update.removedBubbles.isEmpty()) {
- for (int i = 0; i < update.removedBubbles.size(); i++) {
- RemovedBubble removedBubble = update.removedBubbles.get(i);
- BubbleBarBubble bubble = mBubbles.remove(removedBubble.getKey());
- if (bubble != null) {
- mBubbleBarViewController.removeBubble(bubble);
- } else {
- Log.w(TAG, "trying to remove bubble that doesn't exist: "
- + removedBubble.getKey());
+
+ if (update.addedBubble != null && update.removedBubbles.size() == 1) {
+ // we're adding and removing a bubble at the same time. handle this as a single update.
+ RemovedBubble removedBubble = update.removedBubbles.get(0);
+ BubbleBarBubble bubbleToRemove = mBubbles.remove(removedBubble.getKey());
+ mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
+ if (bubbleToRemove != null) {
+ mBubbleBarViewController.addBubbleAndRemoveBubble(update.addedBubble,
+ bubbleToRemove, isExpanding, suppressAnimation);
+ } else {
+ mBubbleBarViewController.addBubble(update.addedBubble, isExpanding,
+ suppressAnimation);
+ Log.w(TAG, "trying to remove bubble that doesn't exist: " + removedBubble.getKey());
+ }
+ } else {
+ if (!update.removedBubbles.isEmpty()) {
+ for (int i = 0; i < update.removedBubbles.size(); i++) {
+ RemovedBubble removedBubble = update.removedBubbles.get(i);
+ BubbleBarBubble bubble = mBubbles.remove(removedBubble.getKey());
+ if (bubble != null) {
+ mBubbleBarViewController.removeBubble(bubble);
+ } else {
+ Log.w(TAG, "trying to remove bubble that doesn't exist: "
+ + removedBubble.getKey());
+ }
}
}
- }
- if (update.addedBubble != null) {
- mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
- mBubbleBarViewController.addBubble(update.addedBubble, isExpanding, suppressAnimation);
- if (isCollapsed) {
- // If we're collapsed, the most recently added bubble will be selected.
- bubbleToSelect = update.addedBubble;
+ if (update.addedBubble != null) {
+ mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
+ mBubbleBarViewController.addBubble(update.addedBubble, isExpanding,
+ suppressAnimation);
}
-
}
+
+ if (update.addedBubble != null && isCollapsed) {
+ // If we're collapsed, the most recently added bubble will be selected.
+ bubbleToSelect = update.addedBubble;
+ }
+
if (update.currentBubbles != null && !update.currentBubbles.isEmpty()) {
// Iterate in reverse because new bubbles are added in front and the list is in order.
for (int i = update.currentBubbles.size() - 1; i >= 0; i--) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 5708403..753237a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -626,6 +626,7 @@
/**
* Set bubble bar relative pivot value for X and Y, applied as a fraction of view width/height
* respectively. If the value is not in range of 0 to 1 it will be normalized.
+ *
* @param x relative X pivot value in range 0..1
* @param y relative Y pivot value in range 0..1
*/
@@ -665,7 +666,9 @@
}
/** Add a new bubble to the bubble bar. */
- public void addBubble(View bubble, FrameLayout.LayoutParams lp) {
+ public void addBubble(View bubble) {
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) mIconSize, (int) mIconSize,
+ Gravity.LEFT);
if (isExpanded()) {
// if we're expanded scale the new bubble in
bubble.setScaleX(0f);
@@ -702,14 +705,58 @@
}
}
+ /** Add a new bubble and remove an old bubble from the bubble bar. */
+ public void addBubbleAndRemoveBubble(View addedBubble, View removedBubble) {
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) mIconSize, (int) mIconSize,
+ Gravity.LEFT);
+ if (!isExpanded()) {
+ removeView(removedBubble);
+ addView(addedBubble, 0, lp);
+ return;
+ }
+ addedBubble.setScaleX(0f);
+ addedBubble.setScaleY(0f);
+ addView(addedBubble, 0, lp);
+
+ int indexOfSelectedBubble = indexOfChild(mSelectedBubbleView);
+ int indexOfBubbleToRemove = indexOfChild(removedBubble);
+
+ mBubbleAnimator = new BubbleAnimator(mIconSize, mExpandedBarIconsSpacing,
+ getChildCount(), mBubbleBarLocation.isOnLeft(isLayoutRtl()));
+ BubbleAnimator.Listener listener = new BubbleAnimator.Listener() {
+
+ @Override
+ public void onAnimationEnd() {
+ removeView(removedBubble);
+ updateWidth();
+ mBubbleAnimator = null;
+ }
+
+ @Override
+ public void onAnimationCancel() {
+ addedBubble.setScaleX(1);
+ addedBubble.setScaleY(1);
+ removedBubble.setScaleX(0);
+ removedBubble.setScaleY(0);
+ }
+
+ @Override
+ public void onAnimationUpdate(float animatedFraction) {
+ addedBubble.setScaleX(animatedFraction);
+ addedBubble.setScaleY(animatedFraction);
+ removedBubble.setScaleX(1 - animatedFraction);
+ removedBubble.setScaleY(1 - animatedFraction);
+ updateBubblesLayoutProperties(mBubbleBarLocation);
+ invalidate();
+ }
+ };
+ mBubbleAnimator.animateNewAndRemoveOld(indexOfSelectedBubble, indexOfBubbleToRemove,
+ listener);
+ }
+
// TODO: (b/280605790) animate it
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
- if (getChildCount() + 1 > MAX_BUBBLES) {
- // the last child view is the overflow bubble and we shouldn't remove that. remove the
- // second to last child view.
- removeViewInLayout(getChildAt(getChildCount() - 2));
- }
super.addView(child, index, params);
updateWidth();
updateBubbleAccessibilityStates();
@@ -913,7 +960,7 @@
final float iconAndSpacing = getScaledIconSize() + mExpandedBarIconsSpacing;
float translationX;
if (mBubbleAnimator != null && mBubbleAnimator.isRunning()) {
- return mBubbleAnimator.getExpandedBubbleTranslationX(bubbleIndex) + mBubbleBarPadding;
+ return mBubbleAnimator.getBubbleTranslationX(bubbleIndex) + mBubbleBarPadding;
} else if (onLeft) {
translationX = mBubbleBarPadding + (bubbleCount - bubbleIndex - 1) * iconAndSpacing;
} else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index da0826b..dbc78db 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -25,10 +25,8 @@
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
-import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -386,7 +384,7 @@
/**
* Removes the provided bubble from the bubble bar.
*/
- public void removeBubble(BubbleBarItem b) {
+ public void removeBubble(BubbleBarBubble b) {
if (b != null) {
mBarView.removeBubble(b.getView());
} else {
@@ -394,13 +392,23 @@
}
}
+ /** Adds a new bubble and removes an old bubble at the same time. */
+ public void addBubbleAndRemoveBubble(BubbleBarBubble addedBubble,
+ BubbleBarBubble removedBubble, boolean isExpanding, boolean suppressAnimation) {
+ mBarView.addBubbleAndRemoveBubble(addedBubble.getView(), removedBubble.getView());
+ addedBubble.getView().setOnClickListener(mBubbleClickListener);
+ mBubbleDragController.setupBubbleView(addedBubble.getView());
+ if (!suppressAnimation) {
+ animateBubbleNotification(addedBubble, isExpanding);
+ }
+ }
+
/**
* Adds the provided bubble to the bubble bar.
*/
public void addBubble(BubbleBarItem b, boolean isExpanding, boolean suppressAnimation) {
if (b != null) {
- mBarView.addBubble(
- b.getView(), new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
+ mBarView.addBubble(b.getView());
b.getView().setOnClickListener(mBubbleClickListener);
mBubbleDragController.setupBubbleView(b.getView());
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index 74ddf90..185f85f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -184,7 +184,8 @@
/** Whether bubbles are showing on the launcher home page. */
public boolean isBubblesShowingOnHome() {
- return mBubblesShowingOnHome;
+ boolean hasBubbles = mBarViewController != null && mBarViewController.hasBubbles();
+ return mBubblesShowingOnHome && hasBubbles;
}
// TODO: when tapping on an app in overview, this is a bit delayed compared to taskbar stashing
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt
index 7672743..8af8ffb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt
@@ -56,6 +56,20 @@
animator.start()
}
+ fun animateNewAndRemoveOld(
+ selectedBubbleIndex: Int,
+ removedBubbleIndex: Int,
+ listener: Listener
+ ) {
+ animator = createAnimator(listener)
+ state =
+ State.AddingAndRemoving(
+ selectedBubbleIndex = selectedBubbleIndex,
+ removedBubbleIndex = removedBubbleIndex
+ )
+ animator.start()
+ }
+
private fun createAnimator(listener: Listener): ValueAnimator {
val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATION_DURATION_MS)
animator.addUpdateListener { animation ->
@@ -83,28 +97,35 @@
}
/**
- * The translation X of the bubble at index [bubbleIndex] according to the progress of the
- * animation.
+ * The translation X of the bubble at index [bubbleIndex] when the bubble bar is expanded
+ * according to the progress of this animation.
*
* Callers should verify that the animation is running before calling this.
*
* @see isRunning
*/
- fun getExpandedBubbleTranslationX(bubbleIndex: Int): Float {
+ fun getBubbleTranslationX(bubbleIndex: Int): Float {
return when (val state = state) {
State.Idle -> 0f
is State.AddingBubble ->
- getExpandedBubbleTranslationXWhileScalingBubble(
+ getBubbleTranslationXWhileScalingBubble(
bubbleIndex = bubbleIndex,
scalingBubbleIndex = 0,
bubbleScale = animator.animatedFraction
)
is State.RemovingBubble ->
- getExpandedBubbleTranslationXWhileScalingBubble(
+ getBubbleTranslationXWhileScalingBubble(
bubbleIndex = bubbleIndex,
scalingBubbleIndex = state.bubbleIndex,
bubbleScale = 1 - animator.animatedFraction
)
+ is State.AddingAndRemoving ->
+ getBubbleTranslationXWhileAddingBubbleAtLimit(
+ bubbleIndex = bubbleIndex,
+ removedBubbleIndex = state.removedBubbleIndex,
+ addedBubbleScale = animator.animatedFraction,
+ removedBubbleScale = 1 - animator.animatedFraction
+ )
}
}
@@ -121,6 +142,14 @@
State.Idle -> 0f
is State.AddingBubble -> animator.animatedFraction
is State.RemovingBubble -> 1 - animator.animatedFraction
+ is State.AddingAndRemoving -> {
+ // since we're adding a bubble and removing another bubble, their sizes together
+ // equal to a single bubble. the width is the same as having bubbleCount - 1
+ // bubbles at full scale.
+ val totalSpace = (bubbleCount - 2) * expandedBarIconSpacing
+ val totalIconSize = (bubbleCount - 1) * iconSize
+ return totalIconSize + totalSpace
+ }
}
// When this animator is running the bubble bar is expanded so it's safe to assume that we
// have at least 2 bubbles, but should update the logic to support optional overflow.
@@ -144,7 +173,7 @@
State.Idle -> 0f
is State.AddingBubble -> {
val tx =
- getExpandedBubbleTranslationXWhileScalingBubble(
+ getBubbleTranslationXWhileScalingBubble(
bubbleIndex = state.selectedBubbleIndex,
scalingBubbleIndex = 0,
bubbleScale = animator.animatedFraction
@@ -152,6 +181,17 @@
tx + iconSize / 2f
}
is State.RemovingBubble -> getArrowPositionWhenRemovingBubble(state)
+ is State.AddingAndRemoving -> {
+ // we never remove the selected bubble, so the arrow stays pointing to its center
+ val tx =
+ getBubbleTranslationXWhileAddingBubbleAtLimit(
+ bubbleIndex = state.selectedBubbleIndex,
+ removedBubbleIndex = state.removedBubbleIndex,
+ addedBubbleScale = animator.animatedFraction,
+ removedBubbleScale = 1 - animator.animatedFraction
+ )
+ tx + iconSize / 2f
+ }
}
}
@@ -160,7 +200,7 @@
// if we're not removing the selected bubble, the selected bubble doesn't change so just
// return the translation X of the selected bubble and add half icon
val tx =
- getExpandedBubbleTranslationXWhileScalingBubble(
+ getBubbleTranslationXWhileScalingBubble(
bubbleIndex = state.selectedBubbleIndex,
scalingBubbleIndex = state.bubbleIndex,
bubbleScale = 1 - animator.animatedFraction
@@ -208,7 +248,7 @@
* @param scalingBubbleIndex the index of the bubble that is animating
* @param bubbleScale the current scale of the animating bubble
*/
- private fun getExpandedBubbleTranslationXWhileScalingBubble(
+ private fun getBubbleTranslationXWhileScalingBubble(
bubbleIndex: Int,
scalingBubbleIndex: Int,
bubbleScale: Float
@@ -256,6 +296,68 @@
}
}
+ private fun getBubbleTranslationXWhileAddingBubbleAtLimit(
+ bubbleIndex: Int,
+ removedBubbleIndex: Int,
+ addedBubbleScale: Float,
+ removedBubbleScale: Float
+ ): Float {
+ val iconAndSpacing = iconSize + expandedBarIconSpacing
+ // the bubbles are scaling from the center, so we need to adjust their translation so
+ // that the distance to the adjacent bubble scales at the same rate.
+ val addedBubblePivotAdjustment = -(1 - addedBubbleScale) * iconSize / 2f
+ val removedBubblePivotAdjustment = -(1 - removedBubbleScale) * iconSize / 2f
+
+ return if (onLeft) {
+ // this is how many bubbles there are to the left of the current bubble.
+ // when the bubble bar is on the right the added bubble is the right-most bubble so it
+ // doesn't affect the translation of any other bubble.
+ // when the removed bubble is to the left of the current bubble, we need to subtract it
+ // from bubblesToLeft and use removedBubbleScale instead when calculating the
+ // translation.
+ val bubblesToLeft = bubbleCount - bubbleIndex - 1
+ when {
+ bubbleIndex == 0 ->
+ // this is the added bubble and it's the right-most bubble. account for all the
+ // other bubbles -- including the removed bubble -- and adjust for the added
+ // bubble pivot.
+ (bubblesToLeft - 1 + removedBubbleScale) * iconAndSpacing +
+ addedBubblePivotAdjustment
+ bubbleIndex < removedBubbleIndex ->
+ // the removed bubble is to the left so account for it
+ (bubblesToLeft - 1 + removedBubbleScale) * iconAndSpacing
+ bubbleIndex == removedBubbleIndex -> {
+ // this is the removed bubble. all the bubbles to the left are at full scale
+ // but we need to scale the spacing between the removed bubble and the bubble to
+ // its left because the removed bubble disappears towards the left side
+ val totalIconSize = bubblesToLeft * iconSize
+ val totalSpacing =
+ (bubblesToLeft - 1 + removedBubbleScale) * expandedBarIconSpacing
+ totalIconSize + totalSpacing + removedBubblePivotAdjustment
+ }
+ else ->
+ // both added and removed bubbles are to the right so they don't affect the tx
+ bubblesToLeft * iconAndSpacing
+ }
+ } else {
+ when {
+ bubbleIndex == 0 -> addedBubblePivotAdjustment // we always add bubbles at index 0
+ bubbleIndex < removedBubbleIndex ->
+ // the bar is on the right and the removed bubble is on the right. the current
+ // bubble is unaffected by the removed bubble. only need to factor in the added
+ // bubble's scale.
+ iconAndSpacing * (bubbleIndex - 1 + addedBubbleScale)
+ bubbleIndex == removedBubbleIndex ->
+ // the bar is on the right, and this is the animating bubble.
+ iconAndSpacing * (bubbleIndex - 1 + addedBubbleScale) +
+ removedBubblePivotAdjustment
+ else ->
+ // both the added and the removed bubbles are to the left of the current bubble
+ iconAndSpacing * (bubbleIndex - 2 + addedBubbleScale + removedBubbleScale)
+ }
+ }
+ }
+
val isRunning: Boolean
get() = state != State.Idle
@@ -277,6 +379,10 @@
/** Whether the bubble being removed is also the last bubble. */
val removingLastBubble: Boolean
) : State
+
+ /** A new bubble is being added and an old bubble is being removed from the bubble bar. */
+ data class AddingAndRemoving(val selectedBubbleIndex: Int, val removedBubbleIndex: Int) :
+ State
}
/** Callbacks for the animation. */
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 037f2f6..be6f690 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -1200,7 +1200,6 @@
: Display.DEFAULT_DISPLAY);
activityOptions.options.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
- addLaunchCookie(item, activityOptions.options);
return activityOptions;
}
@@ -1225,19 +1224,6 @@
}
/**
- * Adds a new launch cookie for the activity launch if supported.
- *
- * @param info the item info for the launch
- * @param opts the options to set the launchCookie on.
- */
- public void addLaunchCookie(ItemInfo info, ActivityOptions opts) {
- IBinder launchCookie = getLaunchCookie(info);
- if (launchCookie != null) {
- opts.setLaunchCookie(launchCookie);
- }
- }
-
- /**
* Return a new launch cookie for the activity launch if supported.
*
* @param info the item info for the launch
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 4bc3c16..3c7f335 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -252,7 +252,7 @@
mTaskBeingDragged, maxDuration, currentInterpolator);
// Since the thumbnail is what is filling the screen, based the end displacement on it.
- View thumbnailView = mTaskBeingDragged.getFirstThumbnailViewDeprecated();
+ View thumbnailView = mTaskBeingDragged.getFirstSnapshotView();
mTempCords[1] = orientationHandler.getSecondaryDimension(thumbnailView);
dl.getDescendantCoordRelativeToSelf(thumbnailView, mTempCords);
mEndDisplacement = secondaryLayerDimension - mTempCords[1];
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 93f72fc..bdbe826 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -138,8 +138,8 @@
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
+import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
-import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -1205,17 +1205,28 @@
}
/** @return Whether this was the task we were waiting to appear, and thus handled it. */
- protected boolean handleTaskAppeared(RemoteAnimationTarget[] appearedTaskTarget) {
+ protected boolean handleTaskAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets,
+ @NonNull ActiveGestureLog.CompoundString failureReason) {
if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
+ failureReason.append("State handler was invalidated");
return false;
}
- boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTarget).anyMatch(
- mGestureState.mLastStartedTaskIdPredicate);
- if (mStateCallback.hasStates(STATE_START_NEW_TASK) && hasStartedTaskBefore) {
- reset();
- return true;
+ boolean stateStartNewTaskSet = mStateCallback.hasStates(STATE_START_NEW_TASK);
+ if (!stateStartNewTaskSet || !hasStartedTaskBefore(appearedTaskTargets)) {
+ if (!stateStartNewTaskSet) {
+ failureReason.append("STATE_START_NEW_TASK was never set");
+ } else {
+ TaskInfo taskInfo = appearedTaskTargets[0].taskInfo;
+ failureReason.append("Unexpected task appeared")
+ .append(" id=")
+ .append(taskInfo.taskId)
+ .append(" pkg=")
+ .append(taskInfo.baseIntent.getComponent().getPackageName());
+ }
+ return false;
}
- return false;
+ reset();
+ return true;
}
private float dpiFromPx(float pixels) {
@@ -1796,6 +1807,8 @@
&& (windowRotation == ROTATION_90 || windowRotation == ROTATION_270)) {
builder.setFromRotation(mRemoteTargetHandles[0].getTaskViewSimulator(), windowRotation,
taskInfo.displayCutoutInsets);
+ } else if (taskInfo.displayCutoutInsets != null) {
+ builder.setDisplayCutoutInsets(taskInfo.displayCutoutInsets);
}
final SwipePipToHomeAnimator swipePipToHomeAnimator = builder.build();
AnimatorPlaybackController activityAnimationToHome =
@@ -2400,14 +2413,18 @@
}
}
+ private boolean hasStartedTaskBefore(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
+ return Arrays.stream(appearedTaskTargets)
+ .anyMatch(mGestureState.mLastStartedTaskIdPredicate);
+ }
+
@Override
public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
if (mRecentsAnimationController == null) {
return;
}
- boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch(
- mGestureState.mLastStartedTaskIdPredicate);
- if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED) && !hasStartedTaskBefore) {
+ if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED)
+ && !hasStartedTaskBefore(appearedTaskTargets)) {
// This is a special case, if a task is started mid-gesture that wasn't a part of a
// previous quickswitch task launch, then cancel the animation back to the app
RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
@@ -2421,7 +2438,11 @@
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
- if (!handleTaskAppeared(appearedTaskTargets)) {
+ ActiveGestureLog.CompoundString handleTaskFailureReason =
+ new ActiveGestureLog.CompoundString("handleTaskAppeared check failed: ");
+ if (!handleTaskAppeared(appearedTaskTargets, handleTaskFailureReason)) {
+ ActiveGestureLog.INSTANCE.addLog(handleTaskFailureReason);
+ finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
Optional<RemoteAnimationTarget> taskTargetOptional =
diff --git a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
index 9c188f3..45e5554 100644
--- a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
+++ b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
@@ -23,7 +23,7 @@
import com.android.launcher3.popup.SystemShortcut
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsViewContainer
-import com.android.quickstep.views.TaskView.TaskContainer
+import com.android.quickstep.views.TaskContainer
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.shared.DesktopModeStatus
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 625b6c6..9b66154 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -64,6 +64,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
import com.android.quickstep.util.TransformParams;
@@ -170,14 +171,16 @@
}
@Override
- protected boolean handleTaskAppeared(RemoteAnimationTarget[] appearedTaskTarget) {
+ protected boolean handleTaskAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget,
+ @NonNull ActiveGestureLog.CompoundString failureReason) {
if (mActiveAnimationFactory != null
&& mActiveAnimationFactory.handleHomeTaskAppeared(appearedTaskTarget)) {
mActiveAnimationFactory = null;
+ failureReason.append("(FallbackSwipeHandler) should be handled as home task appeared");
return false;
}
- return super.handleTaskAppeared(appearedTaskTarget);
+ return super.handleTaskAppeared(appearedTaskTarget, failureReason);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index a71e314..9c64576 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -36,6 +36,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
import com.android.launcher3.R;
import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -101,7 +102,7 @@
mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
} catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
- mUserPreferenceChangeReceiver.register(mContext, ACTION_PREFERRED_ACTIVITY_CHANGED);
+ mUserPreferenceChangeReceiver.registerAsync(mContext, ACTION_PREFERRED_ACTIVITY_CHANGED);
updateOverviewTargets();
}
@@ -114,6 +115,8 @@
mOverviewChangeListener = overviewChangeListener;
}
+ /** Called on {@link TouchInteractionService#onSystemUiFlagsChanged} */
+ @UiThread
public void onSystemUiStateChanged() {
if (mDeviceState.isHomeDisabled() != mIsHomeDisabled) {
updateOverviewTargets();
@@ -128,6 +131,7 @@
* Update overview intent and {@link BaseActivityInterface} based off the current launcher home
* component.
*/
+ @UiThread
private void updateOverviewTargets() {
ComponentName defaultHome = PackageManagerWrapper.getInstance()
.getHomeActivities(new ArrayList<>());
@@ -187,8 +191,9 @@
unregisterOtherHomeAppUpdateReceiver();
mUpdateRegisteredPackage = defaultHome.getPackageName();
- mOtherHomeAppUpdateReceiver.registerPkgActions(mContext, mUpdateRegisteredPackage,
- ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED);
+ mOtherHomeAppUpdateReceiver.registerPkgActionsAsync(
+ mContext, mUpdateRegisteredPackage, ACTION_PACKAGE_ADDED,
+ ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED);
}
}
mOverviewChangeListener.accept(mIsHomeAndOverviewSame);
@@ -198,13 +203,13 @@
* Clean up any registered receivers.
*/
public void onDestroy() {
- mContext.unregisterReceiver(mUserPreferenceChangeReceiver);
+ mUserPreferenceChangeReceiver.unregisterReceiverSafelyAsync(mContext);
unregisterOtherHomeAppUpdateReceiver();
}
private void unregisterOtherHomeAppUpdateReceiver() {
if (mUpdateRegisteredPackage != null) {
- mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
+ mOtherHomeAppUpdateReceiver.unregisterReceiverSafelyAsync(mContext);
mUpdateRegisteredPackage = null;
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 66091d4..3d4167a 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -44,6 +44,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
@@ -324,7 +325,9 @@
// leftover TYPE_FREEFORM tasks created when flag was on should be ignored.
if (enableDesktopWindowingMode()) {
GroupTask desktopTask = createDesktopTask(rawTask);
- allTasks.add(desktopTask);
+ if (desktopTask != null) {
+ allTasks.add(desktopTask);
+ }
}
continue;
}
@@ -368,8 +371,13 @@
return allTasks;
}
- private DesktopTask createDesktopTask(GroupedRecentTaskInfo recentTaskInfo) {
+ private @Nullable DesktopTask createDesktopTask(GroupedRecentTaskInfo recentTaskInfo) {
ArrayList<Task> tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size());
+ int[] minimizedTaskIds = recentTaskInfo.getMinimizedTaskIds();
+ if (minimizedTaskIds.length == recentTaskInfo.getTaskInfoList().size()) {
+ // All Tasks are minimized -> don't create a DesktopTask
+ return null;
+ }
for (ActivityManager.RecentTaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
Task.TaskKey key = new Task.TaskKey(taskInfo);
Task task = Task.from(key, taskInfo, false);
@@ -377,6 +385,8 @@
task.positionInParent = taskInfo.positionInParent;
task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
task.isVisible = taskInfo.isVisible;
+ task.isMinimized =
+ Arrays.stream(minimizedTaskIds).anyMatch(taskId -> taskId == taskInfo.taskId);
tasks.add(task);
}
return new DesktopTask(tasks);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 7adce74..a7d3890 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -33,6 +33,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
@@ -412,7 +413,8 @@
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED
| SYSUI_STATE_MAGNIFICATION_OVERLAP
| SYSUI_STATE_DEVICE_DREAMING
- | SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
+ | SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION
+ | SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
return (gestureDisablingStates & mSystemUiStateFlags) == 0 && homeOrOverviewEnabled;
}
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index b183ae3..b7f3f65 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -44,9 +44,8 @@
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
-import com.android.quickstep.views.TaskThumbnailViewDeprecated;
+import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
-import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -134,20 +133,20 @@
mApplicationContext = taskContainer.getTaskView().getContext().getApplicationContext();
mTaskContainer = taskContainer;
mImageApi = new ImageActionsApi(
- mApplicationContext, mTaskContainer.getThumbnailViewDeprecated()::getThumbnail);
+ mApplicationContext, mTaskContainer::getThumbnail);
}
protected T getActionsView() {
if (mActionsView == null) {
mActionsView = BaseActivity.fromContext(
- mTaskContainer.getThumbnailViewDeprecated().getContext()).findViewById(
+ mTaskContainer.getTaskView().getContext()).findViewById(
R.id.overview_actions_view);
}
return mActionsView;
}
- public TaskThumbnailViewDeprecated getThumbnailView() {
- return mTaskContainer.getThumbnailViewDeprecated();
+ public TaskView getTaskView() {
+ return mTaskContainer.getTaskView();
}
/**
@@ -159,8 +158,7 @@
if (thumbnail != null) {
getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
- boolean isAllowedByPolicy =
- mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot();
+ boolean isAllowedByPolicy = mTaskContainer.isRealSnapshot();
getActionsView().setCallbacks(new OverlayUICallbacksImpl(isAllowedByPolicy, task));
}
}
@@ -172,7 +170,7 @@
*/
public void endLiveTileMode(@NonNull Runnable callback) {
RecentsView recentsView =
- mTaskContainer.getThumbnailViewDeprecated().getTaskView().getRecentsView();
+ mTaskContainer.getTaskView().getRecentsView();
// Task has already been dismissed
if (recentsView == null) return;
recentsView.switchToScreenshot(
@@ -185,8 +183,8 @@
*/
@SuppressLint("NewApi")
protected void saveScreenshot(Task task) {
- if (mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot()) {
- mImageApi.saveScreenshot(mTaskContainer.getThumbnailViewDeprecated().getThumbnail(),
+ if (mTaskContainer.isRealSnapshot()) {
+ mImageApi.saveScreenshot(mTaskContainer.getThumbnail(),
getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key);
} else {
showBlockedByPolicyMessage();
@@ -194,17 +192,14 @@
}
protected void enterSplitSelect() {
- RecentsView overviewPanel =
- mTaskContainer.getThumbnailViewDeprecated().getTaskView().getRecentsView();
+ RecentsView overviewPanel = mTaskContainer.getTaskView().getRecentsView();
// Task has already been dismissed
if (overviewPanel == null) return;
- overviewPanel.initiateSplitSelect(
- mTaskContainer.getThumbnailViewDeprecated().getTaskView());
+ overviewPanel.initiateSplitSelect(mTaskContainer.getTaskView());
}
protected void saveAppPair() {
- GroupedTaskView taskView =
- (GroupedTaskView) mTaskContainer.getThumbnailViewDeprecated().getTaskView();
+ GroupedTaskView taskView = (GroupedTaskView) mTaskContainer.getTaskView();
taskView.getRecentsView().getSplitSelectController().getAppPairsController()
.saveAppPair(taskView);
}
@@ -250,11 +245,11 @@
*/
public Rect getTaskSnapshotBounds() {
int[] location = new int[2];
- mTaskContainer.getThumbnailViewDeprecated().getLocationOnScreen(location);
+ mTaskContainer.getSnapshotView().getLocationOnScreen(location);
return new Rect(location[0], location[1],
- mTaskContainer.getThumbnailViewDeprecated().getWidth() + location[0],
- mTaskContainer.getThumbnailViewDeprecated().getHeight() + location[1]);
+ mTaskContainer.getSnapshotView().getWidth() + location[0],
+ mTaskContainer.getSnapshotView().getHeight() + location[1]);
}
/**
@@ -264,7 +259,7 @@
*/
@RequiresApi(api = Build.VERSION_CODES.Q)
public Insets getTaskSnapshotInsets() {
- return mTaskContainer.getThumbnailViewDeprecated().getScaledInsets();
+ return mTaskContainer.getScaledInsets();
}
/**
@@ -275,14 +270,14 @@
protected void showBlockedByPolicyMessage() {
ActivityContext activityContext = ActivityContext.lookupContext(
- mTaskContainer.getThumbnailViewDeprecated().getContext());
+ mTaskContainer.getTaskView().getContext());
String message = activityContext.getStringCache() != null
? activityContext.getStringCache().disabledByAdminMessage
- : mTaskContainer.getThumbnailViewDeprecated().getContext().getString(
+ : mTaskContainer.getTaskView().getContext().getString(
R.string.blocked_by_policy);
Snackbar.show(BaseActivity.fromContext(
- mTaskContainer.getThumbnailViewDeprecated().getContext()), message, null);
+ mTaskContainer.getTaskView().getContext()), message, null);
}
/** Called when the snapshot has updated its full screen drawing parameters. */
@@ -304,8 +299,7 @@
@Override
public void onClick(View view) {
- saveScreenshot(
- mTaskContainer.getThumbnailViewDeprecated().getTaskView().getFirstTask());
+ saveScreenshot(mTaskContainer.getTaskView().getFirstTask());
dismissTaskMenuView();
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index f7e1b4e..77124bf 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -20,6 +20,7 @@
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.view.Surface.ROTATION_0;
+import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
@@ -55,9 +56,8 @@
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
-import com.android.quickstep.views.TaskThumbnailViewDeprecated;
+import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
-import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
@@ -174,7 +174,7 @@
private Handler mHandler;
private final RecentsView mRecentsView;
- private final TaskThumbnailViewDeprecated mThumbnailView;
+ private final TaskContainer mTaskContainer;
private final TaskView mTaskView;
private final LauncherEvent mLauncherEvent;
@@ -186,7 +186,7 @@
mHandler = new Handler(Looper.getMainLooper());
mTaskView = taskContainer.getTaskView();
mRecentsView = container.getOverviewPanel();
- mThumbnailView = taskContainer.getThumbnailViewDeprecated();
+ mTaskContainer = taskContainer;
}
@Override
@@ -220,20 +220,25 @@
};
final int[] position = new int[2];
- mThumbnailView.getLocationOnScreen(position);
- final int width = (int) (mThumbnailView.getWidth() * mTaskView.getScaleX());
- final int height = (int) (mThumbnailView.getHeight() * mTaskView.getScaleY());
+ View snapShotView = mTaskContainer.getSnapshotView();
+ snapShotView.getLocationOnScreen(position);
+ final int width = (int) (snapShotView.getWidth() * mTaskView.getScaleX());
+ final int height = (int) (snapShotView.getHeight() * mTaskView.getScaleY());
final Rect taskBounds = new Rect(position[0], position[1],
position[0] + width, position[1] + height);
// Take the thumbnail of the task without a scrim and apply it back after
- float alpha = mThumbnailView.getDimAlpha();
// TODO(b/348643341) add ability to get override the scrim for this Bitmap retrieval
- mThumbnailView.setDimAlpha(0);
+ float alpha = 0f;
+ if (!enableRefactorTaskThumbnail()) {
+ alpha = mTaskContainer.getThumbnailViewDeprecated().getDimAlpha();
+ mTaskContainer.getThumbnailViewDeprecated().setDimAlpha(0);
+ }
Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
- taskBounds.width(), taskBounds.height(), mThumbnailView, 1f,
- Color.BLACK);
- mThumbnailView.setDimAlpha(alpha);
+ taskBounds.width(), taskBounds.height(), snapShotView, 1f, Color.BLACK);
+ if (!enableRefactorTaskThumbnail()) {
+ mTaskContainer.getThumbnailViewDeprecated().setDimAlpha(alpha);
+ }
AppTransitionAnimationSpecsFuture future =
new AppTransitionAnimationSpecsFuture(mHandler) {
@@ -493,18 +498,8 @@
TaskContainer taskContainer) {
boolean isTablet = container.getDeviceProfile().isTablet;
boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview();
- // Extra conditions if it's not grid-only overview
if (!isGridOnlyOverview) {
- RecentsOrientedState orientedState = taskContainer.getTaskView().getOrientedState();
- boolean isFakeLandscape = !orientedState.isRecentsActivityRotationAllowed()
- && orientedState.getTouchRotation() != ROTATION_0;
- if (!isFakeLandscape) {
- return null;
- }
- // Disallow "Select" when swiping up from landscape due to rotated thumbnail.
- if (orientedState.getDisplayRotation() != ROTATION_0) {
- return null;
- }
+ return null;
}
SystemShortcut modalStateSystemShortcut =
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index ecd84f8..bd44283 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -80,7 +80,6 @@
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskThumbnailViewDeprecated;
import com.android.quickstep.views.TaskView;
import com.android.systemui.animation.RemoteAnimationTargetCompat;
import com.android.systemui.shared.recents.model.Task;
@@ -334,7 +333,7 @@
// During animation we apply transformation on the thumbnailView (and not the rootView)
// to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
// Mt K(0)` K(t) Mt`
- TaskThumbnailViewDeprecated[] thumbnails = v.getThumbnailViews();
+ View[] thumbnails = v.getSnapshotViews();
// In case simulator copies and thumbnail size do no match, ensure we get the lesser.
// This ensures we do not create arrays with empty elements or attempt to references
@@ -344,7 +343,7 @@
Matrix[] mt = new Matrix[matrixSize];
Matrix[] mti = new Matrix[matrixSize];
for (int i = 0; i < matrixSize; i++) {
- TaskThumbnailViewDeprecated ttv = thumbnails[i];
+ View ttv = thumbnails[i];
RectF localBounds = new RectF(0, 0, ttv.getWidth(), ttv.getHeight());
float[] tvBoundsMapped = new float[]{0, 0, ttv.getWidth(), ttv.getHeight()};
getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
@@ -391,7 +390,7 @@
out.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- for (TaskThumbnailViewDeprecated ttv : thumbnails) {
+ for (View ttv : thumbnails) {
ttv.setAnimationMatrix(null);
}
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index bfdc3df..ee93cd6 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -70,6 +70,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.util.ArraySet;
@@ -126,6 +127,7 @@
import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
import com.android.systemui.shared.system.InputConsumerController;
@@ -330,6 +332,49 @@
});
}
+ @BinderThread
+ @Override
+ public void updateWallpaperVisibility(int displayId, boolean visible) {
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis ->
+ executeForTaskbarManager(
+ taskbarManager -> taskbarManager.setWallpaperVisible(visible))
+ ));
+ }
+
+ @BinderThread
+ @Override
+ public void checkNavBarModes() {
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis ->
+ executeForTaskbarManager(TaskbarManager::checkNavBarModes)
+ ));
+ }
+
+ @BinderThread
+ @Override
+ public void finishBarAnimations() {
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis ->
+ executeForTaskbarManager(TaskbarManager::finishBarAnimations)
+ ));
+ }
+
+ @BinderThread
+ @Override
+ public void touchAutoDim(boolean reset) {
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis ->
+ executeForTaskbarManager(taskbarManager -> taskbarManager.touchAutoDim(reset))
+ ));
+ }
+
+ @BinderThread
+ @Override
+ public void transitionTo(@BarTransitions.TransitionMode int barMode,
+ boolean animate) {
+ MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis ->
+ executeForTaskbarManager(
+ taskbarManager -> taskbarManager.transitionTo(barMode, animate))
+ ));
+ }
+
/**
* Preloads the Overview activity.
* <p>
@@ -359,6 +404,12 @@
}
@Override
+ public void onTransitionModeUpdated(int barMode, boolean checkBarModes) {
+ executeForTaskbarManager(taskbarManager ->
+ taskbarManager.onTransitionModeUpdated(barMode, checkBarModes));
+ }
+
+ @Override
public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
executeForTaskbarManager(taskbarManager ->
taskbarManager.onNavButtonsDarkIntensityChanged(darkIntensity));
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 6f9cbfd..c3d74bb 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -22,7 +22,6 @@
import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH;
import static com.android.launcher3.model.data.AppInfo.PACKAGE_KEY_COMPARATOR;
-import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SUPPORTS_MULTI_INSTANCE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -45,8 +44,6 @@
import com.android.internal.jank.Cuj;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.R;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.apppairs.AppPairIcon;
@@ -69,6 +66,7 @@
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.views.GroupedTaskView;
+import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
@@ -135,7 +133,7 @@
}
GroupedTaskView gtv = (GroupedTaskView) taskView;
- List<TaskView.TaskContainer> containers = gtv.getTaskContainers();
+ List<TaskContainer> containers = gtv.getTaskContainers();
ComponentKey taskKey1 = TaskUtils.getLaunchComponentKeyForTask(
containers.get(0).getTask().key);
ComponentKey taskKey2 = TaskUtils.getLaunchComponentKeyForTask(
@@ -172,7 +170,7 @@
*/
public void saveAppPair(GroupedTaskView gtv) {
InteractionJankMonitorWrapper.begin(gtv, Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
- List<TaskView.TaskContainer> containers = gtv.getTaskContainers();
+ List<TaskContainer> containers = gtv.getTaskContainers();
WorkspaceItemInfo recentsInfo1 = containers.get(0).getItemInfo();
WorkspaceItemInfo recentsInfo2 = containers.get(1).getItemInfo();
WorkspaceItemInfo app1 = resolveAppPairWorkspaceInfo(recentsInfo1);
diff --git a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
index cda87c0..c26fc0c5 100644
--- a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
+++ b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
@@ -18,8 +18,6 @@
import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
import static android.content.Intent.ACTION_TIME_CHANGED;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -64,9 +62,7 @@
private AsyncClockEventDelegate(Context context) {
super(context);
mContext = context;
-
- UI_HELPER_EXECUTOR.execute(() ->
- mReceiver.register(mContext, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED));
+ mReceiver.registerAsync(mContext, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED);
}
@Override
@@ -127,6 +123,6 @@
public void close() {
mDestroyed = true;
SettingsCache.INSTANCE.get(mContext).unregister(mFormatUri, this);
- UI_HELPER_EXECUTOR.execute(() -> mReceiver.unregisterReceiverSafely(mContext));
+ mReceiver.unregisterReceiverSafelyAsync(mContext);
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 7ea04b1..49e1c88 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -43,6 +43,7 @@
import com.android.app.animation.Interpolators
import com.android.launcher3.DeviceProfile
import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.InsettableFrameLayout
import com.android.launcher3.QuickstepTransitionManager
import com.android.launcher3.R
@@ -67,9 +68,9 @@
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.SplitInstructionsView
+import com.android.quickstep.views.TaskContainer
import com.android.quickstep.views.TaskThumbnailViewDeprecated
import com.android.quickstep.views.TaskView
-import com.android.quickstep.views.TaskView.TaskContainer
import com.android.quickstep.views.TaskViewIcon
import com.android.wm.shell.shared.TransitionUtil
import java.util.Optional
@@ -118,8 +119,8 @@
if (container.task.getKey().getId() == splitSelectStateController.initialTaskId) {
val drawable = getDrawable(container.iconView, splitSelectSource)
return SplitAnimInitProps(
- container.thumbnailViewDeprecated,
- container.thumbnailViewDeprecated.thumbnail,
+ container.snapshotView,
+ container.thumbnail,
drawable!!,
fadeWithThumbnail = true,
isStagedTask = true,
@@ -137,8 +138,8 @@
taskView.taskContainers.first().let {
val drawable = getDrawable(it.iconView, splitSelectSource)
return SplitAnimInitProps(
- it.thumbnailViewDeprecated,
- it.thumbnailViewDeprecated.thumbnail,
+ it.snapshotView,
+ it.thumbnail,
drawable!!,
fadeWithThumbnail = true,
isStagedTask = true,
@@ -165,27 +166,37 @@
/**
* 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 [TaskThumbnailViewDeprecated]'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.
+ * with [TaskContainer]'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 taskContainer 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 [taskContainer])
*/
fun addInitialSplitFromPair(
- taskIdAttributeContainer: TaskContainer,
+ taskContainer: TaskContainer,
builder: PendingAnimation,
deviceProfile: DeviceProfile,
taskViewWidth: Int,
taskViewHeight: Int,
isPrimaryTaskSplitting: Boolean
) {
- val thumbnail = taskIdAttributeContainer.thumbnailViewDeprecated
- val iconView: View = taskIdAttributeContainer.iconView.asView()
- builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailViewDeprecated.SPLASH_ALPHA, 1f))
- thumbnail.setShowSplashForSplitSelection(true)
+ val snapshot = taskContainer.snapshotView
+ val iconView: View = taskContainer.iconView.asView()
+ // TODO(334826842): Switch to splash state in TaskThumbnailView
+ if (!enableRefactorTaskThumbnail()) {
+ val thumbnailViewDeprecated = taskContainer.thumbnailViewDeprecated
+ builder.add(
+ ObjectAnimator.ofFloat(
+ thumbnailViewDeprecated,
+ TaskThumbnailViewDeprecated.SPLASH_ALPHA,
+ 1f
+ )
+ )
+ thumbnailViewDeprecated.setShowSplashForSplitSelection(true)
+ }
// With the new `IconAppChipView`, we always want to keep the chip pinned to the
// top left of the task / thumbnail.
if (enableOverviewIconMenu()) {
@@ -202,14 +213,10 @@
}
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
+ val centerThumbnailTranslationX: Float = (taskViewWidth - snapshot.width) / 2f
+ val finalScaleX: Float = taskViewWidth.toFloat() / snapshot.width
builder.add(
- ObjectAnimator.ofFloat(
- thumbnail,
- TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_X,
- centerThumbnailTranslationX
- )
+ ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, centerThumbnailTranslationX)
)
if (!enableOverviewIconMenu()) {
// icons are anchored from Gravity.END, so need to use negative translation
@@ -218,21 +225,15 @@
ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, -centerIconTranslationX)
)
}
- builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_X, finalScaleX))
+ builder.add(ObjectAnimator.ofFloat(snapshot, 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
+ snapshot.scaleY = 1f
val translateYResetVal: Float =
if (!isPrimaryTaskSplitting) 0f
else deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
- builder.add(
- ObjectAnimator.ofFloat(
- thumbnail,
- TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_Y,
- translateYResetVal
- )
- )
+ builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, translateYResetVal))
} else {
val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
// Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
@@ -247,36 +248,26 @@
// thumbnail needs to take that into account. We should migrate to only using
// translations otherwise this asymmetry causes problems..
if (isPrimaryTaskSplitting) {
- centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
+ centerThumbnailTranslationY = (thumbnailSize - snapshot.height) / 2f
centerThumbnailTranslationY +=
deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
} else {
- centerThumbnailTranslationY = (thumbnailSize - thumbnail.height) / 2f
+ centerThumbnailTranslationY = (thumbnailSize - snapshot.height) / 2f
}
- val finalScaleY: Float = thumbnailSize.toFloat() / thumbnail.height
+ val finalScaleY: Float = thumbnailSize.toFloat() / snapshot.height
builder.add(
- ObjectAnimator.ofFloat(
- thumbnail,
- TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_Y,
- centerThumbnailTranslationY
- )
+ ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, centerThumbnailTranslationY)
)
if (!enableOverviewIconMenu()) {
// icons are anchored from Gravity.END, so need to use negative translation
builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, 0f))
}
- builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_Y, finalScaleY))
+ builder.add(ObjectAnimator.ofFloat(snapshot, View.SCALE_Y, finalScaleY))
// Reset other dimensions
- thumbnail.scaleX = 1f
- builder.add(
- ObjectAnimator.ofFloat(
- thumbnail,
- TaskThumbnailViewDeprecated.SPLIT_SELECT_TRANSLATE_X,
- 0f
- )
- )
+ snapshot.scaleX = 1f
+ builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, 0f))
}
}
@@ -495,7 +486,8 @@
depthController: DepthController?,
info: TransitionInfo?,
t: Transaction?,
- finishCallback: Runnable
+ finishCallback: Runnable,
+ cornerRadius: Float
) {
if (info == null && t == null) {
// (Legacy animation) Tapping a split tile in Overview
@@ -559,7 +551,8 @@
"unexpected null"
}
- composeFadeInSplitLaunchAnimator(initialTaskId, secondTaskId, info, t, finishCallback)
+ composeFadeInSplitLaunchAnimator(initialTaskId, secondTaskId, info, t, finishCallback,
+ cornerRadius)
}
}
@@ -1033,11 +1026,12 @@
*/
@VisibleForTesting
fun composeFadeInSplitLaunchAnimator(
- initialTaskId: Int,
- secondTaskId: Int,
- transitionInfo: TransitionInfo,
- t: Transaction,
- finishCallback: Runnable
+ initialTaskId: Int,
+ secondTaskId: Int,
+ transitionInfo: TransitionInfo,
+ t: Transaction,
+ finishCallback: Runnable,
+ cornerRadius: Float
) {
var splitRoot1: Change? = null
var splitRoot2: Change? = null
@@ -1115,6 +1109,7 @@
override fun onAnimationStart(animation: Animator) {
for (leash in openingTargets) {
animTransaction.show(leash).setAlpha(leash, 0.0f)
+ animTransaction.setCornerRadius(leash, cornerRadius);
}
animTransaction.apply()
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
index 38bbe60..4820c35 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
@@ -31,29 +31,13 @@
null
} else {
SplitConfigurationOptions.SplitBounds(
- shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds,
- shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId,
+ shellSplitBounds.leftTopBounds,
+ shellSplitBounds.rightBottomBounds,
+ shellSplitBounds.leftTopTaskId,
+ shellSplitBounds.rightBottomTaskId,
shellSplitBounds.snapPosition
)
}
}
-
- /** Converts the launcher version of SplitBounds to the shell version */
- @JvmStatic
- fun convertLauncherSplitBoundsToShell(
- launcherSplitBounds: SplitConfigurationOptions.SplitBounds?
- ): SplitBounds? {
- return if (launcherSplitBounds == null) {
- null
- } else {
- SplitBounds(
- launcherSplitBounds.leftTopBounds,
- launcherSplitBounds.rightBottomBounds,
- launcherSplitBounds.leftTopTaskId,
- launcherSplitBounds.rightBottomTaskId,
- launcherSplitBounds.snapPosition
- )
- }
- }
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 7e7c794..d906bb3 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -104,6 +104,7 @@
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.splitscreen.ISplitSelectListener;
@@ -778,7 +779,8 @@
info, t, () -> {
finishAdapter.run();
cleanup(true /*success*/);
- });
+ },
+ QuickStepContract.getWindowCornerRadius(mContainer.asContext()));
});
}
@@ -826,7 +828,8 @@
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
Runnable finishedCallback) {
postAsyncCallback(mHandler,
- () -> mSplitAnimationController.playSplitLaunchAnimation(mLaunchingTaskView,
+ () -> mSplitAnimationController
+ .playSplitLaunchAnimation(mLaunchingTaskView,
mLaunchingIconView, mInitialTaskId, mSecondTaskId, apps, wallpapers,
nonApps, mStateManager, mDepthController, null /* info */, null /* t */,
() -> {
@@ -835,7 +838,8 @@
mSuccessCallback.accept(true);
}
resetState();
- }));
+ },
+ QuickStepContract.getWindowCornerRadius(mContainer.asContext())));
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 2b944bc..88c3a08 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -442,13 +442,21 @@
return this;
}
+ public Builder setDisplayCutoutInsets(@NonNull Rect displayCutoutInsets) {
+ mDisplayCutoutInsets = new Rect(displayCutoutInsets);
+ return this;
+ }
+
public SwipePipToHomeAnimator build() {
if (mDestinationBoundsTransformed.isEmpty()) {
mDestinationBoundsTransformed.set(mDestinationBounds);
}
// adjust the mSourceRectHint / mAppBounds by display cutout if applicable.
if (mSourceRectHint != null && mDisplayCutoutInsets != null) {
- if (mFromRotation == Surface.ROTATION_90) {
+ if (mFromRotation == Surface.ROTATION_0 && mDisplayCutoutInsets.top >= 0) {
+ // TODO: this is to special case the issues on Pixel Foldable device(s).
+ mSourceRectHint.offset(mDisplayCutoutInsets.left, mDisplayCutoutInsets.top);
+ } else if (mFromRotation == Surface.ROTATION_90) {
mSourceRectHint.offset(mDisplayCutoutInsets.left, mDisplayCutoutInsets.top);
} else if (mFromRotation == Surface.ROTATION_270) {
mAppBounds.inset(mDisplayCutoutInsets);
@@ -462,15 +470,6 @@
}
}
- private static class RotatedPosition {
- private final float degree;
- private final float positionX;
- private final float positionY;
-
- private RotatedPosition(float degree, float positionX, float positionY) {
- this.degree = degree;
- this.positionX = positionX;
- this.positionY = positionY;
- }
+ private record RotatedPosition(float degree, float positionX, float positionY) {
}
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 49f4e5f..d9b7d20 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -27,7 +27,6 @@
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
-import static com.android.quickstep.util.SplitScreenUtils.convertLauncherSplitBoundsToShell;
import android.animation.TimeInterpolator;
import android.content.Context;
@@ -247,8 +246,6 @@
} else {
mStagePosition = runningTarget.taskId == splitInfo.leftTopTaskId
? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT;
- mPositionHelper.setSplitBounds(convertLauncherSplitBoundsToShell(mSplitBounds),
- mStagePosition);
}
calculateTaskSize();
}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 4c78e21..55bbd50 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -128,7 +128,7 @@
}
val thumbWidth = (taskSize.width() * scaleWidth).toInt()
val thumbHeight = (taskSize.height() * scaleHeight).toInt()
- it.thumbnailViewDeprecated.measure(
+ it.snapshotView.measure(
MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY)
)
@@ -139,8 +139,8 @@
var taskY = (positionInParent.y * scaleHeight).toInt()
// move task down by margin size
taskY += thumbnailTopMarginPx
- it.thumbnailViewDeprecated.x = taskX.toFloat()
- it.thumbnailViewDeprecated.y = taskY.toFloat()
+ it.snapshotView.x = taskX.toFloat()
+ it.snapshotView.y = taskY.toFloat()
if (DEBUG) {
Log.d(
TAG,
@@ -193,6 +193,7 @@
}
val taskContainer =
TaskContainer(
+ this,
task,
// TODO(b/338360089): Support new TTV for DesktopTaskView
thumbnailView = null,
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 4a5b9e4..9f268a0 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -332,12 +332,12 @@
(FrameLayout.LayoutParams) mBanner.getLayoutParams();
DeviceProfile deviceProfile = mContainer.getDeviceProfile();
layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams)
- mTaskView.getFirstThumbnailViewDeprecated().getLayoutParams()).bottomMargin;
+ mTaskView.getFirstSnapshotView().getLayoutParams()).bottomMargin;
RecentsPagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler();
Pair<Float, Float> translations = orientationHandler
.getDwbLayoutTranslations(mTaskView.getMeasuredWidth(),
mTaskView.getMeasuredHeight(), mSplitBounds, deviceProfile,
- mTaskView.getThumbnailViews(), mTask.key.id, mBanner);
+ mTaskView.getSnapshotViews(), mTask.key.id, mBanner);
mSplitOffsetTranslationX = translations.first;
mSplitOffsetTranslationY = translations.second;
updateTranslationY();
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 6296b0e..b070244 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -33,10 +33,8 @@
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.util.RecentsOrientedState
-import com.android.quickstep.util.SplitScreenUtils.Companion.convertLauncherSplitBoundsToShell
import com.android.quickstep.util.SplitSelectStateController
import com.android.systemui.shared.recents.model.Task
-import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
import com.android.systemui.shared.system.InteractionJankMonitorWrapper
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition
@@ -70,36 +68,20 @@
val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
if (initSplitTaskId == INVALID_TASK_ID) {
pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
- taskContainers[0].thumbnailViewDeprecated,
- taskContainers[1].thumbnailViewDeprecated,
+ taskContainers[0].snapshotView,
+ taskContainers[1].snapshotView,
widthSize,
heightSize,
splitBoundsConfig,
container.deviceProfile,
layoutDirection == LAYOUT_DIRECTION_RTL
)
- // Should we be having a separate translation step apart from the measuring above?
- // The following only applies to large screen for now, but for future reference
- // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
- // translation directions
- taskContainers[0]
- .thumbnailViewDeprecated
- .applySplitSelectTranslateX(taskContainers[0].thumbnailViewDeprecated.translationX)
- taskContainers[0]
- .thumbnailViewDeprecated
- .applySplitSelectTranslateY(taskContainers[0].thumbnailViewDeprecated.translationY)
- taskContainers[1]
- .thumbnailViewDeprecated
- .applySplitSelectTranslateX(taskContainers[1].thumbnailViewDeprecated.translationX)
- taskContainers[1]
- .thumbnailViewDeprecated
- .applySplitSelectTranslateY(taskContainers[1].thumbnailViewDeprecated.translationY)
} else {
// Currently being split with this taskView, let the non-split selected thumbnail
// take up full thumbnail area
taskContainers
.firstOrNull { it.task.key.id != initSplitTaskId }
- ?.thumbnailViewDeprecated
+ ?.snapshotView
?.measure(
widthMeasureSpec,
MeasureSpec.makeMeasureSpec(
@@ -147,23 +129,7 @@
)
taskContainers.forEach { it.bind() }
- this.splitBoundsConfig =
- splitBoundsConfig?.also {
- taskContainers[0]
- .thumbnailViewDeprecated
- .previewPositionHelper
- .setSplitBounds(
- convertLauncherSplitBoundsToShell(it),
- PreviewPositionHelper.STAGE_POSITION_TOP_OR_LEFT
- )
- taskContainers[1]
- .thumbnailViewDeprecated
- .previewPositionHelper
- .setSplitBounds(
- convertLauncherSplitBoundsToShell(it),
- PreviewPositionHelper.STAGE_POSITION_BOTTOM_OR_RIGHT
- )
- }
+ this.splitBoundsConfig = splitBoundsConfig
taskContainers.forEach { it.digitalWellBeingToast?.setSplitBounds(splitBoundsConfig) }
setOrientationState(orientedState)
}
@@ -230,8 +196,8 @@
taskContainers[0].iconView.asView(),
taskContainers[1].iconView.asView(),
taskIconHeight,
- taskContainers[0].thumbnailViewDeprecated.measuredWidth,
- taskContainers[0].thumbnailViewDeprecated.measuredHeight,
+ taskContainers[0].snapshotView.measuredWidth,
+ taskContainers[0].snapshotView.measuredHeight,
measuredHeight,
measuredWidth,
isRtl,
@@ -326,7 +292,7 @@
// Check which of the two apps was selected
if (
taskContainers[1].iconView.asView().containsPoint(lastTouchDownPosition) ||
- taskContainers[1].thumbnailViewDeprecated.containsPoint(lastTouchDownPosition)
+ taskContainers[1].snapshotView.containsPoint(lastTouchDownPosition)
) {
return 1
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index d806e3d..8553635 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -212,7 +212,6 @@
import com.android.quickstep.util.TaskVisualsChangeListener;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.VibrationConstants;
-import com.android.quickstep.views.TaskView.TaskContainer;
import com.android.systemui.plugins.ResourceProvider;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -1826,8 +1825,13 @@
((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
mTaskOverlayFactory, groupTask.mSplitBounds);
} else if (taskView instanceof DesktopTaskView) {
- ((DesktopTaskView) taskView).bind(((DesktopTask) groupTask).tasks,
- mOrientationState, mTaskOverlayFactory);
+ // Minimized tasks should not be shown in Overview
+ List<Task> nonMinimizedTasks =
+ ((DesktopTask) groupTask).tasks.stream()
+ .filter(task -> !task.isMinimized)
+ .toList();
+ ((DesktopTaskView) taskView).bind(nonMinimizedTasks, mOrientationState,
+ mTaskOverlayFactory);
mDesktopTaskView = (DesktopTaskView) taskView;
} else {
Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
new file mode 100644
index 0000000..cfdee6c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.views
+
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Insets
+import android.view.View
+import com.android.launcher3.Flags
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.model.data.ItemInfoWithIcon
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.TransformingTouchDelegate
+import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.TaskUtils
+import com.android.quickstep.task.thumbnail.TaskThumbnail
+import com.android.quickstep.task.thumbnail.TaskThumbnailView
+import com.android.quickstep.task.viewmodel.TaskContainerData
+import com.android.systemui.shared.recents.model.Task
+
+/** Holder for all Task dependent information. */
+class TaskContainer(
+ val taskView: TaskView,
+ val task: Task,
+ val thumbnailView: TaskThumbnailView?,
+ val thumbnailViewDeprecated: TaskThumbnailViewDeprecated,
+ val iconView: TaskViewIcon,
+ /**
+ * This technically can be a vanilla [android.view.TouchDelegate] class, however that class
+ * requires setting the touch bounds at construction, so we'd repeatedly be created many
+ * instances unnecessarily as scrolling occurs, whereas [TransformingTouchDelegate] allows touch
+ * delegated bounds only to be updated.
+ */
+ val iconTouchDelegate: TransformingTouchDelegate,
+ /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
+ @SplitConfigurationOptions.StagePosition val stagePosition: Int,
+ val digitalWellBeingToast: DigitalWellBeingToast?,
+ val showWindowsView: View?,
+ taskOverlayFactory: TaskOverlayFactory
+) {
+ val overlay: TaskOverlayFactory.TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
+ val taskContainerData = TaskContainerData()
+
+ val snapshotView: View
+ get() = thumbnailView ?: thumbnailViewDeprecated
+
+ // TODO(b/349120849): Extract ThumbnailData from TaskContainerData/TaskThumbnailViewModel
+ val thumbnail: Bitmap?
+ get() = thumbnailViewDeprecated.thumbnail
+
+ // TODO(b/349120849): Extract ThumbnailData from TaskContainerData/TaskThumbnailViewModel
+ val isRealSnapshot: Boolean
+ get() = thumbnailViewDeprecated.isRealSnapshot()
+
+ // TODO(b/349120849): Extract ThumbnailData from TaskContainerData/TaskThumbnailViewModel
+ val scaledInsets: Insets
+ get() = thumbnailViewDeprecated.scaledInsets
+
+ /** Builds proto for logging */
+ val itemInfo: WorkspaceItemInfo
+ get() =
+ WorkspaceItemInfo().apply {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK
+ container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER
+ val componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key)
+ user = componentKey.user
+ intent = Intent().setComponent(componentKey.componentName)
+ title = task.title
+ taskView.recentsView?.let { screenId = it.indexOfChild(taskView) }
+ if (Flags.privateSpaceRestrictAccessibilityDrag()) {
+ if (
+ UserCache.getInstance(taskView.context)
+ .getUserInfo(componentKey.user)
+ .isPrivate
+ ) {
+ runtimeStatusFlags =
+ runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
+ }
+ }
+ }
+
+ fun destroy() {
+ digitalWellBeingToast?.destroy()
+ thumbnailView?.let { taskView.removeView(it) }
+ }
+
+ fun bind() {
+ if (Flags.enableRefactorTaskThumbnail() && thumbnailView != null) {
+ thumbnailViewDeprecated.setTaskOverlay(overlay)
+ bindThumbnailView()
+ } else {
+ thumbnailViewDeprecated.bind(task, overlay)
+ }
+ }
+
+ // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
+ // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
+ fun bindThumbnailView() {
+ // TODO(b/343364498): Existing view has shouldShowScreenshot as an override as well but
+ // this should be decided inside TaskThumbnailViewModel.
+ thumbnailView?.viewModel?.bind(TaskThumbnail(task.key.id, taskView.isRunningTask))
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 4f446b2..63bc509 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -55,7 +55,6 @@
import com.android.quickstep.TaskUtils;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.TaskCornerRadius;
-import com.android.quickstep.views.TaskView.TaskContainer;
/**
* Contains options for a recent task when long-pressing its icon.
@@ -238,12 +237,12 @@
mContainer.getDragLayer().getDescendantRectRelativeToSelf(
enableOverviewIconMenu()
? getIconView().findViewById(R.id.icon_view_menu_anchor)
- : taskContainer.getThumbnailViewDeprecated(),
+ : taskContainer.getSnapshotView(),
sTempRect);
Rect insets = mContainer.getDragLayer().getInsets();
BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
params.width = orientationHandler.getTaskMenuWidth(
- taskContainer.getThumbnailViewDeprecated(), deviceProfile,
+ taskContainer.getSnapshotView(), deviceProfile,
taskContainer.getStagePosition());
// Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
params.gravity = Gravity.LEFT;
@@ -277,10 +276,10 @@
// Margin that insets the menuView inside the taskView
float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
setTranslationX(orientationHandler.getTaskMenuX(thumbnailAlignedX,
- mTaskContainer.getThumbnailViewDeprecated(), deviceProfile, taskInsetMargin,
+ mTaskContainer.getSnapshotView(), deviceProfile, taskInsetMargin,
getIconView()));
setTranslationY(orientationHandler.getTaskMenuY(
- thumbnailAlignedY, mTaskContainer.getThumbnailViewDeprecated(),
+ thumbnailAlignedY, mTaskContainer.getSnapshotView(),
mTaskContainer.getStagePosition(), this, taskInsetMargin,
getIconView()));
}
@@ -316,7 +315,7 @@
.createRevealAnimator(this, closing, revealAnimationStartProgress);
mRevealAnimator.setInterpolator(enableOverviewIconMenu() ? Interpolators.EMPHASIZED
: Interpolators.DECELERATE);
-
+ AnimatorSet.Builder openCloseAnimatorBuilder = mOpenCloseAnimator.play(mRevealAnimator);
if (enableOverviewIconMenu()) {
IconAppChipView iconAppChip = (IconAppChipView) mTaskContainer.getIconView().asView();
@@ -334,11 +333,13 @@
closing ? mMenuTranslationYBeforeOpen
: mMenuTranslationYBeforeOpen + additionalTranslationY);
translationYAnim.setInterpolator(EMPHASIZED);
+ openCloseAnimatorBuilder.with(translationYAnim);
ObjectAnimator menuTranslationYAnim = ObjectAnimator.ofFloat(
iconAppChip.getMenuTranslationY(),
MULTI_PROPERTY_VALUE, closing ? 0 : additionalTranslationY);
menuTranslationYAnim.setInterpolator(EMPHASIZED);
+ openCloseAnimatorBuilder.with(menuTranslationYAnim);
float additionalTranslationX = 0;
if (mContainer.getDeviceProfile().isLandscape
@@ -354,20 +355,15 @@
closing ? mMenuTranslationXBeforeOpen
: mMenuTranslationXBeforeOpen - additionalTranslationX);
translationXAnim.setInterpolator(EMPHASIZED);
+ openCloseAnimatorBuilder.with(translationXAnim);
ObjectAnimator menuTranslationXAnim = ObjectAnimator.ofFloat(
iconAppChip.getMenuTranslationX(),
MULTI_PROPERTY_VALUE, closing ? 0 : -additionalTranslationX);
menuTranslationXAnim.setInterpolator(EMPHASIZED);
-
- mOpenCloseAnimator.playTogether(translationYAnim, translationXAnim,
- menuTranslationXAnim, menuTranslationYAnim);
+ openCloseAnimatorBuilder.with(menuTranslationXAnim);
}
- mOpenCloseAnimator.playTogether(mRevealAnimator,
- ObjectAnimator.ofFloat(
- mTaskContainer.getThumbnailViewDeprecated(), DIM_ALPHA,
- closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA),
- ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
+ openCloseAnimatorBuilder.with(ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
if (enableRefactorTaskThumbnail()) {
mRevealAnimator.addUpdateListener(animation -> {
float animatedFraction = animation.getAnimatedFraction();
@@ -375,6 +371,10 @@
mTaskContainer.getTaskContainerData()
.getTaskMenuOpenProgress().setValue(openProgress);
});
+ } else {
+ openCloseAnimatorBuilder.with(ObjectAnimator.ofFloat(
+ mTaskContainer.getThumbnailViewDeprecated(), DIM_ALPHA,
+ closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA));
}
mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
@Override
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index 659cc0c..e10d38c 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -37,7 +37,6 @@
import com.android.launcher3.popup.SystemShortcut
import com.android.launcher3.util.Themes
import com.android.quickstep.TaskOverlayFactory
-import com.android.quickstep.views.TaskView.TaskContainer
class TaskMenuViewWithArrow<T> : ArrowPopup<T> where T : RecentsViewContainer, T : Context {
companion object {
@@ -58,7 +57,9 @@
}
constructor(context: Context) : super(context)
+
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
constructor(
context: Context,
attrs: AttributeSet,
@@ -80,6 +81,7 @@
private var alignedOptionIndex: Int = 0
private val extraSpaceForRowAlignment: Int
get() = optionMeasuredHeight * alignedOptionIndex
+
private val menuPaddingEnd = context.resources.getDimensionPixelSize(R.dimen.task_card_margin)
private lateinit var taskView: TaskView
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
index 4283d0e..2afb6a6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
@@ -99,36 +99,6 @@
}
};
- /** Use to animate thumbnail translationX while first app in split selection is initiated */
- public static final Property<TaskThumbnailViewDeprecated, Float> SPLIT_SELECT_TRANSLATE_X =
- new FloatProperty<TaskThumbnailViewDeprecated>("splitSelectTranslateX") {
- @Override
- public void setValue(TaskThumbnailViewDeprecated thumbnail,
- float splitSelectTranslateX) {
- thumbnail.applySplitSelectTranslateX(splitSelectTranslateX);
- }
-
- @Override
- public Float get(TaskThumbnailViewDeprecated thumbnailView) {
- return thumbnailView.mSplitSelectTranslateX;
- }
- };
-
- /** Use to animate thumbnail translationY while first app in split selection is initiated */
- public static final Property<TaskThumbnailViewDeprecated, Float> SPLIT_SELECT_TRANSLATE_Y =
- new FloatProperty<TaskThumbnailViewDeprecated>("splitSelectTranslateY") {
- @Override
- public void setValue(TaskThumbnailViewDeprecated thumbnail,
- float splitSelectTranslateY) {
- thumbnail.applySplitSelectTranslateY(splitSelectTranslateY);
- }
-
- @Override
- public Float get(TaskThumbnailViewDeprecated thumbnailView) {
- return thumbnailView.mSplitSelectTranslateY;
- }
- };
-
private final RecentsViewContainer mContainer;
private TaskOverlay<?> mOverlay;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -160,8 +130,6 @@
private boolean mOverlayEnabled;
/** Used as a placeholder when the original thumbnail animates out to. */
private boolean mShowSplashForSplitSelection;
- private float mSplitSelectTranslateX;
- private float mSplitSelectTranslateY;
public TaskThumbnailViewDeprecated(Context context) {
this(context, null);
@@ -415,31 +383,6 @@
}
}
- /** See {@link #SPLIT_SELECT_TRANSLATE_X} */
- protected void applySplitSelectTranslateX(float splitSelectTranslateX) {
- mSplitSelectTranslateX = splitSelectTranslateX;
- applyTranslateX();
- }
-
- /** See {@link #SPLIT_SELECT_TRANSLATE_Y} */
- protected void applySplitSelectTranslateY(float splitSelectTranslateY) {
- mSplitSelectTranslateY = splitSelectTranslateY;
- applyTranslateY();
- }
-
- private void applyTranslateX() {
- setTranslationX(mSplitSelectTranslateX);
- }
-
- private void applyTranslateY() {
- setTranslationY(mSplitSelectTranslateY);
- }
-
- protected void resetViewTransforms() {
- mSplitSelectTranslateX = 0;
- mSplitSelectTranslateY = 0;
- }
-
public TaskView getTaskView() {
return (TaskView) getParent();
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 7a3b00f..b922df4 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -22,7 +22,6 @@
import android.annotation.IdRes
import android.app.ActivityOptions
import android.content.Context
-import android.content.Intent
import android.graphics.Canvas
import android.graphics.PointF
import android.graphics.Rect
@@ -50,16 +49,11 @@
import com.android.launcher3.Flags.enableGridOnlyOverview
import com.android.launcher3.Flags.enableOverviewIconMenu
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
-import com.android.launcher3.Flags.privateSpaceRestrictAccessibilityDrag
-import com.android.launcher3.LauncherSettings
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.config.FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH
import com.android.launcher3.logging.StatsLogManager.LauncherEvent
import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.ItemInfoWithIcon
-import com.android.launcher3.model.data.WorkspaceItemInfo
-import com.android.launcher3.pm.UserCache
import com.android.launcher3.testing.TestLogging
import com.android.launcher3.testing.shared.TestProtocol
import com.android.launcher3.util.CancellableTask
@@ -82,13 +76,9 @@
import com.android.quickstep.RemoteAnimationTargets
import com.android.quickstep.TaskAnimationManager
import com.android.quickstep.TaskOverlayFactory
-import com.android.quickstep.TaskOverlayFactory.TaskOverlay
-import com.android.quickstep.TaskUtils
import com.android.quickstep.TaskViewUtils
import com.android.quickstep.orientation.RecentsPagedOrientationHandler
-import com.android.quickstep.task.thumbnail.TaskThumbnail
import com.android.quickstep.task.thumbnail.TaskThumbnailView
-import com.android.quickstep.task.viewmodel.TaskContainerData
import com.android.quickstep.task.viewmodel.TaskViewData
import com.android.quickstep.util.ActiveGestureErrorDetector
import com.android.quickstep.util.ActiveGestureLog
@@ -138,8 +128,8 @@
/** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */
get() = taskContainers.map { it.task.key.id }.toIntArray()
- val thumbnailViews: Array<TaskThumbnailViewDeprecated>
- get() = taskContainers.map { it.thumbnailViewDeprecated }.toTypedArray()
+ val snapshotViews: Array<View>
+ get() = taskContainers.map { it.snapshotView }.toTypedArray()
val isGridTask: Boolean
/** Returns whether the task is part of overview grid and not being focused. */
@@ -171,6 +161,11 @@
get() = taskContainers[0].thumbnailViewDeprecated
@get:Deprecated("Use [taskContainers] instead.")
+ val firstSnapshotView: View
+ /** Returns the first snapshotView of the TaskView. */
+ get() = taskContainers[0].snapshotView
+
+ @get:Deprecated("Use [taskContainers] instead.")
val firstItemInfo: ItemInfo
get() = taskContainers[0].itemInfo
@@ -695,6 +690,7 @@
}
val iconView = getOrInflateIconView(iconViewId)
return TaskContainer(
+ this,
task,
thumbnailView,
thumbnailViewDeprecated,
@@ -1197,10 +1193,10 @@
this,
container.task,
container.iconView.drawable,
- container.thumbnailViewDeprecated,
- container.thumbnailViewDeprecated.thumbnail, /* intent */
- null, /* user */
- null,
+ container.snapshotView,
+ container.thumbnail,
+ /* intent */ null,
+ /* user */ null,
container.itemInfo
)
}
@@ -1512,6 +1508,10 @@
gridTranslationY = 0f
boxTranslationY = 0f
nonGridPivotTranslationX = 0f
+ taskContainers.forEach {
+ it.snapshotView.translationX = 0f
+ it.snapshotView.translationY = 0f
+ }
resetViewTransforms()
}
@@ -1537,10 +1537,6 @@
alpha = stableAlpha
setIconScaleAndDim(1f)
setColorTint(0f, 0)
- if (!enableRefactorTaskThumbnail()) {
- // TODO(b/335399428) add split select functionality to new TTV
- taskContainers.forEach { it.thumbnailViewDeprecated.resetViewTransforms() }
- }
}
private fun getGridTrans(endTranslation: Float) =
@@ -1599,78 +1595,6 @@
override fun close() {}
}
- /** Holder for all Task dependent information. */
- inner class TaskContainer(
- val task: Task,
- val thumbnailView: TaskThumbnailView?,
- val thumbnailViewDeprecated: TaskThumbnailViewDeprecated,
- val iconView: TaskViewIcon,
- /**
- * This technically can be a vanilla [android.view.TouchDelegate] class, however that class
- * requires setting the touch bounds at construction, so we'd repeatedly be created many
- * instances unnecessarily as scrolling occurs, whereas [TransformingTouchDelegate] allows
- * touch delegated bounds only to be updated.
- */
- val iconTouchDelegate: TransformingTouchDelegate,
- /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
- @StagePosition val stagePosition: Int,
- val digitalWellBeingToast: DigitalWellBeingToast?,
- val showWindowsView: View?,
- taskOverlayFactory: TaskOverlayFactory
- ) {
- val overlay: TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
- val taskContainerData = TaskContainerData()
-
- val snapshotView: View
- get() = thumbnailView ?: thumbnailViewDeprecated
-
- /** Builds proto for logging */
- val itemInfo: WorkspaceItemInfo
- get() =
- WorkspaceItemInfo().apply {
- itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK
- container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER
- val componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key)
- user = componentKey.user
- intent = Intent().setComponent(componentKey.componentName)
- title = task.title
- recentsView?.let { screenId = it.indexOfChild(this@TaskView) }
- if (privateSpaceRestrictAccessibilityDrag()) {
- if (
- UserCache.getInstance(context).getUserInfo(componentKey.user).isPrivate
- ) {
- runtimeStatusFlags =
- runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
- }
- }
- }
-
- val taskView: TaskView
- get() = this@TaskView
-
- fun destroy() {
- digitalWellBeingToast?.destroy()
- thumbnailView?.let { taskView.removeView(it) }
- }
-
- fun bind() {
- if (enableRefactorTaskThumbnail() && thumbnailView != null) {
- thumbnailViewDeprecated.setTaskOverlay(overlay)
- bindThumbnailView()
- } else {
- thumbnailViewDeprecated.bind(task, overlay)
- }
- }
-
- // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
- // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
- fun bindThumbnailView() {
- // TODO(b/343364498): Existing view has shouldShowScreenshot as an override as well but
- // this should be decided inside TaskThumbnailViewModel.
- thumbnailView?.viewModel?.bind(TaskThumbnail(task.key.id, isRunningTask))
- }
- }
-
companion object {
private const val TAG = "TaskView"
const val FLAG_UPDATE_ICON = 1
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRule.kt
new file mode 100644
index 0000000..3b53cdc
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRule.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.taskbar
+
+import com.android.launcher3.taskbar.TaskbarModeRule.Mode
+import com.android.launcher3.taskbar.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.NavigationMode
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.spy
+
+/**
+ * Allows tests to specify which Taskbar [Mode] to run under.
+ *
+ * [context] should match the test's target context, so that [MainThreadInitializedObject] instances
+ * are properly sandboxed.
+ *
+ * Annotate tests with [TaskbarMode] to set a mode. If the annotation is omitted for any tests, this
+ * rule is a no-op.
+ *
+ * Make sure this rule precedes any rules that depend on [DisplayController], or else the instance
+ * might be inconsistent across the test lifecycle.
+ */
+class TaskbarModeRule(private val context: SandboxContext) : TestRule {
+ /** The selected Taskbar mode. */
+ enum class Mode {
+ TRANSIENT,
+ PINNED,
+ THREE_BUTTONS,
+ }
+
+ /** Overrides Taskbar [mode] for a test. */
+ @Retention(AnnotationRetention.RUNTIME)
+ @Target(AnnotationTarget.FUNCTION)
+ annotation class TaskbarMode(val mode: Mode)
+
+ override fun apply(base: Statement, description: Description): Statement {
+ val taskbarMode = description.getAnnotation(TaskbarMode::class.java) ?: return base
+
+ return object : Statement() {
+ override fun evaluate() {
+ val mode = taskbarMode.mode
+
+ context.putObject(
+ DisplayController.INSTANCE,
+ object : DisplayController(context) {
+ override fun getInfo(): Info {
+ return spy(super.getInfo()) {
+ on { isTransientTaskbar } doReturn (mode == Mode.TRANSIENT)
+ on { isPinnedTaskbar } doReturn (mode == Mode.PINNED)
+ on { navigationMode } doReturn
+ when (mode) {
+ Mode.TRANSIENT,
+ Mode.PINNED -> NavigationMode.NO_BUTTON
+ Mode.THREE_BUTTONS -> NavigationMode.THREE_BUTTONS
+ }
+ }
+ }
+ },
+ )
+
+ base.evaluate()
+ }
+ }
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRuleTest.kt
new file mode 100644
index 0000000..7dfbb9a
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRuleTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.taskbar
+
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.taskbar.TaskbarModeRule.Mode.PINNED
+import com.android.launcher3.taskbar.TaskbarModeRule.Mode.THREE_BUTTONS
+import com.android.launcher3.taskbar.TaskbarModeRule.Mode.TRANSIENT
+import com.android.launcher3.taskbar.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.NavigationMode
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+class TaskbarModeRuleTest {
+
+ private val context = SandboxContext(getInstrumentation().targetContext)
+
+ @get:Rule val taskbarModeRule = TaskbarModeRule(context)
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testTaskbarMode_transient_overridesDisplayController() {
+ assertThat(DisplayController.isTransientTaskbar(context)).isTrue()
+ assertThat(DisplayController.isPinnedTaskbar(context)).isFalse()
+ assertThat(DisplayController.getNavigationMode(context)).isEqualTo(NavigationMode.NO_BUTTON)
+ }
+
+ @Test
+ @TaskbarMode(TRANSIENT)
+ fun testTaskbarMode_transient_overridesDeviceProfile() {
+ val dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context)
+ assertThat(dp.isTransientTaskbar).isTrue()
+ assertThat(dp.isGestureMode).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testTaskbarMode_pinned_overridesDisplayController() {
+ assertThat(DisplayController.isTransientTaskbar(context)).isFalse()
+ assertThat(DisplayController.isPinnedTaskbar(context)).isTrue()
+ assertThat(DisplayController.getNavigationMode(context)).isEqualTo(NavigationMode.NO_BUTTON)
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testTaskbarMode_pinned_overridesDeviceProfile() {
+ val dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context)
+ assertThat(dp.isTransientTaskbar).isFalse()
+ assertThat(dp.isGestureMode).isTrue()
+ }
+
+ @Test
+ @TaskbarMode(THREE_BUTTONS)
+ fun testTaskbarMode_threeButtons_overridesDisplayController() {
+ assertThat(DisplayController.isTransientTaskbar(context)).isFalse()
+ assertThat(DisplayController.isPinnedTaskbar(context)).isFalse()
+ assertThat(DisplayController.getNavigationMode(context))
+ .isEqualTo(NavigationMode.THREE_BUTTONS)
+ }
+
+ @Test
+ @TaskbarMode(THREE_BUTTONS)
+ fun testTaskbarMode_threeButtons_overridesDeviceProfile() {
+ val dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context)
+ assertThat(dp.isTransientTaskbar).isFalse()
+ assertThat(dp.isGestureMode).isFalse()
+ }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
index a999e7f..bbf738e 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
@@ -18,6 +18,7 @@
import android.app.Instrumentation
import android.app.PendingIntent
+import android.content.Context
import android.content.IIntentSender
import android.content.Intent
import androidx.test.platform.app.InstrumentationRegistry
@@ -30,13 +31,16 @@
import com.android.quickstep.TouchInteractionService
import com.android.quickstep.TouchInteractionService.TISBinder
import org.junit.Assume.assumeTrue
-import org.junit.rules.MethodRule
-import org.junit.runners.model.FrameworkMethod
+import org.junit.rules.TestRule
+import org.junit.runner.Description
import org.junit.runners.model.Statement
/**
* Manages the Taskbar lifecycle for unit tests.
*
+ * Tests should pass in themselves as [testInstance]. They also need to provide their target
+ * [context] through the constructor.
+ *
* See [InjectController] for grabbing controller(s) under test with minimal boilerplate.
*
* The rule interacts with [TaskbarManager] on the main thread. A good rule of thumb for tests is
@@ -58,12 +62,11 @@
* }
* ```
*/
-class TaskbarUnitTestRule : MethodRule {
+class TaskbarUnitTestRule(private val testInstance: Any, private val context: Context) : TestRule {
private val instrumentation = InstrumentationRegistry.getInstrumentation()
private val serviceTestRule = ServiceTestRule()
private lateinit var taskbarManager: TaskbarManager
- private lateinit var target: Any
val activityContext: TaskbarActivityContext
get() {
@@ -71,12 +74,10 @@
?: throw RuntimeException("Failed to obtain TaskbarActivityContext.")
}
- override fun apply(base: Statement, method: FrameworkMethod, target: Any): Statement {
+ override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
- this@TaskbarUnitTestRule.target = target
- val context = instrumentation.targetContext
instrumentation.runOnMainSync {
assumeTrue(
LauncherAppState.getIDP(context).getDeviceProfile(context).isTaskbarPresent
@@ -139,11 +140,11 @@
private fun injectControllers() {
val controllers = activityContext.controllers
val controllerFieldsByType = controllers.javaClass.fields.associateBy { it.type }
- target.javaClass.fields
+ testInstance.javaClass.fields
.filter { it.isAnnotationPresent(InjectController::class.java) }
.forEach {
it.set(
- target,
+ testInstance,
controllerFieldsByType[it.type]?.get(controllers)
?: throw NoSuchElementException("Failed to find controller for ${it.type}"),
)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
index fe4e2d2..bfad697 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
@@ -42,7 +42,8 @@
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
class TaskbarAllAppsControllerTest {
- @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule()
+ @get:Rule
+ val taskbarUnitTestRule = TaskbarUnitTestRule(this, getInstrumentation().targetContext)
@get:Rule val animatorTestRule = AnimatorTestRule(this)
@InjectController lateinit var allAppsController: TaskbarAllAppsController
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt
index 20bd617..d5a76a2 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt
@@ -80,6 +80,31 @@
assertThat(bubbleAnimator.isRunning).isFalse()
}
+ @Test
+ fun animateNewAndRemoveOld_isRunning() {
+ bubbleAnimator =
+ BubbleAnimator(
+ iconSize = 40f,
+ expandedBarIconSpacing = 10f,
+ bubbleCount = 5,
+ onLeft = false
+ )
+ val listener = TestBubbleAnimatorListener()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleAnimator.animateNewAndRemoveOld(
+ selectedBubbleIndex = 3,
+ removedBubbleIndex = 2,
+ listener
+ )
+ }
+
+ assertThat(bubbleAnimator.isRunning).isTrue()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ animatorTestRule.advanceTimeBy(250)
+ }
+ assertThat(bubbleAnimator.isRunning).isFalse()
+ }
+
private class TestBubbleAnimatorListener : BubbleAnimator.Listener {
override fun onAnimationUpdate(animatedFraction: Float) {}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
index eebd8f9..72bdc16 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
@@ -41,7 +41,8 @@
@EmulatedDevices(["pixelFoldable2023"])
class TaskbarOverlayControllerTest {
- @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule()
+ @get:Rule
+ val taskbarUnitTestRule = TaskbarUnitTestRule(this, getInstrumentation().targetContext)
@InjectController lateinit var overlayController: TaskbarOverlayController
private val taskbarContext: TaskbarActivityContext
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index d40f8ab..fd7ecb0 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -33,9 +33,8 @@
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.quickstep.views.GroupedTaskView
import com.android.quickstep.views.IconView
-import com.android.quickstep.views.TaskThumbnailViewDeprecated
+import com.android.quickstep.views.TaskContainer
import com.android.quickstep.views.TaskView
-import com.android.quickstep.views.TaskView.TaskContainer
import com.android.systemui.shared.recents.model.Task
import org.junit.Assert.assertEquals
import org.junit.Before
@@ -59,7 +58,7 @@
private val mockSplitSelectStateController: SplitSelectStateController = mock()
// TaskView
private val mockTaskView: TaskView = mock()
- private val mockThumbnailView: TaskThumbnailViewDeprecated = mock()
+ private val mockSnapshotView: View = mock()
private val mockBitmap: Bitmap = mock()
private val mockIconView: IconView = mock()
private val mockTaskViewDrawable: Drawable = mock()
@@ -87,8 +86,8 @@
@Before
fun setup() {
- whenever(mockTaskContainer.thumbnailViewDeprecated).thenReturn(mockThumbnailView)
- whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap)
+ whenever(mockTaskContainer.snapshotView).thenReturn(mockSnapshotView)
+ whenever(mockTaskContainer.thumbnail).thenReturn(mockBitmap)
whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
@@ -180,7 +179,6 @@
whenever(mockTaskContainer.task).thenReturn(mockTask)
whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
- whenever(mockTaskContainer.thumbnailViewDeprecated).thenReturn(mockThumbnailView)
whenever(mockTask.getKey()).thenReturn(mockTaskKey)
whenever(mockTaskKey.getId()).thenReturn(taskId)
whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId)
@@ -227,7 +225,8 @@
depthController,
null /* info */,
null /* t */,
- {} /* finishCallback */
+ {} /* finishCallback */,
+ 1f /* cornerRadius */
)
verify(spySplitAnimationController)
@@ -263,7 +262,8 @@
depthController,
transitionInfo,
transaction,
- {} /* finishCallback */
+ {} /* finishCallback */,
+ 1f /* cornerRadius */
)
verify(spySplitAnimationController)
@@ -291,7 +291,8 @@
depthController,
transitionInfo,
transaction,
- {} /* finishCallback */
+ {} /* finishCallback */,
+ 1f /* cornerRadius */
)
verify(spySplitAnimationController)
@@ -319,7 +320,8 @@
depthController,
transitionInfo,
transaction,
- {} /* finishCallback */
+ {} /* finishCallback */,
+ 1f /* cornerRadius */
)
verify(spySplitAnimationController)
@@ -346,7 +348,8 @@
depthController,
transitionInfo,
transaction,
- {} /* finishCallback */
+ {} /* finishCallback */,
+ 1f /* cornerRadius */
)
verify(spySplitAnimationController)
@@ -373,7 +376,8 @@
depthController,
transitionInfo,
transaction,
- {} /* finishCallback */
+ {} /* finishCallback */,
+ 1f /* cornerRadius */
)
verify(spySplitAnimationController)
@@ -385,7 +389,7 @@
val spySplitAnimationController = spy(splitAnimationController)
doNothing()
.whenever(spySplitAnimationController)
- .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any())
+ .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any(), any())
spySplitAnimationController.playSplitLaunchAnimation(
null /* launchingTaskView */,
@@ -399,10 +403,11 @@
depthController,
transitionInfo,
transaction,
- {} /* finishCallback */
+ {} /* finishCallback */,
+ 1f /* cornerRadius */
)
verify(spySplitAnimationController)
- .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any())
+ .composeFadeInSplitLaunchAnimator(any(), any(), any(), any(), any(), any())
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index 50b5df1..f160ce2 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -32,6 +32,7 @@
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.quickstep.TaskOverlayFactory.TaskOverlay
import com.android.quickstep.views.LauncherRecentsView
+import com.android.quickstep.views.TaskContainer
import com.android.quickstep.views.TaskThumbnailViewDeprecated
import com.android.quickstep.views.TaskView
import com.android.quickstep.views.TaskViewIcon
@@ -186,8 +187,9 @@
}
}
- private fun createTaskContainer(task: Task): TaskView.TaskContainer {
- return taskView.TaskContainer(
+ private fun createTaskContainer(task: Task): TaskContainer {
+ return TaskContainer(
+ taskView,
task,
thumbnailView = null,
thumbnailViewDeprecated,
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
index 03244eb..ce16b70 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -32,6 +32,8 @@
import com.android.launcher3.util.LooperExecutor;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import org.junit.Before;
@@ -40,8 +42,11 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
@SmallTest
public class RecentTasksListTest {
@@ -104,4 +109,52 @@
assertEquals(taskDescription, taskList.get(0).task1.taskDescription.getLabel());
assertNull(taskList.get(0).task2.taskDescription.getLabel());
}
+
+ @Test
+ public void loadTasksInBackground_freeformTask_createsDesktopTask() {
+ ActivityManager.RecentTaskInfo[] tasks = {
+ createRecentTaskInfo(1 /* taskId */),
+ createRecentTaskInfo(4 /* taskId */),
+ createRecentTaskInfo(5 /* taskId */)};
+ GroupedRecentTaskInfo recentTaskInfos = GroupedRecentTaskInfo.forFreeformTasks(
+ tasks, Collections.emptySet() /* minimizedTaskIds */);
+ when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
+ .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
+
+ List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
+ Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */);
+
+ assertEquals(1, taskList.size());
+ assertEquals(TaskView.Type.DESKTOP, taskList.get(0).taskViewType);
+ List<Task> actualFreeformTasks = taskList.get(0).getTasks();
+ assertEquals(3, actualFreeformTasks.size());
+ assertEquals(1, actualFreeformTasks.get(0).key.id);
+ assertEquals(4, actualFreeformTasks.get(1).key.id);
+ assertEquals(5, actualFreeformTasks.get(2).key.id);
+ }
+
+ @Test
+ public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask() {
+ ActivityManager.RecentTaskInfo[] tasks = {
+ createRecentTaskInfo(1 /* taskId */),
+ createRecentTaskInfo(4 /* taskId */),
+ createRecentTaskInfo(5 /* taskId */)};
+ Set<Integer> minimizedTaskIds =
+ Arrays.stream(new Integer[]{1, 4, 5}).collect(Collectors.toSet());
+ GroupedRecentTaskInfo recentTaskInfos =
+ GroupedRecentTaskInfo.forFreeformTasks(tasks, minimizedTaskIds);
+ when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
+ .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
+
+ List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
+ Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */);
+
+ assertEquals(0, taskList.size());
+ }
+
+ private ActivityManager.RecentTaskInfo createRecentTaskInfo(int taskId) {
+ ActivityManager.RecentTaskInfo recentTaskInfo = new ActivityManager.RecentTaskInfo();
+ recentTaskInfo.taskId = taskId;
+ return recentTaskInfo;
+ }
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
index 07d8f61..6e25b10 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
@@ -31,6 +31,7 @@
import com.android.launcher3.Launcher;
import com.android.quickstep.views.DigitalWellBeingToast;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
import org.junit.Test;
@@ -86,7 +87,7 @@
final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
return getFromLauncher(launcher -> {
- TaskView.TaskContainer taskContainer = task.getTaskContainers().get(0);
+ TaskContainer taskContainer = task.getTaskContainers().get(0);
assertTrue("Latest task is not Calculator", CALCULATOR_PACKAGE.equals(
taskContainer.getTask().getTopComponent().getPackageName()));
return taskContainer.getDigitalWellBeingToast();
diff --git a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
index b7fd8be..2087016 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
@@ -69,7 +69,6 @@
}
@Test
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/288939273
public void testSplitTaskTapBothIconMenus() {
createAndLaunchASplitPair();
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index 8adf793..733ea4e 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -72,7 +72,6 @@
}
@Test
- @TestStabilityRule.Stability(flavors = PLATFORM_POSTSUBMIT | LOCAL) // b/295225524
public void testSplitAppFromHomeWithItself() throws Exception {
// Currently only tablets have Taskbar in Overview, so test is only active on tablets
assumeTrue(mLauncher.isTablet());
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index b39945b..27ce075 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -67,7 +67,7 @@
<string name="widget_category_note_taking" msgid="3469689394504266039">"یادداشتبرداری"</string>
<string name="widget_add_button_label" msgid="2761267068711937179">"افزودن"</string>
<string name="widget_add_button_content_description" msgid="1810530016360039643">"افزودن ابزارک <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
- <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"برای تغییر تنظیمات ابزارک، ضربه بزنید"</string>
+ <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"برای تغییر تنظیمات ابزارک، تکضرب بزنید"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"تغییر تنظیمات ابزارک"</string>
<string name="all_apps_search_bar_hint" msgid="1390553134053255246">"جستجوی برنامهها"</string>
<string name="all_apps_loading_message" msgid="5813968043155271636">"درحال بارگیری برنامهها…"</string>
@@ -99,7 +99,7 @@
<string name="permdesc_write_settings" msgid="726859348127868466">"به برنامه اجازه میدهد تنظیمات و میانبرهای صفحه اصلی را تغییر دهد."</string>
<string name="gadget_error_text" msgid="740356548025791839">"ابزارک را نمیتوان بار کرد"</string>
<string name="gadget_setup_text" msgid="8348374825537681407">"تنظیمات ابزارک"</string>
- <string name="gadget_complete_setup_text" msgid="309040266978007925">"برای تکمیل راهاندازی ضربه بزنید"</string>
+ <string name="gadget_complete_setup_text" msgid="309040266978007925">"برای تکمیل راهاندازی تکضرب بزنید"</string>
<string name="uninstall_system_app_text" msgid="4172046090762920660">"این برنامه سیستمی است و حذف نصب نمیشود."</string>
<string name="folder_hint_text" msgid="5174843001373488816">"ویرایش نام"</string>
<string name="disabled_app_label" msgid="6673129024321402780">"<xliff:g id="APP_NAME">%1$s</xliff:g> غیرفعال شد"</string>
@@ -108,8 +108,8 @@
<string name="workspace_scroll_format" msgid="8458889198184077399">"صفحه اصلی %1$d از %2$d"</string>
<string name="workspace_new_page" msgid="257366611030256142">"صفحه اصلی جدید"</string>
<string name="folder_opened" msgid="94695026776264709">"پوشه باز شده، <xliff:g id="WIDTH">%1$d</xliff:g> در <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
- <string name="folder_tap_to_close" msgid="4625795376335528256">"برای بستن پوشه، ضربه بزنید"</string>
- <string name="folder_tap_to_rename" msgid="4017685068016979677">"برای ذخیره تغییر نام، ضربه بزنید"</string>
+ <string name="folder_tap_to_close" msgid="4625795376335528256">"برای بستن پوشه، تکضرب بزنید"</string>
+ <string name="folder_tap_to_rename" msgid="4017685068016979677">"برای ذخیره تغییر نام، تکضرب بزنید"</string>
<string name="folder_closed" msgid="4100806530910930934">"پوشه بسته شد"</string>
<string name="folder_renamed" msgid="1794088362165669656">"نام پوشه به <xliff:g id="NAME">%1$s</xliff:g> تغییر کرد"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"پوشه: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> مورد"</string>
@@ -139,7 +139,7 @@
<string name="app_installing_title" msgid="5864044122733792085">"<xliff:g id="NAME">%1$s</xliff:g> درحال نصب است، <xliff:g id="PROGRESS">%2$s</xliff:g> تکمیل شده است"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"درحال بارگیری <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="PROGRESS">%2$s</xliff:g> کامل شد"</string>
<string name="app_waiting_download_title" msgid="7053938513995617849">"<xliff:g id="NAME">%1$s</xliff:g> درانتظار نصب"</string>
- <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> بایگانی شده است. برای بارگیری و بازیابی ضربه بزنید."</string>
+ <string name="app_archived_title" msgid="7717956158562544081">"<xliff:g id="NAME">%1$s</xliff:g> بایگانی شده است. برای بارگیری و بازیابی تکضرب بزنید."</string>
<string name="dialog_update_title" msgid="114234265740994042">"برنامه باید بهروز شود"</string>
<string name="dialog_update_message" msgid="4176784553982226114">"برنامه برای این نماد بهروز نشده است. میتوانید آن را بهصورت دستی بهروز کنید تا میانبر دوباره فعال شود، یا نماد را بردارید."</string>
<string name="dialog_update" msgid="2178028071796141234">"بهروزرسانی"</string>
@@ -187,7 +187,7 @@
<string name="developer_options_filter_hint" msgid="5896817443635989056">"فیلتر"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"ناموفق بود: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
<string name="private_space_label" msgid="2359721649407947001">"فضای خصوصی"</string>
- <string name="private_space_secondary_label" msgid="9203933341714508907">"برای راهاندازی یا باز کردن، ضربه بزنید"</string>
+ <string name="private_space_secondary_label" msgid="9203933341714508907">"برای راهاندازی یا باز کردن، تکضرب بزنید"</string>
<string name="ps_container_title" msgid="4391796149519594205">"خصوصی"</string>
<string name="ps_container_settings" msgid="6059734123353320479">"تنظیمات «فضای خصوصی»"</string>
<string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"خصوصی، باز."</string>
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 3b8ff62..239967d 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -116,13 +116,13 @@
SimpleBroadcastReceiver modelChangeReceiver =
new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
- modelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
+ modelChangeReceiver.registerAsync(mContext, Intent.ACTION_LOCALE_CHANGED,
ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
if (BuildConfig.IS_STUDIO_BUILD) {
mContext.registerReceiver(modelChangeReceiver, new IntentFilter(ACTION_FORCE_ROLOAD),
RECEIVER_EXPORTED);
}
- mOnTerminateCallback.add(() -> mContext.unregisterReceiver(modelChangeReceiver));
+ mOnTerminateCallback.add(() -> modelChangeReceiver.unregisterReceiverSafelyAsync(mContext));
SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
.addUserEventListener(mModel::onUserEvent);
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index ba34f59..2a47222 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -305,9 +305,7 @@
@Override
public int getScrollBarTop() {
- return ActivityContext.lookupContext(getContext()).getAppsView().isSearchSupported()
- ? getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding)
- : 0;
+ return getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding);
}
@Override
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 6f021ea..0f4204f 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -48,6 +48,7 @@
import android.content.Intent;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
@@ -89,6 +90,8 @@
* logic in the Personal tab.
*/
public class PrivateProfileManager extends UserProfileManager {
+
+ private static final String TAG = "PrivateProfileManager";
private static final int EXPAND_COLLAPSE_DURATION = 800;
private static final int SETTINGS_OPACITY_DURATION = 400;
private static final int TEXT_UNLOCK_OPACITY_DURATION = 300;
@@ -362,6 +365,7 @@
} else {
// Ensure any unwanted animations to not happen.
settingAndLockGroup.setLayoutTransition(null);
+ Log.d(TAG, "bindPrivateSpaceHeaderViewElements: removing transitions ");
}
updateView();
}
@@ -597,6 +601,9 @@
}
attachFloatingMaskView(expand);
ViewGroup settingsAndLockGroup = mPSHeader.findViewById(R.id.settingsAndLockGroup);
+ TextView lockText = mPSHeader.findViewById(R.id.lock_text);
+ PrivateSpaceSettingsButton privateSpaceSettingsButton =
+ mPSHeader.findViewById(R.id.ps_settings_button);
if (settingsAndLockGroup.getLayoutTransition() == null) {
// Set a new transition if the current ViewGroup does not already contain one as each
// transition should only happen once when applied.
@@ -612,13 +619,15 @@
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
+ Log.d(TAG, "updatePrivateStateAnimator: Private space animation expanding: "
+ + expand);
mStatsLogManager.logger().sendToInteractionJankMonitor(
expand
? LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_BEGIN
: LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_BEGIN,
mAllApps.getActiveRecyclerView());
// Animate the collapsing of the text at the same time while updating lock button.
- mPSHeader.findViewById(R.id.lock_text).setVisibility(expand ? VISIBLE : GONE);
+ lockText.setVisibility(expand ? VISIBLE : GONE);
setAnimationRunning(true);
}
@@ -636,6 +645,11 @@
? LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END
: LAUNCHER_PRIVATE_SPACE_LOCK_ANIMATION_END,
mAllApps.getActiveRecyclerView());
+ Log.d(TAG, "updatePrivateStateAnimator: lockText visibility: "
+ + lockText.getVisibility() + " lockTextAlpha: " + lockText.getAlpha());
+ Log.d(TAG, "updatePrivateStateAnimator: settingsCog visibility: "
+ + privateSpaceSettingsButton.getVisibility()
+ + " settingsCogAlpha: " + privateSpaceSettingsButton.getAlpha());
if (!expand) {
mAllApps.mAH.get(MAIN).mRecyclerView.removeItemDecoration(
mPrivateAppsSectionDecorator);
@@ -717,15 +731,19 @@
@Override
public void startTransition(LayoutTransition transition, ViewGroup viewGroup,
View view, int i) {
+ Log.d(TAG, "updatePrivateStateAnimator: transition started: " + transition);
}
@Override
public void endTransition(LayoutTransition transition, ViewGroup viewGroup,
View view, int i) {
settingsAndLockGroup.setLayoutTransition(null);
mReadyToAnimate = false;
+ Log.d(TAG, "updatePrivateStateAnimator: transition finished: " + transition);
}
});
settingsAndLockGroup.setLayoutTransition(settingsAndLockTransition);
+ Log.d(TAG, "updatePrivateStateAnimator: setting transition: "
+ + settingsAndLockTransition);
}
/** Change the settings gear alpha when expanded or collapsed. */
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 9824992..37a8d9b 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -60,6 +60,7 @@
*/
public class FolderAnimationManager {
+ private static final float EXTRA_FOLDER_REVEAL_RADIUS_PERCENTAGE = 0.125F;
private static final int FOLDER_NAME_ALPHA_DURATION = 32;
private static final int LARGE_FOLDER_FOOTER_DURATION = 128;
@@ -158,12 +159,9 @@
mFolder.mFooter.setPivotX(0);
mFolder.mFooter.setPivotY(0);
- // We want to create a small X offset for the preview items, so that they follow their
- // expected path to their final locations. ie. an icon should not move right, if it's final
- // location is to its left. This value is arbitrarily defined.
- int previewItemOffsetX = (int) (previewSize / 2);
+ int previewItemOffsetX = 0;
if (Utilities.isRtl(mContext.getResources())) {
- previewItemOffsetX = (int) (lp.width * initialScale - initialSize - previewItemOffsetX);
+ previewItemOffsetX = (int) (lp.width * initialScale - initialSize);
}
final int paddingOffsetX = (int) (mContent.getPaddingLeft() * initialScale);
@@ -239,29 +237,19 @@
play(a, shapeDelegate.createRevealAnimator(
mFolder, startRect, endRect, finalRadius, !mIsOpening));
- // Create reveal animator for the folder content (capture the top 4 icons 2x2)
- int width = mDeviceProfile.folderCellLayoutBorderSpacePx.x
- + mDeviceProfile.folderCellWidthPx * 2;
- int rtlExtraWidth = 0;
- int height = mDeviceProfile.folderCellLayoutBorderSpacePx.y
- + mDeviceProfile.folderCellHeightPx * 2;
int page = mIsOpening ? mContent.getCurrentPage() : mContent.getDestinationPage();
- // In RTL we want to move to the last 2 columns of icons in the folder.
if (Utilities.isRtl(mContext.getResources())) {
page = (mContent.getPageCount() - 1) - page;
- CellLayout clAtPage = mContent.getPageAt(page);
- if (clAtPage != null) {
- int numExtraRows = clAtPage.getCountX() - 2;
- rtlExtraWidth = (int) Math.max(numExtraRows * (mDeviceProfile.folderCellWidthPx
- + mDeviceProfile.folderCellLayoutBorderSpacePx.x), rtlExtraWidth);
- }
}
- int left = mContent.getPaddingLeft() + page * lp.width;
+ int left = page * lp.width;
+
+ int extraRadius = (int) ((mDeviceProfile.folderIconSizePx / initialScale)
+ * EXTRA_FOLDER_REVEAL_RADIUS_PERCENTAGE);
Rect contentStart = new Rect(
- left + rtlExtraWidth,
- 0,
- left + width + mContent.getPaddingRight() + rtlExtraWidth,
- height);
+ (int) (left + (startRect.left / initialScale)) - extraRadius,
+ (int) (startRect.top / initialScale) - extraRadius,
+ (int) (left + (startRect.right / initialScale)) + extraRadius,
+ (int) (startRect.bottom / initialScale) + extraRadius);
Rect contentEnd = new Rect(left, 0, left + lp.width, lp.height);
play(a, shapeDelegate.createRevealAnimator(
mFolder.getContent(), contentStart, contentEnd, finalRadius, !mIsOpening));
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index ed25186..cf03462 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -93,12 +93,12 @@
@Override
public void close() {
- MODEL_EXECUTOR.execute(() -> mUserChangeReceiver.unregisterReceiverSafely(mContext));
+ MODEL_EXECUTOR.execute(() -> mUserChangeReceiver.unregisterReceiverSafelySync(mContext));
}
@WorkerThread
private void initAsync() {
- mUserChangeReceiver.register(mContext,
+ mUserChangeReceiver.registerSync(mContext,
Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
Intent.ACTION_MANAGED_PROFILE_REMOVED,
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index f46dcd3..78709b8 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -46,7 +46,6 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
@@ -67,10 +66,7 @@
import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.views.FloatingIconView;
-import com.android.launcher3.views.Snackbar;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.widget.PendingAddShortcutInfo;
-import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.WidgetAddFlowHandler;
import com.android.launcher3.widget.WidgetManagerHelper;
@@ -127,20 +123,6 @@
}
} else if (tag instanceof ItemClickProxy) {
((ItemClickProxy) tag).onItemClicked(v);
- } else if (tag instanceof PendingAddShortcutInfo) {
- CharSequence msg = Utilities.wrapForTts(
- launcher.getText(R.string.long_press_shortcut_to_add),
- launcher.getString(R.string.long_accessible_way_to_add_shortcut));
- Snackbar.show(launcher, msg, null);
- } else if (tag instanceof PendingAddWidgetInfo) {
- if (DEBUG) {
- String targetPackage = ((PendingAddWidgetInfo) tag).getTargetPackage();
- Log.d(TAG, "onClick: PendingAddWidgetInfo clicked for package=" + targetPackage);
- }
- CharSequence msg = Utilities.wrapForTts(
- launcher.getText(R.string.long_press_widget_to_add),
- launcher.getString(R.string.long_accessible_way_to_add));
- Snackbar.show(launcher, msg, null);
}
}
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 7f36d6f..3dcc663 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -132,11 +132,11 @@
mWindowContext.registerComponentCallbacks(this);
} else {
mWindowContext = null;
- mReceiver.register(mContext, ACTION_CONFIGURATION_CHANGED);
+ mReceiver.registerAsync(mContext, ACTION_CONFIGURATION_CHANGED);
}
// Initialize navigation mode change listener
- mReceiver.registerPkgActions(mContext, TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED);
+ mReceiver.registerPkgActionsAsync(mContext, TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED);
WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(context);
Context displayInfoContext = getDisplayInfoContext(display);
@@ -223,6 +223,7 @@
} else {
// TODO: unregister broadcast receiver
}
+ mReceiver.unregisterReceiverSafelyAsync(mContext);
}
/**
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
index 94f9e4f..2737249 100644
--- a/src/com/android/launcher3/util/LockedUserState.kt
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -25,6 +25,7 @@
val isUserUnlockedAtLauncherStartup: Boolean
var isUserUnlocked: Boolean
private set
+
private val mUserUnlockedActions: RunnableList = RunnableList()
@VisibleForTesting
@@ -50,22 +51,18 @@
if (isUserUnlocked) {
notifyUserUnlocked()
} else {
- mUserUnlockedReceiver.register(mContext, Intent.ACTION_USER_UNLOCKED)
+ mUserUnlockedReceiver.registerAsync(mContext, Intent.ACTION_USER_UNLOCKED)
}
}
private fun notifyUserUnlocked() {
mUserUnlockedActions.executeAllAndDestroy()
- Executors.THREAD_POOL_EXECUTOR.execute {
- mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
- }
+ mUserUnlockedReceiver.unregisterReceiverSafelyAsync(mContext)
}
/** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */
override fun close() {
- Executors.THREAD_POOL_EXECUTOR.execute {
- mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
- }
+ mUserUnlockedReceiver.unregisterReceiverSafelyAsync(mContext)
}
/**
diff --git a/src/com/android/launcher3/util/ScreenOnTracker.java b/src/com/android/launcher3/util/ScreenOnTracker.java
index e16e477..c1d192c 100644
--- a/src/com/android/launcher3/util/ScreenOnTracker.java
+++ b/src/com/android/launcher3/util/ScreenOnTracker.java
@@ -42,12 +42,12 @@
// Assume that the screen is on to begin with
mContext = context;
mIsScreenOn = true;
- mReceiver.register(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
+ mReceiver.registerAsync(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
}
@Override
public void close() {
- mReceiver.unregisterReceiverSafely(mContext);
+ mReceiver.unregisterReceiverSafelyAsync(mContext);
}
private void onReceive(Intent intent) {
diff --git a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
index 064bcd0..5f39cce 100644
--- a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
+++ b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
@@ -15,14 +15,21 @@
*/
package com.android.launcher3.util;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Looper;
import android.os.PatternMatcher;
import android.text.TextUtils;
import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.BuildConfig;
import java.util.function.Consumer;
@@ -39,21 +46,63 @@
mIntentConsumer.accept(intent);
}
- /**
- * Helper method to register multiple actions
- */
- public void register(Context context, String... actions) {
+ /** Helper method to register multiple actions. Caller should be on main thread. */
+ @UiThread
+ public void registerAsync(Context context, String... actions) {
+ assertOnMainThread();
+ UI_HELPER_EXECUTOR.execute(() -> registerSync(context, actions));
+ }
+
+ /** Helper method to register multiple actions. Caller should be on main thread. */
+ @WorkerThread
+ public void registerSync(Context context, String... actions) {
+ assertOnBgThread();
context.registerReceiver(this, getFilter(actions));
}
/**
- * Helper method to register multiple actions associated with a paction
+ * Helper method to register multiple actions associated with a action. Caller should be from
+ * main thread.
*/
- public void registerPkgActions(Context context, @Nullable String pkg, String... actions) {
+ @UiThread
+ public void registerPkgActionsAsync(Context context, @Nullable String pkg, String... actions) {
+ assertOnMainThread();
+ UI_HELPER_EXECUTOR.execute(() -> registerPkgActionsSync(context, pkg, actions));
+ }
+
+ /**
+ * Helper method to register multiple actions associated with a action. Caller should be from
+ * bg thread.
+ */
+ @WorkerThread
+ public void registerPkgActionsSync(Context context, @Nullable String pkg, String... actions) {
+ assertOnBgThread();
context.registerReceiver(this, getPackageFilter(pkg, actions));
}
/**
+ * Unregisters the receiver ignoring any errors on bg thread. Caller should be on main thread.
+ */
+ @UiThread
+ public void unregisterReceiverSafelyAsync(Context context) {
+ assertOnMainThread();
+ UI_HELPER_EXECUTOR.execute(() -> unregisterReceiverSafelySync(context));
+ }
+
+ /**
+ * Unregisters the receiver ignoring any errors on bg thread. Caller should be on bg thread.
+ */
+ @WorkerThread
+ public void unregisterReceiverSafelySync(Context context) {
+ assertOnBgThread();
+ try {
+ context.unregisterReceiver(this);
+ } catch (IllegalArgumentException e) {
+ // It was probably never registered or already unregistered. Ignore.
+ }
+ }
+
+ /**
* Creates an intent filter to listen for actions with a specific package in the data field.
*/
public static IntentFilter getPackageFilter(String pkg, String... actions) {
@@ -73,14 +122,19 @@
return filter;
}
- /**
- * Unregisters the receiver ignoring any errors
- */
- public void unregisterReceiverSafely(Context context) {
- try {
- context.unregisterReceiver(this);
- } catch (IllegalArgumentException e) {
- // It was probably never registered or already unregistered. Ignore.
+ private static void assertOnBgThread() {
+ if (BuildConfig.IS_STUDIO_BUILD && isMainThread()) {
+ throw new IllegalStateException("Should not be called from main thread!");
}
}
+
+ private static void assertOnMainThread() {
+ if (BuildConfig.IS_STUDIO_BUILD && !isMainThread()) {
+ throw new IllegalStateException("Should not be called from bg thread!");
+ }
+ }
+
+ private static boolean isMainThread() {
+ return Thread.currentThread() == Looper.getMainLooper().getThread();
+ }
}
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index b97b889..a2277a0 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -198,10 +198,11 @@
public void setWindowToken(IBinder token) {
mWindowToken = token;
if (mWindowToken == null && mRegistered) {
- mWallpaperChangeReceiver.unregisterReceiverSafely(mWorkspace.getContext());
+ mWallpaperChangeReceiver.unregisterReceiverSafelyAsync(mWorkspace.getContext());
mRegistered = false;
} else if (mWindowToken != null && !mRegistered) {
- mWallpaperChangeReceiver.register(mWorkspace.getContext(), ACTION_WALLPAPER_CHANGED);
+ mWallpaperChangeReceiver.registerAsync(
+ mWorkspace.getContext(), ACTION_WALLPAPER_CHANGED);
onWallpaperChanged();
mRegistered = true;
}
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 1368084..c59e295 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -331,8 +331,21 @@
* status bar, into account.
*/
protected void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthUsed = getInsetsWidth();
+
DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
+ measureChildWithMargins(mContent, widthMeasureSpec,
+ widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding);
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
+ MeasureSpec.getSize(heightMeasureSpec));
+ }
+
+ /**
+ * Returns the width used on left and right by the insets / padding.
+ */
+ protected int getInsetsWidth() {
int widthUsed;
+ DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
if (deviceProfile.isTablet) {
widthUsed = Math.max(2 * getTabletHorizontalMargin(deviceProfile),
2 * (mInsets.left + mInsets.right));
@@ -343,11 +356,7 @@
widthUsed = Math.max(padding.left + padding.right,
2 * (mInsets.left + mInsets.right));
}
-
- measureChildWithMargins(mContent, widthMeasureSpec,
- widthUsed, heightMeasureSpec, deviceProfile.bottomSheetTopPadding);
- setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
- MeasureSpec.getSize(heightMeasureSpec));
+ return widthUsed;
}
/** Returns the horizontal margins to be applied to the widget sheet. **/
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 9929892..fd15677 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -55,7 +55,6 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -416,19 +415,18 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
+ updateMaxSpansPerRow(availableWidth);
doMeasure(widthMeasureSpec, heightMeasureSpec);
-
- if (updateMaxSpansPerRow()) {
- doMeasure(widthMeasureSpec, heightMeasureSpec);
- }
}
- /** Returns {@code true} if the max spans have been updated. */
- private boolean updateMaxSpansPerRow() {
- if (getMeasuredWidth() == 0) return false;
-
- @Px int maxHorizontalSpan = getContentView().getMeasuredWidth()
- - (2 * mContentHorizontalMargin);
+ /** Returns {@code true} if the max spans have been updated.
+ *
+ * @param availableWidth Total width available within parent (includes insets).
+ */
+ private void updateMaxSpansPerRow(int availableWidth) {
+ @Px int maxHorizontalSpan = getAvailableWidthForSuggestions(
+ availableWidth - getInsetsWidth());
if (mMaxSpanPerRow != maxHorizontalSpan) {
mMaxSpanPerRow = maxHorizontalSpan;
mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(
@@ -439,16 +437,15 @@
mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(
maxHorizontalSpan);
}
- onRecommendedWidgetsBound();
- return true;
+ post(this::onRecommendedWidgetsBound);
}
- return false;
}
- protected View getContentView() {
- return mHasWorkProfile
- ? mViewPager
- : mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
+ /**
+ * Returns the width available to display suggestions.
+ */
+ protected int getAvailableWidthForSuggestions(int pickerAvailableWidth) {
+ return pickerAvailableWidth - (2 * mContentHorizontalMargin);
}
@Override
@@ -493,7 +490,7 @@
.mWidgetsListAdapter.hasVisibleEntries());
if (mIsNoWidgetsViewNeeded != isNoWidgetsViewNeeded) {
mIsNoWidgetsViewNeeded = isNoWidgetsViewNeeded;
- onRecommendedWidgetsBound();
+ post(this::onRecommendedWidgetsBound);
}
}
@@ -549,7 +546,7 @@
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE);
// Visibility of recommended widgets, recycler views and headers are handled in methods
// below.
- onRecommendedWidgetsBound();
+ post(this::onRecommendedWidgetsBound);
onWidgetsBound();
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java b/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java
index 854700f..6a1921e 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListItemAnimator.java
@@ -16,6 +16,8 @@
package com.android.launcher3.widget.picker;
+import static android.animation.ValueAnimator.areAnimatorsEnabled;
+
import static com.android.launcher3.widget.picker.WidgetsListAdapter.VIEW_TYPE_WIDGETS_LIST;
import androidx.recyclerview.widget.DefaultItemAnimator;
@@ -26,6 +28,14 @@
public static final int MOVE_DURATION_MS = 90;
public static final int ADD_DURATION_MS = 120;
+ // DefaultItemAnimator runs change and move animations before running add animations (i.e.
+ // before expanded list item's content start animating to become visible on screen).
+ public static final int WIDGET_LIST_ITEM_APPEARANCE_START_DELAY =
+ areAnimatorsEnabled() ? (CHANGE_DURATION_MS + MOVE_DURATION_MS) : 0;
+ // Delay after which all item animations are ran and list item's content is visible.
+ public static final int WIDGET_LIST_ITEM_APPEARANCE_DELAY =
+ WIDGET_LIST_ITEM_APPEARANCE_START_DELAY + ADD_DURATION_MS;
+
public WidgetsListItemAnimator() {
super();
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index 45d733a..679b0f5 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -15,10 +15,7 @@
*/
package com.android.launcher3.widget.picker;
-import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.CHANGE_DURATION_MS;
-import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.MOVE_DURATION_MS;
-
-import static android.animation.ValueAnimator.areAnimatorsEnabled;
+import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.WIDGET_LIST_ITEM_APPEARANCE_START_DELAY;
import android.content.Context;
import android.graphics.Bitmap;
@@ -157,8 +154,7 @@
// Pass resize delay to let the "move" and "change" animations run before resizing the
// row.
tableRow.setupRow(widgetItems.size(),
- /*resizeDelayMs=*/
- areAnimatorsEnabled() ? (CHANGE_DURATION_MS + MOVE_DURATION_MS) : 0);
+ /*resizeDelayMs=*/ WIDGET_LIST_ITEM_APPEARANCE_START_DELAY);
if (tableRow.getChildCount() > widgetItems.size()) {
for (int j = widgetItems.size(); j < tableRow.getChildCount(); j++) {
tableRow.getChildAt(j).setVisibility(View.GONE);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 5d71db6..840d98a 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER;
import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree;
import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree;
+import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.WIDGET_LIST_ITEM_APPEARANCE_DELAY;
import android.content.Context;
import android.graphics.Rect;
@@ -31,6 +32,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
@@ -281,10 +283,19 @@
mRightPane.removeAllViews();
mRightPane.addView(mWidgetRecommendationsContainer);
mRightPaneScrollView.setScrollY(0);
- mRightPane.setAccessibilityPaneTitle(suggestionsRightPaneTitle);
mSuggestedWidgetsPackageUserKey = PackageUserKey.fromPackageItemInfo(packageItemInfo);
final boolean isChangingHeaders = mSelectedHeader == null
|| !mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey);
+ // If the initial focus view is still focused, it is likely a programmatic header
+ // click.
+ if (mSelectedHeader != null
+ && !getAccessibilityInitialFocusView().isAccessibilityFocused()) {
+ post(() -> {
+ mRightPaneScrollView.setAccessibilityPaneTitle(suggestionsRightPaneTitle);
+ mRightPaneScrollView.performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ });
+ }
if (isChangingHeaders) {
// If switching from another header, unselect any WidgetCells. This is necessary
// because we do not clear/recycle the WidgetCells in the recommendations container
@@ -296,7 +307,6 @@
mSelectedHeader = mSuggestedWidgetsPackageUserKey;
});
mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader);
- mRightPane.setAccessibilityPaneTitle(suggestionsRightPaneTitle);
}
@Override
@@ -313,6 +323,30 @@
}
@Override
+ @Px
+ protected int getAvailableWidthForSuggestions(int pickerAvailableWidth) {
+ int rightPaneWidth = (int) Math.ceil(0.67 * pickerAvailableWidth);
+
+ if (mDeviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) {
+ // See onLayout
+ int leftPaneWidth = (int) (0.33 * pickerAvailableWidth);
+ @Px int minLeftPaneWidthPx = Utilities.dpToPx(MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP);
+ @Px int maxLeftPaneWidthPx = Utilities.dpToPx(MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP);
+ if (leftPaneWidth < minLeftPaneWidthPx) {
+ leftPaneWidth = minLeftPaneWidthPx;
+ } else if (leftPaneWidth > maxLeftPaneWidthPx) {
+ leftPaneWidth = maxLeftPaneWidthPx;
+ }
+ rightPaneWidth = pickerAvailableWidth - leftPaneWidth;
+ }
+
+ // Since suggestions are shown in right pane, the available width is 2/3 of total width of
+ // bottom sheet.
+ return rightPaneWidth - getResources().getDimensionPixelSize(
+ R.dimen.widget_list_horizontal_margin_two_pane); // right pane end margin.
+ }
+
+ @Override
public void onActivePageChanged(int currentActivePage) {
super.onActivePageChanged(currentActivePage);
@@ -323,12 +357,14 @@
mActivePage = currentActivePage;
- if (mSuggestedWidgetsHeader == null) {
- mAdapters.get(currentActivePage).mWidgetsListAdapter.selectFirstHeaderEntry();
- mAdapters.get(currentActivePage).mWidgetsRecyclerView.scrollToTop();
- } else if (currentActivePage == PERSONAL_TAB || currentActivePage == WORK_TAB) {
- mSuggestedWidgetsHeader.callOnClick();
- }
+ // When using talkback, swiping left while on right pane, should navigate to the widgets
+ // list on left.
+ mAdapters.get(mActivePage).mWidgetsRecyclerView.setAccessibilityTraversalBefore(
+ mRightPaneScrollView.getId());
+
+ // On page change, select the first item in the list to show in the right pane.
+ mAdapters.get(currentActivePage).mWidgetsListAdapter.selectFirstHeaderEntry();
+ mAdapters.get(currentActivePage).mWidgetsRecyclerView.scrollToTop();
}
@Override
@@ -372,17 +408,16 @@
}
- @Override
- protected View getContentView() {
- return mRightPane;
- }
-
private HeaderChangeListener getHeaderChangeListener() {
return new HeaderChangeListener() {
@Override
public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) {
final boolean isSameHeader = mSelectedHeader != null
&& mSelectedHeader.equals(selectedHeader);
+ // If the initial focus view is still focused, it is likely a programmatic header
+ // click.
+ final boolean isUserClick = mSelectedHeader != null
+ && !getAccessibilityInitialFocusView().isAccessibilityFocused();
mSelectedHeader = selectedHeader;
WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider()
.getSelectedAppWidgets(selectedHeader);
@@ -427,11 +462,14 @@
};
mRightPane.removeAllViews();
mRightPane.addView(widgetsRowViewHolder.itemView);
+ if (isUserClick) {
+ mRightPaneScrollView.setAccessibilityPaneTitle(getContext().getString(
+ R.string.widget_picker_right_pane_accessibility_title,
+ contentEntry.mPkgItem.title));
+ postDelayed(() -> focusOnFirstWidgetCell(widgetsRowViewHolder.tableContainer),
+ WIDGET_LIST_ITEM_APPEARANCE_DELAY);
+ }
mRightPaneScrollView.setScrollY(0);
- mRightPane.setAccessibilityPaneTitle(
- getContext().getString(
- R.string.widget_picker_right_pane_accessibility_title,
- contentEntry.mPkgItem.title));
}
};
}
@@ -445,6 +483,18 @@
}
}
+ /**
+ * Requests focus on the first widget cell in the given widget section.
+ */
+ private static void focusOnFirstWidgetCell(ViewGroup parent) {
+ if (parent == null) return;
+ WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell);
+ if (cell != null) {
+ cell.performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
+ }
+
private static void unselectWidgetCell(ViewGroup parent, WidgetItem item) {
if (parent == null || item == null) return;
WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell wc
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
index b239aed..ec83b8b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
@@ -8,7 +8,6 @@
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
-import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.DeviceFlagsValueProvider
@@ -34,6 +33,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
class GeneratedPreviewTest {
@get:Rule val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
private val providerName =
@@ -104,7 +104,6 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
fun widgetItem_hasGeneratedPreview() {
assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isTrue()
assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
@@ -112,7 +111,6 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
fun widgetItem_hasGeneratedPreview_noPreview() {
appWidgetProviderInfo.generatedPreviewCategories = 0
createWidgetItem()
@@ -122,7 +120,6 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
fun widgetItem_hasGeneratedPreview_nullPreview() {
appWidgetProviderInfo.generatedPreviewCategories =
WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD
@@ -134,22 +131,12 @@
}
@Test
- @RequiresFlagsDisabled(FLAG_ENABLE_GENERATED_PREVIEWS)
- fun widgetItem_hasGeneratedPreview_flagDisabled() {
- assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_HOME_SCREEN)).isFalse()
- assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
- assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
- }
-
- @Test
- @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
fun widgetItem_getGeneratedPreview() {
val preview = widgetItem.generatedPreviews.get(WIDGET_CATEGORY_HOME_SCREEN)
assertThat(preview).isEqualTo(generatedPreview)
}
@Test
- @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
fun widgetCell_showGeneratedPreview() {
widgetCell.applyFromCellItem(widgetItem)
DatabaseWidgetPreviewLoader.getLoaderExecutor().submit {}.get()
@@ -157,12 +144,4 @@
assertThat(widgetCell.appWidgetHostViewPreview?.appWidgetInfo)
.isEqualTo(appWidgetProviderInfo)
}
-
- @Test
- @RequiresFlagsDisabled(FLAG_ENABLE_GENERATED_PREVIEWS)
- fun widgetCell_showGeneratedPreview_flagDisabled() {
- widgetCell.applyFromCellItem(widgetItem)
- DatabaseWidgetPreviewLoader.getLoaderExecutor().submit {}.get()
- assertThat(widgetCell.appWidgetHostViewPreview).isNull()
- }
}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index a6f4441..6e01f9e 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -239,7 +239,8 @@
final CountDownLatch count = new CountDownLatch(2);
final SimpleBroadcastReceiver broadcastReceiver =
new SimpleBroadcastReceiver(i -> count.countDown());
- broadcastReceiver.registerPkgActions(mTargetContext, pkg,
+ // We OK to make binder calls on main thread in test.
+ broadcastReceiver.registerPkgActionsSync(mTargetContext, pkg,
Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED);
mDevice.executeShellCommand("pm clear " + pkg);
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
index e92d641..ae24a57 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
@@ -247,7 +247,6 @@
}
@ScreenRecordRule.ScreenRecord // b/329935119
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/329935119
@Test
@PortraitLandscape
public void testEmptyPageDoesNotGetRemovedIfPagePairIsNotEmpty() {