Merge "Do not crash DesktopVisibilityController on unknown displays" into main
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/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/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
new file mode 100644
index 0000000..adbcc75
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
@@ -0,0 +1,192 @@
+/*
+ * 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.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) { "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.resources.displayMetrics,
+ change,
+ transaction,
+ onAnimFinish,
+ )
+ }
+
+ 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()
+ }
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
index 2406fb6..578bba5 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
@@ -17,27 +17,18 @@
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.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 +39,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,
- private val launchType: AppLaunchType,
+class DesktopAppLaunchTransition
+@JvmOverloads
+constructor(
+ context: Context,
+ 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,7 +63,7 @@
override fun startAnimation(
token: IBinder,
info: TransitionInfo,
- t: Transaction,
+ transaction: Transaction,
transitionFinishedCallback: IRemoteTransitionFinishedCallback,
) {
val safeTransitionFinishedCallback = RemoteRunnable {
@@ -76,7 +71,7 @@
}
mainExecutor.execute {
runAnimators(info, safeTransitionFinishedCallback)
- t.apply()
+ transaction.apply()
}
}
@@ -86,77 +81,10 @@
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 {
/** 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/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..d1e63f3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -47,7 +47,8 @@
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).
@@ -1533,9 +1541,9 @@
return new RemoteTransition(
new DesktopAppLaunchTransition(
this,
- getMainExecutor(),
appLaunchType,
- cujType
+ cujType,
+ getMainExecutor()
),
"TaskbarDesktopAppLaunch");
}
@@ -1558,7 +1566,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;
}
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..46802c3 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;
@@ -53,6 +52,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;
@@ -118,11 +118,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 +141,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 +164,35 @@
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 boolean mUserUnlocked = false;
@@ -191,6 +205,7 @@
@Override
public void run() {
int displayId = getDefaultDisplayId();
+ debugTaskbarManager("mActivityOnDestroyCallback running!", displayId);
if (mActivity != null) {
displayId = mActivity.getDisplayId();
mActivity.removeOnDeviceProfileChangeListener(
@@ -204,7 +219,6 @@
mRecentsViewContainer = null;
}
mActivity = null;
- debugWhyTaskbarNotDestroyed("clearActivity");
TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
taskbar.setUIController(TaskbarUIController.DEFAULT);
@@ -217,28 +231,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 +263,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 +296,10 @@
mTaskbarBroadcastReceiver.register(RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
});
- debugWhyTaskbarNotDestroyed("TaskbarManager created");
+ 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 +308,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 +369,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 +384,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 +433,7 @@
return ql.getUnfoldTransitionProgressProvider();
}
} else {
- return SystemUiProxy.INSTANCE.get(mPrimaryWindowContext).getUnfoldTransitionProvider();
+ return SystemUiProxy.INSTANCE.get(mBaseContext).getUnfoldTransitionProvider();
}
return null;
}
@@ -530,13 +471,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 +494,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 +507,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 +525,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 +540,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 +578,8 @@
}
public void onLongPressHomeEnabled(boolean assistantLongPressEnabled) {
- if (mDefaultNavButtonController != null) {
- mDefaultNavButtonController.setAssistantLongPressEnabled(assistantLongPressEnabled);
+ if (mPrimaryNavButtonController != null) {
+ mPrimaryNavButtonController.setAssistantLongPressEnabled(assistantLongPressEnabled);
}
}
@@ -746,13 +702,26 @@
* 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;
+ }
+
+ // TODO (b/391965805): remove once onDisplayAddSystemDecorations is working.
+ WindowManager wm = getWindowManager(displayId);
+ if (wm == null || !wm.shouldShowSystemDecors(displayId)) {
return;
}
Context newWindowContext = createWindowContext(displayId);
if (newWindowContext != null) {
addWindowContextToMap(displayId, newWindowContext);
+ createTaskbarRootLayout(displayId);
+ createNavButtonController(displayId);
+ createAndRegisterComponentCallbacks(displayId);
+ recreateTaskbarForDisplay(displayId);
}
}
@@ -761,12 +730,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);
}
}
@@ -796,7 +769,7 @@
*/
public void destroy() {
mRecentsViewContainer = null;
- debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
+ debugTaskbarManager("TaskbarManager#destroy()");
removeActivityCallbacksAndListeners();
mTaskbarBroadcastReceiver.unregisterReceiverSafely();
@@ -809,7 +782,7 @@
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();
destroyAllTaskbars();
}
@@ -836,15 +809,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 +842,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 +895,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 +1080,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 +1155,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 +1166,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 +1196,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 +1213,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 +1256,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 +1308,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/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/touchcontrollers/TaskViewDismissTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
index 7a3cdb7..88b7155 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
@@ -16,6 +16,7 @@
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
@@ -52,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
@@ -112,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
}
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/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/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/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 0218a58..8100f25 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -851,6 +851,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();
@@ -916,10 +917,12 @@
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());
@@ -2917,28 +2920,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));
+ }
}
}
}
@@ -3491,8 +3506,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 +3626,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();
});
}
@@ -4339,6 +4356,11 @@
PendingAnimation pendingAnimation,
SplitAnimationTimings splitTimings,
int index) {
+ // 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 +5677,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 +5699,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 +5819,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) {
@@ -6943,7 +6979,7 @@
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);
}
@@ -6951,7 +6987,7 @@
* Animates RecentsView's scale to the provided value, using spring animations.
*/
public SpringAnimation animateRecentsScale(float scale) {
- return mUtils.animateRecentsScale(scale);
+ return mDismissUtils.animateRecentsScale(scale);
}
public interface TaskLaunchListener {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index b38f7d1..94e8c03 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -17,31 +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.RECENTS_SCALE_PROPERTY
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
@@ -309,261 +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,
- )
- }
- 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,
- 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(),
- ),
- )
- }
-
- /** 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) }
- }
-
companion object {
val TEMP_RECT = Rect()
-
- // 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/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/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/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/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 79d3c19..aaaa0e4 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -62,6 +62,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -557,6 +558,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/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f34add8..4561506 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;
@@ -420,7 +421,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.
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index c2d6df5..870e6d6 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(
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/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/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/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/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..8cdf380 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)
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/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/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;
}