Merge "[Launcher Jank] Add TODO for LauncherPreviewRenderer to set bg handler on smartspace BaseTemplateCard" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 88804b7..21b9863 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -310,3 +310,9 @@
bug: "341795751"
}
+flag {
+ name: "enable_new_archiving_icon"
+ namespace: "launcher"
+ description: "Archived apps will use new icon in app title"
+ bug: "350758155"
+}
diff --git a/go/quickstep/res/values-fr-rCA/strings.xml b/go/quickstep/res/values-fr-rCA/strings.xml
index 2cc9d8f..e48faeb 100644
--- a/go/quickstep/res/values-fr-rCA/strings.xml
+++ b/go/quickstep/res/values-fr-rCA/strings.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="app_share_drop_target_label" msgid="5804774105974539508">"Partager application"</string>
+ <string name="app_share_drop_target_label" msgid="5804774105974539508">"Partager appli"</string>
<string name="action_listen" msgid="2370304050784689486">"Écouter"</string>
<string name="action_translate" msgid="8028378961867277746">"Traduire"</string>
<string name="action_search" msgid="6269564710943755464">"Lentille"</string>
@@ -9,12 +9,12 @@
<string name="dialog_cancel" msgid="6464336969134856366">"ANNULER"</string>
<string name="dialog_settings" msgid="6564397136021186148">"PARAMÈTRES"</string>
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Traduire ou écouter le texte à l\'écran"</string>
- <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Des renseignements comme du texte sur votre écran, des adresses Web et des captures d\'écran peuvent être partagés avec Google.\n\nPour modifier les renseignements que vous partagez, accédez à "<b>"Paramètres > Applications > Applications par défaut > Application d\'assistant numérique"</b>"."</string>
+ <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Des renseignements comme du texte sur votre écran, des adresses Web et des captures d\'écran peuvent être partagés avec Google.\n\nPour modifier les renseignements que vous partagez, accédez à "<b>"Paramètres > Applis > Applis par défaut > Appli d\'assistant numérique"</b>"."</string>
<string name="assistant_not_selected_title" msgid="5017072974603345228">"Choisir un assistant pour utiliser cette fonctionnalité"</string>
- <string name="assistant_not_selected_text" msgid="3244613673884359276">"Pour écouter ou traduire le texte affiché sur votre écran, choisissez l\'application d\'un assistant numérique dans les paramètres"</string>
+ <string name="assistant_not_selected_text" msgid="3244613673884359276">"Pour écouter ou traduire le texte affiché sur votre écran, choisissez l\'appli d\'un assistant numérique dans les paramètres"</string>
<string name="assistant_not_supported_title" msgid="1675788067597484142">"Modifier votre assistant pour utiliser cette fonctionnalité"</string>
- <string name="assistant_not_supported_text" msgid="1708031078549268884">"Pour écouter ou traduire le texte affiché sur votre écran, modifiez l\'application de votre assistant numérique dans les paramètres"</string>
+ <string name="assistant_not_supported_text" msgid="1708031078549268884">"Pour écouter ou traduire le texte affiché sur votre écran, modifiez l\'appli de votre assistant numérique dans les paramètres"</string>
<string name="tooltip_listen" msgid="7634466447860989102">"Touchez ce bouton pour écouter le texte affiché sur cet écran"</string>
<string name="tooltip_translate" msgid="4184845868901542567">"Touchez ce bouton pour traduire le texte affiché sur cet écran"</string>
- <string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"Cette application ne peut pas être partagée"</string>
+ <string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"Cette appli ne peut pas être partagée"</string>
</resources>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index edfb59e..9510494 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -23,34 +23,34 @@
<string name="recent_task_option_freeform" msgid="48863056265284071">"Forme libre"</string>
<string name="recent_task_option_desktop" msgid="8280879717125435668">"Ordinateur de bureau"</string>
<string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
- <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres d\'utilisation de l\'application"</string>
+ <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres d\'utilisation de l\'appli"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"Tout effacer"</string>
- <string name="accessibility_recent_apps" msgid="4058661986695117371">"Applications récentes"</string>
+ <string name="accessibility_recent_apps" msgid="4058661986695117371">"Applis récentes"</string>
<string name="task_view_closed" msgid="9170038230110856166">"Tâche fermée"</string>
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> : <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"< 1 min"</string>
<string name="time_left_for_app" msgid="3111996412933644358">"Il reste <xliff:g id="TIME">%1$s</xliff:g> aujourd\'hui"</string>
- <string name="title_app_suggestions" msgid="4185902664111965088">"Suggestions d\'applications"</string>
- <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Vos prédictions d\'applications"</string>
- <string name="hotseat_edu_title_migrate" msgid="306578144424489980">"Obtenir des suggestions d\'applications dans la rangée du bas de votre écran d\'accueil"</string>
- <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Retrouvez des suggestions d\'applications dans la rangée des favoris de votre écran d\'accueil"</string>
- <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accédez facilement aux applications que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applications dans la rangée du bas seront déplacées vers votre écran d\'accueil."</string>
- <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accédez facilement aux applications que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applications dans la rangée des favoris seront déplacées vers votre écran d\'accueil."</string>
- <string name="hotseat_edu_accept" msgid="1611544083278999837">"Obtenir des suggestions d\'applications"</string>
+ <string name="title_app_suggestions" msgid="4185902664111965088">"Suggestions d\'applis"</string>
+ <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Vos prédictions d\'applis"</string>
+ <string name="hotseat_edu_title_migrate" msgid="306578144424489980">"Obtenir des suggestions d\'applis dans la rangée du bas de votre écran d\'accueil"</string>
+ <string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"Retrouvez des suggestions d\'applis dans la rangée des favoris de votre écran d\'accueil"</string>
+ <string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"Accédez facilement aux applis que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applis dans la rangée du bas seront déplacées vers votre écran d\'accueil."</string>
+ <string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Accédez facilement aux applis que vous utilisez le plus, directement à l\'écran d\'accueil. Les suggestions changeront en fonction de vos habitudes. Les applis dans la rangée des favoris seront déplacées vers votre écran d\'accueil."</string>
+ <string name="hotseat_edu_accept" msgid="1611544083278999837">"Obtenir des suggestions d\'applis"</string>
<string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Non merci"</string>
<string name="hotseat_prediction_settings" msgid="6246554993566070818">"Paramètres"</string>
- <string name="hotseat_auto_enrolled" msgid="522100018967146807">"Les applications les plus utilisées s\'affichent ici et changent en fonction des habitudes"</string>
- <string name="hotseat_tip_no_empty_slots" msgid="1325212677738179185">"Faites glisser des applications hors de la rangée du bas pour obtenir des suggestions d\'applications"</string>
- <string name="hotseat_tip_gaps_filled" msgid="3035673010274223538">"Applications suggérées ajoutées à l\'espace vide"</string>
- <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Les suggestions d\'applications sont activées"</string>
- <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Les suggestions d\'applications sont désactivées"</string>
- <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Application prédite : <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+ <string name="hotseat_auto_enrolled" msgid="522100018967146807">"Les applis les plus utilisées s\'affichent ici et changent en fonction des habitudes"</string>
+ <string name="hotseat_tip_no_empty_slots" msgid="1325212677738179185">"Faites glisser des applis hors de la rangée du bas pour obtenir des suggestions d\'applis"</string>
+ <string name="hotseat_tip_gaps_filled" msgid="3035673010274223538">"Applis suggérées ajoutées à l\'espace vide"</string>
+ <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Les suggestions d\'applis sont activées"</string>
+ <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Les suggestions d\'applis sont désactivées"</string>
+ <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Appli prédite : <xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Faites pivoter votre appareil"</string>
<string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Veuillez faire pivoter votre appareil pour terminer le tutoriel de navigation par gestes"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Assurez-vous de balayer l\'écran à partir de l\'extrémité droite ou gauche"</string>
<string name="back_gesture_feedback_cancelled" msgid="762621530959111290">"Assurez-vous de balayer l\'écran à partir de l\'extrémité droite ou gauche vers le centre, puis allons-y"</string>
<string name="back_gesture_feedback_complete_with_overview_follow_up" msgid="9176400654037014471">"Vous avez appris à balayer de la droite pour revenir en arrière. Apprenez comment changer d\'appli."</string>
- <string name="back_gesture_feedback_complete_with_follow_up" msgid="8653374779579748392">"Vous avez appris le geste de retour en arrière. Maintenant, apprenez comment changer d\'application."</string>
+ <string name="back_gesture_feedback_complete_with_follow_up" msgid="8653374779579748392">"Vous avez appris le geste de retour en arrière. Maintenant, apprenez comment changer d\'appli."</string>
<string name="back_gesture_feedback_complete_without_follow_up" msgid="197189945858268342">"Vous avez appris le geste de retour en arrière"</string>
<string name="back_gesture_feedback_swipe_in_nav_bar" msgid="9157480023651452969">"Assurez-vous de ne pas balayer trop près du bas de l\'écran"</string>
<string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Modifiez la sensibilité du geste de retour dans Paramètres"</string>
@@ -74,11 +74,11 @@
<string name="overview_gesture_feedback_home_detected" msgid="663432226180397138">"Essayez de tenir la fenêtre plus longtemps avant de relâcher"</string>
<string name="overview_gesture_feedback_wrong_swipe_direction" msgid="1191055451018584958">"Assurez-vous de balayer l\'écran vers le haut, puis de faire une pause"</string>
<string name="overview_gesture_feedback_complete_with_follow_up" msgid="3544611727467765026">"Vous avez appris à utiliser les gestes. Pour les désactiver, accédez au menu Paramètres."</string>
- <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Vous avez appris le geste de changement d\'application"</string>
- <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Balayez pour basculer entre les applications"</string>
- <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Pour changer d\'application, balayez l\'écran de bas en haut, maintenez le doigt dessus, puis relâchez-le."</string>
+ <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Vous avez appris le geste de changement d\'appli"</string>
+ <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Balayez pour basculer entre les applis"</string>
+ <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Pour changer d\'appli, balayez l\'écran de bas en haut, maintenez le doigt dessus, puis relâchez-le."</string>
<string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Pour changer d\'appli, balayez l\'écran de bas en haut avec deux doigts, maintenez-les et relâchez-les."</string>
- <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Changer d\'application"</string>
+ <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Changer d\'appli"</string>
<string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Balayez l\'écran de bas en haut, maintenez le doigt en place, puis relâchez-le"</string>
<string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Bien joué!"</string>
<string name="gesture_tutorial_confirm_title" msgid="6201516182040074092">"Terminé"</string>
@@ -98,14 +98,14 @@
<string name="action_split" msgid="2098009717623550676">"Partager"</string>
<string name="action_save_app_pair" msgid="5974823919237645229">"Enr. paire d\'applis"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Toucher une autre appli pour partager l\'écran"</string>
- <string name="toast_contextual_split_select_app" msgid="433510957123687090">"Choisir une autre application pour utiliser l\'Écran divisé"</string>
+ <string name="toast_contextual_split_select_app" msgid="433510957123687090">"Choisir une autre appli pour utiliser l\'Écran divisé"</string>
<string name="toast_split_select_app_cancel" msgid="1532690483356445639"><b>"Annuler"</b></string>
<string name="toast_split_select_cont_desc" msgid="2119685056059607602">"Quitter la sélection d\'écran divisé"</string>
- <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Choisir une autre application pour utiliser l\'écran partagé"</string>
- <string name="blocked_by_policy" msgid="2071401072261365546">"L\'application ou votre organisation n\'autorise pas cette action"</string>
- <string name="split_widgets_not_supported" msgid="1355743038053053866">"Les widgets ne sont actuellement pas pris en charge. Veuillez sélectionner une autre application"</string>
+ <string name="toast_split_app_unsupported" msgid="2360229567007828914">"Choisir une autre appli pour utiliser l\'écran partagé"</string>
+ <string name="blocked_by_policy" msgid="2071401072261365546">"L\'appli ou votre organisation n\'autorise pas cette action"</string>
+ <string name="split_widgets_not_supported" msgid="1355743038053053866">"Les widgets ne sont actuellement pas pris en charge. Veuillez sélectionner une autre appli"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Ignorer le tutoriel sur la navigation?"</string>
- <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Vous trouverez le tutoriel dans l\'application <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Vous trouverez le tutoriel dans l\'appli <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Annuler"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Ignorer"</string>
<string name="accessibility_rotate_button" msgid="4771825231336502943">"Faire pivoter l\'écran"</string>
@@ -137,7 +137,7 @@
<string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Séparateur de la barre des tâches"</string>
<string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Déplacer vers le coin supérieur gauche de l\'écran"</string>
<string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Déplacer vers le coin inférieur droit de l\'écran"</string>
- <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afficher # autre application.}one{Afficher # autre application.}other{Afficher # autres applications.}}"</string>
+ <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afficher # autre appli.}one{Afficher # autre appli.}other{Afficher # autres applis.}}"</string>
<string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Afficher # appli de bureau.}one{Afficher # appli de bureau.}other{Afficher # applis de bureau.}}"</string>
<string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> et <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
<string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bulle"</string>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index dd3d16e..6aa755a 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -94,7 +94,7 @@
<string name="default_device_name" msgid="6660656727127422487">"enheten"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Innstillinger for systemnavigasjon"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Del"</string>
- <string name="action_screenshot" msgid="8171125848358142917">"Skjermdump"</string>
+ <string name="action_screenshot" msgid="8171125848358142917">"Skjermbilde"</string>
<string name="action_split" msgid="2098009717623550676">"Del opp"</string>
<string name="action_save_app_pair" msgid="5974823919237645229">"Lagre apptilkobling"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Trykk på en annen app for å bruke delt skjerm"</string>
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 7235b63..8984086 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -44,7 +44,6 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.widget.BaseWidgetSheet;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
@@ -81,6 +80,15 @@
*/
private static final String EXTRA_ADDED_APP_WIDGETS = "added_app_widgets";
/**
+ * Intent extra for the string representing the title displayed within the picker header.
+ */
+ private static final String EXTRA_PICKER_TITLE = "picker_title";
+ /**
+ * Intent extra for the string representing the description displayed within the picker header.
+ */
+ private static final String EXTRA_PICKER_DESCRIPTION = "picker_description";
+
+ /**
* A unique identifier of the surface hosting the widgets;
* <p>"widgets" is reserved for home screen surface.</p>
* <p>"widgets_hub" is reserved for glanceable hub surface.</p>
@@ -108,6 +116,10 @@
// Widgets existing on the host surface.
@NonNull
private List<AppWidgetProviderInfo> mAddedWidgets = new ArrayList<>();
+ @Nullable
+ private String mTitle;
+ @Nullable
+ private String mDescription;
/** A set of user ids that should be filtered out from the selected widgets. */
@NonNull
@@ -137,6 +149,9 @@
}
private void parseIntentExtras() {
+ mTitle = getIntent().getStringExtra(EXTRA_PICKER_TITLE);
+ mDescription = getIntent().getStringExtra(EXTRA_PICKER_DESCRIPTION);
+
// A value of 0 for either size means that no filtering will occur in that dimension. If
// both values are 0, then no size filtering will occur.
mDesiredWidgetWidth =
@@ -241,7 +256,7 @@
/** Updates the model with widgets, applies filters and launches the widgets sheet once
* widgets are available */
private void refreshAndBindWidgets() {
- MODEL_EXECUTOR.getHandler().postDelayed(() -> {
+ MODEL_EXECUTOR.execute(() -> {
LauncherAppState app = LauncherAppState.getInstance(this);
mModel.update(app, null);
final List<WidgetsListBaseEntry> allWidgets =
@@ -271,7 +286,7 @@
mUiSurface, allWidgetItems);
mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
}
- }, mDeviceProfile.bottomSheetOpenDuration);
+ });
}
private void bindWidgets(List<WidgetsListBaseEntry> widgets) {
@@ -280,7 +295,8 @@
private void openWidgetsSheet() {
MAIN_EXECUTOR.execute(() -> {
- BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true);
+ WidgetsFullSheet widgetSheet = WidgetsFullSheet.show(this, true);
+ widgetSheet.mayUpdateTitleAndDescription(mTitle, mDescription);
widgetSheet.disableNavBarScrim(true);
widgetSheet.addOnCloseListener(this::finish);
});
diff --git a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
index 41fcf61..8c98bab 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
@@ -59,6 +59,11 @@
public class WidgetPredictionsRequester {
private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
private static final String BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets";
+ // container/screenid/[positionx,positiony]/[spanx,spany]
+ // Matches the format passed used by PredictionHelper; But, position and size values aren't
+ // used, so, we pass default values.
+ @VisibleForTesting
+ static final String LAUNCH_LOCATION = "workspace/1/[0,0]/[2,2]";
@Nullable
private AppPredictor mAppPredictor;
@@ -86,7 +91,7 @@
*/
public void request(List<AppWidgetProviderInfo> existingWidgets,
Consumer<List<ItemInfo>> callback) {
- Bundle bundle = buildBundleForPredictionSession(existingWidgets, mUiSurface);
+ Bundle bundle = buildBundleForPredictionSession(existingWidgets);
Predicate<WidgetItem> filter = notOnUiSurfaceFilter(existingWidgets);
MODEL_EXECUTOR.execute(() -> {
@@ -112,17 +117,14 @@
* Returns a bundle that can be passed in a prediction session
*
* @param addedWidgets widgets that are already added by the user in the ui surface
- * @param uiSurface a unique identifier of the surface hosting widgets; format
- * "widgets_xx"; note - "widgets" is reserved for home screen surface.
*/
@VisibleForTesting
- static Bundle buildBundleForPredictionSession(List<AppWidgetProviderInfo> addedWidgets,
- String uiSurface) {
+ static Bundle buildBundleForPredictionSession(List<AppWidgetProviderInfo> addedWidgets) {
Bundle bundle = new Bundle();
ArrayList<AppTargetEvent> addedAppTargetEvents = new ArrayList<>();
for (AppWidgetProviderInfo info : addedWidgets) {
ComponentName componentName = info.provider;
- AppTargetEvent appTargetEvent = buildAppTargetEvent(uiSurface, info, componentName);
+ AppTargetEvent appTargetEvent = buildAppTargetEvent(info, componentName);
addedAppTargetEvents.add(appTargetEvent);
}
bundle.putParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, addedAppTargetEvents);
@@ -134,13 +136,13 @@
* predictor.
* Also see {@link PredictionHelper}
*/
- private static AppTargetEvent buildAppTargetEvent(String uiSurface, AppWidgetProviderInfo info,
+ private static AppTargetEvent buildAppTargetEvent(AppWidgetProviderInfo info,
ComponentName componentName) {
AppTargetId appTargetId = new AppTargetId("widget:" + componentName.getPackageName());
AppTarget appTarget = new AppTarget.Builder(appTargetId, componentName.getPackageName(),
/*user=*/ info.getProfile()).setClassName(componentName.getClassName()).build();
- return new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN)
- .setLaunchLocation(uiSurface).build();
+ return new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN).setLaunchLocation(
+ LAUNCH_LOCATION).build();
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 6c3b4ad..0f17a85 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -95,6 +95,7 @@
import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.TaskItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.PopupDataProvider;
@@ -794,6 +795,11 @@
*/
public void setUIController(@NonNull TaskbarUIController uiController) {
mControllers.setUiController(uiController);
+ if (mControllers.bubbleControllers.isEmpty()) {
+ // if the bubble bar was visible in a previous configuration of taskbar and is being
+ // recreated now without bubbles, clean up any bubble bar adjustments from hotseat
+ bubbleBarVisibilityChanged(/* isVisible= */ false);
+ }
}
/**
@@ -1132,6 +1138,11 @@
mControllers.uiController.onTaskbarIconLaunched(api);
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
}
+ } else if (tag instanceof TaskItemInfo info) {
+ UI_HELPER_EXECUTOR.execute(() ->
+ SystemUiProxy.INSTANCE.get(this).showDesktopApp(info.getTaskId()));
+ mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(
+ /* stash= */ true);
} else if (tag instanceof WorkspaceItemInfo) {
// Tapping a launchable icon on Taskbar
WorkspaceItemInfo info = (WorkspaceItemInfo) tag;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 58c5e83..0645972 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -282,6 +282,11 @@
}
uiController.dumpLogs(prefix + "\t", pw);
rotationButtonController.dumpLogs(prefix + "\t", pw);
+ if (bubbleControllers.isPresent()) {
+ bubbleControllers.get().dump(pw);
+ } else {
+ pw.println(String.format("%s\t%s", prefix, "Bubble controllers are empty."));
+ }
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 0b7ae39..5024cd8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -196,26 +196,26 @@
final TaskbarRecentAppsController recentAppsController =
mControllers.taskbarRecentAppsController;
hotseatItemInfos = recentAppsController.updateHotseatItemInfos(hotseatItemInfos);
- Set<String> runningPackages = recentAppsController.getRunningAppPackages();
- Set<String> minimizedPackages = recentAppsController.getMinimizedAppPackages();
+ Set<Integer> runningTaskIds = recentAppsController.getRunningTaskIds();
+ Set<Integer> minimizedTaskIds = recentAppsController.getMinimizedTaskIds();
if (mDeferUpdatesForSUW) {
ItemInfo[] finalHotseatItemInfos = hotseatItemInfos;
mDeferredUpdates = () ->
commitHotseatItemUpdates(finalHotseatItemInfos,
- recentAppsController.getShownTasks(), runningPackages,
- minimizedPackages);
+ recentAppsController.getShownTasks(), runningTaskIds,
+ minimizedTaskIds);
} else {
commitHotseatItemUpdates(hotseatItemInfos,
- recentAppsController.getShownTasks(), runningPackages, minimizedPackages);
+ recentAppsController.getShownTasks(), runningTaskIds, minimizedTaskIds);
}
}
private void commitHotseatItemUpdates(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks,
- Set<String> runningPackages, Set<String> minimizedPackages) {
+ Set<Integer> runningTaskIds, Set<Integer> minimizedTaskIds) {
mContainer.updateHotseatItems(hotseatItemInfos, recentTasks);
mControllers.taskbarViewController.updateIconViewsRunningStates(
- runningPackages, minimizedPackages);
+ runningTaskIds, minimizedTaskIds);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index d26a36d..ea091ca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -166,8 +166,12 @@
if (buttonType == BUTTON_SPACE) {
return false;
}
- // Provide the same haptic feedback that the system offers for virtual keys.
- view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+
+ // Provide the same haptic feedback that the system offers for long press.
+ // The haptic feedback from long pressing on the home button is handled by circle to search.
+ if (buttonType != BUTTON_HOME) {
+ view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ }
switch (buttonType) {
case BUTTON_HOME:
logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
@@ -179,10 +183,12 @@
return true;
case BUTTON_BACK:
logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS);
- return backRecentsLongpress(buttonType);
+ backRecentsLongpress(buttonType);
+ return true;
case BUTTON_RECENTS:
logEvent(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS);
- return backRecentsLongpress(buttonType);
+ backRecentsLongpress(buttonType);
+ return true;
case BUTTON_IME_SWITCH:
default:
return false;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index 36828a8..5c08116 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -18,6 +18,8 @@
import androidx.annotation.VisibleForTesting
import com.android.launcher3.Flags.enableRecentsInTaskbar
import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.TaskItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.statehandlers.DesktopVisibilityController
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
import com.android.launcher3.util.CancellableTask
@@ -58,9 +60,13 @@
// Initialized in init.
private lateinit var controllers: TaskbarControllers
- private var shownHotseatItems: List<ItemInfo> = emptyList()
+ var shownHotseatItems: List<ItemInfo> = emptyList()
+ private set
+
private var allRecentTasks: List<GroupTask> = emptyList()
private var desktopTask: DesktopTask? = null
+ // Keeps track of the order in which running tasks appear.
+ private var orderedRunningTaskIds = emptyList<Int>()
var shownTasks: List<GroupTask> = emptyList()
private set
@@ -70,9 +76,9 @@
private val isInDesktopMode: Boolean
get() = desktopVisibilityController?.areDesktopTasksVisible() ?: false
- val runningAppPackages: Set<String>
+ val runningTaskIds: Set<Int>
/**
- * Returns the package names of apps that should be indicated as "running" to the user.
+ * Returns the task IDs of apps that should be indicated as "running" to the user.
* Specifically, we return all the open tasks if we are in Desktop mode, else emptySet().
*/
get() {
@@ -80,22 +86,19 @@
return emptySet()
}
val tasks = desktopTask?.tasks ?: return emptySet()
- return tasks.map { task -> task.key.packageName }.toSet()
+ return tasks.map { task -> task.key.id }.toSet()
}
- val minimizedAppPackages: Set<String>
+ val minimizedTaskIds: Set<Int>
/**
- * Returns the package names of apps that should be indicated as "minimized" to the user.
- * Specifically, we return all the running packages where all the tasks in that package are
- * minimized (not visible).
+ * Returns the task IDs for the tasks that should be indicated as "minimized" to the user.
*/
get() {
if (!canShowRunningApps || !isInDesktopMode) {
return emptySet()
}
val desktopTasks = desktopTask?.tasks ?: return emptySet()
- val packageToTasks = desktopTasks.groupBy { it.key.packageName }
- return packageToTasks.filterValues { tasks -> tasks.all { !it.isVisible } }.keys
+ return desktopTasks.filter { !it.isVisible }.map { task -> task.key.id }.toSet()
}
private val recentTasksChangedListener =
@@ -137,25 +140,39 @@
.filter { itemInfo -> !itemInfo.isPredictedItem }
.toMutableList()
+ if (isInDesktopMode && canShowRunningApps) {
+ shownHotseatItems =
+ updateHotseatItemsFromRunningTasks(
+ getOrderedAndWrappedDesktopTasks(),
+ shownHotseatItems
+ )
+ }
+
onRecentsOrHotseatChanged()
return shownHotseatItems.toTypedArray()
}
+ private fun getOrderedAndWrappedDesktopTasks(): List<GroupTask> {
+ val tasks = desktopTask?.tasks ?: emptyList()
+ // Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
+ val orderFromId = orderedRunningTaskIds.withIndex().associate { (index, id) -> id to index }
+ val sortedTasks = tasks.sortedWith(compareBy(nullsLast()) { orderFromId[it.key.id] })
+ return sortedTasks.map { GroupTask(it) }
+ }
+
private fun reloadRecentTasksIfNeeded() {
if (!recentsModel.isTaskListValid(taskListChangeId)) {
taskListChangeId =
recentsModel.getTasks { tasks ->
allRecentTasks = tasks
- val oldRunningPackages = runningAppPackages
- val oldMinimizedPackages = minimizedAppPackages
+ val oldRunningTaskdIds = runningTaskIds
+ val oldMinimizedTaskIds = minimizedTaskIds
desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().firstOrNull()
- val runningPackagesChanged = oldRunningPackages != runningAppPackages
- val minimizedPackagessChanged = oldMinimizedPackages != minimizedAppPackages
+ val runningTasksChanged = oldRunningTaskdIds != runningTaskIds
+ val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds
if (
- onRecentsOrHotseatChanged() ||
- runningPackagesChanged ||
- minimizedPackagessChanged
+ onRecentsOrHotseatChanged() || runningTasksChanged || minimizedTasksChanged
) {
controllers.taskbarViewController.commitRunningAppsToUI()
}
@@ -170,6 +187,7 @@
*/
private fun onRecentsOrHotseatChanged(): Boolean {
val oldShownTasks = shownTasks
+ orderedRunningTaskIds = updateOrderedRunningTaskIds()
shownTasks =
if (isInDesktopMode) {
computeShownRunningTasks()
@@ -201,22 +219,39 @@
return shownTasksChanged
}
+ private fun updateOrderedRunningTaskIds(): MutableList<Int> {
+ val desktopTaskAsList = getOrderedAndWrappedDesktopTasks()
+ val desktopTaskIds = desktopTaskAsList.map { it.task1.key.id }
+ var newOrder =
+ orderedRunningTaskIds
+ .filter { it in desktopTaskIds } // Only keep the tasks that are still running
+ .toMutableList()
+ // Add new tasks not already listed
+ newOrder.addAll(desktopTaskIds.filter { it !in newOrder })
+ return newOrder
+ }
+
private fun computeShownRunningTasks(): List<GroupTask> {
if (!canShowRunningApps) {
return emptyList()
}
- val tasks = desktopTask?.tasks ?: emptyList()
- // Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
- var desktopTaskAsList = tasks.map { GroupTask(it) }
- // TODO(b/315344726 Multi-instance support): dedupe Tasks of the same package too.
- desktopTaskAsList = dedupeHotseatTasks(desktopTaskAsList, shownHotseatItems)
- val desktopPackages = desktopTaskAsList.map { it.packageNames }
- // Remove any missing Tasks.
- val newShownTasks = shownTasks.filter { it.packageNames in desktopPackages }.toMutableList()
- val newShownPackages = newShownTasks.map { it.packageNames }
+ val desktopTaskAsList = getOrderedAndWrappedDesktopTasks()
+ val desktopTaskIds = desktopTaskAsList.map { it.task1.key.id }
+ val shownTaskIds = shownTasks.map { it.task1.key.id }
+ // TODO(b/315344726 Multi-instance support): only show one icon per package once we support
+ // taskbar multi-instance menus
+ val shownHotseatItemTaskIds =
+ shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
+ // Remove any newly-missing Tasks, and actual group-tasks
+ val newShownTasks =
+ shownTasks
+ .filter { !it.hasMultipleTasks() }
+ .filter { it.task1.key.id in desktopTaskIds }
+ .toMutableList()
// Add any new Tasks, maintaining the order from previous shownTasks.
- newShownTasks.addAll(desktopTaskAsList.filter { it.packageNames !in newShownPackages })
- return newShownTasks.toList()
+ newShownTasks.addAll(desktopTaskAsList.filter { it.task1.key.id !in shownTaskIds })
+ // Remove any tasks already covered by Hotseat icons
+ return newShownTasks.filter { it.task1.key.id !in shownHotseatItemTaskIds }
}
private fun computeShownRecentTasks(): List<GroupTask> {
@@ -245,6 +280,25 @@
}
}
+ /**
+ * Returns the hotseat items updated so that any item that points to a package with a running
+ * task also references that task.
+ */
+ private fun updateHotseatItemsFromRunningTasks(
+ groupTasks: List<GroupTask>,
+ shownHotseatItems: List<ItemInfo>
+ ): List<ItemInfo> =
+ shownHotseatItems.map { itemInfo ->
+ if (itemInfo is TaskItemInfo) {
+ itemInfo
+ } else {
+ val foundTask =
+ groupTasks.find { task -> task.task1.key.packageName == itemInfo.targetPackage }
+ ?: return@map itemInfo
+ TaskItemInfo(foundTask.task1.key.id, itemInfo as WorkspaceItemInfo)
+ }
+ }
+
override fun dumpLogs(prefix: String, pw: PrintWriter) {
pw.println("$prefix TaskbarRecentAppsController:")
pw.println("$prefix\tcanShowRunningApps=$canShowRunningApps")
@@ -253,8 +307,8 @@
pw.println("$prefix\tallRecentTasks=${allRecentTasks.map { it.packageNames }}")
pw.println("$prefix\tdesktopTask=${desktopTask?.packageNames}")
pw.println("$prefix\tshownTasks=${shownTasks.map { it.packageNames }}")
- pw.println("$prefix\trunningTasks=$runningAppPackages")
- pw.println("$prefix\tminimizedTasks=$minimizedAppPackages")
+ pw.println("$prefix\trunningTaskIds=$runningTaskIds")
+ pw.println("$prefix\tminimizedTaskIds=$minimizedTaskIds")
}
private val GroupTask.packageNames: List<String>
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 170e018..a2278ec 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -270,7 +270,7 @@
foundTask,
taskContainer.getIconView().getDrawable(),
taskContainer.getSnapshotView(),
- taskContainer.getThumbnail(),
+ taskContainer.getSplitAnimationThumbnail(),
null /* intent */,
null /* user */,
info);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index e59a016..527e3a3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -63,6 +63,7 @@
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.TaskItemInfo;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherBindableItemsContainer;
@@ -515,35 +516,38 @@
return mTaskbarView.getTaskbarDividerView();
}
- /** Updates which icons are marked as running given the Set of currently running packages. */
- public void updateIconViewsRunningStates(Set<String> runningPackages,
- Set<String> minimizedPackages) {
+ /**
+ * Updates which icons are marked as running or minimized given the Sets of currently running
+ * and minimized tasks.
+ */
+ public void updateIconViewsRunningStates(Set<Integer> runningTaskIds,
+ Set<Integer> minimizedTaskIds) {
for (View iconView : getIconViews()) {
if (iconView instanceof BubbleTextView btv) {
btv.updateRunningState(
- getRunningAppState(btv, runningPackages, minimizedPackages));
+ getRunningAppState(btv, runningTaskIds, minimizedTaskIds));
}
}
}
private BubbleTextView.RunningAppState getRunningAppState(
BubbleTextView btv,
- Set<String> runningPackages,
- Set<String> minimizedPackages) {
+ Set<Integer> runningTaskIds,
+ Set<Integer> minimizedTaskIds) {
Object tag = btv.getTag();
- if (tag instanceof ItemInfo itemInfo) {
- if (minimizedPackages.contains(itemInfo.getTargetPackage())) {
+ if (tag instanceof TaskItemInfo itemInfo) {
+ if (minimizedTaskIds.contains(itemInfo.getTaskId())) {
return BubbleTextView.RunningAppState.MINIMIZED;
}
- if (runningPackages.contains(itemInfo.getTargetPackage())) {
+ if (runningTaskIds.contains(itemInfo.getTaskId())) {
return BubbleTextView.RunningAppState.RUNNING;
}
}
if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
- if (minimizedPackages.contains(groupTask.task1.key.getPackageName())) {
+ if (minimizedTaskIds.contains(groupTask.task1.key.id)) {
return BubbleTextView.RunningAppState.MINIMIZED;
}
- if (runningPackages.contains(groupTask.task1.key.getPackageName())) {
+ if (runningTaskIds.contains(groupTask.task1.key.id)) {
return BubbleTextView.RunningAppState.RUNNING;
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 4100e51..a3832cd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -394,7 +394,8 @@
BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
// If we're not stashed, we're visible so animate
bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */);
- mBubbleBarViewController.animateBubbleNotification(bb, /* isExpanding= */ false);
+ mBubbleBarViewController.animateBubbleNotification(
+ bb, /* isExpanding= */ false, /* isUpdate= */ true);
}
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
// Create the new list
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 402b091..fd989b1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -46,9 +46,10 @@
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.taskbar.bubbles.animation.BubbleAnimator;
import com.android.launcher3.util.DisplayController;
-import com.android.wm.shell.Flags;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -258,10 +259,6 @@
if (!isIconSizeOrPaddingUpdated(newIconSize, newBubbleBarPadding)) {
return;
}
- if (!Flags.animateBubbleSizeChange()) {
- setIconSizeAndPadding(newIconSize, newBubbleBarPadding);
- return;
- }
if (mScalePaddingAnimator != null && mScalePaddingAnimator.isRunning()) {
mScalePaddingAnimator.cancel();
}
@@ -899,6 +896,13 @@
float fullElevationForChild = (MAX_BUBBLES * mBubbleElevation) - i;
bv.setZ(fullElevationForChild * elevationState);
+ // only update the dot scale if we're expanding or collapsing
+ // TODO b/351904597: update the dot for the first bubble after removal and reorder
+ // since those might happen when the bar is collapsed and will need their dot back
+ if (mWidthAnimator.isRunning()) {
+ bv.setDotScale(widthState);
+ }
+
if (mIsBarExpanded) {
// If bar is on the right, account for bubble bar expanding and shifting left
final float expandedBarShift = onLeft ? 0 : currentWidth - expandedWidth;
@@ -907,7 +911,6 @@
bv.setTranslationX(widthState * (targetX - collapsedX) + collapsedX);
// When we're expanded, the badge is visible for all bubbles
bv.updateBadgeVisibility(/* show= */ true);
- bv.setDotScale(widthState);
bv.setAlpha(1);
} else {
// If bar is on the right, account for bubble bar expanding and shifting left
@@ -916,7 +919,6 @@
bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
// The badge is always visible for the first bubble
bv.updateBadgeVisibility(/* show= */ i == 0);
- bv.setDotScale(widthState);
// If we're fully collapsed, hide all bubbles except for the first 2. If there are
// only 2 bubbles, hide the second bubble as well because it's the overflow.
if (widthState == 0) {
@@ -979,8 +981,7 @@
return translationX - getScaleIconShift();
}
- private float getCollapsedBubbleTranslationX(int bubbleIndex, int bubbleCount,
- boolean onLeft) {
+ private float getCollapsedBubbleTranslationX(int bubbleIndex, int bubbleCount, boolean onLeft) {
if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
return 0;
}
@@ -991,7 +992,9 @@
bubbleIndex == 0 && bubbleCount > MAX_VISIBLE_BUBBLES_COLLAPSED
? mIconOverlapAmount : 0);
} else {
- translationX = mBubbleBarPadding + (bubbleIndex == 0 ? 0 : mIconOverlapAmount);
+ translationX = mBubbleBarPadding + (
+ bubbleIndex == 0 || bubbleCount <= MAX_VISIBLE_BUBBLES_COLLAPSED
+ ? 0 : mIconOverlapAmount);
}
return translationX - getScaleIconShift();
}
@@ -1309,6 +1312,37 @@
});
}
+ /** Dumps the current state of BubbleBarView. */
+ public void dump(PrintWriter pw) {
+ pw.println("BubbleBarView state:");
+ pw.println(" visibility: " + getVisibility());
+ pw.println(" translation Y: " + getTranslationY());
+ pw.println(" bubbles in bar (childCount = " + getChildCount() + ")");
+ for (BubbleView bubbleView: getBubbles()) {
+ BubbleBarItem bubble = bubbleView.getBubble();
+ String key = bubble == null ? "null" : bubble.getKey();
+ pw.println(" bubble key: " + key);
+ }
+ pw.println(" isExpanded: " + isExpanded());
+ pw.println(" mIsAnimatingNewBubble: " + mIsAnimatingNewBubble);
+ if (mBubbleAnimator != null) {
+ pw.println(" mBubbleAnimator.isRunning(): " + mBubbleAnimator.isRunning());
+ pw.println(" mBubbleAnimator is null");
+ }
+ pw.println(" mDragging: " + mDragging);
+ }
+
+ private List<BubbleView> getBubbles() {
+ List<BubbleView> bubbles = new ArrayList<>();
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ if (child instanceof BubbleView bubble) {
+ bubbles.add(bubble);
+ }
+ }
+ return bubbles;
+ }
+
/** Interface for BubbleBarView to communicate with its controller. */
interface Controller {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 40e5b64..ad81509 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -43,6 +43,7 @@
import com.android.quickstep.SystemUiProxy;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
+import java.io.PrintWriter;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
@@ -399,7 +400,7 @@
addedBubble.getView().setOnClickListener(mBubbleClickListener);
mBubbleDragController.setupBubbleView(addedBubble.getView());
if (!suppressAnimation) {
- animateBubbleNotification(addedBubble, isExpanding);
+ animateBubbleNotification(addedBubble, isExpanding, /* isUpdate= */ true);
}
}
@@ -427,18 +428,19 @@
}
return;
}
- animateBubbleNotification(bubble, isExpanding);
+ animateBubbleNotification(bubble, isExpanding, /* isUpdate= */ true);
} else {
Log.w(TAG, "addBubble, bubble was null!");
}
}
/** Animates the bubble bar to notify the user about a bubble change. */
- public void animateBubbleNotification(BubbleBarBubble bubble, boolean isExpanding) {
+ public void animateBubbleNotification(BubbleBarBubble bubble, boolean isExpanding,
+ boolean isUpdate) {
boolean isInApp = mTaskbarStashController.isInApp();
// if this is the first bubble, animate to the initial state. one bubble is the overflow
// so check for at most 2 children.
- if (mBarView.getChildCount() <= 2) {
+ if (mBarView.getChildCount() <= 2 && !isUpdate) {
mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding);
return;
}
@@ -598,4 +600,19 @@
/** Called when bounds have changed */
void onBoundsChanged();
}
+
+ /** Dumps the state of BubbleBarViewController. */
+ public void dump(PrintWriter pw) {
+ pw.println("Bubble bar view controller state:");
+ pw.println(" mHiddenForSysui: " + mHiddenForSysui);
+ pw.println(" mHiddenForNoBubbles: " + mHiddenForNoBubbles);
+ pw.println(" mShouldShowEducation: " + mShouldShowEducation);
+ pw.println(" mBubbleBarTranslationY.value: " + mBubbleBarTranslationY.value);
+ pw.println(" mBubbleBarSwipeUpTranslationY: " + mBubbleBarSwipeUpTranslationY);
+ if (mBarView != null) {
+ mBarView.dump(pw);
+ } else {
+ pw.println(" Bubble bar view is null!");
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index 32d6375..03140fe 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -18,6 +18,8 @@
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.util.RunnableList;
+import java.io.PrintWriter;
+
/**
* Hosts various bubble controllers to facilitate passing between one another.
*/
@@ -94,4 +96,9 @@
bubbleStashedHandleViewController.onDestroy();
bubbleBarController.onDestroy();
}
+
+ /** Dumps bubble controllers state. */
+ public void dump(PrintWriter pw) {
+ bubbleBarViewController.dump(pw);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index 185f85f..74f58ac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -36,6 +36,8 @@
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import java.io.PrintWriter;
+
/**
* Coordinates between controllers such as BubbleBarView and BubbleHandleViewController to
* create a cohesive animation between stashed/unstashed states.
@@ -456,4 +458,13 @@
public void setHandleTranslationY(float ty) {
mHandleViewController.setTranslationYForSwipe(ty);
}
+
+ /** Dumps the state of BubbleStashController. */
+ public void dump(PrintWriter pw) {
+ pw.println("Bubble stash controller state:");
+ pw.println(" mIsStashed: " + mIsStashed);
+ pw.println(" mBubblesShowingOnOverview: " + mBubblesShowingOnOverview);
+ pw.println(" mBubblesShowingOnHome: " + mBubblesShowingOnHome);
+ pw.println(" mIsSysuiLocked: " + mIsSysuiLocked);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 0e26c54..4c468bb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -20,6 +20,7 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Outline;
+import android.graphics.Path;
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -47,7 +48,7 @@
private final ImageView mBubbleIcon;
private final ImageView mAppIcon;
- private final int mBubbleSize;
+ private int mBubbleSize;
private float mDragTranslationX;
private float mOffsetX;
@@ -89,8 +90,6 @@
setLayoutDirection(LAYOUT_DIRECTION_LTR);
LayoutInflater.from(context).inflate(R.layout.bubble_view, this);
-
- mBubbleSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
mBubbleIcon = findViewById(R.id.icon_view);
mAppIcon = findViewById(R.id.app_icon_view);
@@ -107,11 +106,21 @@
}
private void getOutline(Outline outline) {
+ updateBubbleSizeAndDotRender();
final int normalizedSize = IconNormalizer.getNormalizedCircleSize(mBubbleSize);
final int inset = (mBubbleSize - normalizedSize) / 2;
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
}
+ private void updateBubbleSizeAndDotRender() {
+ int updatedBubbleSize = Math.min(getWidth(), getHeight());
+ if (updatedBubbleSize == mBubbleSize) return;
+ mBubbleSize = updatedBubbleSize;
+ if (mBubble == null || mBubble instanceof BubbleBarOverflow) return;
+ Path dotPath = ((BubbleBarBubble) mBubble).getDotPath();
+ mDotRenderer = new DotRenderer(mBubbleSize, dotPath, DEFAULT_PATH_SIZE);
+ }
+
/**
* Set translation-x while this bubble is being dragged.
* Translation applied to the view is a sum of {@code translationX} and offset defined by
@@ -141,6 +150,12 @@
applyDragTranslation();
}
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ updateBubbleSizeAndDotRender();
+ }
+
private void applyDragTranslation() {
setTranslationX(mDragTranslationX + mOffsetX);
}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 867533c..f020c8f 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -152,8 +152,6 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.startingsurface.SplashScreenExitAnimationUtils;
-import kotlin.Unit;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -163,6 +161,8 @@
import java.util.OptionalInt;
import java.util.function.Consumer;
+import kotlin.Unit;
+
/**
* Handles the navigation gestures when Launcher is the default home activity.
*/
@@ -2423,34 +2423,42 @@
if (mRecentsAnimationController == null) {
return;
}
+ final Runnable onFinishComplete = () -> {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "AbsSwipeUpHandler.onTasksAppeared: ")
+ .append("force finish recents animation complete; clearing state callback."));
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
+ };
+ ActiveGestureLog.CompoundString forceFinishReason = new ActiveGestureLog.CompoundString(
+ "Forcefully finishing recents animation: ");
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];
TaskInfo taskInfo = appearedTaskTarget.taskInfo;
- ActiveGestureLog.INSTANCE.addLog(
- new ActiveGestureLog.CompoundString("Unexpected task appeared")
- .append(" id=")
+ ActiveGestureLog.INSTANCE.addLog(forceFinishReason
+ .append("Unexpected task appeared id=")
.append(taskInfo.taskId)
.append(" pkg=")
.append(taskInfo.baseIntent.getComponent().getPackageName()));
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
ActiveGestureLog.CompoundString handleTaskFailureReason =
new ActiveGestureLog.CompoundString("handleTaskAppeared check failed: ");
if (!handleTaskAppeared(appearedTaskTargets, handleTaskFailureReason)) {
- ActiveGestureLog.INSTANCE.addLog(handleTaskFailureReason);
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ ActiveGestureLog.INSTANCE.addLog(forceFinishReason.append(handleTaskFailureReason));
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
RemoteAnimationTarget[] taskTargets = Arrays.stream(appearedTaskTargets)
.filter(mGestureState.mLastStartedTaskIdPredicate)
.toArray(RemoteAnimationTarget[]::new);
if (taskTargets.length == 0) {
- ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id");
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ ActiveGestureLog.INSTANCE.addLog(
+ forceFinishReason.append("No appeared task matching started task id"));
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
RemoteAnimationTarget taskTarget = taskTargets[0];
@@ -2458,13 +2466,13 @@
? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
if (taskView == null || taskView.getTaskContainers().stream().noneMatch(
TaskContainer::getShouldShowSplashView)) {
- ActiveGestureLog.INSTANCE.addLog("Splash not needed");
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ ActiveGestureLog.INSTANCE.addLog(forceFinishReason.append("Splash not needed"));
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
if (mContainer == null) {
- ActiveGestureLog.INSTANCE.addLog("Activity destroyed");
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ ActiveGestureLog.INSTANCE.addLog(forceFinishReason.append("Activity destroyed"));
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
animateSplashScreenExit(mContainer, appearedTaskTargets, taskTargets);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index a7d3890..f902284 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -302,6 +302,10 @@
return mNavBarPosition;
}
+ public NavigationMode getMode() {
+ return mMode;
+ }
+
/**
* @return whether the current nav mode is fully gestural.
*/
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index ba33c62..f813d9a 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -26,6 +26,7 @@
import android.graphics.Matrix.ScaleToFit;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.util.Log;
import android.view.RemoteAnimationTarget;
import androidx.annotation.NonNull;
@@ -381,6 +382,8 @@
protected class SpringAnimationRunner extends AnimationSuccessListener
implements RectFSpringAnim.OnUpdateListener, BuilderProxy {
+ private static final String TAG = "SpringAnimationRunner";
+
final Rect mCropRect = new Rect();
final Matrix mMatrix = new Matrix();
@@ -481,10 +484,26 @@
return;
}
mTargetTaskView.setAlpha(mAnimationFactory.isAnimatingIntoIcon() ? 1f : alpha);
- float width = mThumbnailStartBounds.width();
- float height = mThumbnailStartBounds.height();
- float scale = Math.min(currentRect.width(), currentRect.height())
- / Math.min(width, height);
+ float startWidth = mThumbnailStartBounds.width();
+ float startHeight = mThumbnailStartBounds.height();
+ float currentWidth = currentRect.width();
+ float currentHeight = currentRect.height();
+ float scale;
+
+ boolean isStartWidthValid = Float.compare(startWidth, 0f) > 0;
+ boolean isStartHeightValid = Float.compare(startHeight, 0f) > 0;
+ if (isStartWidthValid && isStartHeightValid) {
+ scale = Math.min(currentWidth, currentHeight) / Math.min(startWidth, startHeight);
+ } else {
+ Log.e(TAG, "TaskView starting bounds are invalid: " + mThumbnailStartBounds);
+ if (isStartWidthValid) {
+ scale = currentWidth / startWidth;
+ } else if (isStartHeightValid) {
+ scale = currentHeight / startHeight;
+ } else {
+ scale = 1f;
+ }
+ }
mTargetTaskView.setScaleX(scale);
mTargetTaskView.setScaleY(scale);
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index ee93cd6..2896979 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -42,6 +42,7 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.NAVIGATION_MODE_SWITCHED;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENTS_ANIMATION_START_PENDING;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
@@ -70,7 +71,6 @@
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;
@@ -102,6 +102,7 @@
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.PluginManagerWrapper;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.ScreenOnTracker;
@@ -622,6 +623,8 @@
private InputManager mInputManager;
private final Set<Integer> mTrackpadsConnected = new ArraySet<>();
+ private NavigationMode mGestureStartNavMode = null;
+
@Override
public void onCreate() {
super.onCreate();
@@ -836,14 +839,26 @@
TestLogging.recordMotionEvent(
TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
- boolean isUserUnlocked = LockedUserState.get(this).isUserUnlocked();
- if (!isUserUnlocked || (mDeviceState.isButtonNavMode()
- && !isTrackpadMotionEvent(event))) {
+ if (!LockedUserState.get(this).isUserUnlocked()) {
+ ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
+ .append("Cannot process input event: user is locked"));
+ return;
+ }
+
+ NavigationMode currentNavMode = mDeviceState.getMode();
+ if (mGestureStartNavMode != null && mGestureStartNavMode != currentNavMode) {
+ ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
+ .append("Navigation mode switched mid-gesture (")
+ .append(mGestureStartNavMode.name())
+ .append(" -> ")
+ .append(currentNavMode.name())
+ .append("); cancelling gesture."),
+ NAVIGATION_MODE_SWITCHED);
+ event.setAction(ACTION_CANCEL);
+ } else if (mDeviceState.isButtonNavMode() && !isTrackpadMotionEvent(event)) {
ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
.append("Cannot process input event: ")
- .append(!isUserUnlocked
- ? "user is locked"
- : "using 3-button nav and event is not a trackpad event"));
+ .append("using 3-button nav and event is not a trackpad event"));
return;
}
@@ -870,6 +885,12 @@
}
}
+ if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
+ mGestureStartNavMode = currentNavMode;
+ } else if (action == ACTION_UP || action == ACTION_CANCEL) {
+ mGestureStartNavMode = null;
+ }
+
SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
CompoundString reasonString = action == ACTION_DOWN
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
index c1eef0b..9c4248c 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
@@ -17,6 +17,7 @@
package com.android.quickstep.recents.data
import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
import kotlinx.coroutines.flow.Flow
interface RecentTasksRepository {
@@ -25,11 +26,17 @@
/**
* Gets the data associated with a task that has id [taskId]. Flow will settle on null if the
- * task was not found.
+ * task was not found. [Task.thumbnail] will settle on null if task is invisible.
*/
fun getTaskDataById(taskId: Int): Flow<Task?>
/**
+ * Gets the [ThumbnailData] associated with a task that has id [taskId]. Flow will settle on
+ * null if the task was not found or is invisible.
+ */
+ fun getThumbnailById(taskId: Int): Flow<ThumbnailData?>
+
+ /**
* Sets the tasks that are visible, indicating that properties relating to visuals need to be
* populated e.g. icons/thumbnails etc.
*/
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 9f3ef4a..4d6dfc3 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -27,6 +27,7 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
@@ -63,6 +64,9 @@
override fun getTaskDataById(taskId: Int): Flow<Task?> =
taskData.map { taskList -> taskList.firstOrNull { it.key.id == taskId } }
+ override fun getThumbnailById(taskId: Int): Flow<ThumbnailData?> =
+ getTaskDataById(taskId).map { it?.thumbnail }.distinctUntilChangedBy { it?.snapshotId }
+
override fun setVisibleTasks(visibleTaskIdList: List<Int>) {
this.visibleTaskIds.value = visibleTaskIdList.toSet()
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 20a081b..22d49c1 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -52,10 +52,10 @@
RecentsViewContainer.containerFromContext<RecentsViewContainer>(context)
.getOverviewPanel<RecentsView<*, *>>()
TaskThumbnailViewModel(
- recentsView.mRecentsViewData,
+ recentsView.mRecentsViewData!!,
(parent as TaskView).taskViewData,
(parent as TaskView).getTaskContainerForTaskThumbnailView(this)!!.taskContainerData,
- recentsView.mTasksRepository,
+ recentsView.mTasksRepository!!,
)
}
diff --git a/quickstep/src/com/android/quickstep/task/util/GetThumbnailUseCase.kt b/quickstep/src/com/android/quickstep/task/util/GetThumbnailUseCase.kt
new file mode 100644
index 0000000..e8dd04c3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/util/GetThumbnailUseCase.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.task.util
+
+import android.graphics.Bitmap
+import com.android.quickstep.recents.data.RecentTasksRepository
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.runBlocking
+
+/** Use case for retrieving thumbnail. */
+class GetThumbnailUseCase(private val taskRepository: RecentTasksRepository) {
+ /** Returns the latest thumbnail associated with [taskId] if loaded, or null otherwise */
+ fun run(taskId: Int): Bitmap? = runBlocking {
+ taskRepository.getThumbnailById(taskId).firstOrNull()?.thumbnail
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
index de39584..5e55e2e 100644
--- a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
+++ b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
@@ -46,7 +46,7 @@
overlay.taskView.context
)
.getOverviewPanel<RecentsView<*, *>>()
- TaskOverlayViewModel(task, recentsView.mRecentsViewData, recentsView.mTasksRepository)
+ TaskOverlayViewModel(task, recentsView.mRecentsViewData!!, recentsView.mTasksRepository!!)
}
// TODO(b/331753115): TaskOverlay should listen for state changes and react.
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
index 4682323..47f32fb 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
@@ -24,7 +24,6 @@
import com.android.systemui.shared.recents.model.Task
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.map
/** View model for TaskOverlay */
@@ -37,10 +36,7 @@
combine(
recentsViewData.overlayEnabled,
recentsViewData.settledFullyVisibleTaskIds.map { it.contains(task.key.id) },
- tasksRepository
- .getTaskDataById(task.key.id)
- .map { it?.thumbnail }
- .distinctUntilChangedBy { it?.snapshotId }
+ tasksRepository.getThumbnailById(task.key.id)
) { isOverlayEnabled, isFullyVisible, thumbnailData ->
if (isOverlayEnabled && isFullyVisible) {
Enabled(
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 3140fff..2398e66 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -40,7 +40,7 @@
SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED,
FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED, RECENT_TASKS_MISSING,
INVALID_VELOCITY_ON_SWIPE_UP, RECENTS_ANIMATION_START_PENDING,
- QUICK_SWITCH_FROM_HOME_FALLBACK, QUICK_SWITCH_FROM_HOME_FAILED,
+ QUICK_SWITCH_FROM_HOME_FALLBACK, QUICK_SWITCH_FROM_HOME_FAILED, NAVIGATION_MODE_SWITCHED,
/**
* These GestureEvents are specifically associated to state flags that get set in
@@ -299,6 +299,13 @@
+ "the current page index and index 0 were missing.",
writer);
break;
+ case NAVIGATION_MODE_SWITCHED:
+ errorDetected |= printErrorIfTrue(
+ true,
+ prefix,
+ /* errorMessage= */ "Navigation mode switched mid-gesture.",
+ writer);
+ break;
case EXPECTING_TASK_APPEARED:
case MOTION_DOWN:
case SET_END_TARGET:
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.kt b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
index 85238ed..7e51fcf 100644
--- a/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
+++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
@@ -50,7 +50,7 @@
private val disappearanceDurationMs: Long,
private val interpolator: Interpolator,
) {
- private val borderAnimationProgress = AnimatedFloat { updateOutline() }
+ private val borderAnimationProgress = AnimatedFloat { _ -> updateOutline() }
private val borderPaint =
Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = borderColor
@@ -224,6 +224,7 @@
val borderWidth: Float
get() = borderWidthPx * animationProgress
+
val alignmentAdjustment: Float
// Outset the border by half the width to create an outwards-growth animation
get() = -borderWidth / 2f + alignmentAdjustmentInset
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 49e1c88..e31a828 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -27,8 +27,10 @@
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.content.Context
import android.graphics.Bitmap
+import android.graphics.Color
import android.graphics.Rect
import android.graphics.RectF
+import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
@@ -120,8 +122,8 @@
val drawable = getDrawable(container.iconView, splitSelectSource)
return SplitAnimInitProps(
container.snapshotView,
- container.thumbnail,
- drawable!!,
+ container.splitAnimationThumbnail,
+ drawable,
fadeWithThumbnail = true,
isStagedTask = true,
iconView = container.iconView.asView()
@@ -139,8 +141,8 @@
val drawable = getDrawable(it.iconView, splitSelectSource)
return SplitAnimInitProps(
it.snapshotView,
- it.thumbnail,
- drawable!!,
+ it.splitAnimationThumbnail,
+ drawable,
fadeWithThumbnail = true,
isStagedTask = true,
iconView = it.iconView.asView()
@@ -152,15 +154,15 @@
/**
* Returns the drawable that's provided in iconView, however if that is null it falls back to
* the drawable that's in splitSelectSource. TaskView's icon drawable can be null if the
- * TaskView is scrolled far enough off screen
+ * TaskView is scrolled far enough off screen.
*
- * @return [Drawable]
+ * @return the [Drawable] icon, or a translucent drawable if none was found
*/
- fun getDrawable(iconView: TaskViewIcon, splitSelectSource: SplitSelectSource?): Drawable? {
- if (iconView.drawable == null && splitSelectSource != null) {
- return splitSelectSource.drawable
- }
- return iconView.drawable
+ fun getDrawable(iconView: TaskViewIcon, splitSelectSource: SplitSelectSource?): Drawable {
+ val drawable =
+ if (iconView.drawable == null && splitSelectSource != null) splitSelectSource.drawable
+ else iconView.drawable
+ return drawable ?: ColorDrawable(Color.TRANSPARENT)
}
/**
@@ -534,7 +536,13 @@
val appPairLaunchingAppIndex = hasChangesForBothAppPairs(launchingIconView, info)
if (appPairLaunchingAppIndex == -1) {
// Launch split app pair animation
- composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
+ composeIconSplitLaunchAnimator(
+ launchingIconView,
+ info,
+ t,
+ finishCallback,
+ cornerRadius
+ )
} else {
composeFullscreenIconSplitLaunchAnimator(
launchingIconView,
@@ -551,8 +559,14 @@
"unexpected null"
}
- composeFadeInSplitLaunchAnimator(initialTaskId, secondTaskId, info, t, finishCallback,
- cornerRadius)
+ composeFadeInSplitLaunchAnimator(
+ initialTaskId,
+ secondTaskId,
+ info,
+ t,
+ finishCallback,
+ cornerRadius
+ )
}
}
@@ -652,15 +666,15 @@
* To find the root shell leash that we want to fade in, we do the following: The Changes we
* receive in transitionInfo are structured like this
*
- * Root (grandparent)
+ * (0) Root (grandparent)
* |
- * |--> Split Root 1 (left/top side parent) (WINDOWING_MODE_MULTI_WINDOW)
+ * |--> (1) Split Root 1 (left/top side parent) (WINDOWING_MODE_MULTI_WINDOW)
* | |
- * | --> App 1 (left/top side child) (WINDOWING_MODE_MULTI_WINDOW)
+ * | --> (1a) App 1 (left/top side child) (WINDOWING_MODE_MULTI_WINDOW)
* |--> Divider
- * |--> Split Root 2 (right/bottom side parent) (WINDOWING_MODE_MULTI_WINDOW)
+ * |--> (2) Split Root 2 (right/bottom side parent) (WINDOWING_MODE_MULTI_WINDOW)
* |
- * --> App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW)
+ * --> (2a) App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW)
*
* We want to animate the Root (grandparent) so that it affects both apps and the divider. To do
* this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the left-side ones,
@@ -675,7 +689,8 @@
launchingIconView: AppPairIcon,
transitionInfo: TransitionInfo,
t: Transaction,
- finishCallback: Runnable
+ finishCallback: Runnable,
+ windowRadius: Float
) {
// If launching an app pair from Taskbar inside of an app context (no access to Launcher),
// use the scale-up animation
@@ -695,48 +710,26 @@
// Create an AnimatorSet that will run both shell and launcher transitions together
val launchAnimation = AnimatorSet()
- var rootCandidate: Change? = null
- for (change in transitionInfo.changes) {
- val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
+ val splitRoots: Pair<Change, List<Change>>? =
+ SplitScreenUtils.extractTopParentAndChildren(transitionInfo)
+ check(splitRoots != null) { "Could not find split roots" }
- // TODO (b/316490565): Replace this logic when SplitBounds is available to
- // startAnimation() and we can know the precise taskIds of launching tasks.
- // Find a change that has WINDOWING_MODE_MULTI_WINDOW.
- if (
- taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW &&
- (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)
- ) {
- // Check if it is a left/top app.
- val isLeftTopApp =
- (dp.isLeftRightSplit && change.endAbsBounds.left == 0) ||
- (!dp.isLeftRightSplit && change.endAbsBounds.top == 0)
- if (isLeftTopApp) {
- // Found one!
- rootCandidate = change
- break
- }
- }
- }
-
- // If we could not find a proper root candidate, something went wrong.
- check(rootCandidate != null) { "Could not find a split root candidate" }
+ // Will point to change (0) in diagram above
+ val mainRootCandidate = splitRoots.first
+ // Will contain changes (1) and (2) in diagram above
+ val leafRoots: List<Change> = splitRoots.second
// Find the place where our left/top app window meets the divider (used for the
// launcher side animation)
+ val leftTopApp =
+ leafRoots.single { change ->
+ (dp.isLeftRightSplit && change.endAbsBounds.left == 0) ||
+ (!dp.isLeftRightSplit && change.endAbsBounds.top == 0)
+ }
val dividerPos =
- if (dp.isLeftRightSplit) rootCandidate.endAbsBounds.right
- else rootCandidate.endAbsBounds.bottom
-
- // Recurse up the tree until parent is null, then we've found our root.
- var parentToken: WindowContainerToken? = rootCandidate.parent
- while (parentToken != null) {
- rootCandidate = transitionInfo.getChange(parentToken) ?: break
- parentToken = rootCandidate.parent
- }
-
- // Make sure nothing weird happened, like getChange() returning null.
- check(rootCandidate != null) { "Failed to find a root leash" }
+ if (dp.isLeftRightSplit) leftTopApp.endAbsBounds.right
+ else leftTopApp.endAbsBounds.bottom
// Create a new floating view in Launcher, positioned above the launching icon
val drawableArea = launchingIconView.iconDrawableArea
@@ -755,9 +748,26 @@
)
floatingView.bringToFront()
- launchAnimation.play(
- getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView, rootCandidate)
+ val iconLaunchValueAnimator =
+ getIconLaunchValueAnimator(
+ t,
+ dp,
+ finishCallback,
+ launcher,
+ floatingView,
+ mainRootCandidate
+ )
+ iconLaunchValueAnimator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator, isReverse: Boolean) {
+ for (c in leafRoots) {
+ t.setCornerRadius(c.leash, windowRadius)
+ t.apply()
+ }
+ }
+ }
)
+ launchAnimation.play(iconLaunchValueAnimator)
launchAnimation.start()
}
@@ -1026,12 +1036,12 @@
*/
@VisibleForTesting
fun composeFadeInSplitLaunchAnimator(
- initialTaskId: Int,
- secondTaskId: Int,
- transitionInfo: TransitionInfo,
- t: Transaction,
- finishCallback: Runnable,
- cornerRadius: Float
+ initialTaskId: Int,
+ secondTaskId: Int,
+ transitionInfo: TransitionInfo,
+ t: Transaction,
+ finishCallback: Runnable,
+ cornerRadius: Float
) {
var splitRoot1: Change? = null
var splitRoot2: Change? = null
@@ -1109,7 +1119,7 @@
override fun onAnimationStart(animation: Animator) {
for (leash in openingTargets) {
animTransaction.show(leash).setAlpha(leash, 0.0f)
- animTransaction.setCornerRadius(leash, cornerRadius);
+ 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 4820c35..d58cb91 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
@@ -16,11 +16,21 @@
package com.android.quickstep.util
+import android.util.Log
+import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.TransitionInfo.FLAG_FIRST_CUSTOM
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.wm.shell.util.SplitBounds
+import java.lang.IllegalStateException
class SplitScreenUtils {
companion object {
+ private const val TAG = "SplitScreenUtils"
+
// TODO(b/254378592): Remove these methods when the two classes are reunited
/** Converts the shell version of SplitBounds to the launcher version */
@JvmStatic
@@ -39,5 +49,51 @@
)
}
}
+
+ /**
+ * Given a TransitionInfo, generates the tree structure for those changes and extracts out
+ * the top most root and it's two immediate children.
+ * Changes can be provided in any order.
+ *
+ * @return a [Pair] where first -> top most split root,
+ * second -> [List] of 2, leftTop/bottomRight stage roots
+ */
+ fun extractTopParentAndChildren(transitionInfo: TransitionInfo):
+ Pair<Change, List<Change>>? {
+ val parentToChildren = mutableMapOf<Change, MutableList<Change>>()
+ val hasParent = mutableSetOf<Change>()
+ // filter out anything that isn't opening and the divider
+ val taskChanges: List<Change> = transitionInfo.changes
+ .filter { change -> (change.mode == TRANSIT_OPEN ||
+ change.mode == TRANSIT_TO_FRONT) && change.flags < FLAG_FIRST_CUSTOM}
+ .toList()
+
+ // 1. Build Parent-Child Relationships
+ for (change in taskChanges) {
+ // TODO (b/316490565): Replace this logic when SplitBounds is available to
+ // startAnimation() and we can know the precise taskIds of launching tasks.
+ change.parent?.let { parent ->
+ parentToChildren
+ .getOrPut(transitionInfo.getChange(parent)!!) { mutableListOf() }
+ .add(change)
+ hasParent.add(change)
+ }
+ }
+
+ // 2. Find Top Parent
+ val topParent = taskChanges.firstOrNull { it !in hasParent }
+
+ // 3. Extract Immediate Children
+ return if (topParent != null) {
+ val immediateChildren = parentToChildren.getOrDefault(topParent, emptyList())
+ if (immediateChildren.size != 2) {
+ throw IllegalStateException("incorrect split stage root size")
+ }
+ Pair(topParent, immediateChildren)
+ } else {
+ Log.w(TAG, "No top parent found")
+ null
+ }
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
index e024995..6bbd6b2 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
+++ b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
@@ -57,6 +57,7 @@
private val container: RecentsViewContainer
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private val dividerPaint = Paint(Paint.ANTI_ALIAS_FLAG)
// Animation interpolators
protected val expandXInterpolator: Interpolator
@@ -105,13 +106,15 @@
)
// Find device-specific measurements
- deviceCornerRadius = QuickStepContract.getWindowCornerRadius(container.asContext())
+ val resources = context.resources
+ deviceCornerRadius = QuickStepContract.getWindowCornerRadius(context)
deviceHalfDividerSize =
- container.asContext().resources.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2f
+ resources.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2f
val dividerCenterPos = dividerPos + deviceHalfDividerSize
desiredSplitRatio =
if (dp.isLeftRightSplit) dividerCenterPos / dp.widthPx
else dividerCenterPos / dp.heightPx
+ dividerPaint.color = resources.getColor(R.color.taskbar_background_dark, null /*theme*/)
}
override fun draw(canvas: Canvas) {
@@ -153,8 +156,12 @@
val leftSide = RectF(0f, 0f, dividerCenterPos - changingDividerSize, height)
// The right half of the background image
val rightSide = RectF(dividerCenterPos + changingDividerSize, 0f, width, height)
+ // Middle part is for divider background
+ val middleRect = RectF(leftSide.right - deviceHalfDividerSize, 0f,
+ rightSide.left + deviceHalfDividerSize, height)
// Draw background
+ canvas.drawRect(middleRect, dividerPaint)
drawCustomRoundedRect(
canvas,
leftSide,
@@ -251,8 +258,12 @@
val topSide = RectF(0f, 0f, width, dividerCenterPos - changingDividerSize)
// The bottom half of the background image
val bottomSide = RectF(0f, dividerCenterPos + changingDividerSize, width, height)
+ // Middle part is for divider background
+ val middleRect = RectF(0f, topSide.bottom - deviceHalfDividerSize,
+ width, bottomSide.top + deviceHalfDividerSize)
// Draw background
+ canvas.drawRect(middleRect, dividerPaint)
drawCustomRoundedRect(
canvas,
topSide,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 7b6d383..cb8ee06 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -461,7 +461,9 @@
private static final float FOREGROUND_SCRIM_TINT = 0.32f;
+ @Nullable
public final RecentsViewData mRecentsViewData = new RecentsViewData();
+ @Nullable
public final TasksRepository mTasksRepository;
protected final RecentsOrientedState mOrientationState;
@@ -3814,7 +3816,7 @@
anim.setFloat(taskView, taskView.getSecondaryDismissTranslationProperty(),
secondaryTranslation, clampToProgress(LINEAR, animationStartProgress,
dismissTranslationInterpolationEnd));
- anim.setFloat(taskView, TaskView.SCALE_AND_DIM_OUT, 0f,
+ anim.add(taskView.getFocusTransitionScaleAndDimOutAnimator(),
clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT));
} else {
float primaryTranslation =
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 0648986..74d120f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -31,6 +31,7 @@
import com.android.quickstep.TaskUtils
import com.android.quickstep.task.thumbnail.TaskThumbnail
import com.android.quickstep.task.thumbnail.TaskThumbnailView
+import com.android.quickstep.task.util.GetThumbnailUseCase
import com.android.quickstep.task.viewmodel.TaskContainerData
import com.android.systemui.shared.recents.model.Task
@@ -56,6 +57,16 @@
val overlay: TaskOverlayFactory.TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
val taskContainerData = TaskContainerData()
+ private val getThumbnailUseCase by lazy {
+ // TODO(b/335649589): Ideally create and obtain this from DI.
+ val recentsView =
+ RecentsViewContainer.containerFromContext<RecentsViewContainer>(
+ overlay.taskView.context
+ )
+ .getOverviewPanel<RecentsView<*, *>>()
+ GetThumbnailUseCase(recentsView.mTasksRepository!!)
+ }
+
init {
if (enableRefactorTaskThumbnail()) {
require(snapshotView is TaskThumbnailView)
@@ -64,6 +75,14 @@
}
}
+ val splitAnimationThumbnail: Bitmap?
+ get() =
+ if (enableRefactorTaskThumbnail()) {
+ getThumbnailUseCase.run(task.key.id)
+ } else {
+ thumbnailViewDeprecated.thumbnail
+ }
+
val thumbnailView: TaskThumbnailView
get() {
require(enableRefactorTaskThumbnail())
@@ -76,10 +95,6 @@
return snapshotView as TaskThumbnailViewDeprecated
}
- // TODO(b/349120849): Extract ThumbnailData from TaskContainerData/TaskThumbnailViewModel
- val thumbnail: Bitmap?
- get() = if (enableRefactorTaskThumbnail()) null else thumbnailViewDeprecated.thumbnail
-
// TODO(b/334826842): Support shouldShowSplashView for new TTV.
val shouldShowSplashView: Boolean
get() =
@@ -114,6 +129,15 @@
}
}
+ fun bind() {
+ if (enableRefactorTaskThumbnail()) {
+ bindThumbnailView()
+ } else {
+ thumbnailViewDeprecated.bind(task, overlay)
+ }
+ overlay.init()
+ }
+
fun destroy() {
digitalWellBeingToast?.destroy()
if (enableRefactorTaskThumbnail()) {
@@ -122,15 +146,6 @@
overlay.destroy()
}
- fun bind() {
- if (enableRefactorTaskThumbnail()) {
- bindThumbnailView()
- overlay.init()
- } 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() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 2e07e36..4e19d34 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -51,6 +51,7 @@
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.R
import com.android.launcher3.Utilities
+import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.config.FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH
import com.android.launcher3.logging.StatsLogManager.LauncherEvent
import com.android.launcher3.model.data.ItemInfo
@@ -419,17 +420,17 @@
focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_FULLSCREEN)
private val focusTransitionScaleAndDim =
focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_SCALE_AND_DIM)
+
/**
- * Variant of [focusTransitionScaleAndDim] that has a built-in interpolator, to be used with
- * [com.android.launcher3.anim.PendingAnimation] via [SCALE_AND_DIM_OUT] only. PendingAnimation
- * doesn't support interpolator per animation, so we'll have to interpolate inside the property.
+ * Returns an animator of [focusTransitionScaleAndDim] that transition out with a built-in
+ * interpolator.
*/
- private var focusTransitionScaleAndDimOut = focusTransitionScaleAndDim.value
- set(value) {
- field = value
- focusTransitionScaleAndDim.value =
- FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(field)
- }
+ fun getFocusTransitionScaleAndDimOutAnimator(): ObjectAnimator =
+ AnimatedFloat { v ->
+ focusTransitionScaleAndDim.value =
+ FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(v)
+ }
+ .animateToValue(1f, 0f)
private var iconAndDimAnimator: ObjectAnimator? = null
// The current background requests to load the task thumbnail and icon
@@ -1179,7 +1180,7 @@
container.task,
container.iconView.drawable,
container.snapshotView,
- container.thumbnail,
+ container.splitAnimationThumbnail,
/* intent */ null,
/* user */ null,
container.itemInfo
@@ -1615,16 +1616,6 @@
override fun get(taskView: TaskView) = taskView.focusTransitionProgress
}
- @JvmField
- val SCALE_AND_DIM_OUT: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("scaleAndDimFastOut") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.focusTransitionScaleAndDimOut = v
- }
-
- override fun get(taskView: TaskView) = taskView.focusTransitionScaleAndDimOut
- }
-
private val SPLIT_SELECT_TRANSLATION_X: FloatProperty<TaskView> =
object : FloatProperty<TaskView>("splitSelectTranslationX") {
override fun setValue(taskView: TaskView, v: Float) {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
index 039dce4..4ea74df 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
@@ -30,6 +30,7 @@
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.WidgetPredictionsRequester.LAUNCH_LOCATION
import com.android.launcher3.model.WidgetPredictionsRequester.buildBundleForPredictionSession
import com.android.launcher3.model.WidgetPredictionsRequester.filterPredictions
import com.android.launcher3.model.WidgetPredictionsRequester.notOnUiSurfaceFilter
@@ -103,7 +104,7 @@
fun buildBundleForPredictionSession_includesAddedAppWidgets() {
val existingWidgets = arrayListOf(widget1aInfo, widget1bInfo, widget2Info)
- val bundle = buildBundleForPredictionSession(existingWidgets, TEST_UI_SURFACE)
+ val bundle = buildBundleForPredictionSession(existingWidgets)
val addedWidgetsBundleExtra =
bundle.getParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, AppTarget::class.java)
@@ -213,7 +214,7 @@
.setClassName(providerClassName)
.build()
return AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN)
- .setLaunchLocation(TEST_UI_SURFACE)
+ .setLaunchLocation(LAUNCH_LOCATION)
.build()
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
index e160627..19990a8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
@@ -32,6 +32,9 @@
override fun getTaskDataById(taskId: Int): Flow<Task?> =
getAllTaskData().map { taskList -> taskList.firstOrNull { it.key.id == taskId } }
+ override fun getThumbnailById(taskId: Int): Flow<ThumbnailData?> =
+ getTaskDataById(taskId).map { it?.thumbnail }
+
override fun setVisibleTasks(visibleTaskIdList: List<Int>) {
visibleTasks.value = visibleTaskIdList
tasks.value = tasks.value.map { it.apply { thumbnail = thumbnailDataMap[it.key.id] } }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/util/GetThumbnailUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/util/GetThumbnailUseCaseTest.kt
new file mode 100644
index 0000000..414f8ca
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/util/GetThumbnailUseCaseTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.task.util
+
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.Color
+import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.task.viewmodel.TaskOverlayViewModelTest
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/** Test for [GetThumbnailUseCase] */
+class GetThumbnailUseCaseTest {
+ private val task =
+ Task(Task.TaskKey(TASK_ID, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+ colorBackground = Color.BLACK
+ }
+ private val thumbnailData =
+ ThumbnailData(
+ thumbnail =
+ mock<Bitmap>().apply {
+ whenever(width).thenReturn(THUMBNAIL_WIDTH)
+ whenever(height).thenReturn(THUMBNAIL_HEIGHT)
+ }
+ )
+
+ private val tasksRepository = FakeTasksRepository()
+ private val systemUnderTest = GetThumbnailUseCase(tasksRepository)
+
+ @Test
+ fun taskNotSeeded_returnsNull() {
+ assertThat(systemUnderTest.run(TASK_ID)).isNull()
+ }
+
+ @Test
+ fun taskNotLoaded_returnsNull() {
+ tasksRepository.seedTasks(listOf(task))
+
+ assertThat(systemUnderTest.run(TASK_ID)).isNull()
+ }
+
+ @Test
+ fun taskNotVisible_returnsNull() {
+ tasksRepository.seedTasks(listOf(task))
+ tasksRepository.seedThumbnailData(mapOf(TaskOverlayViewModelTest.TASK_ID to thumbnailData))
+
+ assertThat(systemUnderTest.run(TASK_ID)).isNull()
+ }
+
+ @Test
+ fun taskVisible_returnsThumbnail() {
+ tasksRepository.seedTasks(listOf(task))
+ tasksRepository.seedThumbnailData(mapOf(TaskOverlayViewModelTest.TASK_ID to thumbnailData))
+ tasksRepository.setVisibleTasks(listOf(TaskOverlayViewModelTest.TASK_ID))
+
+ assertThat(systemUnderTest.run(TASK_ID)).isEqualTo(thumbnailData.thumbnail)
+ }
+
+ companion object {
+ const val TASK_ID = 0
+ const val THUMBNAIL_WIDTH = 100
+ const val THUMBNAIL_HEIGHT = 200
+ }
+}
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 fd7ecb0..f3cde52 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -87,7 +87,7 @@
@Before
fun setup() {
whenever(mockTaskContainer.snapshotView).thenReturn(mockSnapshotView)
- whenever(mockTaskContainer.thumbnail).thenReturn(mockBitmap)
+ whenever(mockTaskContainer.splitAnimationThumbnail).thenReturn(mockBitmap)
whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
@@ -276,7 +276,7 @@
whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper)
doNothing()
.whenever(spySplitAnimationController)
- .composeIconSplitLaunchAnimator(any(), any(), any(), any())
+ .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any())
doReturn(-1).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any())
spySplitAnimationController.playSplitLaunchAnimation(
@@ -296,7 +296,7 @@
)
verify(spySplitAnimationController)
- .composeIconSplitLaunchAnimator(any(), any(), any(), any())
+ .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any())
}
@Test
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index 13c4f72..27e761a 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -26,6 +26,7 @@
import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
import com.android.launcher3.model.data.AppInfo
import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.TaskItemInfo
import com.android.launcher3.statehandlers.DesktopVisibilityController
import com.android.quickstep.RecentsModel
import com.android.quickstep.RecentsModel.RecentTasksChangedListener
@@ -78,6 +79,13 @@
val listenerCaptor = ArgumentCaptor.forClass(RecentTasksChangedListener::class.java)
verify(mockRecentsModel).registerRecentTasksChangedListener(listenerCaptor.capture())
recentTasksChangedListener = listenerCaptor.value
+
+ // Make sure updateHotseatItemInfos() is called after commitRunningAppsToUI()
+ whenever(taskbarViewController.commitRunningAppsToUI()).then {
+ recentAppsController.updateHotseatItemInfos(
+ recentAppsController.shownHotseatItems.toTypedArray()
+ )
+ }
}
@Test
@@ -88,7 +96,7 @@
val newHotseatItems =
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = hotseatPackages,
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = emptyList()
)
assertThat(newHotseatItems.map { it?.targetPackage })
@@ -103,7 +111,7 @@
val newHotseatItems =
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = hotseatPackages,
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = emptyList()
)
assertThat(newHotseatItems.map { it?.targetPackage })
@@ -117,7 +125,7 @@
val newHotseatItems =
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = emptyList()
)
val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
@@ -126,13 +134,58 @@
}
@Test
+ fun updateHotseatItemInfos_inDesktopMode_hotseatPackageHasRunningTask_hotseatItemLinksToTask() {
+ setInDesktopMode(true)
+
+ val newHotseatItems =
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
+ runningTasks = listOf(createTask(id = 1, HOTSEAT_PACKAGE_1)),
+ recentTaskPackages = emptyList()
+ )
+
+ assertThat(newHotseatItems).hasLength(2)
+ assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
+ assertThat(newHotseatItems[1]).isNotInstanceOf(TaskItemInfo::class.java)
+ val hotseatItem1 = newHotseatItems[0] as TaskItemInfo
+ assertThat(hotseatItem1.taskId).isEqualTo(1)
+ }
+
+ @Test
+ fun updateHotseatItemInfos_inDesktopMode_twoRunningTasksSamePackage_hotseatCoversFirstTask() {
+ setInDesktopMode(true)
+
+ val newHotseatItems =
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
+ runningTasks =
+ listOf(
+ createTask(id = 1, HOTSEAT_PACKAGE_1),
+ createTask(id = 2, HOTSEAT_PACKAGE_1)
+ ),
+ recentTaskPackages = emptyList()
+ )
+
+ // First task is in Hotseat Items
+ assertThat(newHotseatItems).hasLength(2)
+ assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
+ assertThat(newHotseatItems[1]).isNotInstanceOf(TaskItemInfo::class.java)
+ val hotseatItem1 = newHotseatItems[0] as TaskItemInfo
+ assertThat(hotseatItem1.taskId).isEqualTo(1)
+ // Second task is in shownTasks
+ val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+ assertThat(shownTasks)
+ .containsExactlyElementsIn(listOf(createTask(id = 2, HOTSEAT_PACKAGE_1)))
+ }
+
+ @Test
fun updateHotseatItemInfos_canShowRecent_notInDesktopMode_returnsNonPredictedHotseatItems() {
recentAppsController.canShowRecentApps = true
setInDesktopMode(false)
val newHotseatItems =
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = emptyList()
)
val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
@@ -146,7 +199,11 @@
setInDesktopMode(true)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
- runningTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
+ runningTasks =
+ listOf(
+ createTask(id = 1, RUNNING_APP_PACKAGE_1),
+ createTask(id = 2, RUNNING_APP_PACKAGE_2)
+ ),
recentTaskPackages = emptyList()
)
assertThat(recentAppsController.shownTasks).isEmpty()
@@ -158,7 +215,7 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
assertThat(recentAppsController.shownTasks).isEmpty()
@@ -169,11 +226,15 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ runningTasks =
+ listOf(
+ createTask(id = 1, RUNNING_APP_PACKAGE_1),
+ createTask(id = 2, RUNNING_APP_PACKAGE_2)
+ ),
recentTaskPackages = emptyList()
)
assertThat(recentAppsController.shownTasks).isEmpty()
- assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+ assertThat(recentAppsController.minimizedTaskIds).isEmpty()
}
@Test
@@ -181,7 +242,7 @@
setInDesktopMode(true)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
assertThat(recentAppsController.shownTasks).isEmpty()
@@ -190,120 +251,161 @@
@Test
fun onRecentTasksChanged_inDesktopMode_shownTasks_returnsRunningTasks() {
setInDesktopMode(true)
- val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTaskPackages,
+ runningTasks = listOf(task1, task2),
recentTaskPackages = emptyList()
)
- val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
- assertThat(shownPackages).containsExactlyElementsIn(runningTaskPackages)
- }
-
- @Test
- fun onRecentTasksChanged_inDesktopMode_runningAppIsHotseatItem_shownTasks_returnsDistinctItems() {
- setInDesktopMode(true)
- prepareHotseatAndRunningAndRecentApps(
- hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
- runningTaskPackages =
- listOf(HOTSEAT_PACKAGE_1, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
- recentTaskPackages = emptyList()
- )
- val expectedPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
- val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
- assertThat(shownPackages).containsExactlyElementsIn(expectedPackages)
+ val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+ assertThat(shownTasks).containsExactlyElementsIn(listOf(task1, task2))
}
@Test
fun onRecentTasksChanged_notInDesktopMode_getRunningApps_returnsEmptySet() {
setInDesktopMode(false)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ runningTasks = listOf(task1, task2),
recentTaskPackages = emptyList()
)
- assertThat(recentAppsController.runningAppPackages).isEmpty()
- assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+ assertThat(recentAppsController.runningTaskIds).isEmpty()
+ assertThat(recentAppsController.minimizedTaskIds).isEmpty()
}
@Test
fun onRecentTasksChanged_inDesktopMode_getRunningApps_returnsAllDesktopTasks() {
setInDesktopMode(true)
- val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTaskPackages,
+ runningTasks = listOf(task1, task2),
recentTaskPackages = emptyList()
)
- assertThat(recentAppsController.runningAppPackages)
- .containsExactlyElementsIn(runningTaskPackages)
- assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+ assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2))
+ assertThat(recentAppsController.minimizedTaskIds).isEmpty()
}
@Test
fun onRecentTasksChanged_inDesktopMode_getRunningApps_includesHotseat() {
setInDesktopMode(true)
- val runningTaskPackages =
- listOf(HOTSEAT_PACKAGE_1, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val runningTasks =
+ listOf(
+ createTask(id = 1, HOTSEAT_PACKAGE_1),
+ createTask(id = 2, RUNNING_APP_PACKAGE_1),
+ createTask(id = 3, RUNNING_APP_PACKAGE_2)
+ )
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
- runningTaskPackages = runningTaskPackages,
+ runningTasks = runningTasks,
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
- assertThat(recentAppsController.runningAppPackages)
- .containsExactlyElementsIn(runningTaskPackages)
- assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+ assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2, 3))
+ assertThat(recentAppsController.minimizedTaskIds).isEmpty()
}
@Test
- fun getMinimizedApps_inDesktopMode_returnsAllAppsRunningAndInvisibleAppsMinimized() {
+ fun onRecentTasksChanged_inDesktopMode_allAppsRunningAndInvisibleAppsMinimized() {
setInDesktopMode(true)
- val runningTaskPackages =
- listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3)
- val minimizedTaskIndices = setOf(2) // RUNNING_APP_PACKAGE_3
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
+ val task3Minimized = createTask(id = 3, RUNNING_APP_PACKAGE_3, isVisible = false)
+ val runningTasks = listOf(task1, task2, task3Minimized)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTaskPackages,
- minimizedTaskIndices = minimizedTaskIndices,
+ runningTasks = runningTasks,
recentTaskPackages = emptyList()
)
- assertThat(recentAppsController.runningAppPackages)
- .containsExactlyElementsIn(runningTaskPackages)
- assertThat(recentAppsController.minimizedAppPackages).containsExactly(RUNNING_APP_PACKAGE_3)
+ assertThat(recentAppsController.runningTaskIds).containsExactly(1, 2, 3)
+ assertThat(recentAppsController.minimizedTaskIds).containsExactly(3)
}
@Test
- fun getMinimizedApps_inDesktopMode_twoTasksSamePackageOneMinimizedReturnsNotMinimized() {
+ fun onRecentTasksChanged_inDesktopMode_samePackage_differentTasks_severalRunningTasks() {
setInDesktopMode(true)
- val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_1)
- val minimizedTaskIndices = setOf(1) // The second RUNNING_APP_PACKAGE_1 task.
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTaskPackages,
- minimizedTaskIndices = minimizedTaskIndices,
+ runningTasks = listOf(task1, task2),
recentTaskPackages = emptyList()
)
- assertThat(recentAppsController.runningAppPackages)
- .containsExactlyElementsIn(runningTaskPackages.toSet())
- assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+ assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1, 2))
}
@Test
fun onRecentTasksChanged_inDesktopMode_shownTasks_maintainsOrder() {
setInDesktopMode(true)
- val originalOrder = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = originalOrder,
+ runningTasks = listOf(task1, task2),
recentTaskPackages = emptyList()
)
+
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1),
+ runningTasks = listOf(task2, task1),
recentTaskPackages = emptyList()
)
- val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
- assertThat(shownPackages).isEqualTo(originalOrder)
+
+ val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+ assertThat(shownTasks).isEqualTo(listOf(task1, task2))
+ }
+
+ @Test
+ fun onRecentTasksChanged_inDesktopMode_multiInstance_shownTasks_maintainsOrder() {
+ setInDesktopMode(true)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1)
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = emptyList(),
+ runningTasks = listOf(task1, task2),
+ recentTaskPackages = emptyList()
+ )
+
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = emptyList(),
+ runningTasks = listOf(task2, task1),
+ recentTaskPackages = emptyList()
+ )
+
+ val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+ assertThat(shownTasks).isEqualTo(listOf(task1, task2))
+ }
+
+ @Test
+ fun updateHotseatItems_inDesktopMode_multiInstanceHotseatPackage_shownItems_maintainsOrder() {
+ setInDesktopMode(true)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1)
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
+ runningTasks = listOf(task1, task2),
+ recentTaskPackages = emptyList()
+ )
+ updateRecentTasks( // Trigger a recent-tasks change before calling updateHotseatItems()
+ runningTasks = listOf(task2, task1),
+ recentTaskPackages = emptyList()
+ )
+
+ prepareHotseatAndRunningAndRecentApps(
+ hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
+ runningTasks = listOf(task2, task1),
+ recentTaskPackages = emptyList()
+ )
+
+ val newHotseatItems = recentAppsController.shownHotseatItems
+ assertThat(newHotseatItems).hasSize(1)
+ assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
+ assertThat((newHotseatItems[0] as TaskItemInfo).taskId).isEqualTo(1)
+ val shownTasks = recentAppsController.shownTasks.map { it.task1 }
+ assertThat(shownTasks).isEqualTo(listOf(task2))
}
@Test
@@ -311,12 +413,12 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1)
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -327,15 +429,17 @@
@Test
fun onRecentTasksChanged_inDesktopMode_addTask_shownTasks_maintainsOrder() {
setInDesktopMode(true)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
+ val task3 = createTask(id = 3, RUNNING_APP_PACKAGE_3)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ runningTasks = listOf(task1, task2),
recentTaskPackages = emptyList()
)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages =
- listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_3),
+ runningTasks = listOf(task2, task1, task3),
recentTaskPackages = emptyList()
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -349,12 +453,12 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_3, RECENT_PACKAGE_2)
)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1)
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -365,15 +469,17 @@
@Test
fun onRecentTasksChanged_inDesktopMode_removeTask_shownTasks_maintainsOrder() {
setInDesktopMode(true)
+ val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
+ val task3 = createTask(id = 3, RUNNING_APP_PACKAGE_3)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages =
- listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3),
+ runningTasks = listOf(task1, task2, task3),
recentTaskPackages = emptyList()
)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1),
+ runningTasks = listOf(task2, task1),
recentTaskPackages = emptyList()
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -385,12 +491,12 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3)
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -401,27 +507,31 @@
@Test
fun onRecentTasksChanged_enterDesktopMode_shownTasks_onlyIncludesRunningTasks() {
setInDesktopMode(false)
- val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
val recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTaskPackages,
+ runningTasks = listOf(runningTask1, runningTask2),
recentTaskPackages = recentTaskPackages
)
+
setInDesktopMode(true)
recentTasksChangedListener.onRecentTasksChanged()
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
- assertThat(shownPackages).containsExactlyElementsIn(runningTaskPackages)
+ assertThat(shownPackages).containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
}
@Test
fun onRecentTasksChanged_exitDesktopMode_shownTasks_onlyIncludesRecentTasks() {
setInDesktopMode(true)
- val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
val recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTaskPackages,
+ runningTasks = listOf(runningTask1, runningTask2),
recentTaskPackages = recentTaskPackages
)
setInDesktopMode(false)
@@ -437,7 +547,7 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
)
val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
@@ -449,9 +559,11 @@
@Test
fun onRecentTasksChanged_notInDesktopMode_hasRecentAndRunningTasks_shownTasks_returnsRecentTaskAndDesktopTile() {
setInDesktopMode(false)
+ val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ runningTasks = listOf(runningTask1, runningTask2),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
val shownPackages = recentAppsController.shownTasks.map { it.packageNames }
@@ -467,7 +579,7 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_SPLIT_PACKAGES_1, RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
val shownPackages = recentAppsController.shownTasks.map { it.packageNames }
@@ -483,14 +595,14 @@
setInDesktopMode(false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
verify(taskbarViewController, times(1)).commitRunningAppsToUI()
// Call onRecentTasksChanged() again with the same tasks, verify it's a no-op.
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = emptyList(),
+ runningTasks = emptyList(),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
)
verify(taskbarViewController, times(1)).commitRunningAppsToUI()
@@ -499,16 +611,18 @@
@Test
fun onRecentTasksChanged_inDesktopMode_noActualChangeToRunning_commitRunningAppsToUI_notCalled() {
setInDesktopMode(true)
+ val runningTask1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
+ val runningTask2 = createTask(id = 2, RUNNING_APP_PACKAGE_2)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ runningTasks = listOf(runningTask1, runningTask2),
recentTaskPackages = emptyList()
)
verify(taskbarViewController, times(1)).commitRunningAppsToUI()
// Call onRecentTasksChanged() again with the same tasks, verify it's a no-op.
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+ runningTasks = listOf(runningTask1, runningTask2),
recentTaskPackages = emptyList()
)
verify(taskbarViewController, times(1)).commitRunningAppsToUI()
@@ -517,21 +631,23 @@
@Test
fun onRecentTasksChanged_onlyMinimizedChanges_commitRunningAppsToUI_isCalled() {
setInDesktopMode(true)
- val runningTasks = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+ val task1Minimized = createTask(id = 1, RUNNING_APP_PACKAGE_1, isVisible = false)
+ val task2Visible = createTask(id = 2, RUNNING_APP_PACKAGE_2)
+ val task2Minimized = createTask(id = 2, RUNNING_APP_PACKAGE_2, isVisible = false)
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTasks,
- minimizedTaskIndices = setOf(0),
+ runningTasks = listOf(task1Minimized, task2Visible),
recentTaskPackages = emptyList()
)
verify(taskbarViewController, times(1)).commitRunningAppsToUI()
+
// Call onRecentTasksChanged() again with a new minimized app, verify we update UI.
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = emptyList(),
- runningTaskPackages = runningTasks,
- minimizedTaskIndices = setOf(0, 1),
+ runningTasks = listOf(task1Minimized, task2Minimized),
recentTaskPackages = emptyList()
)
+
verify(taskbarViewController, times(2)).commitRunningAppsToUI()
}
@@ -539,36 +655,46 @@
fun onRecentTasksChanged_hotseatAppStartsRunning_commitRunningAppsToUI_isCalled() {
setInDesktopMode(true)
val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
+ val originalTasks = listOf(createTask(id = 1, RUNNING_APP_PACKAGE_1))
+ val newTasks =
+ listOf(createTask(id = 1, RUNNING_APP_PACKAGE_1), createTask(id = 2, HOTSEAT_PACKAGE_1))
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = hotseatPackages,
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1),
+ runningTasks = originalTasks,
recentTaskPackages = emptyList()
)
verify(taskbarViewController, times(1)).commitRunningAppsToUI()
+
// Call onRecentTasksChanged() again with a new running app, verify we update UI.
prepareHotseatAndRunningAndRecentApps(
hotseatPackages = hotseatPackages,
- runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, HOTSEAT_PACKAGE_1),
+ runningTasks = newTasks,
recentTaskPackages = emptyList()
)
+
verify(taskbarViewController, times(2)).commitRunningAppsToUI()
}
private fun prepareHotseatAndRunningAndRecentApps(
hotseatPackages: List<String>,
- runningTaskPackages: List<String>,
- minimizedTaskIndices: Set<Int> = emptySet(),
+ runningTasks: List<Task>,
recentTaskPackages: List<String>,
): Array<ItemInfo?> {
val hotseatItems = createHotseatItemsFromPackageNames(hotseatPackages)
- val newHotseatItems =
- recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
- val runningTasks = createDesktopTask(runningTaskPackages, minimizedTaskIndices)
+ recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
+ updateRecentTasks(runningTasks, recentTaskPackages)
+ return recentAppsController.shownHotseatItems.toTypedArray()
+ }
+
+ private fun updateRecentTasks(
+ runningTasks: List<Task>,
+ recentTaskPackages: List<String>,
+ ) {
val recentTasks = createRecentTasksFromPackageNames(recentTaskPackages)
val allTasks =
ArrayList<GroupTask>().apply {
- if (runningTasks != null) {
- add(runningTasks)
+ if (!runningTasks.isEmpty()) {
+ add(DesktopTask(ArrayList(runningTasks)))
}
addAll(recentTasks)
}
@@ -580,20 +706,21 @@
.whenever(mockRecentsModel)
.getTasks(any<Consumer<List<GroupTask>>>())
recentTasksChangedListener.onRecentTasksChanged()
- return newHotseatItems
}
private fun createHotseatItemsFromPackageNames(packageNames: List<String>): List<ItemInfo> {
- return packageNames.map {
- createTestAppInfo(packageName = it).apply {
- container =
- if (it.startsWith("predicted")) {
- CONTAINER_HOTSEAT_PREDICTION
- } else {
- CONTAINER_HOTSEAT
- }
+ return packageNames
+ .map {
+ createTestAppInfo(packageName = it).apply {
+ container =
+ if (it.startsWith("predicted")) {
+ CONTAINER_HOTSEAT_PREDICTION
+ } else {
+ CONTAINER_HOTSEAT
+ }
+ }
}
- }
+ .map { it.makeWorkspaceItem(taskbarActivityContext) }
}
private fun createTestAppInfo(
@@ -601,39 +728,24 @@
className: String = "testClassName"
) = AppInfo(ComponentName(packageName, className), className /* title */, userHandle, Intent())
- private fun createDesktopTask(
- packageNames: List<String>,
- minimizedTaskIndices: Set<Int>
- ): DesktopTask? {
- if (packageNames.isEmpty()) return null
-
- return DesktopTask(
- ArrayList(
- packageNames.mapIndexed { index, packageName ->
- createTask(packageName, index !in minimizedTaskIndices)
- }
- )
- )
- }
-
private fun createRecentTasksFromPackageNames(packageNames: List<String>): List<GroupTask> {
- return packageNames.map {
- if (it.startsWith("split")) {
- val splitPackages = it.split("_")
+ return packageNames.map { packageName ->
+ if (packageName.startsWith("split")) {
+ val splitPackages = packageName.split("_")
GroupTask(
- createTask(splitPackages[0]),
- createTask(splitPackages[1]),
+ createTask(100, splitPackages[0]),
+ createTask(101, splitPackages[1]),
/* splitBounds = */ null
)
} else {
- GroupTask(createTask(it))
+ // Use the number at the end of the test packageName as the id.
+ val id = 1000 + packageName[packageName.length - 1].code
+ GroupTask(createTask(id, packageName))
}
}
}
- private fun createTask(packageName: String, isVisible: Boolean = true): Task {
- // Use the number at the end of the test packageName as the id.
- val id = packageName[packageName.length - 1].code
+ private fun createTask(id: Int, packageName: String, isVisible: Boolean = true): Task {
return Task(
Task.TaskKey(
id,
diff --git a/res/anim-v33/shared_x_axis_activity_close_enter.xml b/res/anim-v33/shared_x_axis_activity_close_enter.xml
deleted file mode 100644
index 3d7ad2b..0000000
--- a/res/anim-v33/shared_x_axis_activity_close_enter.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:showBackdrop="true">
-
- <alpha
- android:fromAlpha="0.0"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/standard_decelerate_interpolator"
- android:startOffset="100"
- android:duration="350" />
-
- <translate
- android:fromXDelta="-25%"
- android:toXDelta="0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/emphasized_interpolator"
- android:startOffset="0"
- android:duration="450" />
-
-</set>
\ No newline at end of file
diff --git a/res/anim-v33/shared_x_axis_activity_close_exit.xml b/res/anim-v33/shared_x_axis_activity_close_exit.xml
deleted file mode 100644
index fb63602..0000000
--- a/res/anim-v33/shared_x_axis_activity_close_exit.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="0.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/standard_accelerate_interpolator"
- android:startOffset="0"
- android:duration="100" />
-
- <translate
- android:fromXDelta="0"
- android:toXDelta="25%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/emphasized_interpolator"
- android:startOffset="0"
- android:duration="450" />
-
-</set>
\ No newline at end of file
diff --git a/res/anim-v33/shared_x_axis_activity_open_enter.xml b/res/anim-v33/shared_x_axis_activity_open_enter.xml
deleted file mode 100644
index cba74ba..0000000
--- a/res/anim-v33/shared_x_axis_activity_open_enter.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:showBackdrop="true">
-
- <alpha
- android:fromAlpha="0.0"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/standard_decelerate_interpolator"
- android:startOffset="100"
- android:duration="350" />
-
- <translate
- android:fromXDelta="25%"
- android:toXDelta="0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/emphasized_interpolator"
- android:startOffset="0"
- android:duration="450" />
-
-</set>
\ No newline at end of file
diff --git a/res/anim-v33/shared_x_axis_activity_open_exit.xml b/res/anim-v33/shared_x_axis_activity_open_exit.xml
deleted file mode 100644
index 22e878d..0000000
--- a/res/anim-v33/shared_x_axis_activity_open_exit.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 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.
- -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="0.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/standard_accelerate_interpolator"
- android:startOffset="0"
- android:duration="100" />
-
- <translate
- android:fromXDelta="0"
- android:toXDelta="-25%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/emphasized_interpolator"
- android:startOffset="0"
- android:duration="450" />
-
-</set>
\ No newline at end of file
diff --git a/res/drawable/ic_close_work_edu.xml b/res/drawable/ic_close_work_edu.xml
new file mode 100644
index 0000000..f336eea
--- /dev/null
+++ b/res/drawable/ic_close_work_edu.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@color/material_color_on_surface"
+ android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
+</vector>
diff --git a/res/drawable/rounded_action_button.xml b/res/drawable/rounded_action_button.xml
index 81e94f7..e283d3f 100644
--- a/res/drawable/rounded_action_button.xml
+++ b/res/drawable/rounded_action_button.xml
@@ -18,11 +18,11 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurfaceVariant" />
+ <solid android:color="@color/material_color_surface_container_low" />
<corners android:radius="@dimen/rounded_button_radius" />
<stroke
android:width="1dp"
- android:color="?androidprv:attr/colorSurfaceVariant" />
+ android:color="@color/material_color_surface_container_low" />
<padding
android:left="@dimen/rounded_button_padding"
android:right="@dimen/rounded_button_padding" />
diff --git a/res/layout/widgets_two_pane_sheet.xml b/res/layout/widgets_two_pane_sheet.xml
index bb2b7bd..ce5eed9 100644
--- a/res/layout/widgets_two_pane_sheet.xml
+++ b/res/layout/widgets_two_pane_sheet.xml
@@ -48,20 +48,23 @@
android:textSize="24sp" />
<TextView
- android:id="@+id/no_widgets_text"
- style="@style/PrimaryHeadline"
+ android:id="@+id/widget_picker_description"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:textSize="18sp"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:layout_below="@id/title"
+ android:maxLines="1"
+ android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
+ android:textColor="?attr/widgetPickerDescriptionColor"
android:visibility="gone"
- tools:text="@string/no_widgets_available" />
+ android:lineHeight="20sp"
+ android:textSize="14sp" />
<LinearLayout
android:id="@+id/linear_layout_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_below="@id/title">
+ android:layout_below="@id/widget_picker_description">
<FrameLayout
android:id="@+id/recycler_view_container"
@@ -124,6 +127,16 @@
android:background="@drawable/widgets_surface_background"
android:importantForAccessibility="yes"
android:id="@+id/right_pane">
+ <TextView
+ android:id="@+id/no_widgets_text"
+ style="@style/PrimaryHeadline"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:textSize="18sp"
+ android:visibility="gone"
+ tools:text="@string/no_widgets_available" />
+
<!-- Shown when there are recommendations to display -->
<LinearLayout
android:id="@+id/widget_recommendations_container"
diff --git a/res/layout/work_apps_edu.xml b/res/layout/work_apps_edu.xml
index 99db8c6..c581ae3 100644
--- a/res/layout/work_apps_edu.xml
+++ b/res/layout/work_apps_edu.xml
@@ -54,7 +54,7 @@
android:layout_gravity="center"
android:contentDescription="@string/accessibility_close"
android:background="@android:color/transparent"
- android:src="@drawable/ic_remove_no_shadow" />
+ android:src="@drawable/ic_close_work_edu" />
</FrameLayout>
</LinearLayout>
</com.android.launcher3.allapps.WorkEduCard>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 27ce075..5cbce53 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -38,7 +38,7 @@
<string name="app_pair_needs_unfold" msgid="4588897528143807002">"برای استفاده از این جفت برنامه، دستگاه را باز کنید"</string>
<string name="app_pair_not_available" msgid="3556767440808032031">"جفت برنامه دردسترس نیست"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"برای جابهجا کردن ابزارک، لمس کنید و نگه دارید."</string>
- <string name="long_accessible_way_to_add" msgid="2733588281439571974">"برای جابهجا کردن ابزارک یا استفاده از کنشهای سفارشی، دوضربه بزنید و نگه دارید."</string>
+ <string name="long_accessible_way_to_add" msgid="2733588281439571974">"برای جابهجا کردن ابزارک یا استفاده از کنشهای سفارشی، دو تکضرب بزنید و نگه دارید."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d عرض در %2$d طول"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"ابزارک <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
@@ -76,7 +76,7 @@
<string name="all_apps_label" msgid="5015784846527570951">"همه برنامهها"</string>
<string name="notifications_header" msgid="1404149926117359025">"اعلانها"</string>
<string name="long_press_shortcut_to_add" msgid="5405328730817637737">"برای جابهجا کردن میانبر، لمس کنید و نگه دارید."</string>
- <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"برای جابهجا کردن میانبر یا استفاده از کنشهای سفارشی، دوضربه بزنید و نگه دارید."</string>
+ <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"برای جابهجا کردن میانبر یا استفاده از کنشهای سفارشی، دو تکضرب بزنید و نگه دارید."</string>
<string name="out_of_space" msgid="6455557115204099579">"فضای خالی در این صفحه اصلی وجود ندارد"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"فضای بیشتری در سینی موارد دلخواه وجود ندارد"</string>
<string name="all_apps_button_label" msgid="8130441508702294465">"فهرست برنامهها"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index f3b08cb..f95f26d 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -21,9 +21,9 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="649227358658669779">"Lanceur3"</string>
<string name="work_folder_name" msgid="3753320833950115786">"Travail"</string>
- <string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string>
- <string name="activity_not_available" msgid="7456344436509528827">"Application indisponible"</string>
- <string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'application téléchargée est désactivée en mode sans échec."</string>
+ <string name="activity_not_found" msgid="8071924732094499514">"L\'appli n\'est pas installée."</string>
+ <string name="activity_not_available" msgid="7456344436509528827">"Appli indisponible"</string>
+ <string name="safemode_shortcut_error" msgid="9160126848219158407">"L\'appli téléchargée est désactivée en mode sans échec."</string>
<string name="safemode_widget_error" msgid="4863470563535682004">"Widgets désactivés en mode sans échec"</string>
<string name="shortcut_not_available" msgid="2536503539825726397">"Le raccourci n\'est pas disponible"</string>
<string name="home_screen" msgid="5629429142036709174">"Accueil"</string>
@@ -34,9 +34,9 @@
<string name="split_app_usage_settings" msgid="7214375263347964093">"Paramètres d\'utilisation pour %1$s"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Enr. paire d\'applis"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
- <string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Cette paire d\'applications n\'est pas prise en charge sur cet appareil"</string>
- <string name="app_pair_needs_unfold" msgid="4588897528143807002">"Déplier l\'appareil pour utiliser cette paire d\'applications"</string>
- <string name="app_pair_not_available" msgid="3556767440808032031">"La Paire d\'applications n\'est pas offerte"</string>
+ <string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"Cette paire d\'applis n\'est pas prise en charge sur cet appareil"</string>
+ <string name="app_pair_needs_unfold" msgid="4588897528143807002">"Déplier l\'appareil pour utiliser cette paire d\'applis"</string>
+ <string name="app_pair_not_available" msgid="3556767440808032031">"La Paire d\'applis n\'est pas offerte"</string>
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Maintenez le doigt sur un widget pour le déplacer."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Touchez 2x un widget et maintenez le doigt dessus pour le déplacer ou utiliser des actions personnalisées."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
@@ -69,20 +69,20 @@
<string name="widget_add_button_content_description" msgid="1810530016360039643">"Ajoutez le widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"Touchez pour modifier les paramètres du widget"</string>
<string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"Modifier les paramètres du widget"</string>
- <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Rechercher dans les applications"</string>
- <string name="all_apps_loading_message" msgid="5813968043155271636">"Chargement des applications en cours…"</string>
- <string name="all_apps_no_search_results" msgid="3200346862396363786">"Aucune application trouvée correspondant à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
- <string name="label_application" msgid="8531721983832654978">"Application"</string>
- <string name="all_apps_label" msgid="5015784846527570951">"Toutes les applications"</string>
+ <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Rechercher dans les applis"</string>
+ <string name="all_apps_loading_message" msgid="5813968043155271636">"Chargement des applis en cours…"</string>
+ <string name="all_apps_no_search_results" msgid="3200346862396363786">"Aucune appli trouvée correspondant à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
+ <string name="label_application" msgid="8531721983832654978">"Appli"</string>
+ <string name="all_apps_label" msgid="5015784846527570951">"Toutes les applis"</string>
<string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
<string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Maintenez le doigt sur un raccourci pour le déplacer."</string>
<string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Touchez deux fois un raccourci et maintenez le doigt dessus pour le déplacer ou utiliser des actions personnalisées."</string>
<string name="out_of_space" msgid="6455557115204099579">"Pas d\'espace libre sur cet écran d\'accueil"</string>
<string name="hotseat_out_of_space" msgid="7448809638125333693">"Il n\'y a plus d\'espace dans la zone des favoris"</string>
- <string name="all_apps_button_label" msgid="8130441508702294465">"Liste des applications"</string>
+ <string name="all_apps_button_label" msgid="8130441508702294465">"Liste des applis"</string>
<string name="all_apps_search_results" msgid="5889367432531296759">"Résultats de recherche"</string>
- <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Liste des applications personnelles"</string>
- <string name="all_apps_button_work_label" msgid="7270707118948892488">"Liste des applications professionnelles"</string>
+ <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Liste des applis personnelles"</string>
+ <string name="all_apps_button_work_label" msgid="7270707118948892488">"Liste des applis professionnelles"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"Supprimer"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"Désinstaller"</string>
<string name="app_info_drop_target_label" msgid="692894985365717661">"Détails de l\'appli"</string>
@@ -92,17 +92,17 @@
<string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne pas suggérer d\'appli"</string>
<string name="pin_prediction" msgid="4196423321649756498">"Épingler la prédiction"</string>
<string name="permlab_install_shortcut" msgid="5632423390354674437">"installer des raccourcis"</string>
- <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permet à une application d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur."</string>
+ <string name="permdesc_install_shortcut" msgid="923466509822011139">"Permet à une appli d\'ajouter des raccourcis sans l\'intervention de l\'utilisateur."</string>
<string name="permlab_read_settings" msgid="5136500343007704955">"lire les paramètres et les raccourcis de la page d\'accueil"</string>
- <string name="permdesc_read_settings" msgid="4208061150510996676">"Permet à l\'application de lire les paramètres et les raccourcis de l\'écran d\'accueil."</string>
+ <string name="permdesc_read_settings" msgid="4208061150510996676">"Permet à l\'appli de lire les paramètres et les raccourcis de l\'écran d\'accueil."</string>
<string name="permlab_write_settings" msgid="4820028712156303762">"modifier les paramètres et les raccourcis de la page d\'accueil"</string>
- <string name="permdesc_write_settings" msgid="726859348127868466">"Permet à l\'application de modifier les paramètres et les raccourcis de l\'écran d\'accueil."</string>
+ <string name="permdesc_write_settings" msgid="726859348127868466">"Permet à l\'appli de modifier les paramètres et les raccourcis de l\'écran d\'accueil."</string>
<string name="gadget_error_text" msgid="740356548025791839">"Impossible de charger le widget"</string>
<string name="gadget_setup_text" msgid="8348374825537681407">"Paramètres du widget"</string>
<string name="gadget_complete_setup_text" msgid="309040266978007925">"Touchez pour terminer la configuration"</string>
- <string name="uninstall_system_app_text" msgid="4172046090762920660">"Impossible de désinstaller cette application, car il s\'agit d\'une application système."</string>
+ <string name="uninstall_system_app_text" msgid="4172046090762920660">"Impossible de désinstaller cette appli, car il s\'agit d\'une appli système."</string>
<string name="folder_hint_text" msgid="5174843001373488816">"Modifier le nom"</string>
- <string name="disabled_app_label" msgid="6673129024321402780">"L\'application <xliff:g id="APP_NAME">%1$s</xliff:g> est désactivée"</string>
+ <string name="disabled_app_label" msgid="6673129024321402780">"L\'appli <xliff:g id="APP_NAME">%1$s</xliff:g> est désactivée"</string>
<string name="dotted_app_label" msgid="1865617679843363410">"{count,plural, =1{{app_name} a # notification}one{{app_name} a # notification}other{{app_name} a # notifications}}"</string>
<string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d sur %2$d"</string>
<string name="workspace_scroll_format" msgid="8458889198184077399">"Écran d\'accueil %1$d sur %2$d"</string>
@@ -114,7 +114,7 @@
<string name="folder_renamed" msgid="1794088362165669656">"Nouveau nom du dossier : <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="folder_name_format_exact" msgid="8626242716117004803">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> élément(s)"</string>
<string name="folder_name_format_overflow" msgid="4270108890534995199">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> éléments ou plus"</string>
- <string name="app_pair_name_format" msgid="8134106404716224054">"Paire d\'applications : <xliff:g id="APP1">%1$s</xliff:g> et <xliff:g id="APP2">%2$s</xliff:g>"</string>
+ <string name="app_pair_name_format" msgid="8134106404716224054">"Paire d\'applis : <xliff:g id="APP1">%1$s</xliff:g> et <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fond d\'écran et style"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"Modifier l\'écran d\'accueil"</string>
<string name="settings_button_text" msgid="8873672322605444408">"Paramètres d\'accueil"</string>
@@ -125,23 +125,23 @@
<string name="notification_dots_desc_on" msgid="1679848116452218908">"Activé"</string>
<string name="notification_dots_desc_off" msgid="1760796511504341095">"Désactivé"</string>
<string name="title_missing_notification_access" msgid="7503287056163941064">"L\'accès aux notifications est requis"</string>
- <string name="msg_missing_notification_access" msgid="281113995110910548">"Pour afficher les points de notification, activez les notifications d\'application pour <xliff:g id="NAME">%1$s</xliff:g>"</string>
+ <string name="msg_missing_notification_access" msgid="281113995110910548">"Pour afficher les points de notification, activez les notifications d\'appli pour <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="title_change_settings" msgid="1376365968844349552">"Modifier les paramètres"</string>
<string name="notification_dots_service_title" msgid="4284221181793592871">"Afficher les points de notification"</string>
<string name="developer_options_title" msgid="700788437593726194">"Options pour les développeurs"</string>
- <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Ajouter les icônes des applications à l\'écran d\'accueil"</string>
- <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pour les nouvelles applications"</string>
+ <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Ajouter les icônes des applis à l\'écran d\'accueil"</string>
+ <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Pour les nouvelles applis"</string>
<string name="package_state_unknown" msgid="7592128424511031410">"Inconnu"</string>
<string name="abandoned_clean_this" msgid="7610119707847920412">"Supprimer"</string>
<string name="abandoned_search" msgid="891119232568284442">"Rechercher"</string>
- <string name="abandoned_promises_title" msgid="7096178467971716750">"Cette application n\'est pas installée"</string>
- <string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'application liée à cette icône n\'est pas installée. Vous pouvez la supprimer ou rechercher l\'application et l\'installer manuellement."</string>
- <string name="app_installing_title" msgid="5864044122733792085">"Installation de l\'application <xliff:g id="NAME">%1$s</xliff:g> en cours, <xliff:g id="PROGRESS">%2$s</xliff:g> terminée"</string>
+ <string name="abandoned_promises_title" msgid="7096178467971716750">"Cette appli n\'est pas installée"</string>
+ <string name="abandoned_promise_explanation" msgid="3990027586878167529">"L\'appli liée à cette icône n\'est pas installée. Vous pouvez la supprimer ou rechercher l\'appli et l\'installer manuellement."</string>
+ <string name="app_installing_title" msgid="5864044122733792085">"Installation de l\'appli <xliff:g id="NAME">%1$s</xliff:g> en cours, <xliff:g id="PROGRESS">%2$s</xliff:g> terminée"</string>
<string name="app_downloading_title" msgid="8336702962104482644">"Téléchargement de <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> en attente d\'installation"</string>
<string name="app_archived_title" msgid="7717956158562544081">"L\'appli <xliff:g id="NAME">%1$s</xliff:g> est archivée. Touchez le bouton pour télécharger et restaurer l\'appli."</string>
- <string name="dialog_update_title" msgid="114234265740994042">"Mise à jour de l\'application requise"</string>
- <string name="dialog_update_message" msgid="4176784553982226114">"L\'application pour cette icône n\'est pas à jour. Vous pouvez soit la mettre à jour manuellement pour réactiver ce raccourci, soit retirer l\'icône."</string>
+ <string name="dialog_update_title" msgid="114234265740994042">"Mise à jour de l\'appli requise"</string>
+ <string name="dialog_update_message" msgid="4176784553982226114">"L\'appli pour cette icône n\'est pas à jour. Vous pouvez soit la mettre à jour manuellement pour réactiver ce raccourci, soit retirer l\'icône."</string>
<string name="dialog_update" msgid="2178028071796141234">"Mettre à jour"</string>
<string name="dialog_remove" msgid="6510806469849709407">"Retirer"</string>
<string name="widgets_list" msgid="796804551140113767">"Liste des widgets"</string>
@@ -174,15 +174,15 @@
<string name="all_apps_personal_tab" msgid="4190252696685155002">"Personnel"</string>
<string name="all_apps_work_tab" msgid="4884822796154055118">"Travail"</string>
<string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil professionnel"</string>
- <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Les applications professionnelles sont indiquées par un badge et elles sont visibles pour votre administrateur informatique"</string>
+ <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Les applis professionnelles sont indiquées par un badge et elles sont visibles pour votre administrateur informatique"</string>
<string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
- <string name="work_apps_paused_title" msgid="3040901117349444598">"Les applications professionnelles sont interrompues"</string>
- <string name="work_apps_paused_info_body" msgid="1687828929959237477">"Vous ne recevrez pas de notifications de vos applications professionnelles"</string>
- <string name="work_apps_paused_body" msgid="261634750995824906">"Les applications professionnelles ne peuvent ni vous envoyer de notifications, ni utiliser la pile, ni accéder à votre position"</string>
- <string name="work_apps_paused_telephony_unavailable_body" msgid="8358872357502756790">"Vous ne recevrez pas d\'appels téléphoniques, de messages texte ni de notifications de vos applications professionnelles"</string>
- <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"Les applications professionnelles sont indiquées par un badge et sont visibles pour votre administrateur informatique"</string>
+ <string name="work_apps_paused_title" msgid="3040901117349444598">"Les applis professionnelles sont interrompues"</string>
+ <string name="work_apps_paused_info_body" msgid="1687828929959237477">"Vous ne recevrez pas de notifications de vos applis professionnelles"</string>
+ <string name="work_apps_paused_body" msgid="261634750995824906">"Les applis professionnelles ne peuvent ni vous envoyer de notifications, ni utiliser la pile, ni accéder à votre position"</string>
+ <string name="work_apps_paused_telephony_unavailable_body" msgid="8358872357502756790">"Vous ne recevrez pas d\'appels téléphoniques, de messages texte ni de notifications de vos applis professionnelles"</string>
+ <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"Les applis professionnelles sont indiquées par un badge et sont visibles pour votre administrateur informatique"</string>
<string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"OK"</string>
- <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"Mettre en pause les applications professionnelles"</string>
+ <string name="work_apps_pause_btn_text" msgid="4669288269140620646">"Mettre en pause les applis professionnelles"</string>
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Réactiver"</string>
<string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtrer"</string>
<string name="remote_action_failed" msgid="1383965239183576790">"Échec : <xliff:g id="WHAT">%1$s</xliff:g>"</string>
@@ -195,5 +195,5 @@
<string name="ps_container_lock_title" msgid="2640257399982364682">"Verrouiller"</string>
<string name="ps_container_transition" msgid="8667331812048014412">"Transition vers l\'Espace privé"</string>
<string name="ps_add_button_label" msgid="8127988716897128773">"Installer"</string>
- <string name="ps_add_button_content_description" msgid="3254274107740952556">"Installer des applications dans l\'Espace privé"</string>
+ <string name="ps_add_button_content_description" msgid="3254274107740952556">"Installer des applis dans l\'Espace privé"</string>
</resources>
diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml
index d23f4d1..2688b83 100644
--- a/res/values-night-v31/colors.xml
+++ b/res/values-night-v31/colors.xml
@@ -26,6 +26,8 @@
<color name="home_settings_track_off_color">@android:color/system_neutral1_700</color>
<color name="widget_picker_title_color_dark">@android:color/system_neutral1_100</color>
+ <color name="widget_picker_description_color_dark">
+ @android:color/system_neutral2_200</color>
<color name="widget_picker_header_app_title_color_dark">
@android:color/system_neutral1_100</color>
<color name="widget_picker_header_app_subtitle_color_dark">
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index b287b2f..8cb5515 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -186,14 +186,14 @@
<string name="work_apps_enable_btn_text" msgid="1736198302467317371">"ਰੋਕ ਹਟਾਓ"</string>
<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_label" msgid="2359721649407947001">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ"</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_settings" msgid="6059734123353320479">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਸੰਬੰਧੀ ਸੈਟਿੰਗਾਂ"</string>
<string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"ਨਿੱਜੀ, ਅਣਲਾਕ ਕੀਤਾ ਗਿਆ।"</string>
<string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ਨਿੱਜੀ, ਲਾਕ ਕੀਤਾ ਗਿਆ।"</string>
<string name="ps_container_lock_title" msgid="2640257399982364682">"ਲਾਕ ਕਰੋ"</string>
- <string name="ps_container_transition" msgid="8667331812048014412">"ਨਿੱਜੀ ਸਪੇਸ ਨੂੰ ਤਬਦੀਲ ਕਰਨਾ"</string>
+ <string name="ps_container_transition" msgid="8667331812048014412">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਨੂੰ ਤਬਦੀਲ ਕਰਨਾ"</string>
<string name="ps_add_button_label" msgid="8127988716897128773">"ਸਥਾਪਤ ਕਰੋ"</string>
<string name="ps_add_button_content_description" msgid="3254274107740952556">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਵਿੱਚ ਐਪਾਂ ਸਥਾਪਤ ਕਰੋ"</string>
</resources>
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index fa87221..7270366 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -75,6 +75,8 @@
<color name="widget_picker_title_color_light">
@android:color/system_neutral1_900</color>
+ <color name="widget_picker_description_color_light">
+ @android:color/system_neutral2_700</color>
<color name="widget_picker_header_app_title_color_light">
@android:color/system_neutral1_900</color>
<color name="widget_picker_header_app_subtitle_color_light">
diff --git a/res/values-v33/style.xml b/res/values-v33/style.xml
deleted file mode 100644
index 1261b23..0000000
--- a/res/values-v33/style.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright (C) 2022 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.
-*/
--->
-
-<resources>
- <style name="HomeSettings.Theme" parent="@android:style/Theme.DeviceDefault.Settings">
- <item name="android:listPreferredItemPaddingEnd">16dp</item>
- <item name="android:listPreferredItemPaddingStart">24dp</item>
- <item name="android:navigationBarColor">@android:color/transparent</item>
- <item name="android:statusBarColor">@android:color/transparent</item>
- <item name="android:switchStyle">@style/SwitchStyle</item>
- <item name="android:textAppearanceListItem">@style/HomeSettings.PreferenceTitle</item>
- <item name="android:windowActionBar">false</item>
- <item name="android:windowNoTitle">true</item>
- <item name="preferenceTheme">@style/HomeSettings.PreferenceTheme</item>
- <item name="android:windowAnimationStyle">@style/Animation.SharedBackground</item>
- </style>
-
- <style name="Animation.SharedBackground" parent="@android:style/Animation.Activity">
- <item name="android:activityOpenEnterAnimation">@anim/shared_x_axis_activity_open_enter</item>
- <item name="android:activityOpenExitAnimation">@anim/shared_x_axis_activity_open_exit</item>
- <item name="android:activityCloseEnterAnimation">@anim/shared_x_axis_activity_close_enter</item>
- <item name="android:activityCloseExitAnimation">@anim/shared_x_axis_activity_close_exit</item>
- </style>
-</resources>
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index be8b2e1..e4e047e 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -61,6 +61,7 @@
<attr name="preloadIconAccentColor" format="color" />
<attr name="preloadIconBackgroundColor" format="color" />
<attr name="widgetPickerTitleColor" format="color"/>
+ <attr name="widgetPickerDescriptionColor" format="color"/>
<attr name="widgetPickerPrimarySurfaceColor" format="color"/>
<attr name="widgetPickerSecondarySurfaceColor" format="color"/>
<attr name="widgetPickerHeaderAppTitleColor" format="color"/>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index ce80964..8fa1992 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -104,6 +104,7 @@
<color name="widget_picker_primary_surface_color_light">#EFEDED</color>
<color name="widget_picker_secondary_surface_color_light">#FAF9F8</color>
<color name="widget_picker_title_color_light">#1F1F1F</color>
+ <color name="widget_picker_description_color_light">#4C4D50</color>
<color name="widget_picker_header_app_title_color_light">#1F1F1F</color>
<color name="widget_picker_header_app_subtitle_color_light">#444746</color>
<color name="widget_picker_header_background_color_light">#C2E7FF</color>
@@ -123,6 +124,7 @@
<color name="widget_picker_primary_surface_color_dark">#1F2020</color>
<color name="widget_picker_secondary_surface_color_dark">#393939</color>
<color name="widget_picker_title_color_dark">#E3E3E3</color>
+ <color name="widget_picker_description_color_dark">#CCCDCF</color>
<color name="widget_picker_header_app_title_color_dark">#E3E3E3</color>
<color name="widget_picker_header_app_subtitle_color_dark">#C4C7C5</color>
<color name="widget_picker_header_background_color_dark">#004A77</color>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index ae3d3b3..f7273a0 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -243,6 +243,7 @@
<item name="widgetPickerSecondarySurfaceColor">
@color/widget_picker_secondary_surface_color_light</item>
<item name="widgetPickerTitleColor">@color/widget_picker_title_color_light</item>
+ <item name="widgetPickerDescriptionColor">@color/widget_picker_description_color_light</item>
<item name="widgetPickerHeaderAppTitleColor">
@color/widget_picker_header_app_title_color_light</item>
<item name="widgetPickerHeaderAppSubtitleColor">
@@ -282,6 +283,7 @@
@color/widget_picker_secondary_surface_color_dark</item>
<item name="widgetPickerTitleColor">
@color/widget_picker_title_color_dark</item>
+ <item name="widgetPickerDescriptionColor">@color/widget_picker_description_color_dark</item>
<item name="widgetPickerHeaderAppTitleColor">
@color/widget_picker_header_app_title_color_dark</item>
<item name="widgetPickerHeaderAppSubtitleColor">
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index b51e850..ef56246 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -37,6 +37,8 @@
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.debug.TestEvent;
+import com.android.launcher3.debug.TestEventEmitter;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.logging.InstanceId;
@@ -221,6 +223,9 @@
dl.addView(frame);
frame.mIsOpen = true;
frame.post(() -> frame.snapToWidget(false));
+ TestEventEmitter.INSTANCE.get(widget.getContext()).sendEvent(
+ TestEvent.RESIZE_FRAME_SHOWING
+ );
}
private void setCornerRadiusFromWidget() {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 5c052b2..cb897dc 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -166,7 +166,6 @@
import androidx.core.os.BuildCompat;
import androidx.window.embedding.RuleController;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
@@ -181,6 +180,8 @@
import com.android.launcher3.celllayout.CellPosMapper.TwoPanelCellPosMapper;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.debug.TestEvent;
+import com.android.launcher3.debug.TestEventEmitter;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
@@ -595,6 +596,7 @@
RuleController.getInstance(this).setRules(
RuleController.parseRules(this, R.xml.split_configuration));
}
+ TestEventEmitter.INSTANCE.get(this).sendEvent(TestEvent.LAUNCHER_ON_CREATE);
}
protected ModelCallbacks createModelCallbacks() {
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 13062b6..83c34ce 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -11,6 +11,8 @@
import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID
import com.android.launcher3.allapps.AllAppsStore
import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.debug.TestEvent
+import com.android.launcher3.debug.TestEventEmitter
import com.android.launcher3.model.BgDataModel
import com.android.launcher3.model.StringCache
import com.android.launcher3.model.data.AppInfo
@@ -156,6 +158,7 @@
/*pause=*/ false,
deviceProfile.isTwoPanels
)
+ TestEventEmitter.INSTANCE.get(launcher).sendEvent(TestEvent.WORKSPACE_FINISH_LOADING)
}
/**
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index e601a3e..2995e8a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -80,6 +80,8 @@
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.debug.TestEvent;
+import com.android.launcher3.debug.TestEventEmitter;
import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
@@ -314,7 +316,6 @@
*/
public Workspace(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
-
mLauncher = Launcher.getLauncher(context);
mStateTransitionAnimation = new WorkspaceStateTransitionAnimation(mLauncher, this);
mWallpaperManager = WallpaperManager.getInstance(context);
@@ -2218,6 +2219,7 @@
if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
d.stateAnnouncer.completeAction(R.string.item_moved);
}
+ TestEventEmitter.INSTANCE.get(getContext()).sendEvent(TestEvent.WORKSPACE_ON_DROP);
}
@Nullable
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 9623709..89e6adc 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -42,6 +42,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
@@ -260,8 +261,12 @@
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + "\tAllAppsStore Apps[] size: " + mApps.length);
for (int i = 0; i < mApps.length; i++) {
- writer.println(String.format("%s\tPackage index and name: %d/%s", prefix, i,
- mApps[i].componentName.getPackageName()));
+ writer.println(String.format(Locale.getDefault(),
+ "%s\tPackage index, name, and class: " + "%d/%s:%s",
+ prefix,
+ i,
+ mApps[i].componentName.getPackageName(),
+ mApps[i].componentName.getClassName()));
}
}
}
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 98ca420..4b38df8 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
import android.content.Context;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@@ -278,6 +279,13 @@
privateProfileManager.getReadyToAnimate())
&& privateProfileManager.getCurrentState() == STATE_ENABLED
? 0 : 1);
+ Log.d(TAG, "onBindViewHolder: "
+ + "isPrivateSpaceItem: " + isPrivateSpaceItem
+ + " isStateTransitioning: " + privateProfileManager.isStateTransitioning()
+ + " isScrolling: " + privateProfileManager.isScrolling()
+ + " readyToAnimate: " + privateProfileManager.getReadyToAnimate()
+ + " currentState: " + privateProfileManager.getCurrentState()
+ + " currentAlpha: " + icon.getAlpha());
}
// Views can still be bounded before the app list is updated hence showing icons
// after collapsing.
diff --git a/src/com/android/launcher3/anim/AnimatedFloat.java b/src/com/android/launcher3/anim/AnimatedFloat.java
index b414ab6..4441164 100644
--- a/src/com/android/launcher3/anim/AnimatedFloat.java
+++ b/src/com/android/launcher3/anim/AnimatedFloat.java
@@ -20,6 +20,8 @@
import android.animation.ObjectAnimator;
import android.util.FloatProperty;
+import java.util.function.Consumer;
+
/**
* A mutable float which allows animating the value
*/
@@ -38,9 +40,9 @@
}
};
- private static final Runnable NO_OP = () -> { };
+ private static final Consumer<Float> NO_OP = t -> { };
- private final Runnable mUpdateCallback;
+ private final Consumer<Float> mUpdateCallback;
private ObjectAnimator mValueAnimator;
// Only non-null when an animation is playing to this value.
private Float mEndValue;
@@ -52,6 +54,10 @@
}
public AnimatedFloat(Runnable updateCallback) {
+ this(v -> updateCallback.run());
+ }
+
+ public AnimatedFloat(Consumer<Float> updateCallback) {
mUpdateCallback = updateCallback;
}
@@ -60,6 +66,11 @@
value = initialValue;
}
+ public AnimatedFloat(Consumer<Float> updateCallback, float initialValue) {
+ this(updateCallback);
+ value = initialValue;
+ }
+
/**
* Returns an animation from the current value to the given value.
*/
@@ -99,7 +110,7 @@
public void updateValue(float v) {
if (Float.compare(v, value) != 0) {
value = v;
- mUpdateCallback.run();
+ mUpdateCallback.accept(value);
}
}
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index e58890f..47a2bdd 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -59,6 +59,13 @@
add(anim, springProperty);
}
+ /**
+ * Utility method to sent an interpolator on an animation and add it to the list
+ */
+ public void add(Animator anim, TimeInterpolator interpolator) {
+ add(anim, interpolator, SpringProperty.DEFAULT);
+ }
+
@Override
public void add(Animator anim) {
add(anim, SpringProperty.DEFAULT);
diff --git a/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt b/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt
new file mode 100644
index 0000000..650df5a
--- /dev/null
+++ b/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.debug
+
+import android.content.Context
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.SafeCloseable
+
+/** Events fired by the launcher. */
+enum class TestEvent(val event: String) {
+ LAUNCHER_ON_CREATE("LAUNCHER_ON_CREATE"),
+ WORKSPACE_ON_DROP("WORKSPACE_ON_DROP"),
+ RESIZE_FRAME_SHOWING("RESIZE_FRAME_SHOWING"),
+ WORKSPACE_FINISH_LOADING("WORKSPACE_FINISH_LOADING"),
+}
+
+/** Interface to create TestEventEmitters. */
+interface TestEventEmitter : SafeCloseable {
+
+ companion object {
+ @JvmField
+ val INSTANCE =
+ MainThreadInitializedObject<TestEventEmitter> { _: Context? ->
+ TestEventsEmitterProduction()
+ }
+ }
+
+ fun sendEvent(event: TestEvent)
+}
+
+/**
+ * TestEventsEmitterProduction shouldn't do anything since it runs on the launcher code and not on
+ * tests. This is just a placeholder and test should override this class.
+ */
+class TestEventsEmitterProduction : TestEventEmitter {
+
+ override fun close() {}
+
+ override fun sendEvent(event: TestEvent) {}
+}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index bc5a164..c50c008 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -27,6 +27,7 @@
import android.view.View;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.app.animation.Interpolators;
import com.android.launcher3.DragSource;
@@ -69,8 +70,9 @@
*/
protected DragDriver mDragDriver = null;
+ @VisibleForTesting
/** Options controlling the drag behavior. */
- protected DragOptions mOptions;
+ public DragOptions mOptions;
/** Coordinate for motion down event */
protected final Point mMotionDown = new Point();
@@ -79,7 +81,8 @@
protected final Point mTmpPoint = new Point();
- protected DropTarget.DragObject mDragObject;
+ @VisibleForTesting
+ public DropTarget.DragObject mDragObject;
/** Who can receive drop events */
private final ArrayList<DropTarget> mDropTargets = new ArrayList<>();
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
index fbe9e33..bebef70 100644
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
@@ -26,7 +26,7 @@
public class SpringLoadedDragController implements OnAlarmListener {
// how long the user must hover over a mini-screen before it unshrinks
private static final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
- private static final long ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST = 2000;
+ private static final long ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST = 3000;
private static final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
Alarm mAlarm;
diff --git a/src/com/android/launcher3/model/data/TaskItemInfo.kt b/src/com/android/launcher3/model/data/TaskItemInfo.kt
new file mode 100644
index 0000000..fc1cd4d
--- /dev/null
+++ b/src/com/android/launcher3/model/data/TaskItemInfo.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.model.data
+
+/**
+ * Temporary class holding a Task ID to allow us to reference a Task when clicking a hotseat item.
+ *
+ * TODO(b/315344726): Remove this class when we have proper Taskbar support for multi-instance apps
+ */
+class TaskItemInfo(val taskId: Int, itemInfo: WorkspaceItemInfo) : WorkspaceItemInfo(itemInfo)
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
index 9260af9..d84a219 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
@@ -16,7 +16,7 @@
package com.android.launcher3.widget.picker;
-import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering;
+import static com.android.launcher3.widget.util.WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering;
import android.content.ComponentName;
import android.content.Context;
@@ -38,6 +38,7 @@
import com.android.launcher3.pageindicators.PageIndicatorDots;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -57,6 +58,12 @@
private static final String INITIALLY_DISPLAYED_WIDGETS_STATE_KEY =
"widgetRecommendationsView:mDisplayedWidgets";
private static final int MAX_CATEGORIES = 3;
+
+ // Whether to show all widgets in a full page without any limitation on height
+ private boolean mShowFullPageViewIfLowDensity = false;
+ // Number of items below which a category is considered low density.
+ private static final int IDEAL_ITEMS_PER_CATEGORY = 2;
+
private TextView mRecommendationPageTitle;
private final List<String> mCategoryTitles = new ArrayList<>();
@@ -88,6 +95,14 @@
}
/**
+ * When there are less than 3 categories or when at least one category has less than 2 widgets,
+ * all widgets will be shown in a single page without being limited by the available height.
+ */
+ public void enableFullPageViewIfLowDensity() {
+ mShowFullPageViewIfLowDensity = true;
+ }
+
+ /**
* Saves the necessary state in the provided bundle. To be called in case of orientation /
* other config changes.
*/
@@ -170,6 +185,22 @@
return displayedWidgets.size();
}
+ private boolean shouldShowFullPageView(
+ Map<WidgetRecommendationCategory, List<WidgetItem>> recommendations) {
+ if (mShowFullPageViewIfLowDensity) {
+ boolean hasLessCategories = recommendations.size() < MAX_CATEGORIES;
+ long lowDensityCategoriesCount = recommendations.values()
+ .stream()
+ .limit(MAX_CATEGORIES)
+ .filter(items -> items.size() < IDEAL_ITEMS_PER_CATEGORY).count();
+
+ // If there less number of categories or if there are at least 2 categorizes with less
+ // widgets, prefer showing single page view.
+ return hasLessCategories || lowDensityCategoriesCount > 1;
+ }
+ return false;
+ }
+
/**
* Displays the recommendations grouped by categories as pages.
* <p>In case of a single category, no title is displayed for it.</p>
@@ -188,6 +219,14 @@
Map<WidgetRecommendationCategory, List<WidgetItem>> recommendations,
DeviceProfile deviceProfile, final @Px float availableHeight,
final @Px int availableWidth, final @Px int cellPadding, final int requestedPage) {
+ if (shouldShowFullPageView(recommendations)) {
+ // Show all widgets in single page with unlimited available height.
+ return setRecommendations(
+ recommendations.values().stream().flatMap(Collection::stream).toList(),
+ deviceProfile, /*availableHeight=*/ Float.MAX_VALUE, availableWidth,
+ cellPadding);
+
+ }
this.mAvailableHeight = availableHeight;
this.mAvailableWidth = availableWidth;
Context context = getContext();
@@ -325,7 +364,7 @@
// Since we are limited by space, we don't sort recommendations - to show most relevant
// (if possible).
- List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
+ List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithReordering(
filteredRecommendedWidgets,
context,
deviceProfile,
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index fd15677..2e36583 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -679,6 +679,18 @@
return sheet;
}
+ /**
+ * Updates the widget picker's title and description in the header to the provided values (if
+ * present).
+ */
+ public void mayUpdateTitleAndDescription(@Nullable String title,
+ @Nullable String descriptionRes) {
+ if (title != null) {
+ mHeaderTitle.setText(title);
+ }
+ // Full sheet doesn't support a description.
+ }
+
@Override
public void saveHierarchyState(SparseArray<Parcelable> sparseArray) {
Bundle bundle = new Bundle();
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 1ed3d88..0bcab60 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -18,7 +18,7 @@
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx;
-import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_SIZE_COMPARATOR;
+import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_COUNT_COMPARATOR;
import static java.lang.Math.max;
@@ -163,6 +163,6 @@
}
// Perform re-ordering once we have filtered out recommendations that fit.
- return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList();
+ return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_COUNT_COMPARATOR).toList();
}
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 840d98a..c84680d 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -36,6 +36,7 @@
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
+import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -60,9 +61,6 @@
* Popup for showing the full list of available widgets with a two-pane layout.
*/
public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
-
- private static final int PERSONAL_TAB = 0;
- private static final int WORK_TAB = 1;
private static final int MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 268;
private static final int MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 395;
private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry";
@@ -83,6 +81,7 @@
private int mActivePage = -1;
@Nullable
private PackageUserKey mSelectedHeader;
+ private TextView mHeaderDescription;
public WidgetsTwoPaneSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -120,12 +119,17 @@
mWidgetRecommendationsView.initParentViews(mWidgetRecommendationsContainer);
mWidgetRecommendationsView.setWidgetCellLongClickListener(this);
mWidgetRecommendationsView.setWidgetCellOnClickListener(this);
+ if (!mDeviceProfile.isTwoPanels) {
+ mWidgetRecommendationsView.enableFullPageViewIfLowDensity();
+ }
// To save the currently displayed page, so that, it can be requested when rebinding
// recommendations with different size constraints.
mWidgetRecommendationsView.addPageSwitchListener(
newPage -> mRecommendationsCurrentPage = newPage);
mHeaderTitle = mContent.findViewById(R.id.title);
+ mHeaderDescription = mContent.findViewById(R.id.widget_picker_description);
+
mRightPane = mContent.findViewById(R.id.right_pane);
mRightPaneScrollView = mContent.findViewById(R.id.right_pane_scroll_view);
mRightPaneScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
@@ -141,6 +145,17 @@
}
@Override
+ public void mayUpdateTitleAndDescription(@Nullable String title, @Nullable String description) {
+ if (title != null) {
+ mHeaderTitle.setText(title);
+ }
+ if (description != null) {
+ mHeaderDescription.setText(description);
+ mHeaderDescription.setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
protected int getTabletHorizontalMargin(DeviceProfile deviceProfile) {
if (enableCategorizedWidgetSuggestions()) {
// two pane picker is full width for fold as well as tablet.
@@ -371,9 +386,10 @@
protected void updateRecyclerViewVisibility(AdapterHolder adapterHolder) {
// The first item is always an empty space entry. Look for any more items.
boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries();
-
- mRightPane.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
-
+ if (!isWidgetAvailable) {
+ mRightPane.removeAllViews();
+ mRightPane.addView(mNoWidgetsView);
+ }
super.updateRecyclerViewVisibility(adapterHolder);
}
diff --git a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
index edaf474..df72f07 100644
--- a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
+++ b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
@@ -69,6 +69,21 @@
});
/**
+ * Comparator that enables displaying rows with more number of items at the top, and then
+ * rest of widgets shown in increasing order of their size (totalW * H).
+ */
+ public static final Comparator<ArrayList<WidgetItem>> WIDGETS_TABLE_ROW_COUNT_COMPARATOR =
+ Comparator.comparingInt(row -> {
+ if (row.size() > 1) {
+ return -row.size();
+ } else {
+ int rowWidth = row.stream().mapToInt(w -> w.spanX).sum();
+ int rowHeight = row.get(0).spanY;
+ return (rowWidth * rowHeight);
+ }
+ });
+
+ /**
* Groups {@code widgetItems} items into a 2D array which matches their appearance in a UI
* table. This takes liberty to rearrange widgets to make the table visually appealing.
*/
diff --git a/tests/assets/ReorderWidgets/full_reorder_case b/tests/assets/ReorderWidgets/full_reorder_case
index 850e4fd..2890b79 100644
--- a/tests/assets/ReorderWidgets/full_reorder_case
+++ b/tests/assets/ReorderWidgets/full_reorder_case
@@ -17,12 +17,12 @@
# Test 4x4
board: 4x4
xxxx
-bbmm
+bbaa
iimm
-iiaa
+iimm
arguments: 0 2
board: 4x4
xxxx
-bbii
+bbaa
mmii
-mmaa
\ No newline at end of file
+mmii
\ No newline at end of file
diff --git a/tests/assets/ReorderWidgets/push_reorder_case b/tests/assets/ReorderWidgets/push_reorder_case
index 8e845a2..1eacfae 100644
--- a/tests/assets/ReorderWidgets/push_reorder_case
+++ b/tests/assets/ReorderWidgets/push_reorder_case
@@ -17,28 +17,28 @@
#Test 5x5
board: 5x5
xxxxx
-bbbm-
+bbb--
--ccc
--ddd
------
-arguments: 2 1
+----m
+arguments: 2 2
board: 5x5
xxxxx
---m--
bbb--
+--m--
--ccc
--ddd
#6x5 Test
board: 6x5
xxxxxx
-bbbbm-
+bbbb--
--aaa-
--ddd-
-------
-arguments: 2 1
+-----m
+arguments: 2 2
board: 6x5
xxxxxx
---m---
bbbb--
+--m---
--aaa-
--ddd-
\ No newline at end of file
diff --git a/tests/assets/ReorderWidgets/simple_reorder_case b/tests/assets/ReorderWidgets/simple_reorder_case
index 2c50ce4..991ccb5 100644
--- a/tests/assets/ReorderWidgets/simple_reorder_case
+++ b/tests/assets/ReorderWidgets/simple_reorder_case
@@ -21,7 +21,7 @@
--mm-
-----
-----
-arguments: 0 4
+arguments: 0 3
board: 5x5
xxxxx
-----
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
index 419cb3d..f1403e5 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestCaseReader.java
@@ -54,7 +54,7 @@
}
public static class Arguments extends TestSection {
- String[] arguments;
+ public String[] arguments;
public Arguments(String[] arguments) {
super(State.ARGUMENTS);
diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
index 57117cb..430e496 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
+++ b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
@@ -22,8 +22,6 @@
import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -202,7 +200,6 @@
}
@Test
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/339109319
public void openPrivateSpaceSettings_triggersCorrectIntent() {
Intent expectedIntent = ApiWrapper.INSTANCE.get(mContext).getPrivateSpaceSettingsIntent();
ArgumentCaptor<Intent> acIntent = ArgumentCaptor.forClass(Intent.class);
diff --git a/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java b/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java
deleted file mode 100644
index 28a1325..0000000
--- a/tests/src/com/android/launcher3/celllayout/TaplReorderWidgetsTest.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.celllayout;
-
-import static android.platform.uiautomator_helpers.DeviceHelpers.getContext;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.graphics.Point;
-import android.net.Uri;
-import android.util.Log;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.MultipageCellLayout;
-import com.android.launcher3.celllayout.board.CellLayoutBoard;
-import com.android.launcher3.celllayout.board.TestWorkspaceBuilder;
-import com.android.launcher3.celllayout.board.WidgetRect;
-import com.android.launcher3.tapl.Widget;
-import com.android.launcher3.tapl.WidgetResizeFrame;
-import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.util.ModelTestExtensions;
-import com.android.launcher3.util.rule.ShellCommandRule;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TaplReorderWidgetsTest extends AbstractLauncherUiTest<Launcher> {
-
- @Rule
- public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
-
- private static final String TAG = TaplReorderWidgetsTest.class.getSimpleName();
-
- private static final List<String> FOLDABLE_GRIDS = List.of("normal", "practical", "reasonable");
-
- TestWorkspaceBuilder mWorkspaceBuilder;
-
- @Before
- public void setup() throws Throwable {
- mWorkspaceBuilder = new TestWorkspaceBuilder(mTargetContext);
- super.setUp();
- }
-
- @After
- public void tearDown() {
- ModelTestExtensions.INSTANCE.clearModelDb(
- LauncherAppState.getInstance(getContext()).getModel()
- );
- }
-
- /**
- * Validate if the given board represent the current CellLayout
- **/
- private boolean validateBoard(List<CellLayoutBoard> testBoards) {
- ArrayList<CellLayoutBoard> workspaceBoards = workspaceToBoards();
- if (workspaceBoards.size() < testBoards.size()) {
- return false;
- }
- for (int i = 0; i < testBoards.size(); i++) {
- if (testBoards.get(i).compareTo(workspaceBoards.get(i)) != 0) {
- return false;
- }
- }
- return true;
- }
-
- private FavoriteItemsTransaction buildWorkspaceFromBoards(List<CellLayoutBoard> boards,
- FavoriteItemsTransaction transaction) {
- for (int i = 0; i < boards.size(); i++) {
- CellLayoutBoard board = boards.get(i);
- mWorkspaceBuilder.buildFromBoard(board, transaction, i);
- }
- return transaction;
- }
-
- private void printCurrentWorkspace() {
- InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
- ArrayList<CellLayoutBoard> boards = workspaceToBoards();
- for (int i = 0; i < boards.size(); i++) {
- Log.d(TAG, "Screen number " + i);
- Log.d(TAG, ".\n" + boards.get(i).toString(idp.numColumns, idp.numRows));
- }
- }
-
- private ArrayList<CellLayoutBoard> workspaceToBoards() {
- return getFromLauncher(CellLayoutTestUtils::workspaceToBoards);
- }
-
- private WidgetRect getWidgetClosestTo(Point point) {
- ArrayList<CellLayoutBoard> workspaceBoards = workspaceToBoards();
- int maxDistance = 9999;
- WidgetRect bestRect = null;
- for (int i = 0; i < workspaceBoards.get(0).getWidgets().size(); i++) {
- WidgetRect widget = workspaceBoards.get(0).getWidgets().get(i);
- if (widget.getCellX() == 0 && widget.getCellY() == 0) {
- continue;
- }
- int distance = Math.abs(point.x - widget.getCellX())
- + Math.abs(point.y - widget.getCellY());
- if (distance == 0) {
- break;
- }
- if (distance < maxDistance) {
- maxDistance = distance;
- bestRect = widget;
- }
- }
- return bestRect;
- }
-
- /**
- * This function might be odd, its function is to select a widget and leave it in its place.
- * The idea is to make the test broader and also test after a widgets resized because the
- * underlying code does different things in that case
- */
- private void triggerWidgetResize(ReorderTestCase testCase) {
- WidgetRect widgetRect = getWidgetClosestTo(testCase.moveMainTo);
- if (widgetRect == null) {
- // Some test doesn't have a widget in the final position, in those cases we will ignore
- // them
- return;
- }
- Widget widget = mLauncher.getWorkspace().getWidgetAtCell(widgetRect.getCellX(),
- widgetRect.getCellY());
- WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(widgetRect.getCellX(),
- widgetRect.getCellY(), widgetRect.getSpanX(), widgetRect.getSpanY());
- resizeFrame.dismiss();
- }
-
- private void runTestCase(ReorderTestCase testCase) {
- WidgetRect mainWidgetCellPos = CellLayoutBoard.getMainFromList(
- testCase.mStart);
-
- FavoriteItemsTransaction transaction =
- new FavoriteItemsTransaction(mTargetContext);
- transaction = buildWorkspaceFromBoards(testCase.mStart, transaction);
- transaction.commit();
- mLauncher.waitForLauncherInitialized();
- // resetLoaderState triggers the launcher to start loading the workspace which allows
- // waitForLauncherCondition to wait for that condition, otherwise the condition would
- // always be true and it wouldn't wait for the changes to be applied.
- waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
-
- triggerWidgetResize(testCase);
-
- Widget widget = mLauncher.getWorkspace().getWidgetAtCell(mainWidgetCellPos.getCellX(),
- mainWidgetCellPos.getCellY());
- assertNotNull(widget);
- WidgetResizeFrame resizeFrame = widget.dragWidgetToWorkspace(testCase.moveMainTo.x,
- testCase.moveMainTo.y, mainWidgetCellPos.getSpanX(), mainWidgetCellPos.getSpanY());
- resizeFrame.dismiss();
-
- boolean isValid = false;
- for (List<CellLayoutBoard> boards : testCase.mEnd) {
- isValid |= validateBoard(boards);
- if (isValid) break;
- }
- printCurrentWorkspace();
- assertTrue("Non of the valid boards match with the current state", isValid);
- }
-
- /**
- * Run only the test define for the current grid size if such test exist
- *
- * @param testCaseMap map containing all the tests per grid size (Point)
- */
- private boolean runTestCaseMap(Map<Point, ReorderTestCase> testCaseMap, String testName) {
- Point iconGridDimensions = mLauncher.getWorkspace().getIconGridDimensions();
- Log.d(TAG, "Running test " + testName + " for grid " + iconGridDimensions);
- if (!testCaseMap.containsKey(iconGridDimensions)) {
- Log.d(TAG, "The test " + testName + " doesn't support " + iconGridDimensions
- + " grid layout");
- return false;
- }
- runTestCase(testCaseMap.get(iconGridDimensions));
-
- return true;
- }
-
- private void runTestCaseMapForAllGrids(Map<Point, ReorderTestCase> testCaseMap,
- String testName) {
- boolean runAtLeastOnce = false;
- for (String grid : FOLDABLE_GRIDS) {
- applyGridOption(grid);
- mLauncher.waitForLauncherInitialized();
- runAtLeastOnce |= runTestCaseMap(testCaseMap, testName);
- }
- Assume.assumeTrue("None of the grids are supported", runAtLeastOnce);
- }
-
- private void applyGridOption(Object argValue) {
- String testProviderAuthority = mTargetContext.getPackageName() + ".grid_control";
- Uri gridUri = new Uri.Builder()
- .scheme(ContentResolver.SCHEME_CONTENT)
- .authority(testProviderAuthority)
- .appendPath("default_grid")
- .build();
- ContentValues values = new ContentValues();
- values.putObject("name", argValue);
- Assert.assertEquals(1,
- mTargetContext.getContentResolver().update(gridUri, values, null, null));
- }
-
- @Test
- public void simpleReorder() throws Exception {
- runTestCaseMap(getTestMap("ReorderWidgets/simple_reorder_case"),
- "push_reorder_case");
- }
-
- @Test
- public void pushTest() throws Exception {
- runTestCaseMap(getTestMap("ReorderWidgets/push_reorder_case"),
- "push_reorder_case");
- }
-
- @Test
- public void fullReorder() throws Exception {
- runTestCaseMap(getTestMap("ReorderWidgets/full_reorder_case"),
- "full_reorder_case");
- }
-
- @Test
- public void moveOutReorder() throws Exception {
- runTestCaseMap(getTestMap("ReorderWidgets/move_out_reorder_case"),
- "move_out_reorder_case");
- }
-
- @Test
- public void multipleCellLayoutsSimpleReorder() throws Exception {
- Assume.assumeTrue("Test doesn't support foldables", getFromLauncher(
- l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout));
- runTestCaseMapForAllGrids(getTestMap("ReorderWidgets/multiple_cell_layouts_simple_reorder"),
- "multiple_cell_layouts_simple_reorder");
- }
-
- @Test
- public void multipleCellLayoutsNoSpaceReorder() throws Exception {
- Assume.assumeTrue("Test doesn't support foldables", getFromLauncher(
- l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout));
- runTestCaseMapForAllGrids(
- getTestMap("ReorderWidgets/multiple_cell_layouts_no_space_reorder"),
- "multiple_cell_layouts_no_space_reorder");
- }
-
- @Test
- public void multipleCellLayoutsReorderToOtherSide() throws Exception {
- Assume.assumeTrue("Test doesn't support foldables", getFromLauncher(
- l -> l.getWorkspace().getScreenWithId(0) instanceof MultipageCellLayout));
- runTestCaseMapForAllGrids(
- getTestMap("ReorderWidgets/multiple_cell_layouts_reorder_other_side"),
- "multiple_cell_layouts_reorder_other_side");
- }
-
- private void addTestCase(Iterator<CellLayoutTestCaseReader.TestSection> sections,
- Map<Point, ReorderTestCase> testCaseMap) {
- CellLayoutTestCaseReader.Board startBoard =
- ((CellLayoutTestCaseReader.Board) sections.next());
- CellLayoutTestCaseReader.Arguments point =
- ((CellLayoutTestCaseReader.Arguments) sections.next());
- CellLayoutTestCaseReader.Board endBoard =
- ((CellLayoutTestCaseReader.Board) sections.next());
- Point moveTo = new Point(Integer.parseInt(point.arguments[0]),
- Integer.parseInt(point.arguments[1]));
- testCaseMap.put(endBoard.gridSize,
- new ReorderTestCase(startBoard.board, moveTo, endBoard.board));
- }
-
- private Map<Point, ReorderTestCase> getTestMap(String testPath) throws IOException {
- Map<Point, ReorderTestCase> testCaseMap = new HashMap<>();
- Iterator<CellLayoutTestCaseReader.TestSection> iterableSection =
- CellLayoutTestCaseReader.readFromFile(testPath).parse().iterator();
- while (iterableSection.hasNext()) {
- addTestCase(iterableSection, testCaseMap);
- }
- return testCaseMap;
- }
-}
diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt
new file mode 100644
index 0000000..4cecb5a
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/integrationtest/TestUtils.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.celllayout.integrationtest
+
+import android.graphics.Point
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import com.android.launcher3.CellLayout
+import com.android.launcher3.Workspace
+import com.android.launcher3.util.CellAndSpan
+import com.android.launcher3.widget.LauncherAppWidgetHostView
+
+object TestUtils {
+ fun <T> searchChildren(viewGroup: ViewGroup, type: Class<T>): T? where T : View {
+ for (i in 0..<viewGroup.childCount) {
+ val child = viewGroup.getChildAt(i)
+ if (type.isInstance(child)) {
+ return type.cast(child)
+ }
+ if (child is ViewGroup) {
+ val result = searchChildren(child, type)
+ if (result != null) {
+ return result
+ }
+ }
+ }
+ return null
+ }
+
+ fun getWidgetAtCell(
+ workspace: Workspace<*>,
+ cellX: Int,
+ cellY: Int
+ ): LauncherAppWidgetHostView {
+ val view =
+ (workspace.getPageAt(workspace.currentPage) as CellLayout).getChildAt(cellX, cellY)
+ assert(view != null) { "There is no view at $cellX , $cellY" }
+ assert(view is LauncherAppWidgetHostView) { "The view at $cellX , $cellY is not a widget" }
+ return view as LauncherAppWidgetHostView
+ }
+
+ fun getCellTopLeftRelativeToCellLayout(
+ workspace: Workspace<*>,
+ cellAndSpan: CellAndSpan
+ ): Point {
+ val target = Rect()
+ val cellLayout = workspace.getPageAt(workspace.currentPage) as CellLayout
+ cellLayout.cellToRect(
+ cellAndSpan.cellX,
+ cellAndSpan.cellY,
+ cellAndSpan.spanX,
+ cellAndSpan.spanY,
+ target
+ )
+ return Point(target.left, target.top)
+ }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt
new file mode 100644
index 0000000..fb61ced
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.celllayout.integrationtest.events
+
+import android.content.Context
+import com.android.launcher3.debug.TestEvent
+import com.android.launcher3.debug.TestEventEmitter
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Rule to create EventWaiters to wait for events that happens on the Launcher. For reference look
+ * at [TestEvent] for existing events.
+ *
+ * Waiting for event should be used to prevent race conditions, it provides a more precise way of
+ * waiting for events compared to [AbstractLauncherUiTest#waitForLauncherCondition].
+ *
+ * This class overrides the [TestEventEmitter] with [TestEventsEmitterImplementation] and makes sure
+ * to return the [TestEventEmitter] to the previous value when finished.
+ */
+class EventsRule(val context: Context) : TestRule {
+
+ private var prevEventEmitter: TestEventEmitter? = null
+
+ private val eventEmitter = TestEventsEmitterImplementation()
+
+ override fun apply(base: Statement, description: Description?): Statement {
+ return object : Statement() {
+ override fun evaluate() {
+ beforeTest()
+ base.evaluate()
+ afterTest()
+ }
+ }
+ }
+
+ fun createEventWaiter(expectedEvent: TestEvent): EventWaiter {
+ return eventEmitter.createEventWaiter(expectedEvent)
+ }
+
+ private fun beforeTest() {
+ prevEventEmitter = TestEventEmitter.INSTANCE.get(context)
+ TestEventEmitter.INSTANCE.initializeForTesting(eventEmitter)
+ }
+
+ private fun afterTest() {
+ TestEventEmitter.INSTANCE.initializeForTesting(prevEventEmitter)
+ }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt
new file mode 100644
index 0000000..365ad4b
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.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.celllayout.integrationtest.events
+
+import android.util.Log
+import com.android.launcher3.debug.TestEvent
+import com.android.launcher3.debug.TestEventEmitter
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeoutOrNull
+
+enum class EventStatus() {
+ SUCCESS,
+ FAILURE,
+ TIMEOUT,
+}
+
+class EventWaiter(val eventToWait: TestEvent) {
+ private val deferrable = CompletableDeferred<EventStatus>()
+
+ companion object {
+ private const val TAG = "EventWaiter"
+ }
+
+ fun waitForSignal(timeout: Long = TimeUnit.SECONDS.toMillis(10)) = runBlocking {
+ var status = withTimeoutOrNull(timeout) { deferrable.await() }
+ if (status == null) {
+ status = EventStatus.TIMEOUT
+ }
+ if (status != EventStatus.SUCCESS) {
+ throw Exception("Failure waiting for event $eventToWait, failure = $status")
+ }
+ }
+
+ fun terminate() {
+ deferrable.complete(EventStatus.SUCCESS)
+ }
+}
+
+class TestEventsEmitterImplementation() : TestEventEmitter {
+ companion object {
+ private const val TAG = "TestEvents"
+ }
+
+ private val expectedEvents: ArrayDeque<EventWaiter> = ArrayDeque()
+
+ fun createEventWaiter(expectedEvent: TestEvent): EventWaiter {
+ val eventWaiter = EventWaiter(expectedEvent)
+ expectedEvents.add(eventWaiter)
+ return eventWaiter
+ }
+
+ private fun clearQueue() {
+ expectedEvents.clear()
+ }
+
+ override fun sendEvent(event: TestEvent) {
+ Log.d(TAG, "Signal received $event")
+ Log.d(TAG, "Total expected events ${expectedEvents.size}")
+ if (expectedEvents.isEmpty()) return
+ val eventWaiter = expectedEvents.last()
+ if (eventWaiter.eventToWait == event) {
+ Log.d(TAG, "Removing $event")
+ expectedEvents.removeLast()
+ eventWaiter.terminate()
+ } else {
+ Log.d(TAG, "Not matching $event")
+ }
+ }
+
+ override fun close() {
+ clearQueue()
+ }
+}
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
index 5dee322..a148744 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
@@ -40,6 +40,7 @@
import com.android.launcher3.tapl.HomeAppIconMenuItem;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.rule.ScreenRecordRule;
import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.Test;
@@ -115,6 +116,7 @@
@Test
@TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/350557998
+ @ScreenRecordRule.ScreenRecord // b/350557998
public void testShortcutIconWithTheme() throws Exception {
setThemeEnabled(true);
initialize(this);
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
index bc26c00..9e4299e 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
@@ -113,8 +113,6 @@
@Test
@PortraitLandscape
- @ScreenRecordRule.ScreenRecord // b/329935119
- @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/329935119
public void testSinglePageDragIconWhenMultiplePageScrollingIsPossible() {
Workspace workspace = mLauncher.getWorkspace();
@@ -352,11 +350,8 @@
}
private void assertPagesExist(Launcher launcher, int... pageIds) {
- waitForLauncherCondition("Existing page count does NOT match. "
- + "Expected: " + pageIds.length
- + ". Actual: " + launcher.getWorkspace().getPageCount(),
- l -> pageIds.length == l.getWorkspace().getPageCount());
int pageCount = launcher.getWorkspace().getPageCount();
+ assertEquals("Existing page count does NOT match.", pageIds.length, pageCount);
for (int i = 0; i < pageCount; i++) {
CellLayout page = (CellLayout) launcher.getWorkspace().getPageAt(i);
int pageId = launcher.getWorkspace().getCellLayoutId(page);