Merge "Add developer option to launch gesture nav tutorial" into main
diff --git a/Android.bp b/Android.bp
index 1e1e0ad..a4a058f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -455,6 +455,9 @@
extra_check_modules: ["Launcher3LintChecker"],
baseline_filename: "lint-baseline.xml",
},
+ kotlincflags: [
+ "-Xjvm-default=all",
+ ],
}
// Library with all the dependencies for building quickstep
@@ -519,6 +522,9 @@
min_sdk_version: "current",
// TODO(b/319712088): re-enable use_resource_processor
use_resource_processor: false,
+ kotlincflags: [
+ "-Xjvm-default=all",
+ ],
}
// Library with all the source code and dependencies for building Quickstep
@@ -552,6 +558,9 @@
min_sdk_version: "current",
// TODO(b/319712088): re-enable use_resource_processor
use_resource_processor: false,
+ kotlincflags: [
+ "-Xjvm-default=all",
+ ],
}
// Build rule for Quickstep app.
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 80d2eac..fe57da1 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -136,7 +136,7 @@
TODO: Add proper permissions
-->
<provider
- android:name="com.android.launcher3.graphics.GridCustomizationsProvider"
+ android:name="com.android.launcher3.graphics.LauncherCustomizationProvider"
android:authorities="${applicationId}.grid_control"
android:exported="true" />
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index d4cea8d..4f5b1a0 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -622,3 +622,10 @@
description: "Enables custom theme manager in Launcher"
bug: "381897614"
}
+
+flag {
+ name: "enable_alt_tab_kqs_flatenning"
+ namespace: "lse_desktop_experience"
+ description: "Enable Alt + Tab KQS view to show apps in flattened structure"
+ bug: "382769617"
+}
diff --git a/aconfig/launcher_overview.aconfig b/aconfig/launcher_overview.aconfig
index f379e22..5749c51 100644
--- a/aconfig/launcher_overview.aconfig
+++ b/aconfig/launcher_overview.aconfig
@@ -96,3 +96,17 @@
description: "Enable overview on connected displays."
bug: "363251602"
}
+
+flag {
+ name: "enable_overview_background_wallpaper_blur"
+ namespace: "launcher_overview"
+ description: "Enable wallpaper blur in overview."
+ bug: "369975912"
+}
+
+flag {
+ name: "enable_overview_desktop_tile_wallpaper_background"
+ namespace: "launcher_overview"
+ description: "Enable wallpaper background for desktop tasks in overview."
+ bug: "363257721"
+}
diff --git a/quickstep/res/layout/icon_app_chip_view.xml b/quickstep/res/layout/icon_app_chip_view.xml
index 00b5392..0972be1 100644
--- a/quickstep/res/layout/icon_app_chip_view.xml
+++ b/quickstep/res/layout/icon_app_chip_view.xml
@@ -51,7 +51,7 @@
<TextView
android:id="@+id/icon_text_collapsed"
android:layout_width="@dimen/task_thumbnail_icon_menu_text_collapsed_max_width"
- android:layout_height="@dimen/task_thumbnail_icon_menu_app_icon_collapsed_size"
+ android:layout_height="wrap_content"
android:gravity="start|center_vertical"
android:maxLines="1"
android:ellipsize="end"
@@ -62,7 +62,7 @@
<TextView
android:id="@+id/icon_text_expanded"
android:layout_width="@dimen/task_thumbnail_icon_menu_text_expanded_max_width"
- android:layout_height="@dimen/task_thumbnail_icon_menu_app_icon_collapsed_size"
+ android:layout_height="wrap_content"
android:gravity="start|center_vertical"
android:maxLines="1"
android:ellipsize="end"
diff --git a/quickstep/res/layout/taskbar_edu_features.xml b/quickstep/res/layout/taskbar_edu_features.xml
index a7bd184..aa1312e 100644
--- a/quickstep/res/layout/taskbar_edu_features.xml
+++ b/quickstep/res/layout/taskbar_edu_features.xml
@@ -20,7 +20,7 @@
android:layout_height="wrap_content">
<TextView
- android:id="@+id/title"
+ android:id="@+id/taskbar_edu_title"
style="@style/TextAppearance.TaskbarEduTooltip.Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -42,7 +42,7 @@
app:layout_constraintEnd_toEndOf="@id/splitscreen_text"
app:layout_constraintStart_toStartOf="@id/splitscreen_text"
- app:layout_constraintTop_toBottomOf="@id/title" />
+ app:layout_constraintTop_toBottomOf="@id/taskbar_edu_title" />
<TextView
android:id="@+id/splitscreen_text"
@@ -72,7 +72,7 @@
app:layout_constraintEnd_toEndOf="@id/pinning_text"
app:layout_constraintStart_toStartOf="@id/pinning_text"
- app:layout_constraintTop_toBottomOf="@id/title" />
+ app:layout_constraintTop_toBottomOf="@id/taskbar_edu_title" />
<TextView
android:id="@+id/pinning_text"
@@ -96,7 +96,7 @@
app:layout_constraintEnd_toEndOf="@id/suggestions_text"
app:layout_constraintStart_toStartOf="@id/suggestions_text"
- app:layout_constraintTop_toBottomOf="@id/title" />
+ app:layout_constraintTop_toBottomOf="@id/taskbar_edu_title" />
<TextView
android:id="@+id/suggestions_text"
diff --git a/quickstep/res/layout/taskbar_edu_pinning.xml b/quickstep/res/layout/taskbar_edu_pinning.xml
index 27a7b23..5937d62 100644
--- a/quickstep/res/layout/taskbar_edu_pinning.xml
+++ b/quickstep/res/layout/taskbar_edu_pinning.xml
@@ -19,7 +19,7 @@
android:layout_height="wrap_content">
<TextView
- android:id="@+id/title"
+ android:id="@+id/taskbar_edu_title"
style="@style/TextAppearance.TaskbarEduTooltip.Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -37,7 +37,7 @@
app:layout_constraintBottom_toTopOf="@id/pinning_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/title"
+ app:layout_constraintTop_toBottomOf="@id/taskbar_edu_title"
app:lottie_rawRes="@raw/taskbar_edu_pinning"
app:lottie_autoPlay="true"
app:lottie_loop="true" />
diff --git a/quickstep/res/layout/taskbar_edu_search.xml b/quickstep/res/layout/taskbar_edu_search.xml
index ca84f35..ec4d4b4 100644
--- a/quickstep/res/layout/taskbar_edu_search.xml
+++ b/quickstep/res/layout/taskbar_edu_search.xml
@@ -19,7 +19,7 @@
android:layout_height="wrap_content">
<TextView
- android:id="@+id/title"
+ android:id="@+id/taskbar_edu_title"
style="@style/TextAppearance.TaskbarEduTooltip.Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -37,7 +37,7 @@
app:layout_constraintBottom_toTopOf="@id/search_edu_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/title"
+ app:layout_constraintTop_toBottomOf="@id/taskbar_edu_title"
app:lottie_rawRes="@raw/taskbar_edu_search"
app:lottie_autoPlay="true"
app:lottie_loop="true" />
diff --git a/quickstep/res/layout/taskbar_edu_swipe.xml b/quickstep/res/layout/taskbar_edu_swipe.xml
index 3f5e819..9b4809e 100644
--- a/quickstep/res/layout/taskbar_edu_swipe.xml
+++ b/quickstep/res/layout/taskbar_edu_swipe.xml
@@ -19,7 +19,7 @@
android:layout_height="wrap_content">
<TextView
- android:id="@+id/title"
+ android:id="@+id/taskbar_edu_title"
style="@style/TextAppearance.TaskbarEduTooltip.Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -36,7 +36,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/title"
+ app:layout_constraintTop_toBottomOf="@id/taskbar_edu_title"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="@raw/taskbar_edu_stashing" />
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index e937410..7986a55 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -118,7 +118,7 @@
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Taskleiste immer anzeigen"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Damit die Taskleiste immer unten angezeigt wird, halte den Teiler gedrückt"</string>
<string name="taskbar_search_edu_title" msgid="5569194922234364530">"Aktionstaste gedrückt halten, um auf dem Bildschirm zu suchen"</string>
- <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Dieses Produkt verwendet den ausgewählten Teil deines Bildschirms für die Suche. Es gelten die <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Datenschutzerklärung<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> und die <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Nutzungsbedingungen<xliff:g id="END_TOS_LINK"></a></xliff:g> von Google."</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Dieses Produkt sucht dann anhand des ausgewählten Teils deines Displays nach weiteren Informationen. Es gelten die <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Datenschutzerklärung<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> und die <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Nutzungsbedingungen<xliff:g id="END_TOS_LINK"></a></xliff:g> von Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Schließen"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Fertig"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Startbildschirm"</string>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index 5ba3212..6cd544c 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -118,7 +118,7 @@
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Mostrar siempre la Barra de tareas"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Mantén presionado el divisor para mostrar siempre la Barra de tareas en la parte inferior de la pantalla"</string>
<string name="taskbar_search_edu_title" msgid="5569194922234364530">"Mantén presionada la tecla de acción para buscar qué hay en la pantalla"</string>
- <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Este producto usa la parte seleccionada de la pantalla para buscar. Se aplican la <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Política de Privacidad<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> y las <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Condiciones del Servicio<xliff:g id="END_TOS_LINK"></a></xliff:g> de Google."</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Este producto usa la parte seleccionada de la pantalla para hacer búsquedas. Se aplican la <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Política de Privacidad<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> y las <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Condiciones del Servicio<xliff:g id="END_TOS_LINK"></a></xliff:g> de Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Cerrar"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Listo"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Botón de inicio"</string>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index edd55d5..df665ed 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -96,7 +96,7 @@
<string name="action_share" msgid="2648470652637092375">"Compartir"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Facer captura"</string>
<string name="action_split" msgid="2098009717623550676">"Dividir"</string>
- <string name="action_save_app_pair" msgid="5974823919237645229">"Gardar parella apps"</string>
+ <string name="action_save_app_pair" msgid="5974823919237645229">"Gardar parella de apps"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Para usar a pantalla dividida, toca outra app"</string>
<string name="toast_contextual_split_select_app" msgid="433510957123687090">"Escolle outra aplicación para usar a pantalla dividida."</string>
<string name="toast_split_select_app_cancel" msgid="1939025102486630426">"Cancelar"</string>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index ede50cc..fad3164 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -118,7 +118,7 @@
<string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Sempre mostrar a Barra de tarefas"</string>
<string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Toque e pressione o divisor para sempre mostrar a Barra de tarefas na parte de baixo da tela"</string>
<string name="taskbar_search_edu_title" msgid="5569194922234364530">"Toque na tecla de ação e pressione para pesquisar o que está na tela"</string>
- <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"O produto usa a parte selecionada da tela para pesquisar. O uso desses dados está sujeito à <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Política de Privacidade<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> e aos <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Termos de Serviço<xliff:g id="END_TOS_LINK"></a></xliff:g> do Google."</string>
+ <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Este produto usa a parte selecionada da tela para pesquisar. O uso desses dados está sujeito à <xliff:g id="BEGIN_PRIVACY_LINK"><a href="%1$s"></xliff:g>Política de Privacidade<xliff:g id="END_PRIVACY_LINK"></a></xliff:g> e aos <xliff:g id="BEGIN_TOS_LINK"><a href="%2$s"></xliff:g>Termos de Serviço<xliff:g id="END_TOS_LINK"></a></xliff:g> do Google."</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"Fechar"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"Concluído"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"Início"</string>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 5f2a63d..f8ca8d9 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -321,6 +321,7 @@
<item name="android:fontFamily">google-sans-text-medium</item>
<item name="android:textSize">@dimen/task_thumbnail_icon_menu_text_size</item>
<item name="android:textColor">@color/materialColorOnSurface</item>
+ <item name="android:includeFontPadding">false</item>
<item name="android:letterSpacing">0.025</item>
<item name="android:lineHeight">20sp</item>
</style>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index c748385..67feb6a 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -226,7 +226,6 @@
private static final int TASKBAR_TO_HOME_DURATION_FAST = 300;
private static final int TASKBAR_TO_HOME_DURATION_SLOW = 1000;
protected static final int CONTENT_SCALE_DURATION = 350;
- protected static final int CONTENT_SCRIM_DURATION = 350;
private static final int MAX_NUM_TASKS = 5;
@@ -244,7 +243,13 @@
private final StartingWindowListener mStartingWindowListener =
new StartingWindowListener(this);
- private ContentObserver mAnimationRemovalObserver = new ContentObserver(
+
+ // TODO(b/397690719): Investigate the memory leak from TaskStackChangeListeners#mImpl
+ // This is a temporary fix of memory leak b/397690719. We track registered
+ // {@link TaskRestartedDuringLaunchListener}, and remove them on activity destroy.
+ private final List<TaskRestartedDuringLaunchListener> mRegisteredTaskStackChangeListener =
+ new ArrayList<>();
+ private final ContentObserver mAnimationRemovalObserver = new ContentObserver(
ORDERED_BG_EXECUTOR.getHandler()) {
@Override
public void onChange(boolean selfChange) {
@@ -338,7 +343,14 @@
TaskRestartedDuringLaunchListener restartedListener =
new TaskRestartedDuringLaunchListener();
restartedListener.register(onEndCallback::executeAllAndDestroy);
- onEndCallback.add(restartedListener::unregister);
+ mRegisteredTaskStackChangeListener.add(restartedListener);
+ onEndCallback.add(new Runnable() {
+ @Override
+ public void run() {
+ restartedListener.unregister();
+ mRegisteredTaskStackChangeListener.remove(restartedListener);
+ }
+ });
RemoteAnimationRunnerCompat runner = createAppLaunchRunner(v, onEndCallback);
@@ -1210,6 +1222,12 @@
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
ORDERED_BG_EXECUTOR.execute(() -> mLauncher.getContentResolver()
.unregisterContentObserver(mAnimationRemovalObserver));
+ if (BuildConfig.IS_STUDIO_BUILD && !mRegisteredTaskStackChangeListener.isEmpty()) {
+ throw new IllegalStateException("Failed to run onEndCallback created from"
+ + " getActivityLaunchOptions()");
+ }
+ mRegisteredTaskStackChangeListener.forEach(TaskRestartedDuringLaunchListener::unregister);
+ mRegisteredTaskStackChangeListener.clear();
}
/**
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 1cf7dda..f992913 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -322,13 +322,14 @@
stringCache.loadStrings(this);
bindStringCache(stringCache);
- bindWidgets(mModel.getWidgetsByPackageItem(), mModel.getDefaultWidgetsFilter());
+ bindWidgets(mModel.getWidgetsByPackageItemForPicker(),
+ mModel.getDefaultWidgetsFilter());
// Open sheet once widgets are available, so that it doesn't interrupt the open
// animation.
openWidgetsSheet();
if (mUiSurface != null) {
mWidgetPredictionsRequester = new WidgetPredictionsRequester(app.getContext(),
- mUiSurface, mModel.getWidgetsByComponentKey());
+ mUiSurface, mModel.getWidgetsByComponentKeyForPicker());
mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
}
});
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
new file mode 100644
index 0000000..688018b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2025 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.desktop
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Rect
+import android.util.Log
+import android.view.Choreographer
+import android.view.SurfaceControl.Transaction
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_BACK
+import android.window.DesktopModeFlags
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import androidx.core.animation.addListener
+import androidx.core.util.Supplier
+import com.android.app.animation.Interpolators
+import com.android.internal.jank.Cuj
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType
+import com.android.launcher3.desktop.DesktopAppLaunchTransition.Companion.LAUNCH_CHANGE_MODES
+import com.android.wm.shell.shared.animation.MinimizeAnimator
+import com.android.wm.shell.shared.animation.WindowAnimator
+
+/**
+ * Helper class responsible for creating and managing animators for desktop app launch and related
+ * transitions.
+ *
+ * <p>This class handles the complex logic of creating various animators, including launch,
+ * minimize, and trampoline close animations, based on the provided transition information and
+ * launch type. It also utilizes {@link InteractionJankMonitor} to monitor animation jank.
+ *
+ * @param context The application context.
+ * @param launchType The type of app launch, containing animation parameters.
+ * @param cujType The CUJ (Critical User Journey) type for jank monitoring.
+ */
+class DesktopAppLaunchAnimatorHelper(
+ private val context: Context,
+ private val launchType: AppLaunchType,
+ @Cuj.CujType private val cujType: Int,
+ private val transactionSupplier: Supplier<Transaction>,
+) {
+
+ private val interactionJankMonitor = InteractionJankMonitor.getInstance()
+
+ fun createAnimators(info: TransitionInfo, finishCallback: (Animator) -> Unit): List<Animator> {
+ val launchChange = getLaunchChange(info)
+ requireNotNull(launchChange) {
+ val changesString =
+ info.changes.joinToString(", ") { change ->
+ "Change: mode=${change.mode}, " +
+ "taskId=${change.taskInfo?.id}, " +
+ "isFreeform=${change.taskInfo?.isFreeform}"
+ }
+ Log.e(
+ TAG,
+ "No launch change found: Transition type=${info.type}, changes=$changesString",
+ )
+ "expected an app launch Change"
+ }
+
+ val transaction = transactionSupplier.get()
+
+ val minimizeChange = getMinimizeChange(info)
+ val trampolineCloseChange = getTrampolineCloseChange(info)
+
+ val launchAnimator =
+ createLaunchAnimator(
+ launchChange,
+ transaction,
+ finishCallback,
+ isTrampoline = trampolineCloseChange != null,
+ )
+ val animatorsList = mutableListOf(launchAnimator)
+ if (minimizeChange != null) {
+ val minimizeAnimator =
+ createMinimizeAnimator(minimizeChange, transaction, finishCallback)
+ animatorsList.add(minimizeAnimator)
+ }
+ if (trampolineCloseChange != null) {
+ val trampolineCloseAnimator =
+ createTrampolineCloseAnimator(trampolineCloseChange, transaction)
+ animatorsList.add(trampolineCloseAnimator)
+ }
+ return animatorsList
+ }
+
+ private fun getLaunchChange(info: TransitionInfo): Change? =
+ info.changes.firstOrNull { change -> change.mode in LAUNCH_CHANGE_MODES }
+
+ private fun getMinimizeChange(info: TransitionInfo): Change? =
+ info.changes.firstOrNull { change -> change.mode == TRANSIT_TO_BACK }
+
+ private fun getTrampolineCloseChange(info: TransitionInfo): Change? {
+ if (
+ info.changes.size < 2 ||
+ !DesktopModeFlags.ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX.isTrue
+ ) {
+ return null
+ }
+ val openChange =
+ info.changes.firstOrNull { change ->
+ change.mode == TRANSIT_OPEN && change.taskInfo?.isFreeform == true
+ }
+ val closeChange =
+ info.changes.firstOrNull { change ->
+ change.mode == TRANSIT_CLOSE && change.taskInfo?.isFreeform == true
+ }
+ val openPackage = openChange?.taskInfo?.baseIntent?.component?.packageName
+ val closePackage = closeChange?.taskInfo?.baseIntent?.component?.packageName
+ return if (openPackage != null && closePackage != null && openPackage == closePackage) {
+ closeChange
+ } else {
+ null
+ }
+ }
+
+ private fun createLaunchAnimator(
+ change: Change,
+ transaction: Transaction,
+ onAnimFinish: (Animator) -> Unit,
+ isTrampoline: Boolean,
+ ): Animator {
+ val boundsAnimator =
+ WindowAnimator.createBoundsAnimator(
+ context.resources.displayMetrics,
+ launchType.boundsAnimationParams,
+ change,
+ transaction,
+ )
+ val alphaAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = launchType.alphaDurationMs
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { animation ->
+ transaction
+ .setAlpha(change.leash, animation.animatedValue as Float)
+ .setFrameTimeline(Choreographer.getInstance().vsyncId)
+ .apply()
+ }
+ }
+ val clipRect = Rect(change.endAbsBounds).apply { offsetTo(0, 0) }
+ transaction.setCrop(change.leash, clipRect)
+ transaction.setCornerRadius(
+ change.leash,
+ ScreenDecorationsUtils.getWindowCornerRadius(context),
+ )
+ return AnimatorSet().apply {
+ interactionJankMonitor.begin(change.leash, context, context.mainThreadHandler, cujType)
+ if (isTrampoline) {
+ play(alphaAnimator)
+ } else {
+ playTogether(boundsAnimator, alphaAnimator)
+ }
+ addListener(
+ onEnd = { animation ->
+ onAnimFinish(animation)
+ interactionJankMonitor.end(cujType)
+ }
+ )
+ }
+ }
+
+ private fun createMinimizeAnimator(
+ change: Change,
+ transaction: Transaction,
+ onAnimFinish: (Animator) -> Unit,
+ ): Animator {
+ return MinimizeAnimator.create(
+ context,
+ change,
+ transaction,
+ onAnimFinish,
+ interactionJankMonitor,
+ context.mainThreadHandler,
+ )
+ }
+
+ private fun createTrampolineCloseAnimator(change: Change, transaction: Transaction): Animator {
+ return ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = 100L
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { animation ->
+ transaction.setAlpha(change.leash, animation.animatedValue as Float).apply()
+ }
+ }
+ }
+
+ private companion object {
+ const val TAG = "DesktopAppLaunchAnimatorHelper"
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
index 2406fb6..79072a6 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
@@ -17,27 +17,19 @@
package com.android.launcher3.desktop
import android.animation.Animator
-import android.animation.AnimatorSet
-import android.animation.ValueAnimator
import android.content.Context
-import android.graphics.Rect
import android.os.IBinder
-import android.view.Choreographer
+import android.util.Log
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_OPEN
-import android.view.WindowManager.TRANSIT_TO_BACK
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.IRemoteTransitionFinishedCallback
import android.window.RemoteTransitionStub
import android.window.TransitionInfo
-import android.window.TransitionInfo.Change
-import androidx.core.animation.addListener
+import androidx.core.util.Supplier
import com.android.app.animation.Interpolators
import com.android.internal.jank.Cuj
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.policy.ScreenDecorationsUtils
import com.android.quickstep.RemoteRunnable
-import com.android.wm.shell.shared.animation.MinimizeAnimator
import com.android.wm.shell.shared.animation.WindowAnimator
import java.util.concurrent.Executor
@@ -48,14 +40,18 @@
* ([android.view.WindowManager.TRANSIT_TO_BACK]) this transition will apply a minimize animation to
* that window.
*/
-class DesktopAppLaunchTransition(
- private val context: Context,
- private val mainExecutor: Executor,
+class DesktopAppLaunchTransition
+@JvmOverloads
+constructor(
+ context: Context,
private val launchType: AppLaunchType,
@Cuj.CujType private val cujType: Int,
+ private val mainExecutor: Executor,
+ transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
) : RemoteTransitionStub() {
- private val interactionJankMonitor = InteractionJankMonitor.getInstance()
+ private val animatorHelper: DesktopAppLaunchAnimatorHelper =
+ DesktopAppLaunchAnimatorHelper(context, launchType, cujType, transactionSupplier)
enum class AppLaunchType(
val boundsAnimationParams: WindowAnimator.BoundsAnimationParams,
@@ -68,15 +64,16 @@
override fun startAnimation(
token: IBinder,
info: TransitionInfo,
- t: Transaction,
+ transaction: Transaction,
transitionFinishedCallback: IRemoteTransitionFinishedCallback,
) {
+ Log.v(TAG, "startAnimation: launchType=$launchType, cujType=$cujType")
val safeTransitionFinishedCallback = RemoteRunnable {
transitionFinishedCallback.onTransitionFinished(/* wct= */ null, /* sct= */ null)
}
mainExecutor.execute {
runAnimators(info, safeTransitionFinishedCallback)
- t.apply()
+ transaction.apply()
}
}
@@ -86,78 +83,12 @@
animators -= animator
if (animators.isEmpty()) finishedCallback.run()
}
- animators += createAnimators(info, animatorFinishedCallback)
+ animators += animatorHelper.createAnimators(info, animatorFinishedCallback)
animators.forEach { it.start() }
}
- private fun createAnimators(
- info: TransitionInfo,
- finishCallback: (Animator) -> Unit,
- ): List<Animator> {
- val transaction = Transaction()
- val launchAnimator =
- createLaunchAnimator(getLaunchChange(info), transaction, finishCallback)
- val minimizeChange = getMinimizeChange(info) ?: return listOf(launchAnimator)
- val minimizeAnimator =
- MinimizeAnimator.create(
- context.resources.displayMetrics,
- minimizeChange,
- transaction,
- finishCallback,
- )
- return listOf(launchAnimator, minimizeAnimator)
- }
-
- private fun getLaunchChange(info: TransitionInfo): Change =
- requireNotNull(info.changes.firstOrNull { change -> change.mode in LAUNCH_CHANGE_MODES }) {
- "expected an app launch Change"
- }
-
- private fun getMinimizeChange(info: TransitionInfo): Change? =
- info.changes.firstOrNull { change -> change.mode == TRANSIT_TO_BACK }
-
- private fun createLaunchAnimator(
- change: Change,
- transaction: Transaction,
- onAnimFinish: (Animator) -> Unit,
- ): Animator {
- val boundsAnimator =
- WindowAnimator.createBoundsAnimator(
- context.resources.displayMetrics,
- launchType.boundsAnimationParams,
- change,
- transaction,
- )
- val alphaAnimator =
- ValueAnimator.ofFloat(0f, 1f).apply {
- duration = launchType.alphaDurationMs
- interpolator = Interpolators.LINEAR
- addUpdateListener { animation ->
- transaction
- .setAlpha(change.leash, animation.animatedValue as Float)
- .setFrameTimeline(Choreographer.getInstance().vsyncId)
- .apply()
- }
- }
- val clipRect = Rect(change.endAbsBounds).apply { offsetTo(0, 0) }
- transaction.setCrop(change.leash, clipRect)
- transaction.setCornerRadius(
- change.leash,
- ScreenDecorationsUtils.getWindowCornerRadius(context),
- )
- return AnimatorSet().apply {
- interactionJankMonitor.begin(change.leash, context, context.mainThreadHandler, cujType)
- playTogether(boundsAnimator, alphaAnimator)
- addListener(
- onEnd = { animation ->
- onAnimFinish(animation)
- interactionJankMonitor.end(cujType)
- }
- )
- }
- }
-
companion object {
+ const val TAG = "DesktopAppLaunchTransition"
/** Change modes that represent a task becoming visible / launching in Desktop mode. */
val LAUNCH_CHANGE_MODES = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT)
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
index 36c5fba..a72b5c4 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
@@ -48,9 +48,9 @@
RemoteTransition(
DesktopAppLaunchTransition(
context,
- MAIN_EXECUTOR,
AppLaunchType.UNMINIMIZE,
Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_INTENT,
+ MAIN_EXECUTOR,
),
"DesktopWindowLimitUnminimize",
)
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index 40e1c10..9626a61 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -79,7 +79,7 @@
// Widgets (excluding shortcuts & already added widgets) that belong to apps eligible for
// being in predictions.
Map<ComponentKey, WidgetItem> allEligibleWidgets =
- dataModel.widgetsModel.getWidgetsByComponentKey()
+ dataModel.widgetsModel.getWidgetsByComponentKeyForPicker()
.entrySet()
.stream()
.filter(entry -> entry.getValue().widgetInfo != null
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
index 3773d02..6ee43ff 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.os.Debug
import android.util.Log
+import android.util.Slog
import android.util.SparseArray
import android.view.Display.DEFAULT_DISPLAY
import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
@@ -61,8 +62,6 @@
* (Used only when multiple desks are enabled).
*
* @property displayId The ID of the display this object represents.
- * @property canCreateDesks true if it's possible to create new desks on the display represented
- * by this object.
* @property activeDeskId The ID of the active desk on the associated display (if any). It has a
* value of `INACTIVE_DESK_ID` (-1) if there are no active desks. Note that there can only be
* at most one active desk on each display.
@@ -70,11 +69,18 @@
*/
private data class DisplayDeskConfig(
val displayId: Int,
- var canCreateDesks: Boolean,
var activeDeskId: Int = INACTIVE_DESK_ID,
val deskIds: MutableSet<Int>,
)
+ /** True if it is possible to create new desks on current setup. */
+ var canCreateDesks: Boolean = false
+ private set(value) {
+ if (field == value) return
+ field = value
+ desktopVisibilityListeners.forEach { it.onCanCreateDesksChanged(field) }
+ }
+
/** Maps each display by its ID to its desks configuration. */
private val displaysDesksConfigsMap = SparseArray<DisplayDeskConfig>()
@@ -152,7 +158,8 @@
return areDesktopTasksVisible()
}
- val isInDesktopMode = displaysDesksConfigsMap[displayId].activeDeskId != INACTIVE_DESK_ID
+ val activeDeskId = getDisplayDeskConfig(displayId)?.activeDeskId ?: INACTIVE_DESK_ID
+ val isInDesktopMode = activeDeskId != INACTIVE_DESK_ID
if (DEBUG) {
Log.d(TAG, "isInDesktopMode: $isInDesktopMode")
}
@@ -391,7 +398,10 @@
}
}
- private fun onListenerConnected(displayDeskStates: Array<DisplayDeskState>) {
+ private fun onListenerConnected(
+ displayDeskStates: Array<DisplayDeskState>,
+ canCreateDesks: Boolean,
+ ) {
if (!DesktopModeStatus.enableMultipleDesktops(context)) {
return
}
@@ -402,25 +412,24 @@
displaysDesksConfigsMap[displayDeskState.displayId] =
DisplayDeskConfig(
displayId = displayDeskState.displayId,
- canCreateDesks = displayDeskState.canCreateDesk,
activeDeskId = displayDeskState.activeDeskId,
deskIds = displayDeskState.deskIds.toMutableSet(),
)
}
+
+ this.canCreateDesks = canCreateDesks
}
- private fun getDisplayDeskConfig(displayId: Int): DisplayDeskConfig {
- return checkNotNull(displaysDesksConfigsMap[displayId]) {
- "Expected non-null desk config for display: $displayId"
- }
- }
+ private fun getDisplayDeskConfig(displayId: Int) =
+ displaysDesksConfigsMap[displayId]
+ ?: null.also { Slog.e(TAG, "Expected non-null desk config for display: $displayId") }
- private fun onCanCreateDesksChanged(displayId: Int, canCreateDesks: Boolean) {
+ private fun onCanCreateDesksChanged(canCreateDesks: Boolean) {
if (!DesktopModeStatus.enableMultipleDesktops(context)) {
return
}
- getDisplayDeskConfig(displayId).canCreateDesks = canCreateDesks
+ this.canCreateDesks = canCreateDesks
}
private fun onDeskAdded(displayId: Int, deskId: Int) {
@@ -428,7 +437,7 @@
return
}
- getDisplayDeskConfig(displayId).also {
+ getDisplayDeskConfig(displayId)?.also {
check(it.deskIds.add(deskId)) {
"Found a duplicate desk Id: $deskId on display: $displayId"
}
@@ -440,7 +449,7 @@
return
}
- getDisplayDeskConfig(displayId).also {
+ getDisplayDeskConfig(displayId)?.also {
check(it.deskIds.remove(deskId)) {
"Removing non-existing desk Id: $deskId on display: $displayId"
}
@@ -457,7 +466,7 @@
val wasInDesktopMode = isInDesktopModeAndNotInOverview(displayId)
- getDisplayDeskConfig(displayId).also {
+ getDisplayDeskConfig(displayId)?.also {
check(oldActiveDesk == it.activeDeskId) {
"Mismatch between the Shell's oldActiveDesk: $oldActiveDesk, and Launcher's: ${it.activeDeskId}"
}
@@ -525,9 +534,12 @@
) : Stub() {
private val controller = WeakReference(controller)
- override fun onListenerConnected(displayDeskStates: Array<DisplayDeskState>) {
+ override fun onListenerConnected(
+ displayDeskStates: Array<DisplayDeskState>,
+ canCreateDesks: Boolean,
+ ) {
Executors.MAIN_EXECUTOR.execute {
- controller.get()?.onListenerConnected(displayDeskStates)
+ controller.get()?.onListenerConnected(displayDeskStates, canCreateDesks)
}
}
@@ -565,9 +577,9 @@
override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) {}
- override fun onCanCreateDesksChanged(displayId: Int, canCreateDesks: Boolean) {
+ override fun onCanCreateDesksChanged(canCreateDesks: Boolean) {
Executors.MAIN_EXECUTOR.execute {
- controller.get()?.onCanCreateDesksChanged(displayId, canCreateDesks)
+ controller.get()?.onCanCreateDesksChanged(canCreateDesks)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 5af7ff8..5f7a026 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -290,9 +290,9 @@
remoteTransition = new RemoteTransition(
new DesktopAppLaunchTransition(
context,
- MAIN_EXECUTOR,
UNMINIMIZE,
- Cuj.CUJ_DESKTOP_MODE_KEYBOARD_QUICK_SWITCH_APP_LAUNCH
+ Cuj.CUJ_DESKTOP_MODE_KEYBOARD_QUICK_SWITCH_APP_LAUNCH,
+ MAIN_EXECUTOR
),
"DesktopKeyboardQuickSwitchUnminimize");
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index b6b090c..2e5bebc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -119,7 +119,8 @@
mControllers = controllers;
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
Resources resources = mActivity.getResources();
- if (mActivity.isPhoneGestureNavMode() || mActivity.isTinyTaskbar()) {
+ if (mActivity.isPhoneGestureNavMode() || mActivity.isTinyTaskbar()
+ || mActivity.isBubbleBarOnPhone()) {
mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_phone_size);
mStashedHandleWidth =
resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index b9d1275..2e42e45 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -18,6 +18,7 @@
import static android.os.Trace.TRACE_TAG_APP;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
@@ -46,8 +47,8 @@
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
-import static com.android.window.flags.Flags.enableStartLaunchTransitionFromTaskbarBugfix;
-import static com.android.window.flags.Flags.enableTaskbarConnectedDisplays;
+import static com.android.wm.shell.Flags.enableBubbleBar;
+import static com.android.wm.shell.Flags.enableBubbleBarOnPhones;
import static com.android.wm.shell.Flags.enableTinyTaskbar;
import static java.lang.invoke.MethodHandles.Lookup.PROTECTED;
@@ -77,6 +78,7 @@
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.Toast;
+import android.window.DesktopExperienceFlags;
import android.window.DesktopModeFlags;
import android.window.RemoteTransition;
@@ -298,9 +300,10 @@
// If Bubble bar is present, TaskbarControllers depends on it so build it first.
Optional<BubbleControllers> bubbleControllersOptional = Optional.empty();
BubbleBarController.onTaskbarRecreated();
+ final boolean deviceBubbleBarEnabled = enableBubbleBarOnPhones()
+ || (!mDeviceProfile.isPhone && !mDeviceProfile.isVerticalBarLayout());
if (BubbleBarController.isBubbleBarEnabled()
- && !mDeviceProfile.isPhone
- && !mDeviceProfile.isVerticalBarLayout()
+ && deviceBubbleBarEnabled
&& bubbleBarView != null
) {
Optional<BubbleStashedHandleViewController> bubbleHandleController = Optional.empty();
@@ -434,8 +437,9 @@
.setIsTransientTaskbar(true)
.build();
}
- mNavMode = (enableTaskbarConnectedDisplays() && !mIsPrimaryDisplay)
- ? NavigationMode.THREE_BUTTONS : DisplayController.getNavigationMode(this);
+ mNavMode = (DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()
+ && !mIsPrimaryDisplay) ? NavigationMode.THREE_BUTTONS
+ : DisplayController.getNavigationMode(this);
}
@@ -511,6 +515,10 @@
return enableTinyTaskbar() && mDeviceProfile.isPhone && mDeviceProfile.isTaskbarPresent;
}
+ public boolean isBubbleBarOnPhone() {
+ return enableBubbleBarOnPhones() && enableBubbleBar() && mDeviceProfile.isPhone;
+ }
+
/**
* Returns {@code true} iff bubble bar is enabled (but not necessarily visible /
* containing bubbles).
@@ -868,7 +876,7 @@
@Override
public void onPopupVisibilityChanged(boolean isVisible) {
- setTaskbarWindowFocusable(isVisible);
+ setTaskbarWindowFocusable(isVisible /* focusable */, false /* imeFocusable */);
}
@Override
@@ -1227,17 +1235,29 @@
}
/**
- * Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
- * window.
+ * Sets whether the taskbar window should be focusable and IME focusable. This won't be IME
+ * focusable unless it is also focusable.
+ *
+ * @param focusable whether it should be focusable.
+ * @param imeFocusable whether it should be IME focusable.
+ *
+ * @see WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE
+ * @see WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM
*/
- public void setTaskbarWindowFocusable(boolean focusable) {
+ public void setTaskbarWindowFocusable(boolean focusable, boolean imeFocusable) {
if (isPhoneMode()) {
return;
}
if (focusable) {
mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE;
+ if (imeFocusable) {
+ mWindowLayoutParams.flags &= ~FLAG_ALT_FOCUSABLE_IM;
+ } else {
+ mWindowLayoutParams.flags |= FLAG_ALT_FOCUSABLE_IM;
+ }
} else {
mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
+ mWindowLayoutParams.flags &= ~FLAG_ALT_FOCUSABLE_IM;
}
notifyUpdateLayoutParams();
}
@@ -1258,8 +1278,12 @@
}
/**
- * Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
- * window. If we're now focusable, also move nav buttons to a separate window above IME.
+ * Sets whether the taskbar window should be focusable, as well as IME focusable. If we're now
+ * focusable, also move nav buttons to a separate window above IME.
+ *
+ * @param focusable whether it should be focusable.
+ *
+ * @see WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE
*/
public void setTaskbarWindowFocusableForIme(boolean focusable) {
if (focusable) {
@@ -1267,7 +1291,7 @@
} else {
mControllers.navbarButtonsViewController.moveNavButtonsBackToTaskbarWindow();
}
- setTaskbarWindowFocusable(focusable);
+ setTaskbarWindowFocusable(focusable, true /* imeFocusable */);
}
/** Adds the given view to WindowManager with the provided LayoutParams (creates new window). */
@@ -1533,9 +1557,9 @@
return new RemoteTransition(
new DesktopAppLaunchTransition(
this,
- getMainExecutor(),
appLaunchType,
- cujType
+ cujType,
+ getMainExecutor()
),
"TaskbarDesktopAppLaunch");
}
@@ -1558,7 +1582,10 @@
*/
private void launchFromInAppTaskbar(@Nullable RecentsView recents,
@Nullable View launchingIconView, List<? extends ItemInfo> itemInfos) {
- if (recents == null) {
+ boolean launchedFromExternalDisplay =
+ DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()
+ && !mIsPrimaryDisplay;
+ if (recents == null && !launchedFromExternalDisplay) {
return;
}
@@ -1699,7 +1726,7 @@
}
// There is no task associated with this launch - launch a new task through an intent
ActivityOptionsWrapper opts = getActivityLaunchDesktopOptions();
- if (enableStartLaunchTransitionFromTaskbarBugfix()) {
+ if (DesktopModeFlags.ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX.isTrue()) {
mSysUiProxy.startLaunchIntentTransition(intent, opts.options.toBundle(), displayId);
} else {
startActivity(intent, opts.options.toBundle());
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index e44bce1..6d23853 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -108,7 +108,7 @@
fun updateStashedHandleWidth(context: TaskbarActivityContext, res: Resources) {
stashedHandleWidth =
res.getDimensionPixelSize(
- if (context.isPhoneMode || context.isTinyTaskbar) {
+ if (context.isPhoneMode || context.isTinyTaskbar || context.isBubbleBarOnPhone) {
R.dimen.taskbar_stashed_small_screen
} else {
R.dimen.taskbar_stashed_handle_width
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index b91f512..d531e2c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -52,6 +52,7 @@
import android.window.SurfaceSyncGroup;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.app.animation.Interpolators;
import com.android.internal.logging.InstanceId;
@@ -75,6 +76,7 @@
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController;
@@ -517,6 +519,7 @@
return mIsSystemDragInProgress;
}
+ @VisibleForTesting
private void maybeOnDragEnd() {
if (!isDragging()) {
((BubbleTextView) mDragObject.originalView).setIconDisabled(false);
@@ -524,17 +527,38 @@
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, false);
mActivity.onDragEnd();
if (mReturnAnimator == null) {
+ // If an item is dropped on the bubble bar, the bubble bar handles the drop,
+ // so it should not collapse along with the taskbar.
+ boolean droppedOnBubbleBar = notifyBubbleBarItemDropped();
// Upon successful drag, immediately stash taskbar.
// Note, this must be done last to ensure no AutohideSuspendFlags are active, as
// that will prevent us from stashing until the timeout.
- mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
-
+ mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(
+ /* stash = */ true,
+ /* shouldBubblesFollow = */ !droppedOnBubbleBar
+ );
mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo)
.log(LAUNCHER_APP_LAUNCH_DRAGDROP);
}
}
}
+ /**
+ * Exits the Bubble Bar drop target mode if applicable.
+ *
+ * @return {@code true} if drop target mode was active.
+ */
+ private boolean notifyBubbleBarItemDropped() {
+ return mControllers.bubbleControllers.map(bc -> {
+ BubbleBarViewController bubbleBarViewController = bc.bubbleBarViewController;
+ boolean showingDropTarget = bubbleBarViewController.isShowingDropTarget();
+ if (showingDropTarget) {
+ bubbleBarViewController.onItemDroppedInBubbleBarDragZone();
+ }
+ return showingDropTarget;
+ }).orElse(false);
+ }
+
@Override
protected void endDrag() {
if (mDisallowGlobalDrag) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index 26a552e..b4ffb74 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -141,6 +141,10 @@
tooltipStep = TOOLTIP_STEP_FEATURES
inflateTooltip(R.layout.taskbar_edu_swipe)
tooltip?.run {
+ TypefaceUtils.setTypeface(
+ requireViewById(R.id.taskbar_edu_title),
+ TypefaceUtils.FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED,
+ )
requireViewById<LottieAnimationView>(R.id.swipe_animation).supportLightTheme()
show()
}
@@ -180,6 +184,23 @@
pinningEdu.visibility = GONE
}
+ TypefaceUtils.setTypeface(
+ requireViewById(R.id.taskbar_edu_title),
+ TypefaceUtils.FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED,
+ )
+ TypefaceUtils.setTypeface(
+ requireViewById(R.id.splitscreen_text),
+ TypefaceUtils.FONT_FAMILY_BODY_MEDIUM_BASELINE,
+ )
+ TypefaceUtils.setTypeface(
+ requireViewById(R.id.suggestions_text),
+ TypefaceUtils.FONT_FAMILY_BODY_MEDIUM_BASELINE,
+ )
+ TypefaceUtils.setTypeface(
+ requireViewById(R.id.pinning_text),
+ TypefaceUtils.FONT_FAMILY_BODY_MEDIUM_BASELINE,
+ )
+
// Set up layout parameters.
content.updateLayoutParams { width = MATCH_PARENT }
updateLayoutParams<MarginLayoutParams> {
@@ -231,6 +252,15 @@
requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
.supportLightTheme()
+ TypefaceUtils.setTypeface(
+ requireViewById(R.id.taskbar_edu_title),
+ TypefaceUtils.FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED,
+ )
+ TypefaceUtils.setTypeface(
+ requireViewById(R.id.pinning_text),
+ TypefaceUtils.FONT_FAMILY_BODY_MEDIUM_BASELINE,
+ )
+
updateLayoutParams<BaseDragLayer.LayoutParams> {
if (DisplayController.isTransientTaskbar(activityContext)) {
bottomMargin += activityContext.deviceProfile.taskbarHeight
@@ -276,6 +306,13 @@
allowTouchDismissal = true
requireViewById<LottieAnimationView>(R.id.search_edu_animation).supportLightTheme()
val eduSubtitle: TextView = requireViewById(R.id.search_edu_text)
+
+ TypefaceUtils.setTypeface(
+ requireViewById(R.id.taskbar_edu_title),
+ TypefaceUtils.FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED,
+ )
+ TypefaceUtils.setTypeface(eduSubtitle, TypefaceUtils.FONT_FAMILY_BODY_SMALL_BASELINE)
+
showDisclosureText(eduSubtitle)
updateLayoutParams<BaseDragLayer.LayoutParams> {
if (DisplayController.isTransientTaskbar(activityContext)) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 96bcffd..34bb6e0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -32,7 +32,6 @@
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
import static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR;
import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
-import static com.android.window.flags.Flags.enableTaskbarConnectedDisplays;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
@@ -46,6 +45,7 @@
import android.os.Handler;
import android.os.Trace;
import android.provider.Settings;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -53,6 +53,7 @@
import android.view.MotionEvent;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import android.window.DesktopExperienceFlags;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -75,14 +76,20 @@
import com.android.quickstep.fallback.window.RecentsDisplayModel;
import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.util.ContextualSearchInvoker;
+import com.android.quickstep.util.GroupTask;
import com.android.quickstep.views.RecentsViewContainer;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
import java.io.PrintWriter;
+import java.util.Set;
import java.util.StringJoiner;
/**
@@ -118,11 +125,12 @@
Settings.Secure.NAV_BAR_KIDS_MODE);
private final Context mBaseContext;
+ private TaskbarNavButtonCallbacks mNavCallbacks;
// TODO: Remove this during the connected displays lifecycle refactor.
private final Context mPrimaryWindowContext;
private WindowManager mPrimaryWindowManager;
- private final TaskbarNavButtonController mDefaultNavButtonController;
- private final ComponentCallbacks mDefaultComponentCallbacks;
+ private TaskbarNavButtonController mPrimaryNavButtonController;
+ private ComponentCallbacks mPrimaryComponentCallbacks;
private final SimpleBroadcastReceiver mShutdownReceiver;
@@ -140,6 +148,11 @@
private final SparseArray<FrameLayout> mRootLayouts = new SparseArray<>();
/** DisplayId - {@link Boolean} map indicating if RootLayout was added to window. */
private final SparseBooleanArray mAddedRootLayouts = new SparseBooleanArray();
+ /** DisplayId - {@link TaskbarNavButtonController} map for Connected Display. */
+ private final SparseArray<TaskbarNavButtonController> mNavButtonControllers =
+ new SparseArray<>();
+ /** DisplayId - {@link ComponentCallbacks} map for Connected Display. */
+ private final SparseArray<ComponentCallbacks> mComponentCallbacks = new SparseArray<>();
private StatefulActivity mActivity;
private RecentsViewContainer mRecentsViewContainer;
@@ -158,27 +171,94 @@
private class RecreationListener implements DisplayController.DisplayInfoChangeListener {
@Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
-
if ((flags & CHANGE_DENSITY) != 0) {
- Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Display density changed");
+ debugTaskbarManager("onDisplayInfoChanged - Display density changed",
+ context.getDisplayId());
}
if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
- Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Navigation mode changed");
+ debugTaskbarManager("onDisplayInfoChanged - Navigation mode changed",
+ context.getDisplayId());
}
if ((flags & CHANGE_DESKTOP_MODE) != 0) {
- Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Desktop mode changed");
+ debugTaskbarManager("onDisplayInfoChanged - Desktop mode changed",
+ context.getDisplayId());
}
if ((flags & CHANGE_TASKBAR_PINNING) != 0) {
- Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Taskbar pinning changed");
+ debugTaskbarManager("onDisplayInfoChanged - Taskbar pinning changed",
+ context.getDisplayId());
}
if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE | CHANGE_DESKTOP_MODE
| CHANGE_TASKBAR_PINNING)) != 0) {
+ debugTaskbarManager("onDisplayInfoChanged - Recreating Taskbar!",
+ context.getDisplayId());
recreateTaskbar();
}
}
}
- private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> recreateTaskbar();
+ private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> {
+ debugTaskbarManager("Settings changed! Recreating Taskbar!");
+ recreateTaskbar();
+ };
+
+ private final PerceptibleTaskListener mTaskStackListener;
+
+ private class PerceptibleTaskListener implements TaskStackChangeListener {
+ private ArraySet<Integer> mPerceptibleTasks = new ArraySet<Integer>();
+
+ @Override
+ public void onTaskMovedToFront(int taskId) {
+ if (mPerceptibleTasks.contains(taskId)) {
+ return;
+ }
+
+ // This listens to any Task, so we filter them by the ones shown in the launcher.
+ // For Tasks restored after startup, they will by default not be Perceptible, and no
+ // need to until user interacts with it by bringing it to the foreground.
+ for (int i = 0; i < mTaskbars.size(); i++) {
+ // get pinned tasks
+ Set<Integer> taskbarPinnedTasks =
+ mTaskbars.valueAt(i).getControllers().taskbarViewController
+ .getTaskIdsForPinnedApps();
+
+ // mark as perceptible if the foregrounded task is in the list of apps shown in
+ // the launcher.
+ if (taskbarPinnedTasks.contains(taskId)
+ && ActivityManagerWrapper.getInstance()
+ .setTaskIsPerceptible(taskId, true)
+ ) {
+ mPerceptibleTasks.add(taskId);
+ }
+ }
+ }
+
+ /**
+ * Launcher also can display recently launched tasks that are not pinned. Also add
+ * these as perceptible
+ */
+ @Override
+ public void onRecentTaskListUpdated() {
+ for (int i = 0; i < mTaskbars.size(); i++) {
+ for (GroupTask gTask : mTaskbars.valueAt(i).getControllers()
+ .taskbarRecentAppsController.getShownTasks()) {
+ for (Task task : gTask.getTasks()) {
+ int taskId = task.key.id;
+
+ if (!mPerceptibleTasks.contains(taskId)) {
+ ActivityManagerWrapper.getInstance()
+ .setTaskIsPerceptible(taskId, true);
+ mPerceptibleTasks.add(taskId);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onTaskRemoved(int taskId) {
+ mPerceptibleTasks.remove(taskId);
+ }
+ };
private boolean mUserUnlocked = false;
@@ -191,6 +271,7 @@
@Override
public void run() {
int displayId = getDefaultDisplayId();
+ debugTaskbarManager("mActivityOnDestroyCallback running!", displayId);
if (mActivity != null) {
displayId = mActivity.getDisplayId();
mActivity.removeOnDeviceProfileChangeListener(
@@ -204,7 +285,6 @@
mRecentsViewContainer = null;
}
mActivity = null;
- debugWhyTaskbarNotDestroyed("clearActivity");
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
taskbar.setUIController(TaskbarUIController.DEFAULT);
@@ -217,28 +297,27 @@
new UnfoldTransitionProgressProvider.TransitionProgressListener() {
@Override
public void onTransitionStarted() {
- Log.d(TASKBAR_NOT_DESTROYED_TAG,
- "fold/unfold transition started getting called.");
+ debugTaskbarManager("fold/unfold transition started getting called.");
}
@Override
public void onTransitionProgress(float progress) {
- Log.d(TASKBAR_NOT_DESTROYED_TAG,
- "fold/unfold transition progress : " + progress);
+ debugTaskbarManager(
+ "fold/unfold transition progress getting called. | progress="
+ + progress);
}
@Override
public void onTransitionFinishing() {
- Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ debugTaskbarManager(
"fold/unfold transition finishing getting called.");
}
@Override
public void onTransitionFinished() {
- Log.d(TASKBAR_NOT_DESTROYED_TAG,
+ debugTaskbarManager(
"fold/unfold transition finished getting called.");
-
}
};
@@ -250,20 +329,22 @@
RecentsDisplayModel recentsDisplayModel) {
mBaseContext = context;
mAllAppsActionManager = allAppsActionManager;
+ mNavCallbacks = navCallbacks;
mRecentsDisplayModel = recentsDisplayModel;
- mPrimaryWindowContext = createWindowContext(getDefaultDisplayId());
- if (enableTaskbarNoRecreate()) {
- mPrimaryWindowManager = mPrimaryWindowContext.getSystemService(WindowManager.class);
- createTaskbarRootLayout(getDefaultDisplayId());
- }
- mDefaultNavButtonController = createDefaultNavButtonController(context, navCallbacks);
- mDefaultComponentCallbacks = createDefaultComponentCallbacks();
+
+ // Set up primary display.
+ int primaryDisplayId = getDefaultDisplayId();
+ debugTaskbarManager("TaskbarManager constructor", primaryDisplayId);
+ mPrimaryWindowContext = createWindowContext(primaryDisplayId);
+ mPrimaryWindowManager = mPrimaryWindowContext.getSystemService(WindowManager.class);
+ createTaskbarRootLayout(primaryDisplayId);
+ createNavButtonController(primaryDisplayId);
+ createAndRegisterComponentCallbacks(primaryDisplayId);
+
SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
- Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
- mPrimaryWindowContext.registerComponentCallbacks(mDefaultComponentCallbacks);
mShutdownReceiver =
new SimpleBroadcastReceiver(
mPrimaryWindowContext, UI_HELPER_EXECUTOR, i -> destroyAllTaskbars());
@@ -281,82 +362,16 @@
mTaskbarBroadcastReceiver.register(RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
});
- debugWhyTaskbarNotDestroyed("TaskbarManager created");
+ if (ActivityManagerWrapper.usePerceptibleTasks(getPrimaryWindowContext())) {
+ mTaskStackListener = new PerceptibleTaskListener();
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
+ } else {
+ mTaskStackListener = null;
+ }
+ debugTaskbarManager("TaskbarManager created");
recreateTaskbar();
}
- @NonNull
- private TaskbarNavButtonController createDefaultNavButtonController(Context context,
- TaskbarNavButtonCallbacks navCallbacks) {
- return new TaskbarNavButtonController(
- context,
- navCallbacks,
- SystemUiProxy.INSTANCE.get(mPrimaryWindowContext),
- new Handler(),
- new ContextualSearchInvoker(mPrimaryWindowContext));
- }
-
- private ComponentCallbacks createDefaultComponentCallbacks() {
- return new ComponentCallbacks() {
- private Configuration mOldConfig =
- mPrimaryWindowContext.getResources().getConfiguration();
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- Trace.instantForTrack(Trace.TRACE_TAG_APP, "TaskbarManager",
- "onConfigurationChanged: " + newConfig);
- debugWhyTaskbarNotDestroyed(
- "TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
- // TODO: adapt this logic to be specific to different displays.
- DeviceProfile dp = mUserUnlocked
- ? LauncherAppState.getIDP(mPrimaryWindowContext).getDeviceProfile(
- mPrimaryWindowContext)
- : null;
- int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
-
- if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
- Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "onConfigurationChanged: theme changed");
- // Only recreate for theme changes, not other UI mode changes such as docking.
- int oldUiNightMode = (mOldConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
- int newUiNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
- if (oldUiNightMode == newUiNightMode) {
- configDiff &= ~ActivityInfo.CONFIG_UI_MODE;
- }
- }
-
- debugWhyTaskbarNotDestroyed("ComponentCallbacks#onConfigurationChanged() "
- + "configDiff=" + Configuration.configurationDiffToString(configDiff));
- if (configDiff != 0 || getCurrentActivityContext() == null) {
- recreateTaskbar();
- } else {
- // Config change might be handled without re-creating the taskbar
- if (dp != null && !isTaskbarEnabled(dp)) {
- destroyDefaultTaskbar();
- } else {
- if (dp != null && isTaskbarEnabled(dp)) {
- if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
- // Re-initialize for screen size change? Should this be done
- // by looking at screen-size change flag in configDiff in the
- // block above?
- recreateTaskbar();
- } else {
- getCurrentActivityContext().updateDeviceProfile(dp);
- }
- }
- getCurrentActivityContext().onConfigurationChanged(configDiff);
- }
- }
- mOldConfig = new Configuration(newConfig);
- // reset taskbar was pinned value, so we don't automatically unstash taskbar upon
- // user unfolding the device.
- mSharedState.setTaskbarWasPinned(false);
- }
-
- @Override
- public void onLowMemory() { }
- };
- }
-
private void destroyAllTaskbars() {
for (int i = 0; i < mTaskbars.size(); i++) {
int displayId = mTaskbars.keyAt(i);
@@ -365,23 +380,18 @@
}
}
- private void destroyDefaultTaskbar() {
- destroyTaskbarForDisplay(getDefaultDisplayId());
- }
-
private void destroyTaskbarForDisplay(int displayId) {
Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "destroyTaskbarForDisplay: " + displayId);
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
- debugWhyTaskbarNotDestroyed("destroyTaskbarForDisplay: " + taskbar, displayId);
+ debugTaskbarManager("destroyTaskbarForDisplay: " + taskbar, displayId);
if (taskbar != null) {
taskbar.onDestroy();
// remove all defaults that we store
removeTaskbarFromMap(displayId);
}
- // make this display-specific
- DeviceProfile dp = mUserUnlocked ?
- LauncherAppState.getIDP(getWindowContext(displayId)).getDeviceProfile(
- getWindowContext(displayId)) : null;
+ // TODO (b/381113004): make this display-specific via getWindowContext()
+ DeviceProfile dp = mUserUnlocked ? LauncherAppState.getIDP(
+ mPrimaryWindowContext).getDeviceProfile(mPrimaryWindowContext) : null;
if (dp == null || !isTaskbarEnabled(dp)) {
removeTaskbarRootViewFromWindow(displayId);
}
@@ -431,7 +441,10 @@
DisplayController.INSTANCE.get(mPrimaryWindowContext).addChangeListener(
mRecreationListener);
recreateTaskbar();
- addTaskbarRootViewToWindow(getDefaultDisplayId());
+ for (int i = 0; i < mTaskbars.size(); i++) {
+ int displayId = mTaskbars.keyAt(i);
+ addTaskbarRootViewToWindow(displayId);
+ }
}
/**
@@ -443,7 +456,7 @@
}
removeActivityCallbacksAndListeners();
mActivity = activity;
- debugWhyTaskbarNotDestroyed("Set mActivity=" + mActivity);
+ debugTaskbarManager("Set mActivity=" + mActivity);
mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
Log.d(TASKBAR_NOT_DESTROYED_TAG,
"registering activity lifecycle callbacks from setActivity().");
@@ -492,7 +505,7 @@
return ql.getUnfoldTransitionProgressProvider();
}
} else {
- return SystemUiProxy.INSTANCE.get(mPrimaryWindowContext).getUnfoldTransitionProvider();
+ return SystemUiProxy.INSTANCE.get(mBaseContext).getUnfoldTransitionProvider();
}
return null;
}
@@ -530,13 +543,21 @@
/**
* This method is called multiple times (ex. initial init, then when user unlocks) in which case
- * we fully want to destroy the existing default display's taskbar and create a new one.
+ * we fully want to destroy existing taskbars and create all desired new ones.
* In other case (folding/unfolding) we don't need to remove and add window.
*/
@VisibleForTesting
public synchronized void recreateTaskbar() {
- // TODO: make this recreate all taskbars in map.
- recreateTaskbarForDisplay(getDefaultDisplayId());
+ // Handles initial creation case.
+ if (mTaskbars.size() == 0) {
+ recreateTaskbarForDisplay(getDefaultDisplayId());
+ return;
+ }
+
+ for (int i = 0; i < mTaskbars.size(); i++) {
+ int displayId = mTaskbars.keyAt(i);
+ recreateTaskbarForDisplay(displayId);
+ }
}
/**
@@ -545,13 +566,12 @@
* In other case (folding/unfolding) we don't need to remove and add window.
*/
private void recreateTaskbarForDisplay(int displayId) {
- Trace.beginSection("recreateTaskbar");
+ Trace.beginSection("recreateTaskbarForDisplay");
try {
Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "recreateTaskbarForDisplay: " + displayId);
- // TODO: make this code display specific
- DeviceProfile dp = mUserUnlocked ?
- LauncherAppState.getIDP(getWindowContext(displayId)).getDeviceProfile(
- getWindowContext(displayId)) : null;
+ // TODO (b/381113004): make this display-specific via getWindowContext()
+ DeviceProfile dp = mUserUnlocked ? LauncherAppState.getIDP(
+ mPrimaryWindowContext).getDeviceProfile(mPrimaryWindowContext) : null;
// All Apps action is unrelated to navbar unification, so we only need to check DP.
final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
@@ -559,15 +579,17 @@
destroyTaskbarForDisplay(displayId);
+ boolean displayExists = getDisplay(displayId) != null;
boolean isTaskbarEnabled = dp != null && isTaskbarEnabled(dp);
- debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled
+ debugTaskbarManager("recreateTaskbarForDisplay: isTaskbarEnabled=" + isTaskbarEnabled
+ " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
+ " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
- + " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
- if (!isTaskbarEnabled || !isLargeScreenTaskbar) {
- SystemUiProxy.INSTANCE.get(mPrimaryWindowContext)
+ + " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent)
+ + " displayExists=" + displayExists);
+ if (!isTaskbarEnabled || !isLargeScreenTaskbar || !displayExists) {
+ SystemUiProxy.INSTANCE.get(mBaseContext)
.notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
- if (!isTaskbarEnabled) {
+ if (!isTaskbarEnabled || !displayExists) {
return;
}
}
@@ -575,6 +597,11 @@
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (enableTaskbarNoRecreate() || taskbar == null) {
taskbar = createTaskbarActivityContext(dp, displayId);
+ if (taskbar == null) {
+ debugTaskbarManager(
+ "recreateTaskbarForDisplay: new taskbar instance is null!", displayId);
+ return;
+ }
} else {
taskbar.updateDeviceProfile(dp);
}
@@ -585,7 +612,8 @@
// Non default displays should not use LauncherTaskbarUIController as they shouldn't
// have access to the Launcher activity.
- if (enableTaskbarConnectedDisplays() && !isDefaultDisplay(displayId)) {
+ if (!isDefaultDisplay(displayId)
+ && DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
taskbar.setUIController(createTaskbarUIControllerForNonDefaultDisplay(displayId));
} else if (mRecentsViewContainer != null) {
taskbar.setUIController(
@@ -622,8 +650,8 @@
}
public void onLongPressHomeEnabled(boolean assistantLongPressEnabled) {
- if (mDefaultNavButtonController != null) {
- mDefaultNavButtonController.setAssistantLongPressEnabled(assistantLongPressEnabled);
+ if (mPrimaryNavButtonController != null) {
+ mPrimaryNavButtonController.setAssistantLongPressEnabled(assistantLongPressEnabled);
}
}
@@ -746,13 +774,25 @@
* primary device or a previously mirroring display is switched to extended mode.
*/
public void onDisplayAddSystemDecorations(int displayId) {
- if (isDefaultDisplay(displayId) || !enableTaskbarConnectedDisplays()) {
+ Display display = getDisplay(displayId);
+ if (!DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue() || isDefaultDisplay(
+ displayId) || display == null) {
+ debugTaskbarManager("onDisplayAddSystemDecorations: not adding display");
return;
}
Context newWindowContext = createWindowContext(displayId);
if (newWindowContext != null) {
addWindowContextToMap(displayId, newWindowContext);
+ // TODO (b/391965805): remove once onDisplayAddSystemDecorations is working.
+ WindowManager wm = getWindowManager(displayId);
+ if (wm == null || !wm.shouldShowSystemDecors(displayId)) {
+ return;
+ }
+ createTaskbarRootLayout(displayId);
+ createNavButtonController(displayId);
+ createAndRegisterComponentCallbacks(displayId);
+ recreateTaskbarForDisplay(displayId);
}
}
@@ -761,12 +801,16 @@
* removed from the primary device.
*/
public void onDisplayRemoved(int displayId) {
- if (isDefaultDisplay(displayId) || !enableTaskbarConnectedDisplays()) {
+ if (!DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue() || isDefaultDisplay(
+ displayId)) {
return;
}
Context windowContext = getWindowContext(displayId);
if (windowContext != null) {
+ removeNavButtonController(displayId);
+ removeAndUnregisterComponentCallbacks(displayId);
+ destroyTaskbarForDisplay(displayId);
removeWindowContextFromMap(displayId);
}
}
@@ -774,7 +818,11 @@
/**
* Signal from SysUI indicating that system decorations should be removed from the display.
*/
- public void onDisplayRemoveSystemDecorations(int displayId) {}
+ public void onDisplayRemoveSystemDecorations(int displayId) {
+ // The display mirroring starts. The handling logic is the same as when removing a
+ // display.
+ onDisplayRemoved(displayId);
+ }
private void removeActivityCallbacksAndListeners() {
if (mActivity != null) {
@@ -796,7 +844,7 @@
*/
public void destroy() {
mRecentsViewContainer = null;
- debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
+ debugTaskbarManager("TaskbarManager#destroy()");
removeActivityCallbacksAndListeners();
mTaskbarBroadcastReceiver.unregisterReceiverSafely();
@@ -809,8 +857,14 @@
SettingsCache.INSTANCE.get(mPrimaryWindowContext)
.unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
- mPrimaryWindowContext.unregisterComponentCallbacks(mDefaultComponentCallbacks);
+ removeAndUnregisterComponentCallbacks(getDefaultDisplayId());
mShutdownReceiver.unregisterReceiverSafely();
+ if (ActivityManagerWrapper.usePerceptibleTasks(getPrimaryWindowContext())) {
+ for (Integer taskId: mTaskStackListener.mPerceptibleTasks) {
+ ActivityManagerWrapper.getInstance().setTaskIsPerceptible(taskId, false);
+ }
+ }
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
destroyAllTaskbars();
}
@@ -836,15 +890,20 @@
private void addTaskbarRootViewToWindow(int displayId) {
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (!enableTaskbarNoRecreate() || taskbar == null) {
- Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG,
- "addTaskbarRootViewToWindow - taskbar null | displayId=" + displayId);
+ debugTaskbarManager("addTaskbarRootViewToWindow - taskbar null", displayId);
+ return;
+ }
+
+ if (getDisplay(displayId) == null) {
+ debugTaskbarManager("addTaskbarRootViewToWindow - display null", displayId);
return;
}
if (!isTaskbarRootLayoutAddedForDisplay(displayId)) {
FrameLayout rootLayout = getTaskbarRootLayoutForDisplay(displayId);
- if (rootLayout != null) {
- getWindowManager(displayId).addView(rootLayout, taskbar.getWindowLayoutParams());
+ WindowManager windowManager = getWindowManager(displayId);
+ if (rootLayout != null && windowManager != null) {
+ windowManager.addView(rootLayout, taskbar.getWindowLayoutParams());
mAddedRootLayouts.put(displayId, true);
} else {
Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW,
@@ -864,10 +923,14 @@
return;
}
- if (isTaskbarRootLayoutAddedForDisplay(displayId)) {
- getWindowManager(displayId).removeViewImmediate(rootLayout);
+ WindowManager windowManager = getWindowManager(displayId);
+ if (isTaskbarRootLayoutAddedForDisplay(displayId) && windowManager != null) {
+ windowManager.removeViewImmediate(rootLayout);
mAddedRootLayouts.put(displayId, false);
removeTaskbarRootLayoutFromMap(displayId);
+ } else {
+ debugTaskbarManager("removeTaskbarRootViewFromWindow - WindowManager is null",
+ displayId);
}
}
@@ -913,24 +976,164 @@
/**
* Creates a {@link TaskbarActivityContext} for the given display and adds it to the map.
+ * @param dp The {@link DeviceProfile} for the display.
+ * @param displayId The ID of the display.
*/
- private TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp, int displayId) {
- Display display = mBaseContext.getSystemService(DisplayManager.class).getDisplay(
- displayId);
- Context navigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
- ? mBaseContext.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
- : null;
+ private @Nullable TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp,
+ int displayId) {
+ Display display = getDisplay(displayId);
+ if (display == null) {
+ debugTaskbarManager("createTaskbarActivityContext: display null", displayId);
+ return null;
+ }
+
+ Context navigationBarPanelContext = null;
+ if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
+ navigationBarPanelContext = mBaseContext.createWindowContext(display,
+ TYPE_NAVIGATION_BAR_PANEL, null);
+ }
+
+ boolean isPrimaryDisplay = isDefaultDisplay(displayId)
+ || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue();
TaskbarActivityContext newTaskbar = new TaskbarActivityContext(getWindowContext(displayId),
- navigationBarPanelContext, dp, mDefaultNavButtonController,
- mUnfoldProgressProvider, isDefaultDisplay(displayId),
- SystemUiProxy.INSTANCE.get(mPrimaryWindowContext));
+ navigationBarPanelContext, dp, getNavButtonController(displayId),
+ mUnfoldProgressProvider, isPrimaryDisplay,
+ SystemUiProxy.INSTANCE.get(mBaseContext));
addTaskbarToMap(displayId, newTaskbar);
return newTaskbar;
}
/**
+ * Create {@link ComponentCallbacks} for the given display and register it to the relevant
+ * WindowContext. For external displays, populate maps.
+ * @param displayId The ID of the display.
+ */
+ private void createAndRegisterComponentCallbacks(int displayId) {
+ ComponentCallbacks callbacks = new ComponentCallbacks() {
+ private Configuration mOldConfig =
+ getWindowContext(displayId).getResources().getConfiguration();
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, "TaskbarManager",
+ "onConfigurationChanged: " + newConfig);
+ debugTaskbarManager(
+ "TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
+ // TODO (b/381113004): make this display-specific via getWindowContext()
+ DeviceProfile dp = mUserUnlocked ? LauncherAppState.getIDP(
+ mPrimaryWindowContext).getDeviceProfile(mPrimaryWindowContext) : null;
+ int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
+
+ if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
+ Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "onConfigurationChanged: theme changed");
+ // Only recreate for theme changes, not other UI mode changes such as docking.
+ int oldUiNightMode = (mOldConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
+ int newUiNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
+ if (oldUiNightMode == newUiNightMode) {
+ configDiff &= ~ActivityInfo.CONFIG_UI_MODE;
+ }
+ }
+
+ debugTaskbarManager("ComponentCallbacks#onConfigurationChanged() "
+ + "configDiff=" + Configuration.configurationDiffToString(configDiff));
+ if (configDiff != 0 || getCurrentActivityContext() == null) {
+ recreateTaskbar();
+ } else {
+ // Config change might be handled without re-creating the taskbar
+ if (dp != null && !isTaskbarEnabled(dp)) {
+ destroyTaskbarForDisplay(getDefaultDisplayId());
+ } else {
+ if (dp != null && isTaskbarEnabled(dp)) {
+ if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
+ // Re-initialize for screen size change? Should this be done
+ // by looking at screen-size change flag in configDiff in the
+ // block above?
+ recreateTaskbar();
+ } else {
+ getCurrentActivityContext().updateDeviceProfile(dp);
+ }
+ }
+ getCurrentActivityContext().onConfigurationChanged(configDiff);
+ }
+ }
+ mOldConfig = new Configuration(newConfig);
+ // reset taskbar was pinned value, so we don't automatically unstash taskbar upon
+ // user unfolding the device.
+ mSharedState.setTaskbarWasPinned(false);
+ }
+
+ @Override
+ public void onLowMemory() { }
+ };
+ if (isDefaultDisplay(displayId)
+ || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
+ mPrimaryComponentCallbacks = callbacks;
+ mPrimaryWindowContext.registerComponentCallbacks(callbacks);
+ } else {
+ mComponentCallbacks.put(displayId, callbacks);
+ getWindowContext(displayId).registerComponentCallbacks(callbacks);
+ }
+ }
+
+ /**
+ * Unregister {@link ComponentCallbacks} for the given display from its WindowContext. For
+ * external displays, remove from the map.
+ * @param displayId The ID of the display.
+ */
+ private void removeAndUnregisterComponentCallbacks(int displayId) {
+ if (isDefaultDisplay(displayId)
+ || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
+ mPrimaryWindowContext.unregisterComponentCallbacks(mPrimaryComponentCallbacks);
+ } else {
+ ComponentCallbacks callbacks = mComponentCallbacks.get(displayId);
+ getWindowContext(displayId).unregisterComponentCallbacks(callbacks);
+ mComponentCallbacks.delete(displayId);
+ }
+ }
+
+ /**
+ * Creates a {@link TaskbarNavButtonController} for the given display and adds it to the map
+ * if it doesn't already exist.
+ * @param displayId The ID of the display
+ */
+ private void createNavButtonController(int displayId) {
+ if (isDefaultDisplay(displayId)
+ || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
+ mPrimaryNavButtonController = new TaskbarNavButtonController(
+ mPrimaryWindowContext,
+ mNavCallbacks,
+ SystemUiProxy.INSTANCE.get(mBaseContext),
+ new Handler(),
+ new ContextualSearchInvoker(mBaseContext));
+ } else {
+ TaskbarNavButtonController navButtonController = new TaskbarNavButtonController(
+ getWindowContext(displayId),
+ mNavCallbacks,
+ SystemUiProxy.INSTANCE.get(mBaseContext),
+ new Handler(),
+ new ContextualSearchInvoker(mBaseContext));
+ mNavButtonControllers.put(displayId, navButtonController);
+ }
+ }
+
+ private TaskbarNavButtonController getNavButtonController(int displayId) {
+ return (isDefaultDisplay(displayId)
+ || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue())
+ ? mPrimaryNavButtonController : mNavButtonControllers.get(displayId);
+ }
+
+ private void removeNavButtonController(int displayId) {
+ if (isDefaultDisplay(displayId)
+ || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
+ mPrimaryNavButtonController = null;
+ } else {
+ mNavButtonControllers.delete(displayId);
+ }
+ }
+
+ /**
* Adds the {@link TaskbarActivityContext} associated with the given display ID to taskbar
* map if there is not already a taskbar mapped to that displayId.
*
@@ -958,12 +1161,16 @@
*/
private void createTaskbarRootLayout(int displayId) {
Log.d(ILLEGAL_ARGUMENT_WM_ADD_VIEW, "createTaskbarRootLayout: " + displayId);
+ if (!enableTaskbarNoRecreate()) {
+ return;
+ }
+
FrameLayout newTaskbarRootLayout = new FrameLayout(getWindowContext(displayId)) {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// The motion events can be outside the view bounds of task bar, and hence
// manually dispatching them to the drag layer here.
- TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null && taskbar.getDragLayer().isAttachedToWindow()) {
return taskbar.getDragLayer().dispatchTouchEvent(ev);
}
@@ -1029,13 +1236,10 @@
* @param displayId The ID of the display for which to create the window context.
*/
private @Nullable Context createWindowContext(int displayId) {
- DisplayManager displayManager = mBaseContext.getSystemService(DisplayManager.class);
- if (displayManager == null) {
- return null;
- }
-
- Display display = displayManager.getDisplay(displayId);
+ debugTaskbarManager("createWindowContext: " + displayId);
+ Display display = getDisplay(displayId);
if (display == null) {
+ debugTaskbarManager("createWindowContext: display null", displayId);
return null;
}
@@ -1043,10 +1247,29 @@
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && isDefaultDisplay(displayId)) {
windowType = TYPE_NAVIGATION_BAR;
}
+ debugTaskbarManager(
+ "createWindowContext: windowType=" + ((windowType == TYPE_NAVIGATION_BAR)
+ ? "TYPE_NAVIGATION_BAR" : "TYPE_NAVIGATION_BAR_PANEL"), displayId);
return mBaseContext.createWindowContext(display, windowType, null);
}
+ private @Nullable Display getDisplay(int displayId) {
+ DisplayManager displayManager = mBaseContext.getSystemService(DisplayManager.class);
+ if (displayManager == null) {
+ debugTaskbarManager("cannot get DisplayManager", displayId);
+ return null;
+ }
+
+ Display display = displayManager.getDisplay(displayId);
+ if (display == null) {
+ debugTaskbarManager("Cannot get display!", displayId);
+ return null;
+ }
+
+ return displayManager.getDisplay(displayId);
+ }
+
/**
* Retrieves the window context of the taskbar for the specified display.
*
@@ -1054,7 +1277,8 @@
* @return The Window Context {@link Context} for a given display or {@code null}.
*/
private Context getWindowContext(int displayId) {
- return (isDefaultDisplay(displayId) || !enableTaskbarConnectedDisplays())
+ return (isDefaultDisplay(displayId)
+ || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue())
? mPrimaryWindowContext : mWindowContexts.get(displayId);
}
@@ -1070,12 +1294,15 @@
* @return The window manager {@link WindowManager} for a given display or {@code null}.
*/
private @Nullable WindowManager getWindowManager(int displayId) {
- if (isDefaultDisplay(displayId) || !enableTaskbarConnectedDisplays()) {
+ if (isDefaultDisplay(displayId)
+ || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
+ debugTaskbarManager("cannot get mPrimaryWindowManager", displayId);
return mPrimaryWindowManager;
}
Context externalDisplayContext = getWindowContext(displayId);
if (externalDisplayContext == null) {
+ debugTaskbarManager("cannot get externalDisplayContext", displayId);
return null;
}
@@ -1110,18 +1337,20 @@
}
/** Temp logs for b/254119092. */
- public void debugWhyTaskbarNotDestroyed(String debugReason) {
- debugWhyTaskbarNotDestroyed(debugReason, getDefaultDisplayId());
+ public void debugTaskbarManager(String debugReason) {
+ debugTaskbarManager(debugReason, getDefaultDisplayId());
}
/** Temp logs for b/254119092. */
- public void debugWhyTaskbarNotDestroyed(String debugReason, int displayId) {
+ public void debugTaskbarManager(String debugReason, int displayId) {
StringJoiner log = new StringJoiner("\n");
- log.add(debugReason + " displayId=" + displayId);
+ log.add(debugReason + " displayId=" + displayId + " isDefaultDisplay=" + isDefaultDisplay(
+ displayId));
boolean activityTaskbarPresent = mActivity != null
&& mActivity.getDeviceProfile().isTaskbarPresent;
- Context windowContext = getWindowContext(displayId);
+ // TODO (b/381113004): make this display-specific via getWindowContext()
+ Context windowContext = mPrimaryWindowContext;
if (windowContext == null) {
log.add("window context for displayId" + displayId);
return;
@@ -1160,6 +1389,6 @@
}
private final DeviceProfile.OnDeviceProfileChangeListener mDebugActivityDeviceProfileChanged =
- dp -> debugWhyTaskbarNotDestroyed("mActivity onDeviceProfileChanged");
+ dp -> debugTaskbarManager("mActivity onDeviceProfileChanged");
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 6016394..95724ad 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -104,6 +104,8 @@
private static final int FLAG_DELAY_TASKBAR_BG_TAG = 1 << 12;
public static final int FLAG_STASHED_FOR_BUBBLES = 1 << 13; // show handle for stashed hotseat
public static final int FLAG_TASKBAR_HIDDEN = 1 << 14; // taskbar hidden during dream, etc...
+ // taskbar should always be stashed for bubble bar on phone
+ public static final int FLAG_STASHED_BUBBLE_BAR_ON_PHONE = 1 << 15;
// If any of these flags are enabled, isInApp should return true.
private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;
@@ -126,7 +128,7 @@
// If any of these flags are enabled, the taskbar must be stashed.
private static final int FLAGS_FORCE_STASHED = FLAG_STASHED_SYSUI | FLAG_STASHED_DEVICE_LOCKED
| FLAG_STASHED_IN_TASKBAR_ALL_APPS | FLAG_STASHED_SMALL_SCREEN
- | FLAG_STASHED_FOR_BUBBLES;
+ | FLAG_STASHED_FOR_BUBBLES | FLAG_STASHED_BUBBLE_BAR_ON_PHONE;
/**
* How long to stash/unstash when manually invoked via long press.
@@ -359,6 +361,7 @@
// For now, assume we're in an app, since LauncherTaskbarUIController won't be able to tell
// us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
updateStateForFlag(FLAG_IN_APP, true);
+ updateStateForFlag(FLAG_STASHED_BUBBLE_BAR_ON_PHONE, mActivity.isBubbleBarOnPhone());
applyState(/* duration = */ 0);
@@ -574,7 +577,8 @@
*/
public void updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow,
boolean delayTaskbarBackground) {
- if (!DisplayController.isTransientTaskbar(mActivity)) {
+ if (!DisplayController.isTransientTaskbar(mActivity)
+ || mActivity.isBubbleBarOnPhone()) {
return;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt b/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt
index fa551b8..8b53ff1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt
@@ -30,7 +30,10 @@
class TypefaceUtils {
companion object {
+ const val FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED = "variable-headline-small-emphasized"
const val FONT_FAMILY_HEADLINE_LARGE_EMPHASIZED = "variable-headline-large-emphasized"
+ const val FONT_FAMILY_BODY_SMALL_BASELINE = "variable-body-small"
+ const val FONT_FAMILY_BODY_MEDIUM_BASELINE = "variable-body-medium"
const val FONT_FAMILY_LABEL_LARGE_BASELINE = "variable-label-large"
@JvmStatic
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index 249773d..97be2e8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -44,10 +44,13 @@
private val arrowVisibleHeight: Float
private val strokeAlpha: Int
+ private val strokeColor: Int
+ private val strokeColorDropTarget: Int
private val shadowAlpha: Int
private val shadowBlur: Float
private val keyShadowDistance: Float
private var arrowHeightFraction = 1f
+ private var isShowingDropTarget: Boolean = false
var arrowPositionX: Float = 0f
private set
@@ -100,7 +103,9 @@
fillPaint.flags = Paint.ANTI_ALIAS_FLAG
fillPaint.style = Paint.Style.FILL
// configure stroke paint
- strokePaint.color = context.getColor(R.color.taskbar_stroke)
+ strokeColor = context.getColor(R.color.taskbar_stroke)
+ strokeColorDropTarget = context.getColor(com.android.internal.R.color.system_primary_fixed)
+ strokePaint.color = strokeColor
strokePaint.flags = Paint.ANTI_ALIAS_FLAG
strokePaint.style = Paint.Style.STROKE
strokePaint.strokeWidth = res.getDimension(R.dimen.transient_taskbar_stroke_width)
@@ -235,9 +240,25 @@
return max(0f, getScaledArrowHeight() - (arrowHeight - arrowVisibleHeight))
}
+ /** Set whether the background should show the drop target */
+ fun showDropTarget(isDropTarget: Boolean) {
+ if (isShowingDropTarget == isDropTarget) {
+ return
+ }
+ isShowingDropTarget = isDropTarget
+ val strokeColor = if (isDropTarget) strokeColorDropTarget else strokeColor
+ val alpha = if (isDropTarget) DRAG_STROKE_ALPHA else strokeAlpha
+ strokePaint.color = strokeColor
+ strokePaint.alpha = alpha
+ invalidateSelf()
+ }
+
+ fun isShowingDropTarget() = isShowingDropTarget
+
companion object {
private const val DARK_THEME_STROKE_ALPHA = 51
private const val LIGHT_THEME_STROKE_ALPHA = 41
+ private const val DRAG_STROKE_ALPHA = 255
private const val DARK_THEME_SHADOW_ALPHA = 51
private const val LIGHT_THEME_SHADOW_ALPHA = 25
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 4e029e3..5ddbe03 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -33,7 +33,8 @@
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Log;
-import android.widget.Toast;
+
+import androidx.annotation.NonNull;
import com.android.launcher3.taskbar.TaskbarSharedState;
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
@@ -589,15 +590,24 @@
}
@Override
- public void onDragItemOverBubbleBarDragZone(BubbleBarLocation location) {
- //TODO(b/388894910): add meaningful implementation
- MAIN_EXECUTOR.execute(() ->
- Toast.makeText(mContext, "onDragItemOver " + location, Toast.LENGTH_SHORT).show());
+ public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) {
+ MAIN_EXECUTOR.execute(() -> {
+ mBubbleBarViewController.onDragItemOverBubbleBarDragZone(bubbleBarLocation);
+ if (mBubbleBarViewController.isLocationUpdatedForDropTarget()) {
+ mBubbleBarLocationListener.onBubbleBarLocationAnimated(bubbleBarLocation);
+ }
+ });
}
@Override
public void onItemDraggedOutsideBubbleBarDropZone() {
-
+ MAIN_EXECUTOR.execute(() -> {
+ if (mBubbleBarViewController.isLocationUpdatedForDropTarget()) {
+ BubbleBarLocation original = mBubbleBarViewController.getBubbleBarLocation();
+ mBubbleBarLocationListener.onBubbleBarLocationAnimated(original);
+ }
+ mBubbleBarViewController.onItemDraggedOutsideBubbleBarDropZone();
+ });
}
/** Notifies WMShell to show the expanded view. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index c001123..d43ebe2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -536,6 +536,16 @@
return (float) (displayWidth - getWidth() - margin);
}
+ /** Set whether the background should show the drop target */
+ public void showDropTarget(boolean isDropTarget) {
+ mBubbleBarBackground.showDropTarget(isDropTarget);
+ }
+
+ /** Returns whether the Bubble Bar is currently displaying a drop target. */
+ public boolean isShowingDropTarget() {
+ return mBubbleBarBackground.isShowingDropTarget();
+ }
+
/**
* Animate bubble bar to the given location transiently. Does not modify the layout or the value
* returned by {@link #getBubbleBarLocation()}.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 026f239..b90a5b0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -131,12 +131,13 @@
// Whether the bar is hidden when stashed
private boolean mHiddenForStashed;
private boolean mShouldShowEducation;
-
public boolean mOverflowAdded;
+ private boolean mIsLocationUpdatedForDropTarget = false;
private BubbleBarViewAnimator mBubbleBarViewAnimator;
private final FrameLayout mBubbleBarContainer;
private BubbleBarFlyoutController mBubbleBarFlyoutController;
+ private BubbleBarPinController mBubbleBarPinController;
private TaskbarSharedState mTaskbarSharedState;
private final TimeSource mTimeSource = System::currentTimeMillis;
private final int mTaskbarTranslationDelta;
@@ -166,6 +167,7 @@
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleBarController = bubbleControllers.bubbleBarController;
mBubbleDragController = bubbleControllers.bubbleDragController;
+ mBubbleBarPinController = bubbleControllers.bubbleBarPinController;
mTaskbarStashController = controllers.taskbarStashController;
mTaskbarInsetsController = controllers.taskbarInsetsController;
mBubbleBarFlyoutController = new BubbleBarFlyoutController(
@@ -274,7 +276,10 @@
@Override
public boolean isOnLeft() {
- return mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl());
+ boolean shouldRevertLocation =
+ mBarView.isShowingDropTarget() && mIsLocationUpdatedForDropTarget;
+ boolean isOnLeft = mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl());
+ return shouldRevertLocation != isOnLeft;
}
@Override
@@ -524,6 +529,61 @@
mBarView.animateToBubbleBarLocation(bubbleBarLocation);
}
+ /** Returns whether the Bubble Bar is currently displaying a drop target. */
+ public boolean isShowingDropTarget() {
+ return mBarView.isShowingDropTarget();
+ }
+
+ /**
+ * Notifies the controller that a drag event is over the Bubble Bar drop zone. The controller
+ * will display the appropriate drop target and enter drop target mode. The controller will also
+ * update the return value of {@link #isLocationUpdatedForDropTarget()} to true if location was
+ * updated.
+ */
+ public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) {
+ mBarView.showDropTarget(/* isDropTarget = */ true);
+ mIsLocationUpdatedForDropTarget = getBubbleBarLocation() != bubbleBarLocation;
+ if (mIsLocationUpdatedForDropTarget) {
+ animateBubbleBarLocation(bubbleBarLocation);
+ }
+ if (!hasBubbles()) {
+ mBubbleBarPinController.showDropTarget(bubbleBarLocation);
+ }
+ }
+
+ /**
+ * Returns {@code true} if location was updated after most recent
+ * {@link #onDragItemOverBubbleBarDragZone}}.
+ */
+ public boolean isLocationUpdatedForDropTarget() {
+ return mIsLocationUpdatedForDropTarget;
+ }
+
+ /**
+ * Notifies the controller that the drag event is outside the Bubble Bar drop zone.
+ * This will hide the drop target zone if there are no bubbles or return the
+ * Bubble Bar to its original location. The controller will also exit drop target
+ * mode and reset the value returned from {@link #isLocationUpdatedForDropTarget()} to false.
+ */
+ public void onItemDraggedOutsideBubbleBarDropZone() {
+ mBarView.showDropTarget(/* isDropTarget = */ false);
+ if (mIsLocationUpdatedForDropTarget) {
+ animateBubbleBarLocation(getBubbleBarLocation());
+ }
+ mBubbleBarPinController.hideDropTarget();
+ mIsLocationUpdatedForDropTarget = false;
+ }
+
+ /**
+ * Notifies the controller that the drag has completed over the Bubble Bar drop zone.
+ * The controller will hide the drop target if there are no bubbles and exit drop target mode.
+ */
+ public void onItemDroppedInBubbleBarDragZone() {
+ mBarView.showDropTarget(/* isDropTarget = */ false);
+ mBubbleBarPinController.hideDropTarget();
+ mIsLocationUpdatedForDropTarget = false;
+ }
+
/**
* The bounds of the bubble bar.
*/
@@ -996,7 +1056,12 @@
boolean isInApp = mTaskbarStashController.isInApp();
// if this is the first bubble, animate to the initial state.
if (mBarView.getBubbleChildCount() == 1 && !isUpdate) {
- mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding);
+ // If a drop target is visible and the first bubble is added, hide the empty drop target
+ if (mBarView.isShowingDropTarget()) {
+ mBubbleBarPinController.hideDropTarget();
+ }
+ mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding,
+ mBarView.isShowingDropTarget());
return;
}
// if we're not stashed or we're in persistent taskbar, animate for collapsed state.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
index 745c689..30cfafe 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -365,7 +365,12 @@
}
/** Animates to the initial state of the bubble bar, when there are no previous bubbles. */
- fun animateToInitialState(b: BubbleBarBubble, isInApp: Boolean, isExpanding: Boolean) {
+ fun animateToInitialState(
+ b: BubbleBarBubble,
+ isInApp: Boolean,
+ isExpanding: Boolean,
+ isDragging: Boolean = false,
+ ) {
val bubbleView = b.view
val animator = PhysicsAnimator.getInstance(bubbleView)
if (animator.isRunning()) animator.cancel()
@@ -374,15 +379,12 @@
// bubble bar to the handle if we're in an app.
val showAnimation = buildBubbleBarSpringInAnimation()
val hideAnimation =
- if (isInApp && !isExpanding) {
+ if (isInApp && !isExpanding && !isDragging) {
buildBubbleBarToHandleAnimation()
} else {
Runnable {
- moveToState(AnimatingBubble.State.ANIMATING_OUT)
- bubbleBarFlyoutController.collapseFlyout {
- onFlyoutRemoved()
- clearAnimatingBubble()
- }
+ collapseFlyoutAndUpdateState()
+ if (isDragging) return@Runnable
bubbleStashController.showBubbleBarImmediate()
bubbleStashController.updateTaskbarTouchRegion()
}
@@ -440,11 +442,7 @@
// first bounce the bubble bar and show the flyout. Then hide the flyout.
val showAnimation = buildBubbleBarBounceAnimation()
val hideAnimation = Runnable {
- moveToState(AnimatingBubble.State.ANIMATING_OUT)
- bubbleBarFlyoutController.collapseFlyout {
- onFlyoutRemoved()
- clearAnimatingBubble()
- }
+ collapseFlyoutAndUpdateState()
bubbleStashController.showBubbleBarImmediate()
bubbleStashController.updateTaskbarTouchRegion()
}
@@ -454,6 +452,14 @@
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
}
+ private fun collapseFlyoutAndUpdateState() {
+ moveToState(AnimatingBubble.State.ANIMATING_OUT)
+ bubbleBarFlyoutController.collapseFlyout {
+ onFlyoutRemoved()
+ clearAnimatingBubble()
+ }
+ }
+
/**
* The bubble bar animation when it is collapsed is divided into 2 chained animations. The first
* animation is a regular accelerate animation that moves the bubble bar upwards. When it ends
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 37b8dc7..15a27d1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -17,6 +17,7 @@
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
+import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -53,7 +54,6 @@
import com.android.launcher3.celllayout.DelegatedCellDrawing;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.FastBitmapDrawable;
-import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -138,7 +138,7 @@
public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDeviceProfile = ActivityContext.lookupContext(context).getDeviceProfile();
- mNormalizedIconSize = IconNormalizer.getNormalizedCircleSize(getIconSize());
+ mNormalizedIconSize = Math.round(getIconSize() * ICON_VISIBLE_AREA_FACTOR);
int shadowSize = context.getResources().getDimensionPixelSize(
R.dimen.blur_size_thin_outline);
mShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.OUTER);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index bb57d6e..019e746 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -1380,7 +1380,7 @@
SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
if (taskbarManager != null) {
- taskbarManager.debugWhyTaskbarNotDestroyed("QuickstepLauncher#onDeviceProfileChanged");
+ taskbarManager.debugTaskbarManager("QuickstepLauncher#onDeviceProfileChanged");
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
index 6e901ee..1f34969 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -144,7 +144,7 @@
override fun isNonResizeableActivity(lai: LauncherActivityInfo) =
lai.activityInfo.resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE
- override fun supportsMultiInstance(lai: LauncherActivityInfo) : Boolean {
+ override fun supportsMultiInstance(lai: LauncherActivityInfo): Boolean {
return try {
super.supportsMultiInstance(lai) || lai.supportsMultiInstance()
} catch (e: Exception) {
@@ -202,4 +202,7 @@
(appInfo.sourceDir?.hashCode() ?: 0).toString() + " " + appInfo.longVersionCode
override fun getRoundIconRes(appInfo: ApplicationInfo) = appInfo.roundIconRes
+
+ override fun isFileDrawable(shortcutInfo: ShortcutInfo) =
+ shortcutInfo.hasIconFile() || shortcutInfo.hasIconUri()
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 10513c0..b27c6e8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -153,7 +153,7 @@
return new PageAlphaProvider(DECELERATE_2) {
@Override
public float getPageAlpha(int pageIndex) {
- return launcher.getDeviceProfile().shouldShowAllAppsOnSheet()
+ return launcher.getDeviceProfile().isTablet
? superPageAlphaProvider.getPageAlpha(pageIndex)
: 0;
}
@@ -164,7 +164,7 @@
public int getVisibleElements(Launcher launcher) {
int elements = ALL_APPS_CONTENT | FLOATING_SEARCH_BAR;
// When All Apps is presented on a bottom sheet, HOTSEAT_ICONS are visible.
- if (launcher.getDeviceProfile().shouldShowAllAppsOnSheet()) {
+ if (launcher.getDeviceProfile().isTablet) {
elements |= HOTSEAT_ICONS;
}
return elements;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 05d12c3..58b274a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -64,6 +64,7 @@
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
+import android.window.DesktopModeFlags;
import com.android.internal.jank.Cuj;
import com.android.launcher3.LauncherState;
@@ -86,7 +87,6 @@
import com.android.quickstep.util.WorkspaceRevealAnim;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.window.flags.Flags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
/**
@@ -188,7 +188,7 @@
}
if (DesktopModeStatus.canEnterDesktopMode(mLauncher)
//TODO(b/345296916): replace with dev option once in teamfood
- && Flags.enableQuickswitchDesktopSplitBugfix()
+ && DesktopModeFlags.ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX.isTrue()
&& mRecentsView.getNonDesktopTaskViewCount() < 1) {
return false;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
index 98737a5..88b7155 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
@@ -16,9 +16,11 @@
package com.android.launcher3.uioverrides.touchcontrollers
import android.content.Context
+import android.graphics.Rect
import android.view.MotionEvent
import androidx.dynamicanimation.animation.SpringAnimation
import com.android.app.animation.Interpolators.DECELERATE
+import com.android.app.animation.Interpolators.LINEAR
import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.R
import com.android.launcher3.Utilities.EDGE_NAV_BAR
@@ -29,6 +31,7 @@
import com.android.launcher3.util.MSDLPlayerWrapper
import com.android.launcher3.util.TouchController
import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY
import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.TaskView
import com.google.android.msdl.data.model.MSDLToken
@@ -50,6 +53,7 @@
recentsView.pagedOrientationHandler.upDownSwipeDirection,
)
private val isRtl = isRtl(container.resources)
+ private val tempTaskThumbnailBounds = Rect()
private var taskBeingDragged: TaskView? = null
private var springAnimation: SpringAnimation? = null
@@ -57,6 +61,7 @@
private var verticalFactor: Int = 0
private var hasDismissThresholdHapticRun = false
private var initialDisplacement: Float = 0f
+ private var recentsScaleAnimation: SpringAnimation? = null
private fun canInterceptTouch(ev: MotionEvent): Boolean =
when {
@@ -98,6 +103,7 @@
private fun onActionDown(ev: MotionEvent): Boolean {
springAnimation?.cancel()
+ recentsScaleAnimation?.cancel()
if (!canInterceptTouch(ev)) {
return false
}
@@ -108,7 +114,9 @@
recentsView.isTaskViewVisible(it) && container.dragLayer.isEventOverView(it, ev)
}
?.also {
- dismissLength = recentsView.pagedOrientationHandler.getSecondaryDimension(it)
+ // Dismiss length as bottom of task so it is fully off screen when dismissed.
+ it.getThumbnailBounds(tempTaskThumbnailBounds, relativeToDragLayer = true)
+ dismissLength = tempTaskThumbnailBounds.bottom
verticalFactor =
recentsView.pagedOrientationHandler.secondaryTranslationDirectionFactor
}
@@ -162,6 +170,8 @@
}
recentsView.redrawLiveTile()
}
+ val dismissFraction = displacement / (dismissLength * verticalFactor).toFloat()
+ RECENTS_SCALE_PROPERTY.setValue(recentsView, getRecentsScale(dismissFraction))
playDismissThresholdHaptic(displacement)
return true
}
@@ -216,6 +226,10 @@
if (isDismissing) (dismissLength * verticalFactor).toFloat() else 0f
)
}
+ recentsScaleAnimation =
+ recentsView.animateRecentsScale(RECENTS_SCALE_DEFAULT).addEndListener { _, _, _, _ ->
+ recentsScaleAnimation = null
+ }
}
// Returns if the current task being dragged is towards "positive" (e.g. dismissal).
@@ -230,8 +244,54 @@
springAnimation = null
}
+ private fun getRecentsScale(dismissFraction: Float): Float {
+ return when {
+ // Do not scale recents when dragging below origin.
+ dismissFraction <= 0 -> {
+ RECENTS_SCALE_DEFAULT
+ }
+ // Initially scale recents as the drag begins, up to the first threshold.
+ dismissFraction < RECENTS_SCALE_FIRST_THRESHOLD_FRACTION -> {
+ mapToRange(
+ dismissFraction,
+ 0f,
+ RECENTS_SCALE_FIRST_THRESHOLD_FRACTION,
+ RECENTS_SCALE_DEFAULT,
+ RECENTS_SCALE_ON_DISMISS_CANCEL,
+ LINEAR,
+ )
+ }
+ // Keep scale consistent until dragging to the dismiss threshold.
+ dismissFraction < RECENTS_SCALE_DISMISS_THRESHOLD_FRACTION -> {
+ RECENTS_SCALE_ON_DISMISS_CANCEL
+ }
+ // Scale beyond the dismiss threshold again, to indicate dismiss will occur on release.
+ dismissFraction < RECENTS_SCALE_SECOND_THRESHOLD_FRACTION -> {
+ mapToRange(
+ dismissFraction,
+ RECENTS_SCALE_DISMISS_THRESHOLD_FRACTION,
+ RECENTS_SCALE_SECOND_THRESHOLD_FRACTION,
+ RECENTS_SCALE_ON_DISMISS_CANCEL,
+ RECENTS_SCALE_ON_DISMISS_SUCCESS,
+ LINEAR,
+ )
+ }
+ // Keep scale beyond the dismiss threshold scaling consistent.
+ else -> {
+ RECENTS_SCALE_ON_DISMISS_SUCCESS
+ }
+ }
+ }
+
companion object {
private const val DISMISS_THRESHOLD_FRACTION = 0.5f
private const val DISMISS_THRESHOLD_HAPTIC_RANGE = 10f
+
+ private const val RECENTS_SCALE_ON_DISMISS_CANCEL = 0.9875f
+ private const val RECENTS_SCALE_ON_DISMISS_SUCCESS = 0.975f
+ private const val RECENTS_SCALE_DEFAULT = 1f
+ private const val RECENTS_SCALE_FIRST_THRESHOLD_FRACTION = 0.2f
+ private const val RECENTS_SCALE_DISMISS_THRESHOLD_FRACTION = 0.5f
+ private const val RECENTS_SCALE_SECOND_THRESHOLD_FRACTION = 0.575f
}
}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index b43c3ac..3640e1f 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1112,6 +1112,9 @@
public void onGestureEnded(float endVelocityPxPerMs, PointF velocityPxPerMs) {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_speed);
+ Log.d(TAG, "onGestureEnded: mGestureStarted=" + mGestureStarted
+ + ", mIsMotionPaused=" + mIsMotionPaused
+ + ", flingThresholdPassed=" + (Math.abs(endVelocityPxPerMs) > flingThreshold));
boolean isFling = mGestureStarted && !mIsMotionPaused
&& Math.abs(endVelocityPxPerMs) > flingThreshold;
mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
@@ -1260,12 +1263,12 @@
dpiFromPx(velocityPxPerMs.x),
dpiFromPx(velocityPxPerMs.y),
Math.toDegrees(Math.atan2(-velocityPxPerMs.y, velocityPxPerMs.x)));
-
if (mGestureState.isHandlingAtomicEvent()) {
// Button mode, this is only used to go to recents.
return RECENTS;
}
+ Log.d(TAG, "calculateEndTarget: isCancel=" + isCancel + ", isFlingY=" + isFlingY);
GestureEndTarget endTarget;
if (isCancel) {
endTarget = LAST_TASK;
@@ -1275,6 +1278,7 @@
endTarget = calculateEndTargetForNonFling(velocityPxPerMs);
}
+ Log.d(TAG, "calculateEndTarget: endTarget(1)=" + endTarget);
if (mDeviceState.isOverviewDisabled() && endTarget == RECENTS) {
return LAST_TASK;
}
@@ -1293,6 +1297,7 @@
return LAST_TASK;
}
}
+ Log.d(TAG, "calculateEndTarget: endTarget(2)=" + endTarget);
return endTarget;
}
@@ -1301,9 +1306,12 @@
final boolean willGoToNewTask =
isScrollingToNewTask() && Math.abs(velocity.x) > Math.abs(endVelocity);
final boolean isSwipeUp = endVelocity < 0;
+ Log.d(TAG, "calculateEndTargetForFlingY: willGoToNewTask=" + willGoToNewTask
+ + ", isSwipeUp=" + isSwipeUp);
if (!isSwipeUp) {
final boolean isCenteredOnNewTask = mRecentsView != null
&& mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
+ Log.d(TAG, "calculateEndTargetForFlingY: isCenteredOnNewTask=" + isCenteredOnNewTask);
return willGoToNewTask || isCenteredOnNewTask ? NEW_TASK : LAST_TASK;
}
@@ -1316,6 +1324,9 @@
// Fully gestural mode.
final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_speed);
+ Log.d(TAG, "calculateEndTargetForNonFling: isScrollingToNewTask=" + isScrollingToNewTask
+ + ", isFlingX=" + isFlingX
+ + ", mIsMotionPaused=" + mIsMotionPaused);
if (isScrollingToNewTask && isFlingX) {
// Flinging towards new task takes precedence over mIsMotionPaused (which only
// checks y-velocity).
@@ -1325,6 +1336,7 @@
} else if (isScrollingToNewTask) {
return NEW_TASK;
}
+ Log.d(TAG, "calculateEndTargetForNonFling: mCanSlowSwipeGoHome=" + mCanSlowSwipeGoHome);
return velocity.y < 0 && mCanSlowSwipeGoHome ? HOME : LAST_TASK;
}
diff --git a/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt b/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt
index bafb0b2..444e77d 100644
--- a/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt
+++ b/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt
@@ -17,7 +17,7 @@
package com.android.quickstep
import android.content.Context
-import com.android.systemui.shared.system.QuickStepContract
+import com.android.launcher3.R
// DesktopTaskView thumbnail's corner radius is independent of fullscreenProgress.
open class DesktopFullscreenDrawParams
@@ -28,6 +28,6 @@
// computeCornerRadius is used as cornerRadiusProvider, so
// QuickStepContract::getWindowCornerRadius can be mocked properly.
private fun computeCornerRadius(context: Context): Float =
- QuickStepContract.getWindowCornerRadius(context)
+ context.resources.getDimension(R.dimen.desktop_windowing_freeform_rounded_corner_radius)
}
}
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index be0a339..783ec2c 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -514,7 +514,13 @@
private void finishAnimation() {
mLauncher.setPredictiveBackToHomeInProgress(false);
+ if (mBackTarget != null && mBackTarget.leash.isValid()) {
+ mBackTarget.leash.release();
+ }
mBackTarget = null;
+ if (mLauncherTarget != null && mLauncherTarget.leash.isValid()) {
+ mLauncherTarget.leash.release();
+ }
mLauncherTarget = null;
mBackInProgress = false;
mBackProgress = 0;
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index 42aa86e..afdb403 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -293,10 +293,6 @@
val recentsView: RecentsView<*, *>? = recentsViewContainer?.getOverviewPanel()
val deviceProfile = recentsViewContainer?.getDeviceProfile()
val uiController = containerInterface.getTaskbarController()
- val allowQuickSwitch =
- uiController != null &&
- deviceProfile != null &&
- (deviceProfile.isTablet || deviceProfile.isTwoPanels)
val focusedDisplayId = focusState.focusedDisplayId
val focusedDisplayUIController: TaskbarUIController? =
@@ -322,26 +318,26 @@
when (command.type) {
HIDE -> {
- if (!allowQuickSwitch) return true
+ if (uiController == null || deviceProfile?.isTablet == false) return true
keyboardTaskFocusIndex =
if (
enableAltTabKqsOnConnectedDisplays() && focusedDisplayUIController != null
) {
focusedDisplayUIController.launchFocusedTask()
} else {
- uiController!!.launchFocusedTask()
+ uiController.launchFocusedTask()
}
if (keyboardTaskFocusIndex == -1) return true
}
KEYBOARD_INPUT ->
- if (allowQuickSwitch) {
+ if (uiController != null && deviceProfile?.isTablet == true) {
if (
enableAltTabKqsOnConnectedDisplays() && focusedDisplayUIController != null
) {
focusedDisplayUIController.openQuickSwitchView()
} else {
- uiController!!.openQuickSwitchView()
+ uiController.openQuickSwitchView()
}
return true
} else {
@@ -365,7 +361,11 @@
TOGGLE -> {}
}
- recentsView?.setKeyboardTaskFocusIndex(keyboardTaskFocusIndex)
+ recentsView?.setKeyboardTaskFocusIndex(
+ recentsView.indexOfChild(recentsView.taskViews.elementAtOrNull(keyboardTaskFocusIndex))
+ ?: -1
+ )
+
// Handle recents view focus when launching from home
val animatorListener: Animator.AnimatorListener =
object : AnimatorListenerAdapter() {
@@ -526,7 +526,7 @@
// Stops requesting focused after first view gets focused.
recentsView.getTaskViewAt(keyboardTaskFocusIndex).requestFocus() ||
recentsView.nextTaskView.requestFocus() ||
- recentsView.getFirstTaskView().requestFocus() ||
+ recentsView.firstTaskView.requestFocus() ||
recentsView.requestFocus()
}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 9810308..cb11afa 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -286,8 +286,32 @@
mCallbacks.addListener(listener);
final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
+ options.setTransientLaunch();
+ options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
- // TODO:(b/365777482) if flag is enabled, but on launcher it will crash.
+ // Notify taskbar that we should skip reacting to launcher visibility change to
+ // avoid a jumping taskbar.
+ TaskbarUIController taskbarUIController = containerInterface.getTaskbarController();
+ if (enableScalingRevealHomeAnimation() && taskbarUIController != null) {
+ taskbarUIController.setSkipLauncherVisibilityChange(true);
+
+ mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
+ @Override
+ public void onRecentsAnimationCanceled(
+ @NonNull HashMap<Integer, ThumbnailData> thumbnailDatas) {
+ taskbarUIController.setSkipLauncherVisibilityChange(false);
+ }
+
+ @Override
+ public void onRecentsAnimationFinished(
+ @NonNull RecentsAnimationController controller) {
+ taskbarUIController.setSkipLauncherVisibilityChange(false);
+ }
+ });
+ }
+
if(containerInterface.getCreatedContainer() instanceof RecentsWindowManager
&& (Flags.enableFallbackOverviewInWindow()
|| Flags.enableLauncherOverviewInWindow())) {
@@ -297,32 +321,6 @@
.getRecentsWindowManager(mDeviceState.getDisplayId())
.startRecentsWindow(mCallbacks);
} else {
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
- options.setTransientLaunch();
- options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
-
- // Notify taskbar that we should skip reacting to launcher visibility change to
- // avoid a jumping taskbar.
- TaskbarUIController taskbarUIController = containerInterface.getTaskbarController();
- if (enableScalingRevealHomeAnimation() && taskbarUIController != null) {
- taskbarUIController.setSkipLauncherVisibilityChange(true);
-
- mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
- @Override
- public void onRecentsAnimationCanceled(
- @NonNull HashMap<Integer, ThumbnailData> thumbnailDatas) {
- taskbarUIController.setSkipLauncherVisibilityChange(false);
- }
-
- @Override
- public void onRecentsAnimationFinished(
- @NonNull RecentsAnimationController controller) {
- taskbarUIController.setSkipLauncherVisibilityChange(false);
- }
- });
- }
-
mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent,
options, mCallbacks, false /* useSyntheticRecentsTransition */);
}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 35a6e72..37c2d1c 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -38,6 +38,7 @@
import static com.android.launcher3.QuickstepTransitionManager.SPLIT_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
+import static com.android.quickstep.BaseContainerInterface.getTaskDimension;
import static com.android.quickstep.util.AnimUtils.clampToDuration;
import static com.android.wm.shell.shared.TransitionUtil.TYPE_SPLIT_SCREEN_DIM_LAYER;
@@ -65,6 +66,7 @@
import com.android.app.animation.Interpolators;
import com.android.internal.jank.Cuj;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -102,6 +104,11 @@
private TaskViewUtils() {}
+ private static final Rect TEMP_THUMBNAIL_BOUNDS = new Rect();
+ private static final Rect TEMP_FULLSCREEN_BOUNDS = new Rect();
+ private static final PointF TEMP_TASK_DIMENSION = new PointF();
+ private static final PointF TEMP_PIVOT = new PointF();
+
/**
* Try to find a TaskView that corresponds with the component of the launched view.
*
@@ -164,7 +171,7 @@
public static <T extends Context & RecentsViewContainer & StatefulContainer<?>>
void createRecentsWindowAnimator(
@NonNull RecentsView<T, ?> recentsView,
- @NonNull TaskView v,
+ @NonNull TaskView taskView,
boolean skipViewChanges,
@NonNull RemoteAnimationTarget[] appTargets,
@NonNull RemoteAnimationTarget[] wallpaperTargets,
@@ -172,31 +179,31 @@
@Nullable DepthController depthController,
@Nullable TransitionInfo transitionInfo,
PendingAnimation out) {
- boolean isQuickSwitch = v.isEndQuickSwitchCuj();
- v.setEndQuickSwitchCuj(false);
+ boolean isQuickSwitch = taskView.isEndQuickSwitchCuj();
+ taskView.setEndQuickSwitchCuj(false);
final RemoteAnimationTargets targets =
new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
MODE_OPENING);
final RemoteAnimationTarget navBarTarget = targets.getNavBarRemoteAnimationTarget();
- SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
+ SurfaceTransactionApplier applier = new SurfaceTransactionApplier(taskView);
targets.addReleaseCheck(applier);
RemoteTargetHandle[] remoteTargetHandles;
RemoteTargetHandle[] recentsViewHandles = recentsView.getRemoteTargetHandles();
- if (v.isRunningTask() && recentsViewHandles != null) {
+ if (taskView.isRunningTask() && recentsViewHandles != null) {
// Re-use existing handles
remoteTargetHandles = recentsViewHandles;
} else {
- boolean forDesktop = v instanceof DesktopTaskView;
- RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(),
+ boolean forDesktop = taskView instanceof DesktopTaskView;
+ RemoteTargetGluer gluer = new RemoteTargetGluer(taskView.getContext(),
recentsView.getSizeStrategy(), targets, forDesktop);
if (forDesktop) {
remoteTargetHandles = gluer.assignTargetsForDesktop(targets, transitionInfo);
- } else if (v.containsMultipleTasks()) {
+ } else if (taskView.containsMultipleTasks()) {
remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets,
- ((GroupedTaskView) v).getSplitBoundsConfig());
+ ((GroupedTaskView) taskView).getSplitBoundsConfig());
} else {
remoteTargetHandles = gluer.assignTargets(targets);
}
@@ -210,8 +217,8 @@
remoteTargetHandle.getTransformParams().setSyncTransactionApplier(applier);
}
- int taskIndex = recentsView.indexOfChild(v);
- Context context = v.getContext();
+ int taskIndex = recentsView.indexOfChild(taskView);
+ Context context = taskView.getContext();
T container = RecentsViewContainer.containerFromContext(context);
DeviceProfile dp = container.getDeviceProfile();
@@ -219,11 +226,11 @@
boolean parallaxCenterAndAdjacentTask =
!showAsGrid && taskIndex != recentsView.getCurrentPage();
int taskRectTranslationPrimary = recentsView.getScrollOffset(taskIndex);
- int taskRectTranslationSecondary = showAsGrid ? (int) v.getGridTranslationY() : 0;
+ int taskRectTranslationSecondary = showAsGrid ? (int) taskView.getGridTranslationY() : 0;
RemoteTargetHandle[] topMostSimulators = null;
- if (!v.isRunningTask()) {
+ if (!taskView.isRunningTask()) {
// TVSs already initialized from the running task, no need to re-init
for (RemoteTargetHandle targetHandle : remoteTargetHandles) {
TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator();
@@ -237,13 +244,13 @@
tvsLocal.fullScreenProgress.value = 0;
tvsLocal.recentsViewScale.value = 1;
if (!enableGridOnlyOverview()) {
- tvsLocal.setIsGridTask(v.isGridTask());
+ tvsLocal.setIsGridTask(taskView.isGridTask());
}
tvsLocal.getOrientationState().getOrientationHandler().set(tvsLocal,
TaskViewSimulator::setTaskRectTranslation, taskRectTranslationPrimary,
taskRectTranslationSecondary);
- if (v instanceof DesktopTaskView) {
+ if (taskView instanceof DesktopTaskView) {
targetHandle.getTransformParams().setTargetAlpha(1f);
} else {
// Fade in the task during the initial 20% of the animation
@@ -260,8 +267,11 @@
out.setFloat(tvsLocal.recentsViewScale,
AnimatedFloat.VALUE, tvsLocal.getFullScreenScale(),
TOUCH_RESPONSE);
- out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0,
- TOUCH_RESPONSE);
+ if (!enableGridOnlyOverview()) {
+ out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0,
+ TOUCH_RESPONSE);
+ }
+
out.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -271,6 +281,18 @@
showTransaction.getTransaction().show(targets.apps[i].leash);
}
applier.scheduleApply(showTransaction);
+
+ if (enableGridOnlyOverview()) {
+ taskView.getThumbnailBounds(TEMP_THUMBNAIL_BOUNDS, /*relativeToDragLayer=*/
+ true);
+ getTaskDimension(context, container.getDeviceProfile(),
+ TEMP_TASK_DIMENSION);
+ TEMP_FULLSCREEN_BOUNDS.set(0, 0, (int) TEMP_TASK_DIMENSION.x,
+ (int) TEMP_TASK_DIMENSION.y);
+ Utilities.getPivotsForScalingRectToRect(TEMP_THUMBNAIL_BOUNDS,
+ TEMP_FULLSCREEN_BOUNDS, TEMP_PIVOT);
+ tvsLocal.setPivotOverride(TEMP_PIVOT);
+ }
}
});
out.addOnFrameCallback(() -> {
@@ -321,7 +343,7 @@
if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulators != null
&& topMostSimulators.length > 0) {
- out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
+ out.addFloat(taskView, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
RemoteTargetHandle[] simulatorCopies = topMostSimulators;
for (RemoteTargetHandle handle : simulatorCopies) {
@@ -340,7 +362,7 @@
// During animation we apply transformation on the thumbnailView (and not the rootView)
// to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
// Mt K(0)` K(t) Mt`
- View[] thumbnails = v.getSnapshotViews();
+ View[] thumbnails = taskView.getSnapshotViews();
// In case simulator copies and thumbnail size do no match, ensure we get the lesser.
// This ensures we do not create arrays with empty elements or attempt to references
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
index 047658c..cd48136 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
@@ -61,9 +61,13 @@
return dragLayer
}
+ fun initDeviceProfile() {
+ deviceProfile = InvariantDeviceProfile.INSTANCE[this].getDeviceProfile(this)
+ }
+
override fun getDeviceProfile(): DeviceProfile {
if (deviceProfile == null) {
- deviceProfile = InvariantDeviceProfile.INSTANCE[this].getDeviceProfile(this).copy(this)
+ initDeviceProfile()
}
return deviceProfile!!
}
@@ -79,7 +83,10 @@
* @param type The window type to pass to the created WindowManager.LayoutParams.
* @param title The window title to pass to the created WindowManager.LayoutParams.
*/
- fun createDefaultWindowLayoutParams(type: Int, title: String): WindowManager.LayoutParams {
+ private fun createDefaultWindowLayoutParams(
+ type: Int,
+ title: String,
+ ): WindowManager.LayoutParams {
var windowFlags =
(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
index 07288d8..1f4961a 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
@@ -21,6 +21,7 @@
import android.content.ComponentName
import android.content.Context
import android.content.LocusId
+import android.content.res.Configuration
import android.os.Bundle
import android.view.KeyEvent
import android.view.LayoutInflater
@@ -32,6 +33,7 @@
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
import android.window.RemoteTransition
+import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.BaseActivity
import com.android.launcher3.LauncherAnimationRunner
import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory
@@ -135,73 +137,6 @@
listOf(RunnableList(), RunnableList(), RunnableList(), RunnableList())
private var onInitListener: Predicate<Boolean>? = null
- private val taskStackChangeListener =
- object : TaskStackChangeListener {
- override fun onTaskMovedToFront(taskId: Int) {
- if ((isShowing() && isInState(DEFAULT))) {
- // handling state where we end recents animation by swiping livetile away
- // TODO: animate this switch.
- cleanupRecentsWindow()
- }
- }
- }
-
- private val recentsAnimationListener =
- object : RecentsAnimationListener {
- override fun onRecentsAnimationCanceled(thumbnailDatas: HashMap<Int, ThumbnailData>) {
- recentAnimationStopped()
- }
-
- override fun onRecentsAnimationFinished(controller: RecentsAnimationController) {
- recentAnimationStopped()
- }
- }
-
- init {
- TaskStackChangeListeners.getInstance().registerTaskStackListener(taskStackChangeListener)
- }
-
- override fun destroy() {
- super.destroy()
- cleanupRecentsWindow()
- TaskStackChangeListeners.getInstance().unregisterTaskStackListener(taskStackChangeListener)
- callbacks?.removeListener(recentsAnimationListener)
- recentsWindowTracker.onContextDestroyed(this)
- recentsView?.destroy()
- }
-
- override fun startHome() {
- startHome(/* finishRecentsAnimation= */ true)
- }
-
- fun startHome(finishRecentsAnimation: Boolean) {
- val recentsView: RecentsView<*, *> = getOverviewPanel()
-
- if (!finishRecentsAnimation) {
- recentsView.switchToScreenshot(/* onFinishRunnable= */ null)
- startHomeInternal()
- return
- }
- recentsView.switchToScreenshot {
- recentsView.finishRecentsAnimation(/* toRecents= */ true) { startHomeInternal() }
- }
- }
-
- private fun startHomeInternal() {
- val runner = LauncherAnimationRunner(mainThreadHandler, animationToHomeFactory, true)
- val options =
- ActivityOptions.makeRemoteAnimation(
- RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0),
- RemoteTransition(
- runner.toRemoteTransition(),
- iApplicationThread,
- "StartHomeFromRecents",
- ),
- )
- OverviewComponentObserver.startHomeIntentSafely(this, options.toBundle(), TAG)
- stateManager.moveToRestState()
- }
-
private val animationToHomeFactory =
RemoteAnimationFactory {
_: Int,
@@ -229,7 +164,7 @@
anim,
this@RecentsWindowManager,
{
- getStateManager().goToState(BG_LAUNCHER, false)
+ getStateManager().goToState(BG_LAUNCHER, true)
cleanupRecentsWindow()
},
true, /* skipFirstFrame */
@@ -242,17 +177,49 @@
TestLogging.recordEvent(SEQUENCE_MAIN, "onBackInvoked")
}
- private fun cleanupRecentsWindow() {
- RecentsWindowProtoLogProxy.logCleanup(isShowing())
- if (isShowing()) {
- windowManager.removeViewImmediate(windowView)
+ private val taskStackChangeListener =
+ object : TaskStackChangeListener {
+ override fun onTaskMovedToFront(taskId: Int) {
+ if ((isShowing() && isInState(DEFAULT))) {
+ // handling state where we end recents animation by swiping livetile away
+ // TODO: animate this switch.
+ cleanupRecentsWindow()
+ }
+ }
}
- stateManager.moveToRestState()
- callbacks?.removeListener(recentsAnimationListener)
+
+ private val recentsAnimationListener =
+ object : RecentsAnimationListener {
+ override fun onRecentsAnimationCanceled(thumbnailDatas: HashMap<Int, ThumbnailData>) {
+ recentAnimationStopped()
+ }
+
+ override fun onRecentsAnimationFinished(controller: RecentsAnimationController) {
+ recentAnimationStopped()
+ }
+ }
+
+ init {
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(taskStackChangeListener)
}
- private fun isShowing(): Boolean {
- return windowView?.parent != null
+ override fun handleConfigurationChanged(configuration: Configuration?) {
+ initDeviceProfile()
+ AbstractFloatingView.closeOpenViews(
+ this,
+ true,
+ AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv(),
+ )
+ dispatchDeviceProfileChanged()
+ }
+
+ override fun destroy() {
+ super.destroy()
+ cleanupRecentsWindow()
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(taskStackChangeListener)
+ callbacks?.removeListener(recentsAnimationListener)
+ recentsWindowTracker.onContextDestroyed(this)
+ recentsView?.destroy()
}
fun startRecentsWindow(callbacks: RecentsAnimationCallbacks? = null) {
@@ -301,6 +268,51 @@
callbacks?.addListener(recentsAnimationListener)
}
+ override fun startHome() {
+ startHome(/* finishRecentsAnimation= */ true)
+ }
+
+ fun startHome(finishRecentsAnimation: Boolean) {
+ val recentsView: RecentsView<*, *> = getOverviewPanel()
+
+ if (!finishRecentsAnimation) {
+ recentsView.switchToScreenshot /* onFinishRunnable= */ {}
+ startHomeInternal()
+ return
+ }
+ recentsView.switchToScreenshot {
+ recentsView.finishRecentsAnimation(/* toRecents= */ true) { startHomeInternal() }
+ }
+ }
+
+ private fun startHomeInternal() {
+ val runner = LauncherAnimationRunner(mainThreadHandler, animationToHomeFactory, true)
+ val options =
+ ActivityOptions.makeRemoteAnimation(
+ RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0),
+ RemoteTransition(
+ runner.toRemoteTransition(),
+ iApplicationThread,
+ "StartHomeFromRecents",
+ ),
+ )
+ OverviewComponentObserver.startHomeIntentSafely(this, options.toBundle(), TAG)
+ stateManager.moveToRestState()
+ }
+
+ private fun cleanupRecentsWindow() {
+ RecentsWindowProtoLogProxy.logCleanup(isShowing())
+ if (isShowing()) {
+ windowManager.removeViewImmediate(windowView)
+ }
+ stateManager.moveToRestState()
+ callbacks?.removeListener(recentsAnimationListener)
+ }
+
+ private fun isShowing(): Boolean {
+ return windowView?.parent != null
+ }
+
private fun recentAnimationStopped() {
if (isInState(BACKGROUND_APP)) {
cleanupRecentsWindow()
@@ -352,7 +364,6 @@
override fun onStateSetEnd(state: RecentsState) {
super.onStateSetEnd(state)
RecentsWindowProtoLogProxy.logOnStateSetEnd(getStateName(state))
-
if (state == HOME || state == BG_LAUNCHER) {
cleanupRecentsWindow()
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index afe988d..a703c23 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DEEP_PRESS_STASHED_TASKBAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_NAVBAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_STASHED_TASKBAR;
+import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.LogConfig.NAV_HANDLE_LONG_PRESS;
@@ -30,6 +31,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Utilities;
+import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.DeviceConfigWrapper;
@@ -49,6 +51,8 @@
private static final String TAG = "NavHandleLongPressIC";
private static final boolean DEBUG_NAV_HANDLE = Utilities.isPropertyEnabled(
NAV_HANDLE_LONG_PRESS);
+ // Minimum time between touch down and abandon to log.
+ @VisibleForTesting static final long MIN_TIME_TO_LOG_ABANDON_MS = 200;
private NavHandleLongPressHandler mNavHandleLongPressHandler;
private final float mNavHandleWidth;
@@ -62,11 +66,12 @@
private final int mOuterLongPressTimeout;
private final boolean mDeepPressEnabled;
private final NavHandle mNavHandle;
- private final StatsLogManager mStatsLogManager;
+ private StatsLogManager mStatsLogManager;
private final TopTaskTracker mTopTaskTracker;
private final GestureState mGestureState;
- private MotionEvent mCurrentDownEvent;
+ private MotionEvent mCurrentDownEvent; // Down event that started the current gesture.
+ private MotionEvent mCurrentMotionEvent; // Most recent motion event.
private boolean mDeepPressLogged; // Whether deep press has been logged for the current touch.
public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
@@ -125,6 +130,10 @@
@Override
public void onMotionEvent(MotionEvent ev) {
+ if (mCurrentMotionEvent != null) {
+ mCurrentMotionEvent.recycle();
+ }
+ mCurrentMotionEvent = MotionEvent.obtain(ev);
if (mDelegate.allowInterceptByParent()) {
handleMotionEvent(ev);
} else if (MAIN_EXECUTOR.getHandler().hasCallbacks(mTriggerLongPress)) {
@@ -244,6 +253,15 @@
if (DEBUG_NAV_HANDLE) {
Log.d(TAG, "cancelLongPress: " + reason);
}
+ // Log LPNH abandon latency if we didn't trigger but were still prepared to.
+ long latencyMs = mCurrentMotionEvent.getEventTime() - mCurrentDownEvent.getEventTime();
+ if (mState != STATE_ACTIVE && MAIN_EXECUTOR.getHandler().hasCallbacks(mTriggerLongPress)
+ && latencyMs >= MIN_TIME_TO_LOG_ABANDON_MS) {
+ mStatsLogManager.latencyLogger()
+ .withInstanceId(new InstanceIdSequence().newInstanceId())
+ .withLatency(latencyMs)
+ .log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
+ }
mGestureState.setIsInExtendedSlopRegion(false);
MAIN_EXECUTOR.getHandler().removeCallbacks(mTriggerLongPress);
mNavHandleLongPressHandler.onTouchFinished(mNavHandle, reason);
@@ -274,4 +292,9 @@
void setNavHandleLongPressHandler(NavHandleLongPressHandler navHandleLongPressHandler) {
mNavHandleLongPressHandler = navHandleLongPressHandler;
}
+
+ @VisibleForTesting
+ void setStatsLogManager(StatsLogManager statsLogManager) {
+ mStatsLogManager = statsLogManager;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index c986b88..953b0c5 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -92,6 +92,8 @@
private static final String LOG_TAG = "AllSetActivity";
private static final String URI_SYSTEM_NAVIGATION_SETTING =
"#Intent;action=com.android.settings.SEARCH_RESULT_TRAMPOLINE;S.:settings:fragment_args_key=gesture_system_navigation_input_summary;S.:settings:show_fragment=com.android.settings.gestures.SystemNavigationGestureSettings;end";
+ private static final String INTENT_ACTION_ACTIVITY_CLOSED =
+ "com.android.quickstep.interaction.ACTION_ALL_SET_ACTIVITY_CLOSED";
private static final String EXTRA_ACCENT_COLOR_DARK_MODE = "suwColorAccentDark";
private static final String EXTRA_ACCENT_COLOR_LIGHT_MODE = "suwColorAccentLight";
private static final String EXTRA_DEVICE_NAME = "suwDeviceName";
@@ -332,6 +334,7 @@
mLauncherStartAnim.dispatchOnEnd();
mLauncherStartAnim = null;
}
+ sendBroadcast(new Intent(INTENT_ACTION_ACTIVITY_CLOSED));
}
@Override
diff --git a/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
index 608fafd..12616a8 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
@@ -17,6 +17,7 @@
package com.android.quickstep.recents.data
import android.os.UserHandle
+import android.util.Log
import com.android.quickstep.HighResLoadingState.HighResLoadingStateChangedCallback
import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback
@@ -58,7 +59,7 @@
fun onTaskThumbnailChanged(thumbnailData: ThumbnailData?)
/** Informs the listener that the default resolution for loading thumbnails has changed */
- fun onHighResLoadingStateChanged()
+ fun onHighResLoadingStateChanged(highResEnabled: Boolean)
}
}
@@ -91,8 +92,9 @@
}
override fun onHighResLoadingStateChanged(enabled: Boolean) {
+ Log.d(TAG, "onHighResLoadingStateChanged(enabled = $enabled)")
taskThumbnailChangedCallbacks.values.forEach { (_, callback) ->
- callback.onHighResLoadingStateChanged()
+ callback.onHighResLoadingStateChanged(enabled)
}
}
@@ -142,4 +144,8 @@
}
}
}
+
+ companion object {
+ const val TAG = "TaskVisualsChangedDelegateImpl"
+ }
}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index b1a5920..5274ef3 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -160,7 +160,17 @@
updateThumbnail(task.key.id, thumbnailData)
}
- override fun onHighResLoadingStateChanged() {
+ override fun onHighResLoadingStateChanged(highResEnabled: Boolean) {
+ val isTaskVisible = taskRequests.containsKey(task.key.id)
+ if (!isTaskVisible) return
+
+ val isCurrentThumbnailLowRes =
+ tasks.value[task.key.id]?.thumbnail?.reducedResolution
+ val isRequestedResHigherThanCurrent =
+ isCurrentThumbnailLowRes == null ||
+ (isCurrentThumbnailLowRes && highResEnabled)
+ if (!isRequestedResHigherThanCurrent) return
+
recentsCoroutineScope.launch(dispatcherProvider.background) {
updateThumbnail(task.key.id, getThumbnailFromDataSource(task))
}
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index 4b3bef3..c6b3d6a 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -31,7 +31,6 @@
import com.android.quickstep.recents.domain.usecase.GetThumbnailPositionUseCase
import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase
import com.android.quickstep.recents.domain.usecase.OrganizeDesktopTasksUseCase
-import com.android.quickstep.recents.usecase.GetThumbnailUseCase
import com.android.quickstep.recents.viewmodel.RecentsViewData
import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
import com.android.systemui.shared.recents.model.Task
@@ -42,7 +41,7 @@
internal typealias RecentsScopeId = String
-class RecentsDependencies private constructor(private val appContext: Context) {
+class RecentsDependencies private constructor(appContext: Context) {
private val scopes = mutableMapOf<RecentsScopeId, RecentsDependenciesScope>()
init {
@@ -185,7 +184,6 @@
IsThumbnailValidUseCase::class.java ->
IsThumbnailValidUseCase(rotationStateRepository = inject())
GetTaskUseCase::class.java -> GetTaskUseCase(repository = inject())
- GetThumbnailUseCase::class.java -> GetThumbnailUseCase(taskRepository = inject())
GetSysUiStatusNavFlagsUseCase::class.java -> GetSysUiStatusNavFlagsUseCase()
GetThumbnailPositionUseCase::class.java ->
GetThumbnailPositionUseCase(
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
deleted file mode 100644
index b9e9e02..0000000
--- a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.recents.usecase
-
-import android.graphics.Bitmap
-import com.android.quickstep.recents.data.RecentTasksRepository
-
-/** 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? = taskRepository.getCurrentThumbnailById(taskId)?.thumbnail
-}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index e672ec4..e91073a 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -20,6 +20,7 @@
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.Outline
+import android.graphics.Path
import android.graphics.Rect
import android.graphics.drawable.ShapeDrawable
import android.util.AttributeSet
@@ -52,6 +53,7 @@
private val dimAlpha: MultiPropertyFactory<View> by lazy {
MultiPropertyFactory(scrimView, VIEW_ALPHA, ScrimViewAlpha.entries.size, ::maxOf)
}
+ private val outlinePath = Path()
private var onSizeChanged: ((width: Int, height: Int) -> Unit)? = null
private var taskThumbnailViewHeader: TaskThumbnailViewHeader? = null
@@ -96,7 +98,20 @@
outlineProvider =
object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(outlineBounds ?: bounds, cornerRadius)
+ val outlineRect = outlineBounds ?: bounds
+ outlinePath.apply {
+ rewind()
+ addRoundRect(
+ outlineRect.left.toFloat(),
+ outlineRect.top.toFloat(),
+ outlineRect.right.toFloat(),
+ outlineRect.bottom.toFloat(),
+ cornerRadius / scaleX,
+ cornerRadius / scaleY,
+ Path.Direction.CW,
+ )
+ }
+ outline.setPath(outlinePath)
}
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 7d5b471..d2f9652 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -18,6 +18,7 @@
import android.animation.Animator;
import android.animation.RectEvaluator;
+import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -25,6 +26,7 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
+import android.util.Rational;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
@@ -50,8 +52,6 @@
private static final float END_PROGRESS = 1.0f;
- private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.01f;
-
private final int mTaskId;
private final ActivityInfo mActivityInfo;
private final SurfaceControl mLeash;
@@ -158,9 +158,8 @@
// not a valid rectangle to use for cropping app surface
reasonForCreateOverlay = "Source rect hint exceeds display bounds " + sourceRectHint;
sourceRectHint.setEmpty();
- } else if (Math.abs(
- aspectRatio - (sourceRectHint.width() / (float) sourceRectHint.height()))
- > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) {
+ } else if (!PictureInPictureParams.isSameAspectRatio(sourceRectHint,
+ new Rational(destinationBounds.width(), destinationBounds.height()))) {
// The source rect hint does not aspect ratio
reasonForCreateOverlay = "Source rect hint does not match aspect ratio "
+ sourceRectHint + " aspect ratio " + aspectRatio;
diff --git a/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java b/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java
index 91e8376..6e2d469 100644
--- a/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java
+++ b/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java
@@ -16,16 +16,11 @@
package com.android.quickstep.util;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-
-import android.app.Activity;
import android.app.ActivityManager;
import android.util.Log;
import androidx.annotation.NonNull;
-import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
-import com.android.quickstep.RecentsModel;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 09e9c8b..b844079 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -97,6 +97,8 @@
private final FullscreenDrawParams mCurrentFullscreenParams;
public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat();
public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat();
+ public final AnimatedFloat taskGridTranslationX = new AnimatedFloat();
+ public final AnimatedFloat taskGridTranslationY = new AnimatedFloat();
// Carousel properties
public final AnimatedFloat carouselScale = new AnimatedFloat();
@@ -445,6 +447,7 @@
taskPrimaryTranslation.value);
mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
taskSecondaryTranslation.value);
+ mMatrix.postTranslate(taskGridTranslationX.value, taskGridTranslationY.value);
mMatrix.postScale(carouselScale.value, carouselScale.value,
mIsRecentsRtl ? mCarouselTaskSize.right : mCarouselTaskSize.left,
@@ -484,6 +487,8 @@
+ " taskRect: " + mTaskRect
+ " taskPrimaryT: " + taskPrimaryTranslation.value
+ " taskSecondaryT: " + taskSecondaryTranslation.value
+ + " taskGridTranslationX: " + taskGridTranslationX.value
+ + " taskGridTranslationY: " + taskGridTranslationY.value
+ " recentsPrimaryT: " + recentsViewPrimaryTranslation.value
+ " recentsSecondaryT: " + recentsViewSecondaryTranslation.value
+ " recentsScroll: " + recentsViewScroll.value
diff --git a/quickstep/src/com/android/quickstep/views/IconView.kt b/quickstep/src/com/android/quickstep/views/IconView.kt
index 6da52d6..cb69b22 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconView.kt
@@ -78,7 +78,8 @@
override fun setDrawable(d: Drawable?) {
drawable?.callback = null
- drawable = d
+ // Copy drawable so that mutations below do not affect other users of the drawable
+ drawable = d?.constantState?.newDrawable()?.mutate()
drawable?.let {
it.callback = this
setDrawableSizeInternal(width, height)
diff --git a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
new file mode 100644
index 0000000..96eed87
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views
+
+import android.os.VibrationAttributes
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.launcher3.R
+import com.android.launcher3.Utilities.boundToRange
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.DynamicResource
+import com.android.launcher3.util.MSDLPlayerWrapper
+import com.android.quickstep.util.TaskGridNavHelper
+import com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
+import kotlin.math.abs
+
+/**
+ * Helper class for [RecentsView]. This util class contains refactored and extracted functions from
+ * RecentsView related to TaskView dismissal.
+ */
+class RecentsDismissUtils(private val recentsView: RecentsView<*, *>) {
+
+ /**
+ * Creates the spring animations which run when a dragged task view in overview is released.
+ *
+ * <p>When a task dismiss is cancelled, the task will return to its original position via a
+ * spring animation. As it passes the threshold of its settling state, its neighbors will spring
+ * in response to the perceived impact of the settling task.
+ */
+ fun createTaskDismissSettlingSpringAnimation(
+ draggedTaskView: TaskView?,
+ velocity: Float,
+ isDismissing: Boolean,
+ detector: SingleAxisSwipeDetector,
+ dismissLength: Int,
+ onEndRunnable: () -> Unit,
+ ): SpringAnimation? {
+ draggedTaskView ?: return null
+ val taskDismissFloatProperty =
+ FloatPropertyCompat.createFloatPropertyCompat(
+ draggedTaskView.secondaryDismissTranslationProperty
+ )
+ // Animate dragged task towards dismissal or rest state.
+ val draggedTaskViewSpringAnimation =
+ SpringAnimation(draggedTaskView, taskDismissFloatProperty)
+ .setSpring(createExpressiveDismissSpringForce())
+ .setStartVelocity(if (detector.isFling(velocity)) velocity else 0f)
+ .addUpdateListener { animation, value, _ ->
+ if (isDismissing && abs(value) >= abs(dismissLength)) {
+ animation.cancel()
+ } else if (draggedTaskView.isRunningTask && recentsView.enableDrawingLiveTile) {
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
+ taskDismissFloatProperty.getValue(draggedTaskView)
+ }
+ recentsView.redrawLiveTile()
+ }
+ }
+ .addEndListener { _, _, _, _ ->
+ if (isDismissing) {
+ recentsView.dismissTask(
+ draggedTaskView,
+ /* animateTaskView = */ false,
+ /* removeTask = */ true,
+ )
+ } else {
+ recentsView.onDismissAnimationEnds()
+ }
+ onEndRunnable()
+ }
+ if (!isDismissing) {
+ addNeighboringSpringAnimationsForDismissCancel(
+ draggedTaskView,
+ draggedTaskViewSpringAnimation,
+ )
+ }
+ return draggedTaskViewSpringAnimation
+ }
+
+ private fun addNeighboringSpringAnimationsForDismissCancel(
+ draggedTaskView: TaskView,
+ draggedTaskViewSpringAnimation: SpringAnimation,
+ ) {
+ // Empty spring animation exists for conditional start, and to drive neighboring springs.
+ val neighborsToSettle =
+ SpringAnimation(FloatValueHolder()).setSpring(createExpressiveDismissSpringForce())
+ var lastPosition = 0f
+ var startSettling = false
+ draggedTaskViewSpringAnimation.addUpdateListener { _, value, velocity ->
+ // Start the settling animation the first time the dragged task passes the origin (from
+ // negative displacement to positive displacement). We do not check for an exact value
+ // to compare to, as the update listener does not necessarily hit every value (e.g. a
+ // value of zero). Do not check again once it has started settling, as a spring can
+ // bounce past the origin multiple times depending on the stiffness and damping ratio.
+ if (startSettling) return@addUpdateListener
+ if (lastPosition < 0 && value >= 0) {
+ startSettling = true
+ }
+ lastPosition = value
+ if (startSettling) {
+ neighborsToSettle.setStartVelocity(velocity).animateToFinalPosition(0f)
+ playDismissSettlingHaptic(velocity)
+ }
+ }
+
+ // Add tasks before dragged index, fanning out from the dragged task.
+ // The order they are added matters, as each spring drives the next.
+ var previousNeighbor = neighborsToSettle
+ getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = true).forEach {
+ (taskView, offset) ->
+ previousNeighbor =
+ createNeighboringTaskViewSpringAnimation(
+ taskView,
+ offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
+ previousNeighbor,
+ )
+ }
+ // Add tasks after dragged index, fanning out from the dragged task.
+ // The order they are added matters, as each spring drives the next.
+ previousNeighbor = neighborsToSettle
+ getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = false).forEach {
+ (taskView, offset) ->
+ previousNeighbor =
+ createNeighboringTaskViewSpringAnimation(
+ taskView,
+ offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
+ previousNeighbor,
+ )
+ }
+ }
+
+ /**
+ * Gets pairs of (TaskView, offset) adjacent the dragged task in visual order.
+ *
+ * <p>Gets tasks either before or after the dragged task along with their offset from it. The
+ * offset is the distance between indices for carousels, or distance between columns for grids.
+ */
+ private fun getTasksOffsetPairAdjacentToDraggedTask(
+ draggedTaskView: TaskView,
+ towardsStart: Boolean,
+ ): Sequence<Pair<TaskView, Int>> {
+ if (recentsView.showAsGrid()) {
+ val taskGridNavHelper =
+ TaskGridNavHelper(
+ recentsView.topRowIdArray,
+ recentsView.bottomRowIdArray,
+ recentsView.mUtils.getLargeTaskViewIds(),
+ hasAddDesktopButton = false,
+ )
+ return taskGridNavHelper
+ .gridTaskViewIdOffsetPairInTabOrderSequence(
+ draggedTaskView.taskViewId,
+ towardsStart,
+ )
+ .mapNotNull { (taskViewId, columnOffset) ->
+ recentsView.getTaskViewFromTaskViewId(taskViewId)?.let { taskView ->
+ Pair(taskView, columnOffset)
+ }
+ }
+ } else {
+ val taskViewList = recentsView.mUtils.taskViews.toList()
+ val draggedTaskViewIndex = taskViewList.indexOf(draggedTaskView)
+
+ return if (towardsStart) {
+ taskViewList
+ .take(draggedTaskViewIndex)
+ .reversed()
+ .mapIndexed { index, taskView -> Pair(taskView, index + 1) }
+ .asSequence()
+ } else {
+ taskViewList
+ .takeLast(taskViewList.size - draggedTaskViewIndex - 1)
+ .mapIndexed { index, taskView -> Pair(taskView, index + 1) }
+ .asSequence()
+ }
+ }
+ }
+
+ /** Creates a neighboring task view spring, driven by the spring of its neighbor. */
+ private fun createNeighboringTaskViewSpringAnimation(
+ taskView: TaskView,
+ dampingOffsetRatio: Float,
+ previousNeighborSpringAnimation: SpringAnimation,
+ ): SpringAnimation {
+ val neighboringTaskViewSpringAnimation =
+ SpringAnimation(
+ taskView,
+ FloatPropertyCompat.createFloatPropertyCompat(
+ taskView.secondaryDismissTranslationProperty
+ ),
+ )
+ .setSpring(createExpressiveDismissSpringForce(dampingOffsetRatio))
+ // Update live tile on spring animation.
+ if (taskView.isRunningTask && recentsView.enableDrawingLiveTile) {
+ neighboringTaskViewSpringAnimation.addUpdateListener { _, _, _ ->
+ recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+ remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
+ taskView.secondaryDismissTranslationProperty.get(taskView)
+ }
+ recentsView.redrawLiveTile()
+ }
+ }
+ // Drive current neighbor's spring with the previous neighbor's.
+ previousNeighborSpringAnimation.addUpdateListener { _, value, _ ->
+ neighboringTaskViewSpringAnimation.animateToFinalPosition(value)
+ }
+ return neighboringTaskViewSpringAnimation
+ }
+
+ private fun createExpressiveDismissSpringForce(dampingRatioOffset: Float = 0f): SpringForce {
+ val resourceProvider = DynamicResource.provider(recentsView.mContainer)
+ return SpringForce()
+ .setDampingRatio(
+ resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_damping_ratio) +
+ dampingRatioOffset
+ )
+ .setStiffness(
+ resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_stiffness)
+ )
+ }
+
+ /**
+ * Plays a haptic as the dragged task view settles back into its rest state.
+ *
+ * <p>Haptic intensity is proportional to velocity.
+ */
+ private fun playDismissSettlingHaptic(velocity: Float) {
+ val maxDismissSettlingVelocity =
+ recentsView.pagedOrientationHandler.getSecondaryDimension(recentsView)
+ MSDLPlayerWrapper.INSTANCE.get(recentsView.context)
+ .playToken(
+ MSDLToken.CANCEL,
+ InteractionProperties.DynamicVibrationScale(
+ boundToRange(velocity / maxDismissSettlingVelocity, 0f, 1f),
+ VibrationAttributes.Builder()
+ .setUsage(VibrationAttributes.USAGE_TOUCH)
+ .setFlags(VibrationAttributes.FLAG_PIPELINED_EFFECT)
+ .build(),
+ ),
+ )
+ }
+
+ /** Animates RecentsView's scale to the provided value, using spring animations. */
+ fun animateRecentsScale(scale: Float): SpringAnimation {
+ val resourceProvider = DynamicResource.provider(recentsView.mContainer)
+ val dampingRatio = resourceProvider.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio)
+ val stiffness = resourceProvider.getFloat(R.dimen.swipe_up_rect_scale_stiffness)
+
+ // Spring which sets the Recents scale on update. This is needed, as the SpringAnimation
+ // struggles to animate small values like changing recents scale from 0.9 to 1. So
+ // we animate over a larger range (e.g. 900 to 1000) and convert back to the required value.
+ // (This is instead of converting RECENTS_SCALE_PROPERTY to a FloatPropertyCompat and
+ // animating it directly via springs.)
+ val initialRecentsScaleSpringValue =
+ RECENTS_SCALE_SPRING_MULTIPLIER * RECENTS_SCALE_PROPERTY.get(recentsView)
+ return SpringAnimation(FloatValueHolder(initialRecentsScaleSpringValue))
+ .setSpring(
+ SpringForce(initialRecentsScaleSpringValue)
+ .setDampingRatio(dampingRatio)
+ .setStiffness(stiffness)
+ )
+ .addUpdateListener { _, value, _ ->
+ RECENTS_SCALE_PROPERTY.setValue(
+ recentsView,
+ value / RECENTS_SCALE_SPRING_MULTIPLIER,
+ )
+ }
+ .apply { animateToFinalPosition(RECENTS_SCALE_SPRING_MULTIPLIER * scale) }
+ }
+
+ private companion object {
+ // The additional damping to apply to tasks further from the dismissed task.
+ private const val ADDITIONAL_DISMISS_DAMPING_RATIO = 0.15f
+ private const val RECENTS_SCALE_SPRING_MULTIPLIER = 1000f
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 5bfcf43..c5a76cb 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -157,6 +157,7 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StatefulContainer;
@@ -179,6 +180,7 @@
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.ViewPool;
import com.android.launcher3.util.coroutines.DispatcherProvider;
+import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener;
import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.HighResLoadingState;
@@ -265,7 +267,7 @@
CONTAINER_TYPE extends Context & RecentsViewContainer & StatefulContainer<STATE_TYPE>,
STATE_TYPE extends BaseState<STATE_TYPE>> extends PagedView implements Insettable,
HighResLoadingState.HighResLoadingStateChangedCallback,
- TaskVisualsChangeListener {
+ TaskVisualsChangeListener, DesktopVisibilityListener {
private static final String TAG = "RecentsView";
private static final boolean DEBUG = false;
@@ -556,6 +558,10 @@
private final Rect mTaskViewDeadZoneRect = new Rect();
private final Rect mTopRowDeadZoneRect = new Rect();
private final Rect mBottomRowDeadZoneRect = new Rect();
+
+ @Nullable
+ private DesktopVisibilityController mDesktopVisibilityController = null;
+
/**
* Reflects if Recents is currently in the middle of a gesture, and if so, which tasks are
* running. If a gesture is not in progress, this will be null.
@@ -851,6 +857,7 @@
private final RecentsViewModel mRecentsViewModel;
private final RecentsViewModelHelper mHelper;
protected final RecentsViewUtils mUtils = new RecentsViewUtils(this);
+ protected final RecentsDismissUtils mDismissUtils = new RecentsDismissUtils(this);
private final Matrix mTmpMatrix = new Matrix();
@@ -912,14 +919,18 @@
mAddDesktopButton = (AddDesktopButton) LayoutInflater.from(context).inflate(
R.layout.overview_add_desktop_button, this, false);
mAddDesktopButton.setOnClickListener(this::createDesk);
+
+ mDesktopVisibilityController = DesktopVisibilityController.INSTANCE.get(mContext);
}
mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
10 /* initial size */);
+ int groupedViewPoolInitialSize = enableRefactorTaskThumbnail() ? 2 : 10;
mGroupedTaskViewPool = new ViewPool<>(context, this,
- R.layout.task_grouped, 20 /* max size */, 10 /* initial size */);
+ R.layout.task_grouped, 20 /* max size */, groupedViewPoolInitialSize);
+ int desktopViewPoolInitialSize = DesktopModeStatus.canEnterDesktopMode(mContext) ? 1 : 0;
mDesktopTaskViewPool = new ViewPool<>(context, this, R.layout.task_desktop,
- 5 /* max size */, 1 /* initial size */);
+ 5 /* max size */, desktopViewPoolInitialSize);
setOrientationHandler(mOrientationState.getOrientationHandler());
mIsRtl = getPagedOrientationHandler().getRecentsRtlSetting(getResources());
@@ -1224,13 +1235,16 @@
mSyncTransactionApplier = new SurfaceTransactionApplier(this);
runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
.setSyncTransactionApplier(mSyncTransactionApplier));
- RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
+ RecentsModel.INSTANCE.get(mContext).addThumbnailChangeListener(this);
mIPipAnimationListener.setActivityAndRecentsView(mContainer, this);
- SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener(
+ SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener(
mIPipAnimationListener);
mOrientationState.initListeners();
mTaskOverlayFactory.initListeners();
mSplitSelectStateController.registerSplitListener(mSplitSelectionListener);
+ if (mDesktopVisibilityController != null) {
+ mDesktopVisibilityController.registerDesktopVisibilityListener(this);
+ }
}
@Override
@@ -1245,12 +1259,15 @@
runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
.setSyncTransactionApplier(null));
executeSideTaskLaunchCallback();
- RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
- SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener(null);
+ RecentsModel.INSTANCE.get(mContext).removeThumbnailChangeListener(this);
+ SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener(null);
mIPipAnimationListener.setActivityAndRecentsView(null, null);
mOrientationState.destroyListeners();
mTaskOverlayFactory.removeListeners();
mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener);
+ if (mDesktopVisibilityController != null) {
+ mDesktopVisibilityController.unregisterDesktopVisibilityListener(this);
+ }
reset();
}
@@ -2010,7 +2027,7 @@
}
// If the list changed, maybe the focused task doesn't exist anymore.
if (newFocusedTaskView == null) {
- newFocusedTaskView = mUtils.getExpectedFocusedTask();
+ newFocusedTaskView = mUtils.getFirstNonDesktopTaskView();
}
}
setFocusedTaskViewId(
@@ -2116,15 +2133,6 @@
return mTaskViewCount;
}
- /**
- * Transverse RecentsView children to calculate the amount of DesktopTaskViews.
- *
- * @return Number of children that are instances of DesktopTaskView
- */
- private int getDesktopTaskViewCount() {
- return mUtils.getDesktopTaskViewCount();
- }
-
/** Counts {@link TaskView}s that are not {@link DesktopTaskView} instances. */
public int getNonDesktopTaskViewCount() {
return mUtils.getNonDesktopTaskViewCount();
@@ -2917,28 +2925,40 @@
BaseState<?> endState = mSizeStrategy.stateFromGestureEndTarget(endTarget);
if (endState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile())) {
TaskView runningTaskView = getRunningTaskView();
- float runningTaskPrimaryGridTranslation = 0;
- float runningTaskSecondaryGridTranslation = 0;
+ float runningTaskGridTranslationX = 0;
+ float runningTaskGridTranslationY = 0;
if (runningTaskView != null) {
// Apply the grid translation to running task unless it's being snapped to
// and removes the current translation applied to the running task.
- runningTaskPrimaryGridTranslation = runningTaskView.getGridTranslationX()
+ runningTaskGridTranslationX = runningTaskView.getGridTranslationX()
- runningTaskView.getNonGridTranslationX();
- runningTaskSecondaryGridTranslation = runningTaskView.getGridTranslationY();
+ runningTaskGridTranslationY = runningTaskView.getGridTranslationY();
}
for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) {
TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
if (animatorSet == null) {
setGridProgress(1);
- tvs.taskPrimaryTranslation.value = runningTaskPrimaryGridTranslation;
- tvs.taskSecondaryTranslation.value = runningTaskSecondaryGridTranslation;
+ if (enableGridOnlyOverview()) {
+ tvs.taskGridTranslationX.value = runningTaskGridTranslationX;
+ tvs.taskGridTranslationY.value = runningTaskGridTranslationY;
+ } else {
+ tvs.taskPrimaryTranslation.value = runningTaskGridTranslationX;
+ tvs.taskSecondaryTranslation.value = runningTaskGridTranslationY;
+ }
} else {
animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1));
- animatorSet.play(tvs.carouselScale.animateToValue(1));
- animatorSet.play(tvs.taskPrimaryTranslation.animateToValue(
- runningTaskPrimaryGridTranslation));
- animatorSet.play(tvs.taskSecondaryTranslation.animateToValue(
- runningTaskSecondaryGridTranslation));
+ if (enableGridOnlyOverview()) {
+ animatorSet.play(tvs.carouselScale.animateToValue(1));
+ animatorSet.play(tvs.taskGridTranslationX.animateToValue(
+ runningTaskGridTranslationX));
+ animatorSet.play(tvs.taskGridTranslationY.animateToValue(
+ runningTaskGridTranslationY));
+ } else {
+ animatorSet.play(tvs.taskPrimaryTranslation.animateToValue(
+ runningTaskGridTranslationX));
+ animatorSet.play(tvs.taskSecondaryTranslation.animateToValue(
+ runningTaskGridTranslationY));
+ }
}
}
}
@@ -3064,7 +3084,7 @@
focusedTaskViewId = INVALID_TASK_ID;
} else if (enableLargeDesktopWindowingTile()
&& getRunningTaskView() instanceof DesktopTaskView) {
- TaskView focusedTaskView = getTaskViewAt(getDesktopTaskViewCount());
+ TaskView focusedTaskView = mUtils.getFirstNonDesktopTaskView();
focusedTaskViewId =
focusedTaskView != null ? focusedTaskView.getTaskViewId() : INVALID_TASK_ID;
} else {
@@ -3491,8 +3511,13 @@
final TaskView runningTask = getRunningTaskView();
if (showAsGrid() && enableGridOnlyOverview() && runningTask != null) {
runActionOnRemoteHandles(
- remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
- .taskSecondaryTranslation.value = runningTask.getGridTranslationY()
+ remoteTargetHandle -> {
+ remoteTargetHandle.getTaskViewSimulator().taskGridTranslationX.value =
+ runningTask.getGridTranslationX()
+ - runningTask.getNonGridTranslationX();
+ remoteTargetHandle.getTaskViewSimulator().taskGridTranslationY.value =
+ runningTask.getGridTranslationY();
+ }
);
}
@@ -3606,12 +3631,9 @@
if (taskView.isRunningTask()) {
anim.addOnFrameCallback(() -> {
if (!mEnableDrawingLiveTile) return;
- runActionOnRemoteHandles(
- remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
- .taskSecondaryTranslation.value = getPagedOrientationHandler()
- .getSecondaryValue(taskView.getTranslationX(),
- taskView.getTranslationY()
- ));
+ runActionOnRemoteHandles(remoteTargetHandle ->
+ remoteTargetHandle.getTaskViewSimulator().taskSecondaryTranslation.value =
+ taskView.getSecondaryDismissTranslationProperty().get(taskView));
redrawLiveTile();
});
}
@@ -3933,9 +3955,9 @@
int distanceFromDismissedTask = 1;
int slidingTranslation = 0;
if (isSlidingTasks) {
- int nextSnappedPage = isStagingFocusedTask
- ? indexOfChild(mUtils.getFirstSmallTaskView())
- : mUtils.getDesktopTaskViewCount();
+ int nextSnappedPage = indexOfChild(isStagingFocusedTask
+ ? mUtils.getFirstSmallTaskView()
+ : mUtils.getFirstNonDesktopTaskView());
slidingTranslation = getPagedOrientationHandler().getPrimaryScroll(this)
- getScrollForPage(nextSnappedPage);
slidingTranslation += mIsRtl ? newClearAllShortTotalWidthTranslation
@@ -3961,7 +3983,7 @@
Math.abs(i - dismissedIndex),
scrollDiff,
anim,
- splitTimings, i);
+ splitTimings);
needsCurveUpdates = true;
}
} else if (child instanceof TaskView taskView) {
@@ -4337,8 +4359,12 @@
int indexDiff,
int scrollDiffPerPage,
PendingAnimation pendingAnimation,
- SplitAnimationTimings splitTimings,
- int index) {
+ SplitAnimationTimings splitTimings) {
+ // No need to translate the AddDesktopButton on dismissing a TaskView, which should be
+ // always at the right most position, even when dismissing the last TaskView.
+ if (view instanceof AddDesktopButton) {
+ return;
+ }
FloatProperty translationProperty = view instanceof TaskView
? ((TaskView) view).getPrimaryDismissTranslationProperty()
: getPagedOrientationHandler().getPrimaryViewTranslate();
@@ -5655,7 +5681,7 @@
anim.play(ObjectAnimator.ofFloat(this, FULLSCREEN_PROGRESS, 1));
anim.addListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationStart(@NonNull Animator animation, boolean isReverse) {
+ public void onAnimationStart(@NonNull Animator animation) {
taskView.getThumbnailBounds(mTempRect, /*relativeToDragLayer=*/true);
getTaskDimension(mContext, mContainer.getDeviceProfile(), mTempPointF);
Rect fullscreenBounds = new Rect(0, 0, (int) mTempPointF.x,
@@ -5677,6 +5703,18 @@
});
}
}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // If live tile is not launching, reset the pivot applied above.
+ if (!taskView.isRunningTask()) {
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> {
+ remoteTargetHandle.getTaskViewSimulator().setPivotOverride(
+ null);
+ });
+ }
+ }
});
} else if (!showAsGrid) {
// We are launching an adjacent task, so parallax the center and other adjacent task.
@@ -5785,10 +5823,12 @@
mPendingAnimation = new PendingAnimation(duration);
mPendingAnimation.add(anim);
- runActionOnRemoteHandles(
- remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
- .addOverviewToAppAnim(mPendingAnimation, interpolator));
- mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
+ if (taskView.isRunningTask()) {
+ runActionOnRemoteHandles(
+ remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
+ .addOverviewToAppAnim(mPendingAnimation, interpolator));
+ mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
+ }
mPendingAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -6241,12 +6281,12 @@
}
@Override
- protected int getChildVisibleSize(int index) {
- final TaskView taskView = getTaskViewAt(index);
+ protected int getChildVisibleSize(int childIndex) {
+ final TaskView taskView = getTaskViewAt(childIndex);
if (taskView == null) {
- return super.getChildVisibleSize(index);
+ return super.getChildVisibleSize(childIndex);
}
- return (int) (super.getChildVisibleSize(index) * taskView.getSizeAdjustment(
+ return (int) (super.getChildVisibleSize(childIndex) * taskView.getSizeAdjustment(
showAsFullscreen()));
}
@@ -6305,7 +6345,7 @@
* Returns how many pixels the page is offset on the currently laid out dominant axis.
*/
private int getUnclampedScrollOffset(int pageIndex) {
- if (pageIndex == -1) {
+ if (pageIndex == INVALID_PAGE) {
return 0;
}
// Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so that
@@ -6833,6 +6873,11 @@
}
}
+ @Override
+ public void onCanCreateDesksChanged(boolean canCreateDesks) {
+ // TODO: b/389209338 - update the AddDesktopButton's visibility on this.
+ }
+
/** Get the color used for foreground scrimming the RecentsView for sharing. */
public static int getForegroundScrimDimColor(Context context) {
return context.getColor(R.color.overview_foreground_scrim_color);
@@ -6943,10 +6988,17 @@
public SpringAnimation createTaskDismissSettlingSpringAnimation(TaskView draggedTaskView,
float velocity, boolean isDismissing, SingleAxisSwipeDetector detector,
int dismissLength, Function0<Unit> onEndRunnable) {
- return mUtils.createTaskDismissSettlingSpringAnimation(draggedTaskView, velocity,
+ return mDismissUtils.createTaskDismissSettlingSpringAnimation(draggedTaskView, velocity,
isDismissing, detector, dismissLength, onEndRunnable);
}
+ /**
+ * Animates RecentsView's scale to the provided value, using spring animations.
+ */
+ public SpringAnimation animateRecentsScale(float scale) {
+ return mDismissUtils.animateRecentsScale(scale);
+ }
+
public interface TaskLaunchListener {
void onTaskLaunched();
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index d3549fb..f742ec3 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -17,30 +17,16 @@
package com.android.quickstep.views
import android.graphics.Rect
-import android.os.VibrationAttributes
import android.view.View
import androidx.core.view.children
-import androidx.dynamicanimation.animation.FloatPropertyCompat
-import androidx.dynamicanimation.animation.FloatValueHolder
-import androidx.dynamicanimation.animation.SpringAnimation
-import androidx.dynamicanimation.animation.SpringForce
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
import com.android.launcher3.Flags.enableSeparateExternalDisplayTasks
-import com.android.launcher3.R
-import com.android.launcher3.Utilities.boundToRange
-import com.android.launcher3.touch.SingleAxisSwipeDetector
-import com.android.launcher3.util.DynamicResource
import com.android.launcher3.util.IntArray
-import com.android.launcher3.util.MSDLPlayerWrapper
import com.android.quickstep.util.GroupTask
-import com.android.quickstep.util.TaskGridNavHelper
import com.android.quickstep.util.isExternalDisplay
import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA
import com.android.systemui.shared.recents.model.ThumbnailData
-import com.google.android.msdl.data.model.MSDLToken
-import com.google.android.msdl.domain.InteractionProperties
import java.util.function.BiConsumer
-import kotlin.math.abs
/**
* Helper class for [RecentsView]. This util class contains refactored and extracted functions from
@@ -87,7 +73,7 @@
}
/** Counts [TaskView]s that are [DesktopTaskView] instances. */
- fun getDesktopTaskViewCount(): Int = taskViews.count { it is DesktopTaskView }
+ private fun getDesktopTaskViewCount(): Int = taskViews.count { it is DesktopTaskView }
/** Counts [TaskView]s that are not [DesktopTaskView] instances. */
fun getNonDesktopTaskViewCount(): Int = taskViews.count { it !is DesktopTaskView }
@@ -105,7 +91,7 @@
}
/** Returns the expected focus task. */
- fun getExpectedFocusedTask(): TaskView? =
+ fun getFirstNonDesktopTaskView(): TaskView? =
if (enableLargeDesktopWindowingTile()) taskViews.firstOrNull { it !is DesktopTaskView }
else taskViews.firstOrNull()
@@ -308,234 +294,7 @@
}
}
- /**
- * Creates the spring animations which run when a dragged task view in overview is released.
- *
- * <p>When a task dismiss is cancelled, the task will return to its original position via a
- * spring animation. As it passes the threshold of its settling state, its neighbors will spring
- * in response to the perceived impact of the settling task.
- */
- fun createTaskDismissSettlingSpringAnimation(
- draggedTaskView: TaskView?,
- velocity: Float,
- isDismissing: Boolean,
- detector: SingleAxisSwipeDetector,
- dismissLength: Int,
- onEndRunnable: () -> Unit,
- ): SpringAnimation? {
- draggedTaskView ?: return null
- val taskDismissFloatProperty =
- FloatPropertyCompat.createFloatPropertyCompat(
- draggedTaskView.secondaryDismissTranslationProperty
- )
- // Animate dragged task towards dismissal or rest state.
- val draggedTaskViewSpringAnimation =
- SpringAnimation(draggedTaskView, taskDismissFloatProperty)
- .setSpring(createExpressiveDismissSpringForce())
- .setStartVelocity(if (detector.isFling(velocity)) velocity else 0f)
- .addUpdateListener { animation, value, _ ->
- if (isDismissing && abs(value) >= abs(dismissLength)) {
- // TODO(b/393553524): Remove 0 alpha, instead animate task fully off screen.
- draggedTaskView.alpha = 0f
- animation.cancel()
- } else if (draggedTaskView.isRunningTask && recentsView.enableDrawingLiveTile) {
- recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
- remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
- taskDismissFloatProperty.getValue(draggedTaskView)
- }
- recentsView.redrawLiveTile()
- }
- }
- .addEndListener { _, _, _, _ ->
- if (isDismissing) {
- recentsView.dismissTask(
- draggedTaskView,
- /* animateTaskView = */ false,
- /* removeTask = */ true,
- )
- } else {
- recentsView.onDismissAnimationEnds()
- }
- onEndRunnable()
- }
- if (!isDismissing) {
- addNeighboringSpringAnimationsForDismissCancel(
- draggedTaskView,
- draggedTaskViewSpringAnimation,
- recentsView.pageCount,
- )
- }
- return draggedTaskViewSpringAnimation
- }
-
- private fun addNeighboringSpringAnimationsForDismissCancel(
- draggedTaskView: TaskView,
- draggedTaskViewSpringAnimation: SpringAnimation,
- taskCount: Int,
- ) {
- // Empty spring animation exists for conditional start, and to drive neighboring springs.
- val neighborsToSettle =
- SpringAnimation(FloatValueHolder()).setSpring(createExpressiveDismissSpringForce())
- var lastPosition = 0f
- var startSettling = false
- draggedTaskViewSpringAnimation.addUpdateListener { _, value, velocity ->
- // Start the settling animation the first time the dragged task passes the origin (from
- // negative displacement to positive displacement). We do not check for an exact value
- // to compare to, as the update listener does not necessarily hit every value (e.g. a
- // value of zero). Do not check again once it has started settling, as a spring can
- // bounce past the origin multiple times depending on the stiffness and damping ratio.
- if (startSettling) return@addUpdateListener
- if (lastPosition < 0 && value >= 0) {
- startSettling = true
- }
- lastPosition = value
- if (startSettling) {
- neighborsToSettle.setStartVelocity(velocity).animateToFinalPosition(0f)
- playDismissSettlingHaptic(velocity)
- }
- }
-
- // Add tasks before dragged index, fanning out from the dragged task.
- // The order they are added matters, as each spring drives the next.
- var previousNeighbor = neighborsToSettle
- getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = true).forEach {
- (taskView, offset) ->
- previousNeighbor =
- createNeighboringTaskViewSpringAnimation(
- taskView,
- offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
- previousNeighbor,
- )
- }
- // Add tasks after dragged index, fanning out from the dragged task.
- // The order they are added matters, as each spring drives the next.
- previousNeighbor = neighborsToSettle
- getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = false).forEach {
- (taskView, offset) ->
- previousNeighbor =
- createNeighboringTaskViewSpringAnimation(
- taskView,
- offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
- previousNeighbor,
- )
- }
- }
-
- /**
- * Gets pairs of (TaskView, offset) adjacent the dragged task in visual order.
- *
- * <p>Gets tasks either before or after the dragged task along with their offset from it. The
- * offset is the distance between indices for carousels, or distance between columns for grids.
- */
- private fun getTasksOffsetPairAdjacentToDraggedTask(
- draggedTaskView: TaskView,
- towardsStart: Boolean,
- ): Sequence<Pair<TaskView, Int>> {
- if (recentsView.showAsGrid()) {
- val taskGridNavHelper =
- TaskGridNavHelper(
- recentsView.topRowIdArray,
- recentsView.bottomRowIdArray,
- getLargeTaskViewIds(),
- hasAddDesktopButton = false,
- )
- return taskGridNavHelper
- .gridTaskViewIdOffsetPairInTabOrderSequence(
- draggedTaskView.taskViewId,
- towardsStart,
- )
- .mapNotNull { (taskViewId, columnOffset) ->
- recentsView.getTaskViewFromTaskViewId(taskViewId)?.let { taskView ->
- Pair(taskView, columnOffset)
- }
- }
- } else {
- val taskViewList = taskViews.toList()
- val draggedTaskViewIndex = taskViewList.indexOf(draggedTaskView)
-
- return if (towardsStart) {
- taskViewList
- .take(draggedTaskViewIndex)
- .reversed()
- .mapIndexed { index, taskView -> Pair(taskView, index + 1) }
- .asSequence()
- } else {
- taskViewList
- .takeLast(taskViewList.size - draggedTaskViewIndex - 1)
- .mapIndexed { index, taskView -> Pair(taskView, index + 1) }
- .asSequence()
- }
- }
- }
-
- /** Creates a neighboring task view spring, driven by the spring of its neighbor. */
- private fun createNeighboringTaskViewSpringAnimation(
- taskView: TaskView,
- dampingOffsetRatio: Float,
- previousNeighborSpringAnimation: SpringAnimation,
- ): SpringAnimation {
- val neighboringTaskViewSpringAnimation =
- SpringAnimation(
- taskView,
- FloatPropertyCompat.createFloatPropertyCompat(
- taskView.secondaryDismissTranslationProperty
- ),
- )
- .setSpring(createExpressiveDismissSpringForce(dampingOffsetRatio))
- // Update live tile on spring animation.
- if (taskView.isRunningTask && recentsView.enableDrawingLiveTile) {
- neighboringTaskViewSpringAnimation.addUpdateListener { _, _, _ ->
- recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
- remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
- taskView.secondaryDismissTranslationProperty.get(taskView)
- }
- recentsView.redrawLiveTile()
- }
- }
- // Drive current neighbor's spring with the previous neighbor's.
- previousNeighborSpringAnimation.addUpdateListener { _, value, _ ->
- neighboringTaskViewSpringAnimation.animateToFinalPosition(value)
- }
- return neighboringTaskViewSpringAnimation
- }
-
- private fun createExpressiveDismissSpringForce(dampingRatioOffset: Float = 0f): SpringForce {
- val resourceProvider = DynamicResource.provider(recentsView.mContainer)
- return SpringForce()
- .setDampingRatio(
- resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_damping_ratio) +
- dampingRatioOffset
- )
- .setStiffness(
- resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_stiffness)
- )
- }
-
- /**
- * Plays a haptic as the dragged task view settles back into its rest state.
- *
- * <p>Haptic intensity is proportional to velocity.
- */
- private fun playDismissSettlingHaptic(velocity: Float) {
- val maxDismissSettlingVelocity =
- recentsView.pagedOrientationHandler.getSecondaryDimension(recentsView)
- MSDLPlayerWrapper.INSTANCE.get(recentsView.context)
- .playToken(
- MSDLToken.CANCEL,
- InteractionProperties.DynamicVibrationScale(
- boundToRange(velocity / maxDismissSettlingVelocity, 0f, 1f),
- VibrationAttributes.Builder()
- .setUsage(VibrationAttributes.USAGE_TOUCH)
- .setFlags(VibrationAttributes.FLAG_PIPELINED_EFFECT)
- .build(),
- ),
- )
- }
-
companion object {
val TEMP_RECT = Rect()
-
- // The additional damping to apply to tasks further from the dismissed task.
- const val ADDITIONAL_DISMISS_DAMPING_RATIO = 0.15f
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index ce20bf9..7301cfc 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -25,8 +25,6 @@
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.ViewUtils.addAccessibleChildToList
-import com.android.quickstep.recents.di.RecentsDependencies
-import com.android.quickstep.recents.di.getScope
import com.android.quickstep.recents.ui.mapper.TaskUiStateMapper
import com.android.quickstep.recents.ui.viewmodel.TaskData
import com.android.quickstep.task.thumbnail.TaskThumbnailView
@@ -57,13 +55,6 @@
init {
if (enableRefactorTaskThumbnail()) {
require(snapshotView is TaskThumbnailView)
- RecentsDependencies.getScope(snapshotView).apply {
- val taskViewScope = RecentsDependencies.getScope(taskView)
- linkTo(taskViewScope)
-
- val taskContainerScope = RecentsDependencies.getScope(this@TaskContainer)
- linkTo(taskContainerScope)
- }
} else {
require(snapshotView is TaskThumbnailViewDeprecated)
}
@@ -116,8 +107,6 @@
snapshotView.scaleY = 1f
overlay.destroy()
if (enableRefactorTaskThumbnail()) {
- RecentsDependencies.getInstance().removeScope(snapshotView)
- RecentsDependencies.getInstance().removeScope(this)
isThumbnailValid = false
} else {
thumbnailViewDeprecated.setShowSplashForSplitSelection(false)
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 49ec31d..ba54232 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -789,11 +789,11 @@
private fun updateThumbnailValidity(container: TaskContainer) {
container.isThumbnailValid =
- viewModel!!.isThumbnailValid(
+ viewModel?.isThumbnailValid(
thumbnail = container.thumbnailData,
width = container.thumbnailView.width,
height = container.thumbnailView.height,
- )
+ ) ?: return
applyThumbnailSplashAlpha()
}
@@ -810,7 +810,8 @@
*/
private fun updateThumbnailMatrix(container: TaskContainer, width: Int, height: Int) {
val thumbnailPosition =
- viewModel!!.getThumbnailPosition(container.thumbnailData, width, height, isLayoutRtl)
+ viewModel?.getThumbnailPosition(container.thumbnailData, width, height, isLayoutRtl)
+ ?: return
container.updateThumbnailMatrix(thumbnailPosition.matrix)
}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
index b9d2fed..80b2c16 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -16,10 +16,19 @@
package com.android.quickstep.task.thumbnail
import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
import android.graphics.Color
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.drawable.BitmapDrawable
import android.view.LayoutInflater
+import android.view.Surface.ROTATION_0
+import androidx.core.graphics.set
import com.android.launcher3.R
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
import org.junit.Rule
@@ -81,6 +90,96 @@
}
@Test
+ fun taskThumbnailView_liveTile_withoutHeader() {
+ screenshotRule.screenshotTest("taskThumbnailView_liveTile") { activity ->
+ activity.actionBar?.hide()
+ createTaskThumbnailView(activity).apply {
+ setState(TaskThumbnailUiState.LiveTile.WithoutHeader)
+ }
+ }
+ }
+
+ @Test
+ fun taskThumbnailView_image_withoutHeader() {
+ screenshotRule.screenshotTest("taskThumbnailView_image") { activity ->
+ activity.actionBar?.hide()
+ createTaskThumbnailView(activity).apply {
+ setState(
+ SnapshotSplash(
+ Snapshot.WithoutHeader(createBitmap(), ROTATION_0, Color.DKGRAY),
+ null,
+ )
+ )
+ }
+ }
+ }
+
+ @Test
+ fun taskThumbnailView_image_withoutHeader_withImageMatrix() {
+ screenshotRule.screenshotTest("taskThumbnailView_image_withMatrix") { activity ->
+ activity.actionBar?.hide()
+ createTaskThumbnailView(activity).apply {
+ val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200
+ setState(
+ SnapshotSplash(
+ Snapshot.WithoutHeader(
+ createBitmap(
+ width = VIEW_ENV_WIDTH / 2,
+ height = lessThanHeightMatchingAspectRatio,
+ ),
+ ROTATION_0,
+ Color.DKGRAY,
+ ),
+ null,
+ )
+ )
+ setImageMatrix(Matrix().apply { postScale(2f, 2f) })
+ }
+ }
+ }
+
+ @Test
+ fun taskThumbnailView_splash_withoutHeader() {
+ screenshotRule.screenshotTest("taskThumbnailView_partial_splash") { activity ->
+ activity.actionBar?.hide()
+ createTaskThumbnailView(activity).apply {
+ setState(
+ SnapshotSplash(
+ Snapshot.WithoutHeader(createBitmap(), ROTATION_0, Color.DKGRAY),
+ BitmapDrawable(activity.resources, createSplash()),
+ )
+ )
+ updateSplashAlpha(0.5f)
+ }
+ }
+ }
+
+ @Test
+ fun taskThumbnailView_splash_withoutHeader_withImageMatrix() {
+ screenshotRule.screenshotTest("taskThumbnailView_partial_splash_withMatrix") { activity ->
+ activity.actionBar?.hide()
+ createTaskThumbnailView(activity).apply {
+ val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200
+ setState(
+ SnapshotSplash(
+ Snapshot.WithoutHeader(
+ createBitmap(
+ width = VIEW_ENV_WIDTH / 2,
+ height = lessThanHeightMatchingAspectRatio,
+ ),
+ ROTATION_0,
+ Color.DKGRAY,
+ ),
+ BitmapDrawable(activity.resources, createSplash()),
+ )
+ )
+ setImageMatrix(Matrix().apply { postScale(2f, 2f) })
+ updateSplashAlpha(0.5f)
+ }
+ }
+ }
+
+ @Test
fun taskThumbnailView_dimmed_tintAmount() {
screenshotRule.screenshotTest("taskThumbnailView_dimmed_40") { activity ->
activity.actionBar?.hide()
@@ -114,6 +213,18 @@
}
}
+ @Test
+ fun taskThumbnailView_scaled_roundRoundedCorners() {
+ screenshotRule.screenshotTest("taskThumbnailView_scaledRoundedCorners") { activity ->
+ activity.actionBar?.hide()
+ createTaskThumbnailView(activity).apply {
+ scaleX = 0.75f
+ scaleY = 0.3f
+ setState(BackgroundOnly(Color.YELLOW))
+ }
+ }
+ }
+
private fun createTaskThumbnailView(context: Context): TaskThumbnailView {
val taskThumbnailView =
LayoutInflater.from(context).inflate(R.layout.task_thumbnail, null, false)
@@ -122,6 +233,27 @@
return taskThumbnailView
}
+ private fun createSplash() = createBitmap(width = 20, height = 20, rectColorRotation = 1)
+
+ private fun createBitmap(
+ width: Int = VIEW_ENV_WIDTH,
+ height: Int = VIEW_ENV_HEIGHT,
+ rectColorRotation: Int = 0,
+ ) =
+ Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
+ Canvas(this).apply {
+ val paint = Paint()
+ paint.color = BITMAP_RECT_COLORS[rectColorRotation % 4]
+ drawRect(0f, 0f, width / 2f, height / 2f, paint)
+ paint.color = BITMAP_RECT_COLORS[(1 + rectColorRotation) % 4]
+ drawRect(width / 2f, 0f, width.toFloat(), height / 2f, paint)
+ paint.color = BITMAP_RECT_COLORS[(2 + rectColorRotation) % 4]
+ drawRect(0f, height / 2f, width / 2f, height.toFloat(), paint)
+ paint.color = BITMAP_RECT_COLORS[(3 + rectColorRotation) % 4]
+ drawRect(width / 2f, height / 2f, width.toFloat(), height.toFloat(), paint)
+ }
+ }
+
companion object {
@Parameters(name = "{0}")
@JvmStatic
@@ -133,5 +265,8 @@
)
const val CORNER_RADIUS = 56f
+ val BITMAP_RECT_COLORS = listOf(Color.GREEN, Color.RED, Color.BLUE, Color.CYAN)
+ const val VIEW_ENV_WIDTH = 1440
+ const val VIEW_ENV_HEIGHT = 3120
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchTransitionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManagerTest.kt
similarity index 90%
rename from quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchTransitionManagerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManagerTest.kt
index 9ca9fe4..7ebef45 100644
--- a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchTransitionManagerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManagerTest.kt
@@ -27,12 +27,10 @@
import android.window.TransitionFilter
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.quickstep.SystemUiProxy
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.google.common.truth.Truth.assertThat
-import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -43,7 +41,6 @@
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-import org.mockito.quality.Strictness
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -51,12 +48,6 @@
@get:Rule val mSetFlagsRule = SetFlagsRule()
- private val mockitoSession =
- mockitoSession()
- .strictness(Strictness.LENIENT)
- .mockStatic(DesktopModeStatus::class.java)
- .startMocking()
-
private val context = mock<Context>()
private val systemUiProxy = mock<SystemUiProxy>()
private lateinit var transitionManager: DesktopAppLaunchTransitionManager
@@ -68,11 +59,6 @@
transitionManager = DesktopAppLaunchTransitionManager(context, systemUiProxy)
}
- @After
- fun tearDown() {
- mockitoSession.finishMocking()
- }
-
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun registerTransitions_appLaunchFlagEnabled_registersTransition() {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index 46b5659..a456fb9 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -60,6 +60,7 @@
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -573,6 +574,71 @@
}
@Test
+ fun animateToInitialState_whileDragging_inApp() {
+ setUpBubbleBar()
+ setUpBubbleStashController()
+ whenever(bubbleStashController.bubbleBarTranslationY)
+ .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
+
+ val handle = View(context)
+ val handleAnimator = PhysicsAnimator.getInstance(handle)
+ whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+
+ val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
+
+ var notifiedBubbleBarVisible = false
+ val onBubbleBarVisible = Runnable { notifiedBubbleBarVisible = true }
+ val animator =
+ BubbleBarViewAnimator(
+ bubbleBarView,
+ bubbleStashController,
+ flyoutController,
+ bubbleBarParentViewController,
+ onExpanded = emptyRunnable,
+ onBubbleBarVisible = onBubbleBarVisible,
+ animatorScheduler,
+ )
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ bubbleBarView.visibility = INVISIBLE
+ animator.animateToInitialState(
+ bubble,
+ isInApp = true,
+ isExpanding = false,
+ isDragging = true,
+ )
+ }
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ barAnimator.assertIsNotRunning()
+ assertThat(animator.isAnimating).isTrue()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
+ assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1)
+ waitForFlyoutToShow()
+
+ assertThat(animatorScheduler.delayedBlock).isNotNull()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
+
+ waitForFlyoutToHide()
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+ assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2)
+ assertThat(animator.isAnimating).isFalse()
+ assertThat(bubbleBarView.alpha).isEqualTo(1)
+ assertThat(handle.translationY).isEqualTo(0)
+ assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
+ assertThat(notifiedBubbleBarVisible).isTrue()
+
+ verify(bubbleStashController, never()).stashBubbleBarImmediate()
+ }
+
+ @Test
fun animateToInitialState_inApp_autoExpanding() {
setUpBubbleBar()
setUpBubbleStashController()
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
index 7776351..de6920b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
@@ -24,6 +24,8 @@
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_NAVBAR;
+import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.DeviceConfigWrapper.DEFAULT_LPNH_TIMEOUT_MS;
@@ -33,9 +35,11 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.os.SystemClock;
@@ -47,6 +51,7 @@
import com.android.launcher3.dagger.LauncherAppComponent;
import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.util.AllModulesForTest;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
@@ -83,6 +88,7 @@
private NavHandleLongPressInputConsumer mUnderTest;
private SandboxContext mContext;
private float mScreenWidth;
+ private long mDownTimeMs;
@Mock InputConsumer mDelegate;
@Mock InputMonitorCompat mInputMonitor;
@Mock RecentsAnimationDeviceState mDeviceState;
@@ -91,6 +97,9 @@
@Mock NavHandleLongPressHandler mNavHandleLongPressHandler;
@Mock TopTaskTracker mTopTaskTracker;
@Mock TopTaskTracker.CachedTaskInfo mTaskInfo;
+ @Mock StatsLogManager mStatsLogManager;
+ @Mock StatsLogManager.StatsLogger mStatsLogger;
+ @Mock StatsLogManager.StatsLatencyLogger mStatsLatencyLogger;
@Before
public void setup() {
@@ -100,6 +109,11 @@
when(mDelegate.allowInterceptByParent()).thenReturn(true);
mLongPressTriggered.set(false);
when(mNavHandleLongPressHandler.getLongPressRunnable(any())).thenReturn(mLongPressRunnable);
+ when(mStatsLogger.withPackageName(any())).thenReturn(mStatsLogger);
+ when(mStatsLatencyLogger.withInstanceId(any())).thenReturn(mStatsLatencyLogger);
+ when(mStatsLatencyLogger.withLatency(anyLong())).thenReturn(mStatsLatencyLogger);
+ when(mStatsLogManager.logger()).thenReturn(mStatsLogger);
+ when(mStatsLogManager.latencyLogger()).thenReturn(mStatsLatencyLogger);
initializeObjectUnderTest();
}
@@ -124,17 +138,24 @@
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
verify(mNavHandleLongPressHandler, never()).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogManager);
+ verifyNoMoreInteractions(mStatsLogger);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
@Test
public void testDelegateDisallowsTouchInterceptAfterTouchDown() {
+ // Touch down and wait the minimum abandonment time.
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
+ SystemClock.sleep(NavHandleLongPressInputConsumer.MIN_TIME_TO_LOG_ABANDON_MS);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
// Delegate should still get touches unless long press is triggered.
verify(mDelegate).onMotionEvent(any());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ // Child delegate blocks us from intercepting further motion events.
when(mDelegate.allowInterceptByParent()).thenReturn(false);
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_MOVE));
@@ -144,6 +165,9 @@
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ // Because we handled touch down before the child blocked additional events, log abandon.
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
}
@Test
@@ -156,6 +180,12 @@
assertTrue(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
+
+ // Ensure abandon latency is still not logged after long press.
+ mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_UP));
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
@Test
@@ -170,6 +200,8 @@
assertTrue(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
@Test
@@ -184,6 +216,8 @@
assertTrue(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
@Test
@@ -215,6 +249,8 @@
assertTrue(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -235,6 +271,8 @@
assertTrue(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -258,6 +296,8 @@
assertFalse(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
}
@Test
@@ -278,6 +318,8 @@
assertFalse(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
}
@Test
@@ -299,6 +341,8 @@
assertFalse(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
}
@Test
@@ -320,6 +364,8 @@
assertFalse(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
}
@Test
@@ -354,6 +400,8 @@
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
// Touch cancelled.
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -391,6 +439,8 @@
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
// Touch cancelled.
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -408,6 +458,9 @@
assertFalse(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, never()).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogManager);
+ verifyNoMoreInteractions(mStatsLogger);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
@Test
@@ -422,6 +475,21 @@
mUnderTest.onHoverEvent(generateCenteredMotionEvent(ACTION_HOVER_ENTER));
verify(mDelegate, times(2)).onHoverEvent(any());
+
+ verifyNoMoreInteractions(mStatsLogManager);
+ verifyNoMoreInteractions(mStatsLogger);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
+ }
+
+ @Test
+ public void testNoLogsForShortTouch() {
+ mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
+ SystemClock.sleep(10);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_UP));
+ verifyNoMoreInteractions(mStatsLogManager);
+ verifyNoMoreInteractions(mStatsLogger);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
private void initializeObjectUnderTest() {
@@ -437,6 +505,8 @@
mUnderTest = new NavHandleLongPressInputConsumer(mContext, mDelegate, mInputMonitor,
mDeviceState, mNavHandle, mGestureState);
mUnderTest.setNavHandleLongPressHandler(mNavHandleLongPressHandler);
+ mUnderTest.setStatsLogManager(mStatsLogManager);
+ mDownTimeMs = 0;
}
/** Generate a motion event centered horizontally in the screen. */
@@ -449,8 +519,12 @@
return generateMotionEvent(motionAction, mScreenWidth / 2f, y);
}
- private static MotionEvent generateMotionEvent(int motionAction, float x, float y) {
- return MotionEvent.obtain(0, 0, motionAction, x, y, 0);
+ private MotionEvent generateMotionEvent(int motionAction, float x, float y) {
+ if (motionAction == ACTION_DOWN) {
+ mDownTimeMs = SystemClock.uptimeMillis();
+ }
+ long eventTime = SystemClock.uptimeMillis();
+ return MotionEvent.obtain(mDownTimeMs, eventTime, motionAction, x, y, 0);
}
private static AutoCloseable overrideTwoStageFlag(boolean value) {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
index e10afc4..40d5e02 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
@@ -22,7 +22,6 @@
import com.android.systemui.shared.recents.model.ThumbnailData
import kotlinx.coroutines.yield
import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
class FakeTaskThumbnailDataSource : TaskThumbnailDataSource {
@@ -31,6 +30,8 @@
private val completionPrevented: MutableSet<Int> = mutableSetOf()
private val getThumbnailCalls = mutableMapOf<Int, Int>()
+ var highResEnabled = true
+
/** Retrieves and sets a thumbnail on [task] from [taskIdToBitmap]. */
override suspend fun getThumbnail(task: Task): ThumbnailData {
getThumbnailCalls[task.key.id] = (getThumbnailCalls[task.key.id] ?: 0) + 1
@@ -38,9 +39,10 @@
while (task.key.id in completionPrevented) {
yield()
}
- return mock<ThumbnailData>().also {
- whenever(it.thumbnail).thenReturn(taskIdToBitmap[task.key.id])
- }
+ return ThumbnailData(
+ thumbnail = taskIdToBitmap[task.key.id],
+ reducedResolution = !highResEnabled,
+ )
}
fun getNumberOfGetThumbnailCalls(taskId: Int): Int = getThumbnailCalls[taskId] ?: 0
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt
index 41f6bfd..b91f8bd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt
@@ -19,16 +19,19 @@
import android.content.ComponentName
import android.content.Intent
import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback
import com.android.systemui.shared.recents.model.Task.TaskKey
import com.android.systemui.shared.recents.model.ThumbnailData
import com.google.common.truth.Truth.assertThat
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
+@RunWith(AndroidJUnit4::class)
class TaskVisualsChangedDelegateTest {
private val taskVisualsChangeNotifier = FakeTaskVisualsChangeNotifier()
private val highResLoadingStateNotifier = FakeHighResLoadingStateNotifier()
@@ -83,21 +86,21 @@
// Correct match
systemUnderTest.registerTaskIconChangedCallback(
createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
- expectedListener
+ expectedListener,
)
// 1 out of 2 match
systemUnderTest.registerTaskIconChangedCallback(
createTaskKey(id = 2, pkg = PACKAGE_NAME, userId = 1),
- listener
+ listener,
)
systemUnderTest.registerTaskIconChangedCallback(
createTaskKey(id = 3, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 2),
- listener
+ listener,
)
// 0 out of 2 match
systemUnderTest.registerTaskIconChangedCallback(
createTaskKey(id = 4, pkg = PACKAGE_NAME, userId = 2),
- listener
+ listener,
)
systemUnderTest.onTaskIconChanged(ALTERNATIVE_PACKAGE_NAME, UserHandle(1))
@@ -112,11 +115,11 @@
val newListener = mock<TaskIconChangedCallback>()
systemUnderTest.registerTaskIconChangedCallback(
createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
- replacedListener
+ replacedListener,
)
systemUnderTest.registerTaskIconChangedCallback(
createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
- newListener
+ newListener,
)
systemUnderTest.onTaskIconChanged(ALTERNATIVE_PACKAGE_NAME, UserHandle(1))
@@ -132,11 +135,11 @@
val expectedThumbnailData = ThumbnailData(snapshotId = 12345)
systemUnderTest.registerTaskThumbnailChangedCallback(
createTaskKey(id = 1),
- expectedListener
+ expectedListener,
)
systemUnderTest.registerTaskThumbnailChangedCallback(
createTaskKey(id = 2),
- additionalListener
+ additionalListener,
)
systemUnderTest.onTaskThumbnailChanged(1, expectedThumbnailData)
@@ -146,22 +149,41 @@
}
@Test
- fun onHighResLoadingStateChanged_notifiesAllListeners() {
+ fun onHighResLoadingStateChanged_toEnabled_notifiesAllListeners() {
val expectedListener = mock<TaskThumbnailChangedCallback>()
val additionalListener = mock<TaskThumbnailChangedCallback>()
systemUnderTest.registerTaskThumbnailChangedCallback(
createTaskKey(id = 1),
- expectedListener
+ expectedListener,
)
systemUnderTest.registerTaskThumbnailChangedCallback(
createTaskKey(id = 2),
- additionalListener
+ additionalListener,
)
systemUnderTest.onHighResLoadingStateChanged(true)
- verify(expectedListener).onHighResLoadingStateChanged()
- verify(additionalListener).onHighResLoadingStateChanged()
+ verify(expectedListener).onHighResLoadingStateChanged(true)
+ verify(additionalListener).onHighResLoadingStateChanged(true)
+ }
+
+ @Test
+ fun onHighResLoadingStateChanged_toDisabled_notifiesAllListeners() {
+ val expectedListener = mock<TaskThumbnailChangedCallback>()
+ val additionalListener = mock<TaskThumbnailChangedCallback>()
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 1),
+ expectedListener,
+ )
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 2),
+ additionalListener,
+ )
+
+ systemUnderTest.onHighResLoadingStateChanged(false)
+
+ verify(expectedListener).onHighResLoadingStateChanged(false)
+ verify(additionalListener).onHighResLoadingStateChanged(false)
}
@Test
@@ -171,7 +193,7 @@
val expectedThumbnailData = ThumbnailData(snapshotId = 12345)
systemUnderTest.registerTaskThumbnailChangedCallback(
createTaskKey(id = 1),
- replacedListener1
+ replacedListener1,
)
systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), newListener1)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index 823f808..10be6fd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -326,8 +326,9 @@
}
@Test
- fun onHighResLoadingStateChanged_setsNewThumbnailDataOnTask() =
+ fun onHighResLoadingStateChanged_highResReplacesLowResThumbnail() =
testScope.runTest {
+ taskThumbnailDataSource.highResEnabled = false
recentsModel.seedTasks(defaultTaskList)
systemUnderTest.getAllTaskData(forceRefresh = true)
@@ -337,16 +338,77 @@
val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1]
val taskDataFlow = systemUnderTest.getTaskDataById(1)
- val task1ThumbnailValues = mutableListOf<Bitmap?>()
+ val task1ThumbnailValues = mutableListOf<ThumbnailData?>()
testScope.backgroundScope.launch {
- taskDataFlow.map { it?.thumbnail?.thumbnail }.toList(task1ThumbnailValues)
+ taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues)
}
taskThumbnailDataSource.taskIdToBitmap[1] = expectedBitmap
+ taskThumbnailDataSource.highResEnabled = true
taskVisualsChangedDelegate.onHighResLoadingStateChanged(true)
- assertThat(task1ThumbnailValues.first()).isEqualTo(expectedPreviousBitmap)
- assertThat(task1ThumbnailValues.last()).isEqualTo(expectedBitmap)
+ val firstThumbnailValue = task1ThumbnailValues.first()!!
+ assertThat(firstThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap)
+ assertThat(firstThumbnailValue.reducedResolution).isTrue()
+
+ val lastThumbnailValue = task1ThumbnailValues.last()!!
+ assertThat(lastThumbnailValue.thumbnail).isEqualTo(expectedBitmap)
+ assertThat(lastThumbnailValue.reducedResolution).isFalse()
+ }
+
+ @Test
+ fun onHighResLoadingStateChanged_invisibleTaskIgnored() =
+ testScope.runTest {
+ taskThumbnailDataSource.highResEnabled = false
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ systemUnderTest.setVisibleTasks(setOf(1))
+
+ val invisibleTaskId = 2
+ val taskDataFlow = systemUnderTest.getTaskDataById(invisibleTaskId)
+
+ val task2ThumbnailValues = mutableListOf<ThumbnailData?>()
+ testScope.backgroundScope.launch {
+ taskDataFlow.map { it?.thumbnail }.toList(task2ThumbnailValues)
+ }
+
+ taskThumbnailDataSource.highResEnabled = true
+ taskVisualsChangedDelegate.onHighResLoadingStateChanged(true)
+
+ assertThat(task2ThumbnailValues.filterNotNull()).isEmpty()
+ assertThat(taskThumbnailDataSource.getNumberOfGetThumbnailCalls(2)).isEqualTo(0)
+ }
+
+ @Test
+ fun onHighResLoadingStateChanged_lowResDoesNotReplaceHighResThumbnail() =
+ testScope.runTest {
+ taskThumbnailDataSource.highResEnabled = true
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ systemUnderTest.setVisibleTasks(setOf(1))
+
+ val expectedBitmap = mock<Bitmap>()
+ val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1]
+ val taskDataFlow = systemUnderTest.getTaskDataById(1)
+
+ val task1ThumbnailValues = mutableListOf<ThumbnailData?>()
+ testScope.backgroundScope.launch {
+ taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues)
+ }
+
+ taskThumbnailDataSource.taskIdToBitmap[1] = expectedBitmap
+ taskThumbnailDataSource.highResEnabled = false
+ taskVisualsChangedDelegate.onHighResLoadingStateChanged(false)
+
+ val firstThumbnailValue = task1ThumbnailValues.first()!!
+ assertThat(firstThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap)
+ assertThat(firstThumbnailValue.reducedResolution).isFalse()
+
+ val lastThumbnailValue = task1ThumbnailValues.last()!!
+ assertThat(lastThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap)
+ assertThat(lastThumbnailValue.reducedResolution).isFalse()
}
@Test
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
deleted file mode 100644
index 90ef61d..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.recents.usecase
-
-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.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(TASK_ID to thumbnailData))
-
- assertThat(systemUnderTest.run(TASK_ID)).isNull()
- }
-
- @Test
- fun taskVisible_returnsThumbnail() {
- tasksRepository.seedTasks(listOf(task))
- tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
- tasksRepository.setVisibleTasks(setOf(TASK_ID))
-
- assertThat(systemUnderTest.run(TASK_ID)).isEqualTo(thumbnailData.thumbnail)
- }
-
- private companion object {
- const val TASK_ID = 0
- const val THUMBNAIL_WIDTH = 100
- const val THUMBNAIL_HEIGHT = 200
- }
-}
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index fa2eb1e..d52d054 100644
--- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -91,6 +91,7 @@
private AppWidgetProviderInfo mApp4Provider1;
private AppWidgetProviderInfo mApp4Provider2;
private AppWidgetProviderInfo mApp5Provider1;
+ private AppWidgetProviderInfo mApp6PinOnlyProvider1;
private List<AppWidgetProviderInfo> allWidgets;
private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback();
@@ -117,8 +118,14 @@
ComponentName.createRelative("app4", ".provider2"));
mApp5Provider1 = createAppWidgetProviderInfo(
ComponentName.createRelative("app5", "provider1"));
+ mApp6PinOnlyProvider1 = createAppWidgetProviderInfo(
+ ComponentName.createRelative("app6", "provider1"),
+ /*hideFromPicker=*/ true
+ );
+
+
allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1,
- mApp4Provider1, mApp4Provider2, mApp5Provider1);
+ mApp4Provider1, mApp4Provider2, mApp5Provider1, mApp6PinOnlyProvider1);
mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class);
doAnswer(i -> {
@@ -270,6 +277,32 @@
});
}
+ @Test
+ public void widgetsRecommendations_excludesWidgetsHiddenForPicker() {
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
+
+ // Not installed widget - hence eligible
+ AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1",
+ mUserHandle);
+ // Provider marked as hidden from picker - hence not eligible
+ AppTarget widget6 = new AppTarget(new AppTargetId("app6"), "app6", "provider1",
+ mUserHandle);
+
+ mCallback.mRecommendedWidgets = null;
+ mModelHelper.getModel().enqueueModelUpdateTask(
+ newWidgetsPredicationTask(List.of(widget1, widget6)));
+ runOnExecutorSync(MAIN_EXECUTOR, () -> { });
+
+ // Only widget 1 (and no widget 6 as its meant to be hidden from picker).
+ List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
+ .stream()
+ .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
+ .collect(Collectors.toList());
+ assertThat(recommendedWidgets).hasSize(1);
+ assertThat(recommendedWidgets.get(0).componentName.getPackageName()).isEqualTo("app1");
+ });
+ }
+
private void assertWidgetInfo(
LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected) {
assertThat(actual.provider).isEqualTo(expected.provider);
diff --git a/quickstep/tests/src/com/android/launcher3/statehandlers/DesktopVisibilityControllerTest.kt b/quickstep/tests/src/com/android/launcher3/statehandlers/DesktopVisibilityControllerTest.kt
new file mode 100644
index 0000000..4b8f2a2
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/statehandlers/DesktopVisibilityControllerTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2025 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.statehandlers
+
+import android.content.Context
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.quickstep.SystemUiProxy
+import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
+import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import org.junit.After
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+/**
+ * Tests the behavior of [DesktopVisibilityController] in regards to multiple desktops and multiple
+ * displays.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DesktopVisibilityControllerTest {
+
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ private val mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(DesktopModeStatus::class.java)
+ .startMocking()
+
+ private val context = mock<Context>()
+ private val systemUiProxy = mock<SystemUiProxy>()
+ private val lifeCycleTracker = mock<DaggerSingletonTracker>()
+ private lateinit var desktopVisibilityController: DesktopVisibilityController
+
+ @Before
+ fun setUp() {
+ whenever(context.resources).thenReturn(mock())
+ whenever(DesktopModeStatus.enableMultipleDesktops(context)).thenReturn(true)
+ desktopVisibilityController =
+ DesktopVisibilityController(context, systemUiProxy, lifeCycleTracker)
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND)
+ fun noCrashWhenCheckingNonExistentDisplay() {
+ assertFalse(desktopVisibilityController.isInDesktopMode(displayId = 500))
+ assertFalse(desktopVisibilityController.isInDesktopModeAndNotInOverview(displayId = 300))
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index 76aab39..a523e02 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.Display.DEFAULT_DISPLAY
import androidx.test.platform.app.InstrumentationRegistry
@@ -106,6 +107,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun createDesktopTaskShortcutFactory_transparentTask() {
val baseComponent = ComponentName("", /* class */ "")
val taskKey =
diff --git a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
index 818841a..2db94f6 100644
--- a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.view.Display.DEFAULT_DISPLAY
@@ -113,6 +114,7 @@
Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT,
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
)
+ @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun createExternalDisplayTaskShortcut_transparentTask() {
val baseComponent = ComponentName("", /* class */ "")
val taskKey =
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 79d3c19..1c7f51c 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -50,6 +50,7 @@
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.rule.ScreenRecordRule;
import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
@@ -62,6 +63,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -541,6 +543,7 @@
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // TODO(b/396447643): Remove screen record.
public void testDismissCancel() throws Exception {
startTestAppsWithCheck();
Overview overview = mLauncher.goHome().switchToOverview();
@@ -557,6 +560,27 @@
numTasks == null ? 0 : numTasks, recentsView.getTaskViewCount()));
}
+ @Test
+ @PortraitLandscape
+ public void testDismissBottomRow() throws Exception {
+ assumeTrue(mLauncher.isTablet());
+ mLauncher.goHome().switchToOverview().dismissAllTasks();
+ startTestAppsWithCheck();
+ Overview overview = mLauncher.goHome().switchToOverview();
+ assertIsInState("Launcher internal state didn't switch to Overview",
+ ExpectedState.OVERVIEW);
+ final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
+ OverviewTask bottomTask = overview.getCurrentTasksForTablet().stream().max(
+ Comparator.comparingInt(OverviewTask::getTaskCenterY)).get();
+ assertNotNull("bottomTask null", bottomTask);
+
+ bottomTask.dismiss();
+
+ runOnRecentsView(recentsView -> assertEquals(
+ "Dismissing a bottomTask didn't remove 1 bottomTask from Overview",
+ numTasks - 1, recentsView.getTaskViewCount()));
+ }
+
private void startTestAppsWithCheck() throws Exception {
startTestApps();
expectLaunchedAppState();
diff --git a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt
new file mode 100644
index 0000000..b4d9f5b
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2025 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.desktop
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.util.DisplayMetrics
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import androidx.core.util.Supplier
+import com.android.app.animation.Interpolators
+import com.android.internal.jank.Cuj
+import com.android.launcher3.desktop.DesktopAppLaunchAnimatorHelper
+import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.window.flags.Flags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+class DesktopAppLaunchAnimatorHelperTest {
+
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ private val context = mock<Context>()
+ private val resources = mock<Resources>()
+ private val transaction = mock<SurfaceControl.Transaction>()
+ private val transactionSupplier = mock<Supplier<SurfaceControl.Transaction>>()
+
+ private lateinit var helper: DesktopAppLaunchAnimatorHelper
+
+ @Before
+ fun setUp() {
+ helper =
+ DesktopAppLaunchAnimatorHelper(
+ context = context,
+ launchType = AppLaunchType.LAUNCH,
+ cujType = Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_INTENT,
+ transactionSupplier = transactionSupplier,
+ )
+ whenever(transactionSupplier.get()).thenReturn(transaction)
+ whenever(transaction.setCrop(any(), any())).thenReturn(transaction)
+ whenever(transaction.setCornerRadius(any(), any())).thenReturn(transaction)
+
+ whenever(context.resources).thenReturn(resources)
+ whenever(resources.displayMetrics).thenReturn(DisplayMetrics())
+ whenever(context.mainThreadHandler).thenReturn(MAIN_EXECUTOR.handler)
+ }
+
+ @Test
+ fun launchTransition_returnsLaunchAnimator() {
+ val openChange =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_OPEN
+ taskInfo = TASK_INFO_FREEFORM
+ }
+ val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+ transitionInfo.addChange(openChange)
+
+ val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+ assertThat(actual).hasSize(1)
+ assertLaunchAnimator(actual[0])
+ }
+
+ @Test
+ fun minimizeTransition_returnsLaunchAndMinimizeAnimator() {
+ val openChange =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_OPEN
+ taskInfo = TASK_INFO_FREEFORM
+ }
+ val minimizeChange =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_TO_BACK
+ taskInfo = TASK_INFO_FREEFORM
+ }
+ val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+ transitionInfo.addChange(openChange)
+ transitionInfo.addChange(minimizeChange)
+
+ val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+ assertThat(actual).hasSize(2)
+ assertLaunchAnimator(actual[0])
+ assertMinimizeAnimator(actual[1])
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX)
+ fun trampolineTransition_flagEnabled_returnsLaunchAndCloseAnimator() {
+ val openChange =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_OPEN
+ taskInfo = TASK_INFO_FREEFORM
+ }
+ val closeChange =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_CLOSE
+ taskInfo = TASK_INFO_FREEFORM
+ }
+ val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+ transitionInfo.addChange(openChange)
+ transitionInfo.addChange(closeChange)
+
+ val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+ assertThat(actual).hasSize(2)
+ assertTrampolineLaunchAnimator(actual[0])
+ assertCloseAnimator(actual[1])
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX)
+ fun trampolineTransition_flagDisabled_returnsLaunchAnimator() {
+ val openChange =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_OPEN
+ taskInfo = TASK_INFO_FREEFORM
+ }
+ val closeChange =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_CLOSE
+ taskInfo = TASK_INFO_FREEFORM
+ }
+ val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+ transitionInfo.addChange(openChange)
+ transitionInfo.addChange(closeChange)
+
+ val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+ assertThat(actual).hasSize(1)
+ assertLaunchAnimator(actual[0])
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX)
+ fun trampolineTransition_flagEnabled_hitDesktopWindowLimit_returnsLaunchMinimizeCloseAnimator() {
+ val openChange =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_OPEN
+ taskInfo = TASK_INFO_FREEFORM
+ }
+ val minimizeChange =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_TO_BACK
+ taskInfo = TASK_INFO_FREEFORM
+ }
+ val closeChange =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_CLOSE
+ taskInfo = TASK_INFO_FREEFORM
+ }
+ val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+ transitionInfo.addChange(openChange)
+ transitionInfo.addChange(minimizeChange)
+ transitionInfo.addChange(closeChange)
+
+ val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+ assertThat(actual).hasSize(3)
+ assertTrampolineLaunchAnimator(actual[0])
+ assertMinimizeAnimator(actual[1])
+ assertCloseAnimator(actual[2])
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX)
+ fun trampolineTransition_flagDisabled_hitDesktopWindowLimit_returnsLaunchMinimizeAnimator() {
+ val openChange =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_OPEN
+ taskInfo = TASK_INFO_FREEFORM
+ }
+ val minimizeChange =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_TO_BACK
+ taskInfo = TASK_INFO_FREEFORM
+ }
+ val closeChange =
+ TransitionInfo.Change(mock(), mock()).apply {
+ mode = WindowManager.TRANSIT_CLOSE
+ taskInfo = TASK_INFO_FREEFORM
+ }
+ val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+ transitionInfo.addChange(openChange)
+ transitionInfo.addChange(minimizeChange)
+ transitionInfo.addChange(closeChange)
+
+ val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+ assertThat(actual).hasSize(2)
+ assertLaunchAnimator(actual[0])
+ assertMinimizeAnimator(actual[1])
+ }
+
+ private fun assertLaunchAnimator(animator: Animator) {
+ assertThat(animator).isInstanceOf(AnimatorSet::class.java)
+ assertThat((animator as AnimatorSet).childAnimations.size).isEqualTo(2)
+ assertThat(animator.childAnimations[0]).isInstanceOf(ValueAnimator::class.java)
+ assertThat(animator.childAnimations[0].interpolator)
+ .isEqualTo(AppLaunchType.LAUNCH.boundsAnimationParams.interpolator)
+ assertThat(animator.childAnimations[0].duration)
+ .isEqualTo(AppLaunchType.LAUNCH.boundsAnimationParams.durationMs)
+ assertThat(animator.childAnimations[1]).isInstanceOf(ValueAnimator::class.java)
+ assertThat(animator.childAnimations[1].interpolator).isEqualTo(Interpolators.LINEAR)
+ assertThat(animator.childAnimations[1].duration)
+ .isEqualTo(AppLaunchType.LAUNCH.alphaDurationMs)
+ }
+
+ private fun assertTrampolineLaunchAnimator(animator: Animator) {
+ assertThat(animator).isInstanceOf(AnimatorSet::class.java)
+ assertThat((animator as AnimatorSet).childAnimations.size).isEqualTo(1)
+ assertThat(animator.childAnimations[0]).isInstanceOf(ValueAnimator::class.java)
+ assertThat(animator.childAnimations[0].interpolator).isEqualTo(Interpolators.LINEAR)
+ assertThat(animator.childAnimations[0].duration)
+ .isEqualTo(AppLaunchType.LAUNCH.alphaDurationMs)
+ }
+
+ private fun assertMinimizeAnimator(animator: Animator) {
+ assertThat(animator).isInstanceOf(AnimatorSet::class.java)
+ assertThat((animator as AnimatorSet).childAnimations.size).isEqualTo(2)
+ assertThat(animator.childAnimations[0]).isInstanceOf(ValueAnimator::class.java)
+ assertThat(animator.childAnimations[0].interpolator)
+ .isInstanceOf(Interpolators.STANDARD_ACCELERATE::class.java)
+ assertThat(animator.childAnimations[0].duration).isEqualTo(200)
+ assertThat(animator.childAnimations[1]).isInstanceOf(ValueAnimator::class.java)
+ assertThat(animator.childAnimations[1].interpolator)
+ .isInstanceOf(Interpolators.LINEAR::class.java)
+ assertThat(animator.childAnimations[1].duration).isEqualTo(100)
+ }
+
+ private fun assertCloseAnimator(animator: Animator) {
+ assertThat(animator).isInstanceOf(ValueAnimator::class.java)
+ assertThat(animator.interpolator).isInstanceOf(Interpolators.LINEAR::class.java)
+ assertThat(animator.duration).isEqualTo(100)
+ }
+
+ private companion object {
+ val TASK_INFO_FREEFORM =
+ ActivityManager.RunningTaskInfo().apply {
+ baseIntent =
+ Intent().apply {
+ component = ComponentName("com.example.app", "com.example.app.MainActivity")
+ }
+ configuration.windowConfiguration.windowingMode =
+ WindowConfiguration.WINDOWING_MODE_FREEFORM
+ }
+ }
+}
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index bd792ac..1a4a3f2 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -28,7 +28,7 @@
<string name="shortcut_not_available" msgid="2536503539825726397">"Sparčiojo klavišo negalima naudoti"</string>
<string name="home_screen" msgid="5629429142036709174">"Pagrindinis"</string>
<string name="set_default_home_app" msgid="5808906607627586381">"Nustatykite „<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>“ kaip numatytąją pagrindinę programą skiltyje „Nustatymai“"</string>
- <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Išskaidyto ekrano režimas"</string>
+ <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Išskaidytas ekranas"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Programos „%1$s“ informacija"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"„%1$s“ naudojimo nustatymai"</string>
<string name="new_window_option_taskbar" msgid="6448780542727767211">"Naujas langas"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 3031259..46aadc0 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -63,7 +63,7 @@
<string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"ସନ୍ଧାନ ବାକ୍ସରୁ ଟେକ୍ସଟ୍ ଖାଲି କରନ୍ତୁ"</string>
<string name="no_widgets_available" msgid="4337693382501046170">"ୱିଜେଟ୍ ଏବଂ ସର୍ଟକଟଗୁଡ଼ିକ ଉପଲବ୍ଧ ନାହିଁ"</string>
- <string name="no_search_results" msgid="3787956167293097509">"କୌଣସି ୱିଜେଟ୍ କିମ୍ବା ସର୍ଟକଟ୍ ମିଳିଲା ନାହିଁ"</string>
+ <string name="no_search_results" msgid="3787956167293097509">"କୌଣସି ୱିଜେଟ କିମ୍ବା ସର୍ଟକଟ ମିଳିଲା ନାହିଁ"</string>
<string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"ବ୍ୟକ୍ତିଗତ"</string>
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ୱାର୍କ"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index c15e2e9..17ecb78 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -94,7 +94,7 @@
<string name="all_apps_button_work_label" msgid="7270707118948892488">"ਕਾਰਜ-ਸਥਾਨ ਸੰਬੰਧੀ ਐਪਾਂ ਦੀ ਸੂਚੀ"</string>
<string name="remove_drop_target_label" msgid="7812859488053230776">"ਹਟਾਓ"</string>
<string name="uninstall_drop_target_label" msgid="4722034217958379417">"ਅਣਸਥਾਪਤ ਕਰੋ"</string>
- <string name="app_info_drop_target_label" msgid="692894985365717661">"ਐਪ ਜਾਣਕਾਰੀ"</string>
+ <string name="app_info_drop_target_label" msgid="692894985365717661">"ਐਪ ਸੰਬੰਧੀ ਜਾਣਕਾਰੀ"</string>
<string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ਪ੍ਰਾਈਵੇਟ ਵਜੋਂ ਸਥਾਪਤ ਕਰੋ"</string>
<string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ਐਪ ਅਣਸਥਾਪਤ ਕਰੋ"</string>
<string name="install_drop_target_label" msgid="2539096853673231757">"ਸਥਾਪਤ ਕਰੋ"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index b6563b0..4145fb6 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -46,14 +46,14 @@
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d వెడల్పు X %2$d ఎత్తు"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> విడ్జెట్"</string>
<string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> విడ్జెట్, %2$d వెడల్పు %3$d ఎత్తు ఉండాలి"</string>
- <string name="add_item_request_drag_hint" msgid="8730547755622776606">"విడ్జెట్ను మొదటి స్క్రీన్లో తిప్పడానికి దాన్ని తాకి, & నొక్కి పట్టుకోండి"</string>
+ <string name="add_item_request_drag_hint" msgid="8730547755622776606">"విడ్జెట్ను మొదటి స్క్రీన్లో అటు, ఇటు కదపడానికి దాన్ని తాకి, నొక్కి పట్టుకోండి"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"మొదటి స్క్రీన్కు జోడించండి"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"మొదటి స్క్రీన్కు <xliff:g id="WIDGET_NAME">%1$s</xliff:g> విడ్జెట్ జోడించబడింది"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"సూచనలు"</string>
<string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"నిత్యావసరాలు"</string>
<string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"వార్తలు & మ్యాగజైన్లు"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"వినోదం"</string>
- <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"సామాజికం"</string>
+ <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"సోషల్ మీడియా"</string>
<string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"మీ కోసం సూచించినవి"</string>
<string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"కుడి వైపున <xliff:g id="SELECTED_HEADER">%1$s</xliff:g> విడ్జెట్లు, ఎడమ వైపున సెర్చ్, ఇతర ఆప్షన్లు"</string>
<string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# విడ్జెట్}other{# విడ్జెట్లు}}"</string>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 03dd943..bd42b2b 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -27,6 +27,7 @@
import static com.android.launcher3.icons.BitmapInfo.FLAG_SKIP_USER_BADGE;
import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
@@ -76,7 +77,6 @@
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.PreloadIconDrawable;
-import com.android.launcher3.graphics.ShapeDelegate;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
@@ -723,8 +723,7 @@
protected void drawDotIfNecessary(Canvas canvas) {
if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) {
getIconBounds(mDotParams.iconBounds);
- Utilities.scaleRectAboutCenter(mDotParams.iconBounds,
- ShapeDelegate.getNormalizationScale());
+ Utilities.scaleRectAboutCenter(mDotParams.iconBounds, ICON_VISIBLE_AREA_FACTOR);
final int scrollX = getScrollX();
final int scrollY = getScrollY();
canvas.translate(scrollX, scrollY);
@@ -773,9 +772,7 @@
return;
}
getIconBounds(mRunningAppIconBounds);
- Utilities.scaleRectAboutCenter(
- mRunningAppIconBounds,
- ShapeDelegate.getNormalizationScale());
+ Utilities.scaleRectAboutCenter(mRunningAppIconBounds, ICON_VISIBLE_AREA_FACTOR);
final boolean isMinimized = mRunningAppState == RunningAppState.MINIMIZED;
final int indicatorTop = mRunningAppIconBounds.bottom + mRunningAppIndicatorTopMargin;
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f34add8..c85ca49 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -31,6 +31,7 @@
import static com.android.launcher3.testing.shared.ResourceUtils.roundPxValueFromFloat;
import static com.android.wm.shell.Flags.enableBubbleBar;
import static com.android.wm.shell.Flags.enableTinyTaskbar;
+import static com.android.wm.shell.Flags.enableBubbleBarOnPhones;
import android.annotation.SuppressLint;
import android.content.Context;
@@ -54,7 +55,6 @@
import com.android.launcher3.folder.ClippedFolderIconLayoutRule;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.DotRenderer;
-import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.responsive.CalculatedCellSpec;
import com.android.launcher3.responsive.CalculatedHotseatSpec;
@@ -420,7 +420,9 @@
isTablet = info.isTablet(windowBounds);
isPhone = !isTablet;
isTwoPanels = isTablet && isMultiDisplay;
- isTaskbarPresent = (isTablet || (enableTinyTaskbar() && isGestureMode))
+ boolean taskbarOrBubbleBarOnPhones = enableTinyTaskbar()
+ || (enableBubbleBar() && enableBubbleBarOnPhones());
+ isTaskbarPresent = (isTablet || (taskbarOrBubbleBarOnPhones && isGestureMode))
&& wmProxy.isTaskbarDrawnInProcess();
// Some more constants.
@@ -1371,7 +1373,7 @@
updateHotseatSizes(iconSizePx);
// Folder icon
- folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
+ folderIconSizePx = Math.round(iconSizePx * ICON_VISIBLE_AREA_FACTOR);
folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
// Update widget padding:
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index c2d6df5..f189549 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -85,6 +85,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import javax.inject.Inject;
@@ -252,7 +253,7 @@
public Point defaultWallpaperSize;
- private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
+ private final List<OnIDPChangeListener> mChangeListeners = new CopyOnWriteArrayList<>();
@Inject
InvariantDeviceProfile(
@@ -312,7 +313,7 @@
context,
gridName,
displayInfo,
- RestoreDbTask.isPending(mPrefs),
+ (RestoreDbTask.isPending(mPrefs) && !Flags.oneGridSpecs()),
mPrefs.get(FIXED_LANDSCAPE_MODE)
);
@@ -505,6 +506,7 @@
* Updates the current grid, this triggers a new IDP, reloads the database and triggers a grid
* migration.
*/
+ @VisibleForTesting
public void setCurrentGrid(Context context, String newGridName) {
mPrefs.put(GRID_NAME, newGridName);
MAIN_EXECUTOR.execute(() -> {
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 71013c3..bf2ad92 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -16,54 +16,32 @@
package com.android.launcher3;
-import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
-import static android.content.Context.RECEIVER_EXPORTED;
-
-import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
-import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
-import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI;
-
-import android.content.ComponentName;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.pm.LauncherApps;
-import android.content.pm.LauncherApps.ArchiveCompatibilityParams;
import android.util.Log;
import androidx.annotation.Nullable;
-import androidx.core.os.BuildCompat;
+import com.android.launcher3.dagger.LauncherComponentProvider;
import com.android.launcher3.graphics.ThemeManager;
-import com.android.launcher3.graphics.ThemeManager.ThemeChangeListener;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.LauncherIconProvider;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.ModelLauncherCallbacks;
+import com.android.launcher3.model.ModelInitializer;
import com.android.launcher3.model.WidgetsFilterDataProvider;
-import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pm.InstallSessionHelper;
-import com.android.launcher3.pm.InstallSessionTracker;
import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SettingsCache;
-import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.widget.custom.CustomWidgetManager;
public class LauncherAppState implements SafeCloseable {
public static final String TAG = "LauncherAppState";
- public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
@@ -94,82 +72,21 @@
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
() -> context.getPackageManager().isSafeMode());
- mInvariantDeviceProfile.addOnChangeListener(modelPropertiesChanged -> {
- if (modelPropertiesChanged) {
- refreshAndReloadLauncher();
- }
- });
- ThemeChangeListener themeChangeListener = this::refreshAndReloadLauncher;
- ThemeManager.INSTANCE.get(context).addChangeListener(themeChangeListener);
- mOnTerminateCallback.add(() ->
- ThemeManager.INSTANCE.get(context).removeChangeListener(themeChangeListener));
-
- ModelLauncherCallbacks callbacks = mModel.newModelCallbacks();
- LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
- launcherApps.registerCallback(callbacks);
- mOnTerminateCallback.add(() ->
- mContext.getSystemService(LauncherApps.class).unregisterCallback(callbacks));
-
- if (BuildCompat.isAtLeastV() && Flags.enableSupportForArchiving()) {
- ArchiveCompatibilityParams params = new ArchiveCompatibilityParams();
- params.setEnableUnarchivalConfirmation(false);
- params.setEnableIconOverlay(!Flags.useNewIconForArchivedApps());
- launcherApps.setArchiveCompatibility(params);
- }
-
- SimpleBroadcastReceiver modelChangeReceiver =
- new SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR, mModel::onBroadcastIntent);
- modelChangeReceiver.register(
- ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
- if (BuildConfig.IS_STUDIO_BUILD) {
- modelChangeReceiver.register(RECEIVER_EXPORTED, ACTION_FORCE_ROLOAD);
- }
- mOnTerminateCallback.add(() -> modelChangeReceiver.unregisterReceiverSafely());
-
- SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
- .addUserEventListener(mModel::onUserEvent);
- mOnTerminateCallback.add(userChangeListener::close);
-
- if (enableSmartspaceRemovalToggle()) {
- OnSharedPreferenceChangeListener firstPagePinnedItemListener =
- new OnSharedPreferenceChangeListener() {
- @Override
- public void onSharedPreferenceChanged(
- SharedPreferences sharedPreferences, String key) {
- if (SMARTSPACE_ON_HOME_SCREEN.equals(key)) {
- mModel.forceReload();
- }
- }
- };
- LauncherPrefs.getPrefs(mContext).registerOnSharedPreferenceChangeListener(
- firstPagePinnedItemListener);
- mOnTerminateCallback.add(() -> LauncherPrefs.getPrefs(mContext)
- .unregisterOnSharedPreferenceChangeListener(firstPagePinnedItemListener));
- }
-
- LockedUserState.get(context).runOnUserUnlocked(() -> {
- CustomWidgetManager cwm = CustomWidgetManager.INSTANCE.get(mContext);
- mOnTerminateCallback.add(cwm.addWidgetRefreshCallback(mModel::rebindCallbacks)::close);
-
- SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
- mModel::onAppIconChanged, MODEL_EXECUTOR.getHandler());
- mOnTerminateCallback.add(iconChangeTracker::close);
-
- InstallSessionTracker installSessionTracker =
- InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(callbacks);
- mOnTerminateCallback.add(installSessionTracker::unregister);
- });
-
- // Register an observer to rebind the notification listener when dots are re-enabled.
- SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
- SettingsCache.OnChangeListener notificationLister = this::onNotificationSettingsChanged;
- settingsCache.register(NOTIFICATION_BADGING_URI, notificationLister);
- onNotificationSettingsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI));
- mOnTerminateCallback.add(() ->
- settingsCache.unregister(NOTIFICATION_BADGING_URI, notificationLister));
- // Register an observer to notify Launcher about Private Space settings toggle.
- registerPrivateSpaceHideWhenLockListener(settingsCache);
+ ModelInitializer initializer = new ModelInitializer(
+ context,
+ LauncherComponentProvider.get(context).getIconPool(),
+ mIconCache,
+ mInvariantDeviceProfile,
+ ThemeManager.INSTANCE.get(context),
+ UserCache.INSTANCE.get(context),
+ SettingsCache.INSTANCE.get(context),
+ mIconProvider,
+ CustomWidgetManager.INSTANCE.get(context),
+ InstallSessionHelper.INSTANCE.get(context),
+ closeable -> mOnTerminateCallback.add(closeable::close)
+ );
+ initializer.initialize(mModel);
}
public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
@@ -186,32 +103,6 @@
mOnTerminateCallback.add(mModel::destroy);
}
- private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
- if (areNotificationDotsEnabled) {
- NotificationListener.requestRebind(new ComponentName(
- mContext, NotificationListener.class));
- }
- }
-
- private void registerPrivateSpaceHideWhenLockListener(SettingsCache settingsCache) {
- SettingsCache.OnChangeListener psHideWhenLockChangedListener =
- this::onPrivateSpaceHideWhenLockChanged;
- settingsCache.register(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, psHideWhenLockChangedListener);
- mOnTerminateCallback.add(() -> settingsCache.unregister(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI,
- psHideWhenLockChangedListener));
- }
-
- private void onPrivateSpaceHideWhenLockChanged(boolean isPrivateSpaceHideOnLockEnabled) {
- mModel.forceReload();
- }
-
- private void refreshAndReloadLauncher() {
- LauncherIcons.clearPool(mContext);
- mIconCache.updateIconParams(
- mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize);
- mModel.forceReload();
- }
-
/**
* Call from Application.onTerminate(), which is not guaranteed to ever be called.
*/
diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt
index 185629b..6e4276d 100644
--- a/src/com/android/launcher3/LauncherModel.kt
+++ b/src/com/android/launcher3/LauncherModel.kt
@@ -15,13 +15,11 @@
*/
package com.android.launcher3
-import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
import android.os.UserHandle
import android.text.TextUtils
-import android.util.Log
import android.util.Pair
import androidx.annotation.WorkerThread
import com.android.launcher3.celllayout.CellPosMapper
@@ -47,7 +45,6 @@
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.pm.UserCache
import com.android.launcher3.shortcuts.ShortcutRequest
-import com.android.launcher3.testing.shared.TestProtocol.sDebugTracing
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.PackageManagerHelper
@@ -173,15 +170,8 @@
}
}
- fun onBroadcastIntent(intent: Intent) {
- if (DEBUG_RECEIVER || sDebugTracing) Log.d(TAG, "onReceive intent=$intent")
- when (intent.action) {
- LauncherAppState.ACTION_FORCE_ROLOAD ->
- // If we have changed locale we need to clear out the labels in all apps/workspace.
- forceReload()
- DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED ->
- enqueueModelUpdateTask(ReloadStringCacheTask(this.modelDelegate))
- }
+ fun reloadStringCache() {
+ enqueueModelUpdateTask(ReloadStringCacheTask(this.modelDelegate))
}
/**
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 260ff9f..fafa60b 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -116,6 +116,7 @@
ScrimView.ScrimDrawingController {
+ private static final String TAG = "ActivityAllAppsContainerView";
public static final float PULL_MULTIPLIER = .02f;
public static final float FLING_VELOCITY_MULTIPLIER = 1200f;
protected static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page";
@@ -600,6 +601,7 @@
mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN);
findViewById(R.id.tab_personal)
.setOnClickListener((View view) -> {
+ Log.d(TAG, "rebindAdapters: " + "Clicked personal tab.");
if (mViewPager.snapToPage(AdapterHolder.MAIN)) {
mActivityContext.getStatsLogManager().logger()
.log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB);
@@ -607,6 +609,7 @@
});
findViewById(R.id.tab_work)
.setOnClickListener((View view) -> {
+ Log.d(TAG, "rebindAdapters: " + "Clicked work tab.");
if (mViewPager.snapToPage(AdapterHolder.WORK)) {
mActivityContext.getStatsLogManager().logger()
.log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB);
diff --git a/src/com/android/launcher3/allapps/WorkPausedCard.java b/src/com/android/launcher3/allapps/WorkPausedCard.java
index a14ac98..864ede8 100644
--- a/src/com/android/launcher3/allapps/WorkPausedCard.java
+++ b/src/com/android/launcher3/allapps/WorkPausedCard.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
@@ -34,6 +35,7 @@
*/
public class WorkPausedCard extends LinearLayout implements View.OnClickListener {
+ private static final String TAG = "WorkPausedCard";
private final ActivityContext mActivityContext;
private Button mBtn;
@@ -79,6 +81,7 @@
@Override
public void onClick(View view) {
+ Log.d(TAG, "WorkPausedCard clicked.");
mActivityContext.getAppsView().getWorkManager().setWorkProfileEnabled(true);
mActivityContext.getStatsLogManager().logger().log(LAUNCHER_TURN_ON_WORK_APPS_TAP);
}
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index 6d7d193..920efa4 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -200,6 +200,7 @@
private void onWorkFabClicked(View view) {
if (getCurrentState() == STATE_ENABLED && mWorkUtilityView.isEnabled()) {
+ Log.d(TAG, "Work FAB clicked.");
logEvents(LAUNCHER_TURN_OFF_WORK_APPS_TAP);
setWorkProfileEnabled(false);
}
diff --git a/src/com/android/launcher3/allapps/WorkUtilityView.java b/src/com/android/launcher3/allapps/WorkUtilityView.java
index e42a6b9..20ceb15 100644
--- a/src/com/android/launcher3/allapps/WorkUtilityView.java
+++ b/src/com/android/launcher3/allapps/WorkUtilityView.java
@@ -30,6 +30,7 @@
import android.graphics.Rect;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -63,6 +64,7 @@
public class WorkUtilityView extends LinearLayout implements Insettable,
KeyboardInsetAnimationCallback.KeyboardInsetListener {
+ private static final String TAG = "WorkUtilityView";
private static final int TEXT_EXPAND_OPACITY_DURATION = 300;
private static final int TEXT_COLLAPSE_OPACITY_DURATION = 50;
private static final int EXPAND_COLLAPSE_DURATION = 300;
@@ -143,9 +145,11 @@
mSchedulerButton.setOnClickListener(null);
if (shouldUseScheduler()) {
mSchedulerButton.setVisibility(VISIBLE);
- mSchedulerButton.setOnClickListener(view ->
- mActivityContext.startActivitySafely(view,
- new Intent(mWorkSchedulerIntentAction), null /* itemInfo */));
+ mSchedulerButton.setOnClickListener(view -> {
+ Log.d(TAG, "WorkScheduler button clicked.");
+ mActivityContext.startActivitySafely(view,
+ new Intent(mWorkSchedulerIntentAction), null /* itemInfo */);
+ });
}
}
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 31d0da0..f6acda4 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -20,6 +20,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.graphics.GridCustomizationsProxy;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.LauncherIcons.IconPool;
import com.android.launcher3.model.ItemInstallQueue;
@@ -75,6 +76,8 @@
InvariantDeviceProfile getIDP();
IconPool getIconPool();
+ GridCustomizationsProxy getGridCustomizationsProxy();
+
/** Builder for LauncherBaseAppComponent. */
interface Builder {
@BindsInstance Builder appContext(@ApplicationContext Context context);
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index bff323c..981b78b 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -62,7 +62,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.FastBitmapDrawable;
-import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.views.ActivityContext;
@@ -246,10 +246,12 @@
public void setItemInfo(final ItemInfo info) {
// Load the adaptive icon on a background thread and add the view in ui thread.
MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> {
+ ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext());
int w = mWidth;
int h = mHeight;
Pair<AdaptiveIconDrawable, Drawable> fullDrawable = Utilities.getFullDrawable(
- mActivity, info, w, h, true /* shouldThemeIcon */);
+ mActivity, info, w, h,
+ themeManager.isIconThemeEnabled());
if (fullDrawable != null) {
AdaptiveIconDrawable adaptiveIcon = fullDrawable.first;
int blurMargin = (int) mActivity.getResources()
@@ -261,12 +263,7 @@
// be scaled down due to icon normalization.
mBadge = fullDrawable.second;
FastBitmapDrawable.setBadgeBounds(mBadge, bounds);
-
- try (LauncherIcons li = LauncherIcons.obtain(mActivity)) {
- // Since we just want the scale, avoid heavy drawing operations
- Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(
- new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null)));
- }
+ Utilities.scaleRectAboutCenter(bounds, IconNormalizer.ICON_VISIBLE_AREA_FACTOR);
// Shrink very tiny bit so that the clip path is smaller than the original bitmap
// that has anti aliased edges and shadows.
@@ -274,7 +271,6 @@
Utilities.scaleRectAboutCenter(shrunkBounds, 0.98f);
adaptiveIcon.setBounds(shrunkBounds);
- ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext());
final Path mask = (adaptiveIcon instanceof FolderAdaptiveIcon
? themeManager.getFolderShape() : themeManager.getIconShape())
.getPath(shrunkBounds);
@@ -567,7 +563,7 @@
return mContentViewParent;
}
- /** Return true if {@link mContent} is a {@link AppWidgetHostView}. */
+ /** Return true if {@link #mContent} is a {@link AppWidgetHostView}. */
public boolean containsAppWidgetHostView() {
return mContent instanceof AppWidgetHostView;
}
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
similarity index 87%
rename from src/com/android/launcher3/graphics/GridCustomizationsProvider.java
rename to src/com/android/launcher3/graphics/GridCustomizationsProxy.java
index 12c65c7..01c9d7e 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
@@ -22,7 +22,6 @@
import static java.util.Objects.requireNonNullElse;
-import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -45,9 +44,13 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.shapes.IconShapeModel;
import com.android.launcher3.shapes.ShapesProvider;
+import com.android.launcher3.util.ContentProviderProxy.ProxyProvider;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
@@ -62,6 +65,8 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
+import javax.inject.Inject;
+
/**
* Exposes various launcher grid options and allows the caller to change them.
* APIs:
@@ -76,7 +81,7 @@
* rows: number of rows in the grid
* cols: number of columns in the grid
* preview_count: number of previews available for this grid option. The preview uri
- * looks like /preview/<grid-name>/<preview index starting with 0>
+ * looks like /preview/[grid-name]/[preview index starting with 0]
* is_default: true if this grid option is currently set to the system
*
* /get_preview: Open a file stream for the grid preview
@@ -85,7 +90,8 @@
* shape_key: key of the shape to apply
* name: key of the grid to apply
*/
-public class GridCustomizationsProvider extends ContentProvider {
+@LauncherAppSingleton
+public class GridCustomizationsProxy implements ProxyProvider {
private static final String TAG = "GridCustomizationsProvider";
@@ -132,17 +138,31 @@
private final Set<PreviewLifecycleObserver> mActivePreviews =
Collections.newSetFromMap(new ConcurrentHashMap<>());
- @Override
- public boolean onCreate() {
- return true;
+ private final Context mContext;
+ private final ThemeManager mThemeManager;
+ private final LauncherPrefs mPrefs;
+ private final InvariantDeviceProfile mIdp;
+
+ @Inject
+ GridCustomizationsProxy(
+ @ApplicationContext Context context,
+ ThemeManager themeManager,
+ LauncherPrefs prefs,
+ InvariantDeviceProfile idp,
+ DaggerSingletonTracker lifeCycle
+ ) {
+ mContext = context;
+ mThemeManager = themeManager;
+ mPrefs = prefs;
+ mIdp = idp;
+ lifeCycle.addCloseable(() -> mActivePreviews.forEach(PreviewLifecycleObserver::binderDied));
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
- Context context = getContext();
String path = uri.getPath();
- if (context == null || path == null) {
+ if (path == null) {
return null;
}
@@ -151,8 +171,7 @@
if (Flags.newCustomizationPickerUi()) {
MatrixCursor cursor = new MatrixCursor(new String[]{
KEY_SHAPE_KEY, KEY_SHAPE_TITLE, KEY_PATH, KEY_IS_DEFAULT});
- String currentShapePath =
- ThemeManager.INSTANCE.get(context).getIconState().getIconMask();
+ String currentShapePath = mThemeManager.getIconState().getIconMask();
Optional<IconShapeModel> selectedShape = ShapesProvider.INSTANCE.getIconShapes()
.values()
.stream()
@@ -180,8 +199,7 @@
MatrixCursor cursor = new MatrixCursor(new String[]{
KEY_NAME, KEY_GRID_TITLE, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT,
KEY_IS_DEFAULT, KEY_GRID_ICON_ID});
- InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
- List<GridOption> gridOptionList = idp.parseAllGridOptions(getContext());
+ List<GridOption> gridOptionList = mIdp.parseAllGridOptions(mContext);
if (com.android.launcher3.Flags.oneGridSpecs()) {
gridOptionList.sort(Comparator
.comparingInt((GridOption option) -> option.numColumns)
@@ -194,8 +212,8 @@
.add(KEY_ROWS, gridOption.numRows)
.add(KEY_COLS, gridOption.numColumns)
.add(KEY_PREVIEW_COUNT, 1)
- .add(KEY_IS_DEFAULT, idp.numColumns == gridOption.numColumns
- && idp.numRows == gridOption.numRows)
+ .add(KEY_IS_DEFAULT, mIdp.numColumns == gridOption.numColumns
+ && mIdp.numRows == gridOption.numRows)
.add(KEY_GRID_ICON_ID, gridOption.gridIconId);
}
return cursor;
@@ -203,8 +221,7 @@
case GET_ICON_THEMED:
case ICON_THEMED: {
MatrixCursor cursor = new MatrixCursor(new String[]{BOOLEAN_VALUE});
- cursor.newRow().add(BOOLEAN_VALUE,
- ThemeManager.INSTANCE.get(getContext()).isMonoThemeEnabled() ? 1 : 0);
+ cursor.newRow().add(BOOLEAN_VALUE, mThemeManager.isMonoThemeEnabled() ? 1 : 0);
return cursor;
}
default:
@@ -213,38 +230,21 @@
}
@Override
- public String getType(Uri uri) {
- return "vnd.android.cursor.dir/launcher_grid";
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues initialValues) {
- return null;
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- return 0;
- }
-
- @Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
String path = uri.getPath();
- Context context = getContext();
- if (path == null || context == null) {
+ if (path == null) {
return 0;
}
switch (path) {
case KEY_DEFAULT_GRID: {
if (Flags.newCustomizationPickerUi()) {
- LauncherPrefs.INSTANCE.get(context).put(PREF_ICON_SHAPE,
+ mPrefs.put(PREF_ICON_SHAPE,
requireNonNullElse(values.getAsString(KEY_SHAPE_KEY), ""));
}
String gridName = values.getAsString(KEY_NAME);
- InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
// Verify that this is a valid grid option
GridOption match = null;
- for (GridOption option : idp.parseAllGridOptions(context)) {
+ for (GridOption option : mIdp.parseAllGridOptions(mContext)) {
String name = option.name;
if (name != null && name.equals(gridName)) {
match = option;
@@ -255,23 +255,22 @@
return 0;
}
- idp.setCurrentGrid(context, gridName);
+ mIdp.setCurrentGrid(mContext, gridName);
if (Flags.newCustomizationPickerUi()) {
try {
// Wait for device profile to be fully reloaded and applied to the launcher
- loadModelSync(context);
+ loadModelSync(mContext);
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Fail to load model", e);
}
}
- context.getContentResolver().notifyChange(uri, null);
+ mContext.getContentResolver().notifyChange(uri, null);
return 1;
}
case ICON_THEMED:
case SET_ICON_THEMED: {
- ThemeManager.INSTANCE.get(context)
- .setMonoThemeEnabled(values.getAsBoolean(BOOLEAN_VALUE));
- context.getContentResolver().notifyChange(uri, null);
+ mThemeManager.setMonoThemeEnabled(values.getAsBoolean(BOOLEAN_VALUE));
+ mContext.getContentResolver().notifyChange(uri, null);
return 1;
}
default:
@@ -298,12 +297,7 @@
@Override
public Bundle call(@NonNull String method, String arg, Bundle extras) {
- Context context = getContext();
- if (context == null) {
- return null;
- }
-
- if (context.checkPermission("android.permission.BIND_WALLPAPER",
+ if (mContext.checkPermission("android.permission.BIND_WALLPAPER",
Binder.getCallingPid(), Binder.getCallingUid())
!= PackageManager.PERMISSION_GRANTED) {
return null;
@@ -317,14 +311,10 @@
}
private synchronized Bundle getPreview(Bundle request) {
- Context context = getContext();
- if (context == null) {
- return null;
- }
RunnableList lifeCycleTracker = new RunnableList();
try {
PreviewSurfaceRenderer renderer = new PreviewSurfaceRenderer(
- getContext(), lifeCycleTracker, request);
+ mContext, lifeCycleTracker, request);
PreviewLifecycleObserver observer =
new PreviewLifecycleObserver(lifeCycleTracker, renderer);
diff --git a/src/com/android/launcher3/graphics/LauncherCustomizationProvider.kt b/src/com/android/launcher3/graphics/LauncherCustomizationProvider.kt
new file mode 100644
index 0000000..c949e2e
--- /dev/null
+++ b/src/com/android/launcher3/graphics/LauncherCustomizationProvider.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.graphics
+
+import android.content.Context
+import android.net.Uri
+import com.android.launcher3.dagger.LauncherComponentProvider.appComponent
+import com.android.launcher3.util.ContentProviderProxy
+
+/** Provider for various Launcher customizations exposed via a ContentProvider API */
+class LauncherCustomizationProvider : ContentProviderProxy() {
+
+ override fun getProxy(ctx: Context): ProxyProvider? = ctx.appComponent.gridCustomizationsProxy
+
+ override fun getType(uri: Uri) = "vnd.android.cursor.dir/launcher_grid"
+}
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index f0d670e..6fe5804 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -124,7 +124,7 @@
if (Flags.newCustomizationPickerUi()) {
updateColorOverrides(bundle);
}
- mHideQsb = bundle.getBoolean(GridCustomizationsProvider.KEY_HIDE_BOTTOM_ROW);
+ mHideQsb = bundle.getBoolean(GridCustomizationsProxy.KEY_HIDE_BOTTOM_ROW);
mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
mWidth = bundle.getInt(KEY_VIEW_WIDTH);
diff --git a/src/com/android/launcher3/graphics/ShapeDelegate.kt b/src/com/android/launcher3/graphics/ShapeDelegate.kt
index df0c8f9..9033eac 100644
--- a/src/com/android/launcher3/graphics/ShapeDelegate.kt
+++ b/src/com/android/launcher3/graphics/ShapeDelegate.kt
@@ -43,7 +43,6 @@
import androidx.graphics.shapes.transformed
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider
import com.android.launcher3.icons.GraphicsUtils
-import com.android.launcher3.icons.IconNormalizer.normalizeAdaptiveIcon
import com.android.launcher3.views.ClipPathView
/** Abstract representation of the shape of an icon shape */
@@ -342,12 +341,5 @@
centerY = (bottom - top) / 2,
rounding = CornerRounding(cornerR),
)
-
- @JvmStatic
- val normalizationScale =
- normalizeAdaptiveIcon(
- AdaptiveIconDrawable(null, ColorDrawable(Color.BLACK)),
- AREA_CALC_SIZE,
- )
}
}
diff --git a/src/com/android/launcher3/graphics/ThemeManager.kt b/src/com/android/launcher3/graphics/ThemeManager.kt
index 4a0ff8c..de85460 100644
--- a/src/com/android/launcher3/graphics/ThemeManager.kt
+++ b/src/com/android/launcher3/graphics/ThemeManager.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.content.res.Resources
import com.android.launcher3.EncryptionType
+import com.android.launcher3.Item
import com.android.launcher3.LauncherPrefChangeListener
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.backedUpItem
@@ -38,11 +39,12 @@
/** Centralized class for managing Launcher icon theming */
@LauncherAppSingleton
-open class ThemeManager
+class ThemeManager
@Inject
constructor(
- @ApplicationContext protected val context: Context,
- protected val prefs: LauncherPrefs,
+ @ApplicationContext private val context: Context,
+ private val prefs: LauncherPrefs,
+ private val iconControllerFactory: IconControllerFactory,
lifecycle: DaggerSingletonTracker,
) {
@@ -72,21 +74,21 @@
val receiver = SimpleBroadcastReceiver(context, MAIN_EXECUTOR) { verifyIconState() }
receiver.registerPkgActions("android", ACTION_OVERLAY_CHANGED)
- val prefListener = LauncherPrefChangeListener { key ->
- when (key) {
- KEY_THEMED_ICONS,
- KEY_ICON_SHAPE -> verifyIconState()
- }
- }
- prefs.addListener(prefListener, THEMED_ICONS, PREF_ICON_SHAPE)
+ val keys = (iconControllerFactory.prefKeys + PREF_ICON_SHAPE)
+ val keysArray = keys.toTypedArray()
+ val prefKeySet = keys.map { it.sharedPrefKey }
+ val prefListener = LauncherPrefChangeListener { key ->
+ if (prefKeySet.contains(key)) verifyIconState()
+ }
+ prefs.addListener(prefListener, *keysArray)
lifecycle.addCloseable {
receiver.unregisterReceiverSafely()
- prefs.removeListener(prefListener)
+ prefs.removeListener(prefListener, *keysArray)
}
}
- protected fun verifyIconState() {
+ private fun verifyIconState() {
val newState = parseIconState(iconState)
if (newState == iconState) return
iconState = newState
@@ -126,17 +128,13 @@
return IconState(
iconMask = iconMask,
folderShapeMask = folderShapeMask,
- themeController = createThemeController(),
+ themeController = iconControllerFactory.createThemeController(),
iconScale = shapeModel?.iconScale ?: 1f,
iconShape = iconShape,
folderShape = folderShape,
)
}
- protected open fun createThemeController(): IconThemeController? {
- return if (isMonoThemeEnabled) MONO_THEME_CONTROLLER else null
- }
-
data class IconState(
val iconMask: String,
val folderShapeMask: String,
@@ -154,6 +152,15 @@
fun onThemeChanged()
}
+ open class IconControllerFactory @Inject constructor(protected val prefs: LauncherPrefs) {
+
+ open val prefKeys: List<Item> = listOf(THEMED_ICONS)
+
+ open fun createThemeController(): IconThemeController? {
+ return if (prefs.get(THEMED_ICONS)) MONO_THEME_CONTROLLER else null
+ }
+ }
+
companion object {
@JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getThemeManager)
diff --git a/src/com/android/launcher3/icons/CacheableShortcutInfo.kt b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
index 225e12f..50dd146 100644
--- a/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
+++ b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
@@ -30,6 +30,7 @@
import com.android.launcher3.icons.cache.BaseIconCache
import com.android.launcher3.icons.cache.CachingLogic
import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.ApiWrapper
import com.android.launcher3.util.ApplicationInfoWrapper
import com.android.launcher3.util.PackageUserKey
import com.android.launcher3.util.Themes
@@ -114,7 +115,15 @@
d,
IconOptions()
.setExtractedColor(Themes.getColorAccent(context))
- .setSourceHint(getSourceHint(info, cache)),
+ .setSourceHint(
+ getSourceHint(info, cache)
+ .copy(
+ isFileDrawable =
+ ApiWrapper.INSTANCE[context].isFileDrawable(
+ info.shortcutInfo
+ )
+ )
+ ),
)
} ?: BitmapInfo.LOW_RES_INFO
}
diff --git a/src/com/android/launcher3/icons/LauncherIcons.kt b/src/com/android/launcher3/icons/LauncherIcons.kt
index 6c018e8..29c0de1 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.kt
+++ b/src/com/android/launcher3/icons/LauncherIcons.kt
@@ -66,11 +66,16 @@
return userCache.getUserInfo(user)
}
- public override fun getShapePath(drawable: AdaptiveIconDrawable, iconBounds: Rect): Path {
- if (!Flags.enableLauncherIconShapes()) return drawable.iconMask
+ override fun getShapePath(drawable: AdaptiveIconDrawable, iconBounds: Rect): Path {
+ if (!Flags.enableLauncherIconShapes()) return super.getShapePath(drawable, iconBounds)
return themeManager.iconShape.getPath(iconBounds)
}
+ override fun getIconScale(): Float {
+ if (!Flags.enableLauncherIconShapes()) return super.getIconScale()
+ return themeManager.iconState.iconScale
+ }
+
override fun drawAdaptiveIcon(
canvas: Canvas,
drawable: AdaptiveIconDrawable,
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 9a1c874..2f1af68 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -905,6 +905,10 @@
@UiEvent(doc = "Time passed between Contextual Search runnable creation and execution. This"
+ " ensures that Recent animations have finished before Contextual Search starts.")
LAUNCHER_LATENCY_OMNI_RUNNABLE(1546),
+
+ @UiEvent(doc = "Time passed between nav handle touch down and cancellation without "
+ + "triggering Contextual Search")
+ LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON(2171),
;
private final int mId;
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 003bef3..3ee8b87 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -167,7 +167,7 @@
return;
}
Map<PackageItemInfo, List<WidgetItem>>
- widgetsByPackageItem = mBgDataModel.widgetsModel.getWidgetsByPackageItem();
+ widgetsByPackageItem = mBgDataModel.widgetsModel.getWidgetsByPackageItemForPicker();
List<WidgetsListBaseEntry> widgets = new WidgetsListBaseEntriesBuilder(mApp.getContext())
.build(widgetsByPackageItem);
Predicate<WidgetItem> filter = mBgDataModel.widgetsModel.getDefaultWidgetsFilter();
diff --git a/src/com/android/launcher3/model/GridSizeMigrationDBController.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
index 47f13bd..5d0a7bd 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationDBController.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
@@ -238,16 +238,19 @@
Collections.sort(hotseatToBeAdded);
Collections.sort(workspaceToBeAdded);
- List<Integer> idsInUse = dstWorkspaceItems.stream()
+ List<DbEntry> remainingDstHotseatItems = destReader.loadHotseatEntries();
+ List<DbEntry> remainingDstWorkspaceItems = destReader.loadAllWorkspaceEntries();
+ List<Integer> idsInUse = remainingDstHotseatItems.stream()
.map(entry -> entry.id)
.collect(Collectors.toList());
- idsInUse.addAll(dstHotseatItems.stream()
+ idsInUse.addAll(remainingDstWorkspaceItems.stream()
.map(entry -> entry.id)
.collect(Collectors.toList()));
+
// Migrate hotseat
solveHotseatPlacement(helper, destHotseatSize,
- srcReader, destReader, dstHotseatItems, hotseatToBeAdded, idsInUse);
+ srcReader, destReader, remainingDstHotseatItems, hotseatToBeAdded, idsInUse);
// Migrate workspace.
// First we create a collection of the screens
@@ -467,7 +470,7 @@
final Context mContext;
int mLastScreenId = -1;
- final Map<Integer, List<DbEntry>> mWorkspaceEntriesByScreenId =
+ Map<Integer, List<DbEntry>> mWorkspaceEntriesByScreenId =
new ArrayMap<>();
public DbReader(SQLiteDatabase db, String tableName, Context context) {
@@ -539,6 +542,7 @@
}
protected List<DbEntry> loadAllWorkspaceEntries() {
+ mWorkspaceEntriesByScreenId.clear();
final List<DbEntry> workspaceEntries = new ArrayList<>();
Cursor c = queryWorkspace(
new String[]{
diff --git a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
index 9586bf3..5df135a 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
+++ b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
@@ -183,9 +183,11 @@
)
}
+ val remainingDstHotseatItems = destReader.loadHotseatEntries()
+
placeHotseatItems(
itemsToBeAdded,
- dstHotseatItems,
+ remainingDstHotseatItems,
destHotseatSize,
helper,
srcReader,
@@ -265,9 +267,10 @@
)
}
+ val remainingDstWorkspaceItems = destReader.loadAllWorkspaceEntries()
placeWorkspaceItems(
workspaceToBeAdded,
- dstWorkspaceItems,
+ remainingDstWorkspaceItems,
targetSize.x,
targetSize.y,
helper,
diff --git a/src/com/android/launcher3/model/ModelInitializer.kt b/src/com/android/launcher3/model/ModelInitializer.kt
new file mode 100644
index 0000000..69a320a
--- /dev/null
+++ b/src/com/android/launcher3/model/ModelInitializer.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2025 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
+
+import android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED
+import android.content.ComponentName
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.pm.LauncherApps
+import android.content.pm.LauncherApps.ArchiveCompatibilityParams
+import com.android.launcher3.BuildConfig
+import com.android.launcher3.Flags
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener
+import com.android.launcher3.LauncherModel
+import com.android.launcher3.LauncherPrefs.Companion.getPrefs
+import com.android.launcher3.Utilities
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.graphics.ThemeManager
+import com.android.launcher3.graphics.ThemeManager.ThemeChangeListener
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.LauncherIconProvider
+import com.android.launcher3.icons.LauncherIcons.IconPool
+import com.android.launcher3.notification.NotificationListener
+import com.android.launcher3.pm.InstallSessionHelper
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.android.launcher3.util.SafeCloseable
+import com.android.launcher3.util.SettingsCache
+import com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI
+import com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI
+import com.android.launcher3.util.SimpleBroadcastReceiver
+import com.android.launcher3.widget.custom.CustomWidgetManager
+import java.util.function.Consumer
+
+/** Utility class for initializing all model callbacks */
+class ModelInitializer(
+ @ApplicationContext private val context: Context,
+ private val iconPool: IconPool,
+ private val iconCache: IconCache,
+ private val idp: InvariantDeviceProfile,
+ private val themeManager: ThemeManager,
+ private val userCache: UserCache,
+ private val settingsCache: SettingsCache,
+ private val iconProvider: LauncherIconProvider,
+ private val customWidgetManager: CustomWidgetManager,
+ private val installSessionHelper: InstallSessionHelper,
+ private val closeActions: Consumer<SafeCloseable>,
+) {
+
+ fun initialize(model: LauncherModel) {
+ fun refreshAndReloadLauncher() {
+ iconPool.clear()
+ iconCache.updateIconParams(idp.fillResIconDpi, idp.iconBitmapSize)
+ model.forceReload()
+ }
+
+ // IDP changes
+ val idpChangeListener = OnIDPChangeListener { modelChanged ->
+ if (modelChanged) refreshAndReloadLauncher()
+ }
+ idp.addOnChangeListener(idpChangeListener)
+ closeActions.accept { idp.removeOnChangeListener(idpChangeListener) }
+
+ // Theme changes
+ val themeChangeListener = ThemeChangeListener { refreshAndReloadLauncher() }
+ themeManager.addChangeListener(themeChangeListener)
+ closeActions.accept { themeManager.removeChangeListener(themeChangeListener) }
+
+ // System changes
+ val modelCallbacks = model.newModelCallbacks()
+ val launcherApps = context.getSystemService(LauncherApps::class.java)!!
+ launcherApps.registerCallback(modelCallbacks)
+ closeActions.accept { launcherApps.unregisterCallback(modelCallbacks) }
+
+ if (Utilities.ATLEAST_V && Flags.enableSupportForArchiving()) {
+ launcherApps.setArchiveCompatibility(
+ ArchiveCompatibilityParams().apply {
+ setEnableUnarchivalConfirmation(false)
+ setEnableIconOverlay(!Flags.useNewIconForArchivedApps())
+ }
+ )
+ }
+
+ // Device profile policy changes
+ val dpUpdateReceiver =
+ SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR) { model.reloadStringCache() }
+ dpUpdateReceiver.register(ACTION_DEVICE_POLICY_RESOURCE_UPDATED)
+ closeActions.accept { dpUpdateReceiver.unregisterReceiverSafely() }
+
+ // Development helper
+ if (BuildConfig.IS_STUDIO_BUILD) {
+ val reloadReceiver =
+ SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR) { model.forceReload() }
+ reloadReceiver.register(Context.RECEIVER_EXPORTED, ACTION_FORCE_RELOAD)
+ closeActions.accept { reloadReceiver.unregisterReceiverSafely() }
+ }
+
+ // User changes
+ closeActions.accept(userCache.addUserEventListener(model::onUserEvent))
+
+ // Private space settings changes
+ val psSettingsListener = SettingsCache.OnChangeListener { model.forceReload() }
+ settingsCache.register(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, psSettingsListener)
+ closeActions.accept {
+ settingsCache.unregister(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, psSettingsListener)
+ }
+
+ // Notification dots changes
+ val notificationChanges =
+ SettingsCache.OnChangeListener { dotsEnabled ->
+ if (dotsEnabled)
+ NotificationListener.requestRebind(
+ ComponentName(context, NotificationListener::class.java)
+ )
+ }
+ settingsCache.register(NOTIFICATION_BADGING_URI, notificationChanges)
+ notificationChanges.onSettingsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI))
+ closeActions.accept {
+ settingsCache.unregister(NOTIFICATION_BADGING_URI, notificationChanges)
+ }
+
+ // removable smartspace
+ if (Flags.enableSmartspaceRemovalToggle()) {
+ val smartSpacePrefChanges =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
+ if (LoaderTask.SMARTSPACE_ON_HOME_SCREEN == key) model.forceReload()
+ }
+ getPrefs(context).registerOnSharedPreferenceChangeListener(smartSpacePrefChanges)
+ closeActions.accept {
+ getPrefs(context).unregisterOnSharedPreferenceChangeListener(smartSpacePrefChanges)
+ }
+ }
+
+ // Custom widgets
+ closeActions.accept(customWidgetManager.addWidgetRefreshCallback(model::rebindCallbacks))
+
+ // Icon changes
+ closeActions.accept(
+ iconProvider.registerIconChangeListener(model::onAppIconChanged, MODEL_EXECUTOR.handler)
+ )
+
+ // Install session changes
+ closeActions.accept(installSessionHelper.registerInstallTracker(modelCallbacks))
+ }
+
+ companion object {
+ private const val ACTION_FORCE_RELOAD = "force-reload-launcher"
+ }
+}
diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt
index 40ea17d..6e3e35e 100644
--- a/src/com/android/launcher3/model/ModelTaskController.kt
+++ b/src/com/android/launcher3/model/ModelTaskController.kt
@@ -77,7 +77,7 @@
}
fun bindUpdatedWidgets(dataModel: BgDataModel) {
- val widgetsByPackageItem = dataModel.widgetsModel.widgetsByPackageItem
+ val widgetsByPackageItem = dataModel.widgetsModel.widgetsByPackageItemForPicker
val allWidgets = WidgetsListBaseEntriesBuilder(app.context).build(widgetsByPackageItem)
val defaultWidgetsFilter = dataModel.widgetsModel.defaultWidgetsFilter
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index a176465..ab960d8 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -70,6 +70,7 @@
private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsByPackageItem = new HashMap<>();
@Nullable private Predicate<WidgetItem> mDefaultWidgetsFilter = null;
@Nullable private Predicate<WidgetItem> mPredictedWidgetsFilter = null;
+ @Nullable private WidgetValidityCheckForPicker mWidgetValidityCheckForPicker = null;
/**
* Returns all widgets keyed by their component key.
@@ -87,13 +88,44 @@
}
/**
- * Returns widgets grouped by the package item that they should belong to.
+ * Returns widgets (eligible for display in picker) keyed by their component key.
*/
- public synchronized Map<PackageItemInfo, List<WidgetItem>> getWidgetsByPackageItem() {
- if (!WIDGETS_ENABLED) {
+ public synchronized Map<ComponentKey, WidgetItem> getWidgetsByComponentKeyForPicker() {
+ if (!WIDGETS_ENABLED || mWidgetValidityCheckForPicker == null) {
return Collections.emptyMap();
}
- return new HashMap<>(mWidgetsByPackageItem);
+
+ return mWidgetsByPackageItem.values().stream()
+ .flatMap(Collection::stream).distinct()
+ .filter(widgetItem -> mWidgetValidityCheckForPicker.test(widgetItem))
+ .collect(Collectors.toMap(
+ widget -> new ComponentKey(widget.componentName, widget.user),
+ Function.identity()
+ ));
+ }
+
+ /**
+ * Returns widgets (displayable in the widget picker) grouped by the package item that
+ * they should belong to.
+ */
+ public synchronized Map<PackageItemInfo, List<WidgetItem>> getWidgetsByPackageItemForPicker() {
+ if (!WIDGETS_ENABLED || mWidgetValidityCheckForPicker == null) {
+ return Collections.emptyMap();
+ }
+
+ return mWidgetsByPackageItem.entrySet().stream()
+ .collect(
+ Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> entry.getValue().stream()
+ .filter(widgetItem ->
+ mWidgetValidityCheckForPicker.test(widgetItem))
+ .collect(Collectors.toList())
+ )
+ )
+ .entrySet().stream()
+ .filter(entry -> !entry.getValue().isEmpty())
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
/**
@@ -181,6 +213,9 @@
Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
}
+ // Refresh the validity checker with latest app state.
+ mWidgetValidityCheckForPicker = new WidgetValidityCheckForPicker(app);
+
// Temporary cache for {@link PackageItemInfos} to avoid having to go through
// {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
PackageItemInfoCache packageItemInfoCache = new PackageItemInfoCache();
@@ -195,7 +230,6 @@
// add and update.
mWidgetsByPackageItem.putAll(rawWidgetsShortcuts.stream()
- .filter(new WidgetValidityCheck(app))
.filter(new WidgetFlagCheck())
.flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
.map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem)))
@@ -270,12 +304,15 @@
return packageUserKeys;
}
- private static class WidgetValidityCheck implements Predicate<WidgetItem> {
+ /**
+ * Checks if widgets are eligible for displaying in widget picker / tray.
+ */
+ private static class WidgetValidityCheckForPicker implements Predicate<WidgetItem> {
private final InvariantDeviceProfile mIdp;
private final AppFilter mAppFilter;
- WidgetValidityCheck(LauncherAppState app) {
+ WidgetValidityCheckForPicker(LauncherAppState app) {
mIdp = app.getInvariantDeviceProfile();
mAppFilter = new AppFilter(app.getContext());
}
@@ -310,6 +347,10 @@
}
}
+ /**
+ * Checks if certain widgets that are available behind flag can be used across all surfaces in
+ * launcher.
+ */
private static class WidgetFlagCheck implements Predicate<WidgetItem> {
private static final String BUBBLES_SHORTCUT_WIDGET =
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index b9c928c..7451ce2 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -34,13 +34,15 @@
import com.android.launcher3.Flags;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.SafeCloseable;
import java.lang.ref.WeakReference;
import java.util.Objects;
@SuppressWarnings("NewApi")
@WorkerThread
-public class InstallSessionTracker extends PackageInstaller.SessionCallback {
+public class InstallSessionTracker extends PackageInstaller.SessionCallback implements
+ SafeCloseable {
public static final String TAG = "InstallSessionTracker";
@@ -196,7 +198,8 @@
}
}
- public void unregister() {
+ @Override
+ public void close() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
mInstaller.unregisterSessionCallback(this);
} else {
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index b748011..bc5064d 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -110,13 +110,16 @@
public static <T extends Context & ActivityContext> Runnable createUpdateRunnable(
final T context,
final ItemInfo originalInfo,
- final Handler uiHandler, final PopupContainerWithArrow container,
- final List<DeepShortcutView> shortcutViews) {
+ final Handler uiHandler,
+ final PopupContainerWithArrow container,
+ final List<DeepShortcutView> shortcutViews
+ ) {
final ComponentName activity = originalInfo.getTargetComponent();
final UserHandle user = originalInfo.user;
+ final String targetPackage = originalInfo.getTargetPackage();
return () -> {
ApplicationInfoWrapper infoWrapper =
- new ApplicationInfoWrapper(context, originalInfo.getTargetPackage(), user);
+ new ApplicationInfoWrapper(context, targetPackage, user);
List<ShortcutInfo> shortcuts = new ShortcutRequest(context, user)
.withContainer(activity)
.query(ShortcutRequest.PUBLISHED);
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 34c9117..0da431f 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -51,6 +51,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherFiles;
@@ -130,9 +131,11 @@
removeOldDBs(context, oldPhoneFileName);
// The idp before this contains data about the old phone, after this it becomes the idp
// of the current phone.
- FileLog.d(TAG, "Resetting IDP to default for restore dest device");
- idp.reset(context);
- trySettingPreviousGridAsCurrent(context, idp, oldPhoneFileName, previousDbs);
+ if (!Flags.oneGridSpecs()) {
+ FileLog.d(TAG, "Resetting IDP to default for restore dest device");
+ idp.reset(context);
+ trySettingPreviousGridAsCurrent(context, idp, oldPhoneFileName, previousDbs);
+ }
}
diff --git a/src/com/android/launcher3/shapes/ShapesProvider.kt b/src/com/android/launcher3/shapes/ShapesProvider.kt
index 463c816..5427c89 100644
--- a/src/com/android/launcher3/shapes/ShapesProvider.kt
+++ b/src/com/android/launcher3/shapes/ShapesProvider.kt
@@ -193,15 +193,6 @@
"M50 0C77.614 0 100 22.386 100 50C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116 .884 93.916 .1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0Z",
folderPathString = folderShapes["arch"]!!,
),
- "sunny" to
- IconShapeModel(
- key = "sunny",
- title = "sunny",
- pathString =
- "M42.846 4.873C46.084 -.531 53.916 -.531 57.154 4.873L60.796 10.951C62.685 14.103 66.414 15.647 69.978 14.754L76.851 13.032C82.962 11.5 88.5 17.038 86.968 23.149L85.246 30.022C84.353 33.586 85.897 37.315 89.049 39.204L95.127 42.846C100.531 46.084 100.531 53.916 95.127 57.154L89.049 60.796C85.897 62.685 84.353 66.414 85.246 69.978L86.968 76.851C88.5 82.962 82.962 88.5 76.851 86.968L69.978 85.246C66.414 84.353 62.685 85.898 60.796 89.049L57.154 95.127C53.916 100.531 46.084 100.531 42.846 95.127L39.204 89.049C37.315 85.898 33.586 84.353 30.022 85.246L23.149 86.968C17.038 88.5 11.5 82.962 13.032 76.851L14.754 69.978C15.647 66.414 14.103 62.685 10.951 60.796L4.873 57.154C -.531 53.916 -.531 46.084 4.873 42.846L10.951 39.204C14.103 37.315 15.647 33.586 14.754 30.022L13.032 23.149C11.5 17.038 17.038 11.5 23.149 13.032L30.022 14.754C33.586 15.647 37.315 14.103 39.204 10.951L42.846 4.873Z",
- folderPathString = folderShapes["clover"]!!,
- iconScale = 72f / 92f,
- ),
)
} else {
mapOf(
diff --git a/src/com/android/launcher3/testing/TestInformationProvider.java b/src/com/android/launcher3/testing/TestInformationProvider.java
index 17b472a..4b592e7 100644
--- a/src/com/android/launcher3/testing/TestInformationProvider.java
+++ b/src/com/android/launcher3/testing/TestInformationProvider.java
@@ -16,61 +16,40 @@
package com.android.launcher3.testing;
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
+import android.content.Context;
import android.os.Bundle;
import android.util.Log;
-import com.android.launcher3.Utilities;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
-public class TestInformationProvider extends ContentProvider {
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.ContentProviderProxy;
+
+public class TestInformationProvider extends ContentProviderProxy {
private static final String TAG = "TestInformationProvider";
+ @Nullable
@Override
- public boolean onCreate() {
- return true;
- }
-
- @Override
- public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
- return 0;
- }
-
- @Override
- public int delete(Uri uri, String s, String[] strings) {
- return 0;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues contentValues) {
- return null;
- }
-
- @Override
- public String getType(Uri uri) {
- return null;
- }
-
- @Override
- public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
- return null;
- }
-
- @Override
- public Bundle call(String method, String arg, Bundle extras) {
+ public ProxyProvider getProxy(@NonNull Context context) {
if (Utilities.isRunningInTestHarness()) {
- TestInformationHandler handler = TestInformationHandler.newInstance(getContext());
- handler.init(getContext());
+ return new ProxyProvider() {
+ @Nullable
+ @Override
+ public Bundle call(@NonNull String method, @Nullable String arg,
+ @Nullable Bundle extras) {
+ TestInformationHandler handler = TestInformationHandler.newInstance(context);
+ handler.init(context);
- Bundle response = handler.call(method, arg, extras);
- if (response == null) {
- Log.e(TAG, "Couldn't handle method: " + method + "; current handler="
- + handler.getClass().getSimpleName());
- }
- return response;
+ Bundle response = handler.call(method, arg, extras);
+ if (response == null) {
+ Log.e(TAG, "Couldn't handle method: " + method + "; current handler="
+ + handler.getClass().getSimpleName());
+ }
+ return response;
+ }
+ };
}
return null;
}
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index 107bcc1..2cc4909 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -207,6 +207,11 @@
}
config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
+ if (launcher.getDeviceProfile().isPhone) {
+ config.setInterpolator(ANIM_WORKSPACE_FADE, INSTANT);
+ config.setInterpolator(ANIM_HOTSEAT_FADE, INSTANT);
+ config.animFlags |= StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
+ }
} else {
if (config.isUserControlled()) {
config.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR_MANUAL));
@@ -248,6 +253,11 @@
}
config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
+ if (launcher.getDeviceProfile().isPhone) {
+ config.setInterpolator(ANIM_WORKSPACE_FADE, FINAL_FRAME);
+ config.setInterpolator(ANIM_HOTSEAT_FADE, FINAL_FRAME);
+ config.animFlags |= StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
+ }
} else {
config.setInterpolator(ANIM_DEPTH,
config.isUserControlled() ? BLUR_MANUAL : BLUR_ATOMIC);
diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java
index 56337b0..0510d59 100644
--- a/src/com/android/launcher3/util/ApiWrapper.java
+++ b/src/com/android/launcher3/util/ApiWrapper.java
@@ -216,6 +216,13 @@
return 0;
}
+ /**
+ * Checks if the shortcut is using an icon with file or URI source
+ */
+ public boolean isFileDrawable(@NonNull ShortcutInfo shortcutInfo) {
+ return false;
+ }
+
private static class NoopDrawable extends ColorDrawable {
@Override
public int getIntrinsicHeight() {
diff --git a/src/com/android/launcher3/util/ContentProviderProxy.kt b/src/com/android/launcher3/util/ContentProviderProxy.kt
new file mode 100644
index 0000000..db693db
--- /dev/null
+++ b/src/com/android/launcher3/util/ContentProviderProxy.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2025 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.util
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.database.Cursor
+import android.net.Uri
+import android.os.Bundle
+
+/** Wrapper around [ContentProvider] which allows delegating all calls to an interface */
+abstract class ContentProviderProxy : ContentProvider() {
+
+ override fun onCreate() = true
+
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int =
+ checkGetProxy()?.delete(uri, selection, selectionArgs) ?: 0
+
+ /** Do not route this call through proxy as it doesn't generally require initializing objects */
+ override fun getType(uri: Uri): String? = null
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? =
+ checkGetProxy()?.insert(uri, values)
+
+ override fun query(
+ uri: Uri,
+ projection: Array<out String>?,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ sortOrder: String?,
+ ): Cursor? = checkGetProxy()?.query(uri, projection, selection, selectionArgs, sortOrder)
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ ): Int = checkGetProxy()?.update(uri, values, selection, selectionArgs) ?: 0
+
+ override fun call(method: String, arg: String?, extras: Bundle?): Bundle? =
+ checkGetProxy()?.call(method, arg, extras)
+
+ private fun checkGetProxy(): ProxyProvider? = context?.let { getProxy(it) }
+
+ abstract fun getProxy(ctx: Context): ProxyProvider?
+
+ /** Interface for handling the actual content provider calls */
+ interface ProxyProvider {
+
+ fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
+
+ fun insert(uri: Uri, values: ContentValues?): Uri? = null
+
+ fun query(
+ uri: Uri,
+ projection: Array<out String>?,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ sortOrder: String?,
+ ): Cursor? = null
+
+ fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ ): Int = 0
+
+ fun call(method: String, arg: String?, extras: Bundle?): Bundle? = null
+ }
+}
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 647d170..11f0bc2 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -509,7 +509,17 @@
* @param isInDesktopModeAndNotInOverview True if a desktop is currently active on the given
* display, and Overview is currently inactive.
*/
- void onIsInDesktopModeChanged(int displayId, boolean isInDesktopModeAndNotInOverview);
+ default void onIsInDesktopModeChanged(int displayId,
+ boolean isInDesktopModeAndNotInOverview) {
+ }
+
+ /**
+ * Called whenever the conditions that allow the creation of desks change.
+ *
+ * @param canCreateDesks whether it is possible to create new desks.
+ */
+ default void onCanCreateDesksChanged(boolean canCreateDesks) {
+ }
}
}
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index ddf18df..a295d6b 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.Utilities.boundToRange;
import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static java.lang.Math.max;
@@ -44,10 +45,10 @@
import androidx.core.util.Consumer;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
-import com.android.launcher3.graphics.ShapeDelegate;
import com.android.launcher3.graphics.ThemeManager;
/**
@@ -68,6 +69,7 @@
private boolean mIsAdaptiveIcon = false;
private ValueAnimator mRevealAnimator;
+ private float mIconScale;
private final Rect mStartRevealRect = new Rect();
private final Rect mEndRevealRect = new Rect();
@@ -173,9 +175,12 @@
mTaskCornerRadius = cornerRadius / scale;
if (mIsAdaptiveIcon) {
- if (!isOpening && progress >= shapeProgressStart) {
+ final ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext());
+ mIconScale = themeManager.getIconState().getIconScale();
+ if ((!isOpening || Flags.enableLauncherIconShapes())
+ && progress >= shapeProgressStart) {
if (mRevealAnimator == null) {
- mRevealAnimator = ThemeManager.INSTANCE.get(getContext()).getIconShape()
+ mRevealAnimator = themeManager.getIconShape()
.createRevealAnimator(this, mStartRevealRect,
mOutline, mTaskCornerRadius, !isOpening);
mRevealAnimator.addListener(forEndCallback(() -> mRevealAnimator = null));
@@ -259,8 +264,7 @@
mStartRevealRect.set(0, 0, originalWidth, originalHeight);
if (!isFolderIcon) {
- Utilities.scaleRectAboutCenter(mStartRevealRect,
- ShapeDelegate.getNormalizationScale());
+ Utilities.scaleRectAboutCenter(mStartRevealRect, ICON_VISIBLE_AREA_FACTOR);
}
if (dp.isLandscape) {
@@ -310,17 +314,24 @@
@Override
public void draw(Canvas canvas) {
- int count = canvas.save();
+ int count1 = canvas.save();
if (mClipPath != null) {
canvas.clipPath(mClipPath);
}
- super.draw(canvas);
+ int count2 = canvas.save();
+ float iconCenterX =
+ (mFinalDrawableBounds.right - mFinalDrawableBounds.left) / 2f * mIconScale;
+ float iconCenterY =
+ (mFinalDrawableBounds.bottom - mFinalDrawableBounds.top) / 2f * mIconScale;
+ canvas.scale(mIconScale, mIconScale, iconCenterX, iconCenterY);
if (mBackground != null) {
mBackground.draw(canvas);
}
if (mForeground != null) {
mForeground.draw(canvas);
}
+ canvas.restoreToCount(count2);
+ super.draw(canvas);
if (mTaskViewArtist != null) {
canvas.saveLayerAlpha(
0,
@@ -334,7 +345,7 @@
canvas.scale(drawScale, drawScale);
mTaskViewArtist.taskViewDrawCallback.accept(canvas);
}
- canvas.restoreToCount(count);
+ canvas.restoreToCount(count1);
}
void recycle() {
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 22857b1..5b3abc3 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -55,7 +55,7 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.icons.FastBitmapDrawable;
-import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.IconNormalizer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.popup.SystemShortcut;
@@ -463,10 +463,7 @@
Rect bounds = new Rect(0, 0, (int) position.width() + blurSizeOutline,
(int) position.height() + blurSizeOutline);
bounds.inset(blurSizeOutline / 2, blurSizeOutline / 2);
-
- try (LauncherIcons li = LauncherIcons.obtain(l)) {
- Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable));
- }
+ Utilities.scaleRectAboutCenter(bounds, IconNormalizer.ICON_VISIBLE_AREA_FACTOR);
bounds.inset(
(int) (-bounds.width() * AdaptiveIconDrawable.getExtraInsetFraction()),
diff --git a/src/com/android/launcher3/widget/picker/OWNERS b/src/com/android/launcher3/widget/picker/OWNERS
index 6aabbfa..991193f 100644
--- a/src/com/android/launcher3/widget/picker/OWNERS
+++ b/src/com/android/launcher3/widget/picker/OWNERS
@@ -6,7 +6,6 @@
#
# Widget Picker OWNERS
-zakcohen@google.com
shamalip@google.com
wvk@google.com
diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 9d42e1b..3658989 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -294,6 +294,8 @@
gridName: String? = GRID_NAME.defaultValue,
) {
setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
+ // TODO: re-enable as part of b/396211437
+ setFlagsRule.setFlags(false, Flags.FLAG_ENABLE_LAUNCHER_ICON_SHAPES)
val windowsBounds = perDisplayBoundsCache[displayInfo]!!
val realBounds = windowsBounds[rotation]
whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo)
@@ -362,7 +364,13 @@
context.assets.open("dumpTests/$fileName").bufferedReader().use(BufferedReader::readText)
private fun writeToDevice(context: Context, fileName: String, content: String) {
- File(context.getDir("dumpTests", Context.MODE_PRIVATE), fileName).writeText(content)
+ val dir =
+ File(context.filesDir, "dumpTests").also {
+ if (!it.exists()) {
+ it.mkdirs()
+ }
+ }
+ File(dir, fileName).writeText(content)
}
protected fun Float.dpToPx(): Float {
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt
index adf38fe..5607bb4 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt
@@ -275,36 +275,15 @@
val readerGridA = DbReader(db, TMP_TABLE, context)
val readerGridB = DbReader(db, TABLE_NAME, context)
// migrate from A -> B
- if (Flags.gridMigrationRefactor()) {
- var gridSizeMigrationLogic = GridSizeMigrationLogic()
- val idsInUse = mutableListOf<Int>()
- gridSizeMigrationLogic.migrateHotseat(
- 5,
- idp.numDatabaseHotseatIcons,
- readerGridA,
- readerGridB,
- dbHelper,
- idsInUse,
- )
- gridSizeMigrationLogic.migrateWorkspace(
- readerGridA,
- readerGridB,
- dbHelper,
- Point(idp.numColumns, idp.numRows),
- idsInUse,
- )
- } else {
- GridSizeMigrationDBController.migrate(
- dbHelper,
- readerGridA,
- readerGridB,
- 5,
- idp.numDatabaseHotseatIcons,
- Point(idp.numColumns, idp.numRows),
- DeviceGridState(context),
- DeviceGridState(idp),
- )
- }
+ migrateGrid(
+ dbHelper,
+ readerGridA,
+ readerGridB,
+ 5,
+ idp.numDatabaseHotseatIcons,
+ idp.numColumns,
+ idp.numRows,
+ )
// Check hotseat items in grid B
var c =
@@ -388,8 +367,8 @@
locMap = parseLocMap(c)
// Expected workspace items in grid A
// _ _ _ _ _
- // _ _ _ _ 5
- // 9 _ 6 _ 7
+ // 9 _ _ _ 5
+ // _ _ 6 _ 7
// _ _ 8 _ _
// _ _ _ _ _
assertThat(locMap.size.toLong()).isEqualTo(5)
@@ -399,7 +378,7 @@
assertThat(locMap[testPackage7]).isEqualTo(Triple(0, 4, 2))
assertThat(locMap[testPackage8]).isEqualTo(Triple(0, 2, 3))
// Verify items that didn't exist in grid A are added in new screen
- assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 2))
+ assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 1))
// remove item from B
db.delete(TMP_TABLE, "$_ID=7", null)
@@ -493,37 +472,15 @@
val readerGridA = DbReader(db, TMP_TABLE, context)
val readerGridB = DbReader(db, TABLE_NAME, context)
// migrate from A -> B
- if (Flags.gridMigrationRefactor()) {
- var gridSizeMigrationLogic = GridSizeMigrationLogic()
- val idsInUse = mutableListOf<Int>()
- gridSizeMigrationLogic.migrateHotseat(
- 5,
- idp.numDatabaseHotseatIcons,
- readerGridA,
- readerGridB,
- dbHelper,
- idsInUse,
- )
- gridSizeMigrationLogic.migrateWorkspace(
- readerGridA,
- readerGridB,
- dbHelper,
- Point(idp.numColumns, idp.numRows),
- idsInUse,
- )
- } else {
- GridSizeMigrationDBController.migrate(
- dbHelper,
- readerGridA,
- readerGridB,
- 5,
- idp.numDatabaseHotseatIcons,
- Point(idp.numColumns, idp.numRows),
- DeviceGridState(context),
- DeviceGridState(idp),
- )
- }
-
+ migrateGrid(
+ dbHelper,
+ readerGridA,
+ readerGridB,
+ 5,
+ idp.numDatabaseHotseatIcons,
+ idp.numColumns,
+ idp.numRows,
+ )
// Check hotseat items in grid B
var c =
db.query(
@@ -597,6 +554,112 @@
)
}
+ @Test
+ @Throws(Exception::class)
+ @EnableFlags(Flags.FLAG_GRID_MIGRATION_REFACTOR)
+ fun testMigrationToFullGridFlagOn() {
+ testMigrationToFullGrid()
+ }
+
+ @Test
+ @Throws(Exception::class)
+ @DisableFlags(Flags.FLAG_GRID_MIGRATION_REFACTOR)
+ fun testHotseatMigrationToFullGridFlagOff() {
+ testMigrationToFullGrid()
+ }
+
+ @Throws(Exception::class)
+ fun testMigrationToFullGrid() {
+ // Hotseat items in grid A
+ // 1 2 3 4 5
+ addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 2, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
+ addItem(ITEM_TYPE_APPLICATION, 3, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
+ addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage5, 5, TMP_TABLE)
+
+ // Hotseat items in grid B
+ // 6 7 8 9
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 0, CONTAINER_HOTSEAT, 0, 0, testPackage6)
+ addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage7)
+ addItem(ITEM_TYPE_APPLICATION, 2, CONTAINER_HOTSEAT, 0, 0, testPackage8)
+ addItem(ITEM_TYPE_APPLICATION, 3, CONTAINER_HOTSEAT, 0, 0, testPackage9)
+
+ // Workspace items in grid A
+ // _ _ _ _ _
+ // 6 _ _ _ _
+ // _ _ _ _ _
+ // _ _ _ _ _
+ // _ _ _ _ _
+ addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 1, testPackage6, 6, TMP_TABLE)
+
+ // Workspace items in grid B
+ // _ _ _ _
+ // 1 2 3 4
+ // _ _ _ _
+ // _ _ _ _
+ addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 1, testPackage1)
+ addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 1, 1, testPackage2)
+ addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 1, testPackage3)
+ addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 3, 1, testPackage4)
+
+ idp.numDatabaseHotseatIcons = 4
+ idp.numColumns = 4
+ idp.numRows = 4
+ val readerGridA = DbReader(db, TMP_TABLE, context)
+ val readerGridB = DbReader(db, TABLE_NAME, context)
+
+ // migrate from A -> B
+ migrateGrid(
+ dbHelper,
+ readerGridA,
+ readerGridB,
+ 5,
+ idp.numDatabaseHotseatIcons,
+ idp.numColumns,
+ idp.numRows,
+ )
+
+ // Check hotseat items in grid B
+ var c =
+ db.query(
+ TABLE_NAME,
+ arrayOf(SCREEN, INTENT),
+ "container=$CONTAINER_HOTSEAT",
+ null,
+ SCREEN,
+ null,
+ null,
+ ) ?: throw IllegalStateException()
+ // Expected hotseat items in grid B
+ // 1 2 3 4
+ verifyHotseat(
+ c,
+ mutableListOf(testPackage1, testPackage2, testPackage3, testPackage4).toList(),
+ 4,
+ )
+
+ // Check workspace items in grid B
+ c =
+ db.query(
+ TABLE_NAME,
+ arrayOf(SCREEN, CELLX, CELLY, INTENT),
+ "container=$CONTAINER_DESKTOP",
+ null,
+ null,
+ null,
+ null,
+ ) ?: throw IllegalStateException()
+ val locMap = parseLocMap(c)
+ // Expected workspace items in grid B
+ // _ _ _ _
+ // 6 _ _ _
+ // _ _ _ _
+ // _ _ _ _
+ assertThat(locMap.size.toLong()).isEqualTo(1)
+ assertThat(locMap[testPackage6]).isEqualTo(Triple(0, 0, 1))
+ }
+
private fun migrateGrid(
dbHelper: DatabaseHelper,
srcReader: DbReader,
@@ -621,7 +684,7 @@
srcReader,
destReader,
dbHelper,
- Point(idp.numColumns, idp.numRows),
+ Point(pointX, pointY),
idsInUse,
)
} else {
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
index ae4ff04..d704195 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
@@ -119,6 +119,11 @@
// A widget in different package (none of that app's widgets are in widget
// sections xml)
createAppWidgetProviderInfo(AppBTestWidgetComponent),
+ // A widget in different app that is meant to be hidden from picker
+ createAppWidgetProviderInfo(
+ AppCPinOnlyTestWidgetComponent,
+ /*hideFromPicker=*/ true,
+ ),
)
)
@@ -129,12 +134,13 @@
}
@Test
- fun widgetsByPackage_treatsWidgetSectionsAsSeparatePackageItems() {
+ fun widgetsByPackageForPicker_treatsWidgetSectionsAsSeparatePackageItems() {
loadWidgets()
- val packages: Map<PackageItemInfo, List<WidgetItem>> = underTest.widgetsByPackageItem
+ val packages: Map<PackageItemInfo, List<WidgetItem>> =
+ underTest.widgetsByPackageItemForPicker
- // expect 3 package items
+ // expect 3 package items (no app C as its widget is hidden from picker)
// one for the custom section with widget from appA
// one for package section for second widget from appA (that wasn't listed in xml)
// and one for package section for appB
@@ -167,6 +173,13 @@
assertThat(appBPackageSection).hasSize(1)
val widgetsInAppBSection = appBPackageSection.entries.first().value
assertThat(widgetsInAppBSection).hasSize(1)
+
+ // No App C's package section - as the only widget hosted by it is hidden in picker
+ val appCPackageSection =
+ packageSections.filter {
+ it.key.packageName == AppCPinOnlyTestWidgetComponent.packageName
+ }
+ assertThat(appCPackageSection).isEmpty()
}
@Test
@@ -175,7 +188,29 @@
val widgetsByComponentKey: Map<ComponentKey, WidgetItem> = underTest.widgetsByComponentKey
+ // Has all widgets including ones not visible in picker
+ assertThat(widgetsByComponentKey).hasSize(4)
+ widgetsByComponentKey.forEach { entry ->
+ assertThat(entry.key).isEqualTo(entry.value as ComponentKey)
+ }
+ }
+
+ @Test
+ fun widgetComponentMapForPicker_excludesWidgetsHiddenInPicker() {
+ loadWidgets()
+
+ val widgetsByComponentKey: Map<ComponentKey, WidgetItem> =
+ underTest.widgetsByComponentKeyForPicker
+
+ // Has all widgets excluding the appC's widget.
assertThat(widgetsByComponentKey).hasSize(3)
+ assertThat(
+ widgetsByComponentKey.filter {
+ it.key.componentName == AppCPinOnlyTestWidgetComponent
+ }
+ )
+ .isEmpty()
+ // widgets mapped correctly
widgetsByComponentKey.forEach { entry ->
assertThat(entry.key).isEqualTo(entry.value as ComponentKey)
}
@@ -189,7 +224,7 @@
}
@Test
- fun getWidgetsByPackageItem_returnsACopyOfMap() {
+ fun getWidgetsByPackageItemForPicker_returnsACopyOfMap() {
loadWidgets()
val latch = CountDownLatch(1)
@@ -198,8 +233,8 @@
// each "widgetsByPackageItem" read returns a different copy of the map held internally.
// Modifying one shouldn't impact another.
- for ((_, _) in underTest.widgetsByPackageItem.entries) {
- underTest.widgetsByPackageItem.clear()
+ for ((_, _) in underTest.widgetsByPackageItemForPicker.entries) {
+ underTest.widgetsByPackageItemForPicker.clear()
if (update) { // trigger update
update = false
// Similarly, model could update its code independently while a client is
@@ -256,6 +291,9 @@
private val AppBTestWidgetComponent: ComponentName =
ComponentName.createRelative("com.test.package", "TestProvider")
+ private val AppCPinOnlyTestWidgetComponent: ComponentName =
+ ComponentName.createRelative("com.testC.package", "PinOnlyTestProvider")
+
private const val LOAD_WIDGETS_TIMEOUT_SECONDS = 2L
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index 7a403e1..a55d64b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -454,7 +454,7 @@
assertThat(mAllDeepShortcuts).isEmpty()
verify(mockCursor)
.markDeleted(
- "Pinned shortcut not found from request. package=pkg, user=UserHandle{0}",
+ "Pinned shortcut not found from request. package=pkg, user=$mUserHandle",
"shortcut_not_found",
)
}
@@ -513,7 +513,7 @@
verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull())
verify(mockCursor)
.markDeleted(
- "Pinned shortcut not found from request. package=pkg, user=UserHandle{0}",
+ "Pinned shortcut not found from request. package=pkg, user=$mUserHandle",
"shortcut_not_found",
)
}
@@ -533,7 +533,7 @@
whenever(disabledMessage).thenReturn("")
whenever(disabledReason).thenReturn(0)
whenever(persons).thenReturn(EMPTY_PERSON_ARRAY)
- whenever(userHandle).thenReturn(Process.myUserHandle())
+ whenever(userHandle).thenReturn(mUserHandle)
}
mIconRequestInfos = mutableListOf()
// Make sure shortcuts map has expected key from expected package
@@ -565,7 +565,7 @@
mockBgDataModel = mock<BgDataModel>()
mockCursor =
mock<LoaderCursor>().apply {
- user = UserHandle(0)
+ user = mUserHandle
itemType = ITEM_TYPE_FOLDER
id = 1
container = 100
diff --git a/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
index 15a9964..23c1da9 100644
--- a/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
@@ -219,7 +219,7 @@
.whenever(launcherApps)
.unregisterPackageInstallerSessionCallback(installSessionTracker)
// When
- installSessionTracker.unregister()
+ installSessionTracker.close()
// Then
verify(launcherApps).unregisterPackageInstallerSessionCallback(installSessionTracker)
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
index 508c9a4..49d305b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
@@ -63,15 +63,6 @@
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
- fun `verify valid path sunny`() {
- ShapesProvider.iconShapes["sunny"]?.apply {
- GenericPathShape(pathString)
- PathParser.createPathFromPathData(pathString)
- }
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path circle`() {
ShapesProvider.iconShapes["circle"]?.apply {
GenericPathShape(pathString)
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
index a87a208..9fbd7ff 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.util;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER;
+
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -83,14 +85,30 @@
/**
* Creates a {@link AppWidgetProviderInfo} for the provided component name
+ *
+ * @param cn component name of the appwidget provider
+ * @param hideFromPicker indicates if the widget should appear in widget picker
*/
- public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
+ public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn,
+ boolean hideFromPicker) {
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.applicationInfo = new ApplicationInfo();
activityInfo.applicationInfo.uid = Process.myUid();
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
+ if (hideFromPicker) {
+ info.widgetFeatures = WIDGET_FEATURE_HIDE_FROM_PICKER;
+ }
info.providerInfo = activityInfo;
info.provider = cn;
return info;
}
+
+ /**
+ * Creates a {@link AppWidgetProviderInfo} for the provided component name
+ *
+ * @param cn component name of the appwidget provider
+ */
+ public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
+ return createAppWidgetProviderInfo(cn, /*hideFromPicker=*/ false);
+ }
}
diff --git a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
index f6e45a3..05cf926 100644
--- a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
@@ -40,7 +40,6 @@
Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION,
)
setFlagsRule.setFlags(false, Flags.FLAG_ONE_GRID_SPECS)
- setFlagsRule.setFlags(false, Flags.FLAG_ENABLE_LAUNCHER_ICON_SHAPES)
}
@Test
diff --git a/tests/src/com/android/launcher3/widget/picker/OWNERS b/tests/src/com/android/launcher3/widget/picker/OWNERS
index 775b0c7..716ab90 100644
--- a/tests/src/com/android/launcher3/widget/picker/OWNERS
+++ b/tests/src/com/android/launcher3/widget/picker/OWNERS
@@ -5,7 +5,6 @@
#
# Widget Picker OWNERS
-zakcohen@google.com
shamalip@google.com
wvk@google.com
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 1158521..4e94e1e 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -129,7 +129,7 @@
return mTask.getVisibleCenter().x;
}
- int getTaskCenterY() {
+ public int getTaskCenterY() {
return mTask.getVisibleCenter().y;
}