From 2e19e736bd49e9cb570e65154ef724a68195e14f Mon Sep 17 00:00:00 2001 From: Johannes Gallmann Date: Wed, 3 Jul 2024 12:22:55 +0000 Subject: [PATCH 1/8] Use default cross-activity animation in Launcher Home Settings Bug: 347161956 Flag: EXEMPT bugfix Test: Manual, i.e. verifying that default cross-activity animation is played Change-Id: I27c67f727a4168bef12601955a03703becdd1698 --- .../shared_x_axis_activity_close_enter.xml | 42 ------------------- .../shared_x_axis_activity_close_exit.xml | 41 ------------------ .../shared_x_axis_activity_open_enter.xml | 42 ------------------- .../shared_x_axis_activity_open_exit.xml | 41 ------------------ res/values-v33/style.xml | 40 ------------------ 5 files changed, 206 deletions(-) delete mode 100644 res/anim-v33/shared_x_axis_activity_close_enter.xml delete mode 100644 res/anim-v33/shared_x_axis_activity_close_exit.xml delete mode 100644 res/anim-v33/shared_x_axis_activity_open_enter.xml delete mode 100644 res/anim-v33/shared_x_axis_activity_open_exit.xml delete mode 100644 res/values-v33/style.xml diff --git a/res/anim-v33/shared_x_axis_activity_close_enter.xml b/res/anim-v33/shared_x_axis_activity_close_enter.xml deleted file mode 100644 index 3d7ad2bd60..0000000000 --- a/res/anim-v33/shared_x_axis_activity_close_enter.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/res/anim-v33/shared_x_axis_activity_close_exit.xml b/res/anim-v33/shared_x_axis_activity_close_exit.xml deleted file mode 100644 index fb63602d4e..0000000000 --- a/res/anim-v33/shared_x_axis_activity_close_exit.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/res/anim-v33/shared_x_axis_activity_open_enter.xml b/res/anim-v33/shared_x_axis_activity_open_enter.xml deleted file mode 100644 index cba74ba0ec..0000000000 --- a/res/anim-v33/shared_x_axis_activity_open_enter.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/res/anim-v33/shared_x_axis_activity_open_exit.xml b/res/anim-v33/shared_x_axis_activity_open_exit.xml deleted file mode 100644 index 22e878d7f1..0000000000 --- a/res/anim-v33/shared_x_axis_activity_open_exit.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/res/values-v33/style.xml b/res/values-v33/style.xml deleted file mode 100644 index 1261b232cc..0000000000 --- a/res/values-v33/style.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - \ No newline at end of file From 6944c9e63f322265da94211ca505e5981aed0b40 Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Thu, 20 Jun 2024 11:04:04 -0700 Subject: [PATCH 2/8] [Launcher Jank] Add TODO for LauncherPreviewRenderer to set bg handler on smartspace BaseTemplateCard Fix: 347775522 Flag: NONE - performance change Test: manual Change-Id: I19ba67e1eabfe02c45909d85d723bc28a78ae6df --- src/com/android/launcher3/graphics/LauncherPreviewRenderer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index ae8f1d513c..60889417b6 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -508,6 +508,7 @@ public class LauncherPreviewRenderer extends ContextWrapper && !SHOULD_SHOW_FIRST_PAGE_WIDGET) { CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID); View qsb = mHomeElementInflater.inflate(R.layout.qsb_preview, firstScreen, false); + // TODO: set bgHandler on qsb when it is BaseTemplateCard, which requires API changes. CellLayoutLayoutParams lp = new CellLayoutLayoutParams( 0, 0, firstScreen.getCountX(), 1); lp.canReorder = false; From 6a8d81a2faf4183a63b967f0608eba0a917ff3ab Mon Sep 17 00:00:00 2001 From: Jordan Silva Date: Mon, 8 Jul 2024 13:29:48 +0000 Subject: [PATCH 3/8] Optimize build for Launcher3Quickstep, Launcher3Go and Launcher3QuickstepGo This CL adds optimizations for Launcher3Quickstep builds by shrinking unused resources. - Enabled proguard for Launcher3QuickStep - Launcher3Quickstep APK size reduced by -61%, 18.9Mb. - Launcher3QuickstepGo and Launcher3Go APK size reduced by 6.5%, 872kb. Fix: 346288630 Flag: EXEMPT Updating bp files to optimize the build Test: Presubmit and Postsubmit builds Change-Id: I6f8bdee7febbc125e14df3afe7bbd36ba57623f0 --- Android.bp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Android.bp b/Android.bp index 13a926bc9e..962f269aed 100644 --- a/Android.bp +++ b/Android.bp @@ -190,7 +190,7 @@ android_app { ], optimize: { - proguard_flags_files: ["proguard.flags"], + proguard_flags_files: [":launcher-proguard-rules"], // Proguard is disable for testing. Derivarive prjects to keep proguard enabled enabled: false, }, @@ -302,7 +302,9 @@ android_app { static_libs: ["Launcher3QuickStepLib"], optimize: { - enabled: false, + proguard_flags_files: [":launcher-proguard-rules"], + enabled: true, + shrink_resources: true, }, platform_apis: true, @@ -349,6 +351,7 @@ android_app { optimize: { proguard_flags_files: ["proguard.flags"], enabled: true, + shrink_resources: true, }, privileged: true, @@ -385,6 +388,7 @@ android_app { optimize: { proguard_flags_files: ["proguard.flags"], enabled: true, + shrink_resources: true, }, privileged: true, From eef1599c0644e48cc0d515aa9f9f9d14c3fcfe34 Mon Sep 17 00:00:00 2001 From: Fengjiang Li Date: Wed, 10 Jul 2024 13:20:05 -0700 Subject: [PATCH 4/8] Fix NPE of BaseContainerInterface#calculateTaskSizeInternal Fix: 351861547 Flag: NONE - Bug fix Test: Presubmit Change-Id: I984748a6c5487a946a96aa3e2183b5d13a370b6d --- .../com/android/quickstep/BaseContainerInterface.java | 9 +++++++-- src/com/android/launcher3/util/DisplayController.java | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/quickstep/src/com/android/quickstep/BaseContainerInterface.java b/quickstep/src/com/android/quickstep/BaseContainerInterface.java index b1d511cfcd..3a8c141ecf 100644 --- a/quickstep/src/com/android/quickstep/BaseContainerInterface.java +++ b/quickstep/src/com/android/quickstep/BaseContainerInterface.java @@ -42,6 +42,7 @@ import com.android.launcher3.statehandlers.DesktopVisibilityController; import com.android.launcher3.statemanager.BaseState; import com.android.launcher3.taskbar.TaskbarUIController; import com.android.launcher3.util.DisplayController; +import com.android.launcher3.util.WindowBounds; import com.android.launcher3.views.ScrimView; import com.android.quickstep.orientation.RecentsPagedOrientationHandler; import com.android.quickstep.util.ActivityInitListener; @@ -51,6 +52,7 @@ import com.android.quickstep.views.RecentsViewContainer; import com.android.systemui.shared.recents.model.ThumbnailData; import java.util.HashMap; +import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; @@ -269,8 +271,11 @@ public abstract class BaseContainerInterface windowBounds = + displayController.getInfo().getCurrentBounds(); + Rect deviceRotationInsets = windowBounds != null + ? windowBounds.get(orientationHandler.getRotation()).insets + : new Rect(); // Obtain the landscape/seascape insets, and rotate it to portrait perspective. orientationHandler.rotateInsets(deviceRotationInsets, outRect); // Then combine with portrait's insets to leave space for status bar/nav bar in diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java index 3dcc663c91..f65f46a850 100644 --- a/src/com/android/launcher3/util/DisplayController.java +++ b/src/com/android/launcher3/util/DisplayController.java @@ -49,6 +49,7 @@ import android.util.Log; import android.view.Display; import androidx.annotation.AnyThread; +import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; @@ -513,9 +514,8 @@ public class DisplayController implements ComponentCallbacks, SafeCloseable { return Collections.unmodifiableSet(mPerDisplayBounds.keySet()); } - /** - * Returns all {@link WindowBounds}s for the current display. - */ + /** Returns all {@link WindowBounds}s for the current display. */ + @Nullable public List getCurrentBounds() { return mPerDisplayBounds.get(normalizedDisplayInfo); } From b59d86112c55163d89b762ff91a5f7f02b608939 Mon Sep 17 00:00:00 2001 From: Uwais Ashraf Date: Wed, 10 Jul 2024 16:46:15 +0000 Subject: [PATCH 5/8] Add icon loading to TasksRepository Bug: 334826842 Test: TasksRepositoryTest Flag: com.android.launcher3.enable_refactor_task_thumbnail Change-Id: I75f2e0e9aae4663993ca54742f653f4c7c04fdfe --- .../com/android/quickstep/TaskIconCache.java | 7 +- .../quickstep/recents/data/TasksRepository.kt | 74 +++++++++++++++++-- .../task/thumbnail/data/TaskIconDataSource.kt | 25 +++++++ .../recents/data/FakeTaskIconDataSource.kt | 58 +++++++++++++++ .../recents/data/TasksRepositoryTest.kt | 57 ++++++++++++-- 5 files changed, 205 insertions(+), 16 deletions(-) create mode 100644 quickstep/src/com/android/quickstep/task/thumbnail/data/TaskIconDataSource.kt create mode 100644 quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java index b3a9199bf9..1f6c02c7aa 100644 --- a/quickstep/src/com/android/quickstep/TaskIconCache.java +++ b/quickstep/src/com/android/quickstep/TaskIconCache.java @@ -33,6 +33,7 @@ import android.os.UserHandle; import android.text.TextUtils; import android.util.SparseArray; +import androidx.annotation.NonNull; import androidx.annotation.WorkerThread; import com.android.launcher3.R; @@ -48,6 +49,7 @@ import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener; import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.Preconditions; +import com.android.quickstep.task.thumbnail.data.TaskIconDataSource; import com.android.quickstep.util.TaskKeyLruCache; import com.android.quickstep.util.TaskVisualsChangeListener; import com.android.systemui.shared.recents.model.Task; @@ -59,7 +61,7 @@ import java.util.concurrent.Executor; /** * Manages the caching of task icons and related data. */ -public class TaskIconCache implements DisplayInfoChangeListener { +public class TaskIconCache implements TaskIconDataSource, DisplayInfoChangeListener { private final Executor mBgExecutor; @@ -102,7 +104,8 @@ public class TaskIconCache implements DisplayInfoChangeListener { * @param callback The callback to receive the task after its data has been populated. * @return A cancelable handle to the request */ - public CancellableTask getIconInBackground(Task task, GetTaskIconCallback callback) { + @Override + public CancellableTask getIconInBackground(Task task, @NonNull GetTaskIconCallback callback) { Preconditions.assertUIThread(); if (task.icon != null) { // Nothing to load, the icon is already loaded diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt index 9f3ef4adb2..ad0326ab41 100644 --- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt +++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt @@ -16,7 +16,8 @@ package com.android.quickstep.recents.data -import com.android.quickstep.TaskIconCache +import android.graphics.drawable.Drawable +import com.android.quickstep.task.thumbnail.data.TaskIconDataSource import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource import com.android.quickstep.util.GroupTask import com.android.systemui.shared.recents.model.Task @@ -37,7 +38,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine class TasksRepository( private val recentsModel: RecentTasksDataSource, private val taskThumbnailDataSource: TaskThumbnailDataSource, - private val taskIconCache: TaskIconCache, + private val taskIconDataSource: TaskIconDataSource, ) : RecentTasksRepository { private val groupedTaskData = MutableStateFlow(emptyList()) private val _taskData = @@ -45,10 +46,19 @@ class TasksRepository( private val visibleTaskIds = MutableStateFlow(emptySet()) private val taskData: Flow> = - combine(_taskData, getThumbnailQueryResults()) { tasks, results -> + combine(_taskData, getThumbnailQueryResults(), getIconQueryResults()) { + tasks, + thumbnailQueryResults, + iconQueryResults -> tasks.forEach { task -> // Add retrieved thumbnails + remove unnecessary thumbnails - task.thumbnail = results[task.key.id] + task.thumbnail = thumbnailQueryResults[task.key.id] + + // TODO(b/352331675) don't load icons for DesktopTaskView + // Add retrieved icons + remove unnecessary icons + task.icon = iconQueryResults[task.key.id]?.icon + task.titleDescription = iconQueryResults[task.key.id]?.contentDescription + task.title = iconQueryResults[task.key.id]?.title } tasks } @@ -75,7 +85,6 @@ class TasksRepository( suspendCancellableCoroutine { continuation -> val cancellableTask = taskThumbnailDataSource.getThumbnailInBackground(task) { - task.thumbnail = it continuation.resume(it) } continuation.invokeOnCancellation { cancellableTask?.cancel() } @@ -105,6 +114,59 @@ class TasksRepository( } } } + + /** Flow wrapper for [TaskThumbnailDataSource.getThumbnailInBackground] api */ + private fun getIconDataRequest(task: Task): IconDataRequest = + flow { + emit(task.key.id to task.getTaskIconQueryResponse()) + val iconDataResponse: TaskIconQueryResponse? = + suspendCancellableCoroutine { continuation -> + val cancellableTask = + taskIconDataSource.getIconInBackground(task) { + icon, + contentDescription, + title -> + continuation.resume( + TaskIconQueryResponse(icon, contentDescription, title) + ) + } + continuation.invokeOnCancellation { cancellableTask?.cancel() } + } + emit(task.key.id to iconDataResponse) + } + .distinctUntilChanged() + + private fun getIconQueryResults(): Flow> { + val visibleTasks = + combine(_taskData, visibleTaskIds) { tasks, visibleIds -> + tasks.filter { it.key.id in visibleIds } + } + val visibleIconDataRequests: Flow> = + visibleTasks.map { visibleTasksList -> visibleTasksList.map(::getIconDataRequest) } + return visibleIconDataRequests.flatMapLatest { iconRequestFlows: List -> + if (iconRequestFlows.isEmpty()) { + flowOf(emptyMap()) + } else { + combine(iconRequestFlows) { it.toMap() } + } + } + } } -typealias ThumbnailDataRequest = Flow> +private data class TaskIconQueryResponse( + val icon: Drawable, + val contentDescription: String, + val title: String +) + +private fun Task.getTaskIconQueryResponse(): TaskIconQueryResponse? { + val iconVal = icon ?: return null + val titleDescriptionVal = titleDescription ?: return null + val titleVal = title ?: return null + + return TaskIconQueryResponse(iconVal, titleDescriptionVal, titleVal) +} + +private typealias ThumbnailDataRequest = Flow> + +private typealias IconDataRequest = Flow> diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskIconDataSource.kt b/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskIconDataSource.kt new file mode 100644 index 0000000000..ab699c6013 --- /dev/null +++ b/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskIconDataSource.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.task.thumbnail.data + +import com.android.launcher3.util.CancellableTask +import com.android.quickstep.TaskIconCache.GetTaskIconCallback +import com.android.systemui.shared.recents.model.Task + +interface TaskIconDataSource { + fun getIconInBackground(task: Task, callback: GetTaskIconCallback): CancellableTask<*>? +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt new file mode 100644 index 0000000000..242bc731ff --- /dev/null +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt @@ -0,0 +1,58 @@ +/* + * 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.data + +import android.graphics.drawable.Drawable +import com.android.launcher3.util.CancellableTask +import com.android.quickstep.TaskIconCache +import com.android.quickstep.task.thumbnail.data.TaskIconDataSource +import com.android.systemui.shared.recents.model.Task +import com.google.common.truth.Truth.assertThat +import org.mockito.kotlin.mock + +class FakeTaskIconDataSource : TaskIconDataSource { + + val taskIdToDrawable: Map = (0..10).associateWith { mock() } + val taskIdToUpdatingTask: MutableMap Unit> = mutableMapOf() + var shouldLoadSynchronously: Boolean = true + + /** Retrieves and sets an icon on [task] from [taskIdToDrawable]. */ + override fun getIconInBackground( + task: Task, + callback: TaskIconCache.GetTaskIconCallback + ): CancellableTask<*>? { + val wrappedCallback = { + callback.onTaskIconReceived( + taskIdToDrawable.getValue(task.key.id), + "content desc ${task.key.id}", + "title ${task.key.id}" + ) + } + if (shouldLoadSynchronously) { + wrappedCallback() + } else { + taskIdToUpdatingTask[task.key.id] = wrappedCallback + } + return null + } +} + +fun Task.assertHasIconDataFromSource(fakeTaskIconDataSource: FakeTaskIconDataSource) { + assertThat(icon).isEqualTo(fakeTaskIconDataSource.taskIdToDrawable[key.id]) + assertThat(titleDescription).isEqualTo("content desc ${key.id}") + assertThat(title).isEqualTo("title ${key.id}") +} diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt index c28a85a8f8..88fa190c40 100644 --- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt +++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt @@ -18,7 +18,6 @@ package com.android.quickstep.recents.data import android.content.ComponentName import android.content.Intent -import com.android.quickstep.TaskIconCache import com.android.quickstep.util.DesktopTask import com.android.quickstep.util.GroupTask import com.android.systemui.shared.recents.model.Task @@ -31,7 +30,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Test -import org.mockito.kotlin.mock @OptIn(ExperimentalCoroutinesApi::class) class TasksRepositoryTest { @@ -44,10 +42,10 @@ class TasksRepositoryTest { ) private val recentsModel = FakeRecentTasksDataSource() private val taskThumbnailDataSource = FakeTaskThumbnailDataSource() - private val taskIconCache = mock() + private val taskIconDataSource = FakeTaskIconDataSource() private val systemUnderTest = - TasksRepository(recentsModel, taskThumbnailDataSource, taskIconCache) + TasksRepository(recentsModel, taskThumbnailDataSource, taskIconDataSource) @Test fun getAllTaskDataReturnsFlattenedListOfTasks() = runTest { @@ -80,6 +78,22 @@ class TasksRepositoryTest { .isEqualTo(bitmap2) } + @Test + fun setVisibleTasksPopulatesIcons() = runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + + systemUnderTest.setVisibleTasks(listOf(1, 2)) + + // .drop(1) to ignore initial null content before from thumbnail was loaded. + systemUnderTest + .getTaskDataById(1) + .drop(1) + .first()!! + .assertHasIconDataFromSource(taskIconDataSource) + systemUnderTest.getTaskDataById(2).first()!!.assertHasIconDataFromSource(taskIconDataSource) + } + @Test fun changingVisibleTasksContainsAlreadyPopulatedThumbnails() = runTest { recentsModel.seedTasks(defaultTaskList) @@ -101,7 +115,28 @@ class TasksRepositoryTest { } @Test - fun retrievedThumbnailsAreDiscardedWhenTaskBecomesInvisible() = runTest { + fun changingVisibleTasksContainsAlreadyPopulatedIcons() = runTest { + recentsModel.seedTasks(defaultTaskList) + systemUnderTest.getAllTaskData(forceRefresh = true) + + systemUnderTest.setVisibleTasks(listOf(1, 2)) + + // .drop(1) to ignore initial null content before from icon was loaded. + systemUnderTest + .getTaskDataById(2) + .drop(1) + .first()!! + .assertHasIconDataFromSource(taskIconDataSource) + + // Prevent new loading of Drawables + taskThumbnailDataSource.shouldLoadSynchronously = false + systemUnderTest.setVisibleTasks(listOf(2, 3)) + + systemUnderTest.getTaskDataById(2).first()!!.assertHasIconDataFromSource(taskIconDataSource) + } + + @Test + fun retrievedImagesAreDiscardedWhenTaskBecomesInvisible() = runTest { recentsModel.seedTasks(defaultTaskList) val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2] systemUnderTest.getAllTaskData(forceRefresh = true) @@ -109,14 +144,20 @@ class TasksRepositoryTest { systemUnderTest.setVisibleTasks(listOf(1, 2)) // .drop(1) to ignore initial null content before from thumbnail was loaded. - assertThat(systemUnderTest.getTaskDataById(2).drop(1).first()!!.thumbnail!!.thumbnail) - .isEqualTo(bitmap2) + val task2 = systemUnderTest.getTaskDataById(2).drop(1).first()!! + assertThat(task2.thumbnail!!.thumbnail).isEqualTo(bitmap2) + task2.assertHasIconDataFromSource(taskIconDataSource) // Prevent new loading of Bitmaps taskThumbnailDataSource.shouldLoadSynchronously = false + taskIconDataSource.shouldLoadSynchronously = false systemUnderTest.setVisibleTasks(listOf(0, 1)) - assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail).isNull() + val task2AfterVisibleTasksChanged = systemUnderTest.getTaskDataById(2).first()!! + assertThat(task2AfterVisibleTasksChanged.thumbnail).isNull() + assertThat(task2AfterVisibleTasksChanged.icon).isNull() + assertThat(task2AfterVisibleTasksChanged.titleDescription).isNull() + assertThat(task2AfterVisibleTasksChanged.title).isNull() } @Test From d16ea2650eb968269aa178c4630f457f5bfafce9 Mon Sep 17 00:00:00 2001 From: Shamali Patwa Date: Wed, 10 Jul 2024 21:46:49 +0000 Subject: [PATCH 6/8] Align predictive back in standalone picker activity Bug: 346332606 Test: Manual Flag: EXEMPT BUGFIX Change-Id: I34882389029a5f095ab8d910791f7be34f7b8355 --- .../launcher3/WidgetPickerActivity.java | 73 +++++++++++++++++-- 1 file changed, 68 insertions(+), 5 deletions(-) diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java index 89840866c9..44d8a5c647 100644 --- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java +++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java @@ -20,6 +20,7 @@ import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; +import static com.android.launcher3.Flags.enablePredictiveBackGesture; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; @@ -33,6 +34,9 @@ import android.util.Log; import android.view.View; import android.view.WindowInsetsController; import android.view.WindowManager; +import android.window.BackEvent; +import android.window.OnBackAnimationCallback; +import android.window.OnBackInvokedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -124,6 +128,8 @@ public class WidgetPickerActivity extends BaseActivity { /** A set of user ids that should be filtered out from the selected widgets. */ @NonNull Set mFilteredUserIds = new HashSet<>(); + @Nullable + private WidgetsFullSheet mWidgetSheet; @Override protected void onCreate(Bundle savedInstanceState) { @@ -148,6 +154,18 @@ public class WidgetPickerActivity extends BaseActivity { refreshAndBindWidgets(); } + @Override + protected void registerBackDispatcher() { + if (!enablePredictiveBackGesture()) { + super.registerBackDispatcher(); + return; + } + + getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, + new BackAnimationCallback()); + } + private void parseIntentExtras() { mTitle = getIntent().getStringExtra(EXTRA_PICKER_TITLE); mDescription = getIntent().getStringExtra(EXTRA_PICKER_DESCRIPTION); @@ -293,12 +311,12 @@ public class WidgetPickerActivity extends BaseActivity { MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets)); } - private void openWidgetsSheet() { + private void openWidgetsSheet() { MAIN_EXECUTOR.execute(() -> { - WidgetsFullSheet widgetSheet = WidgetsFullSheet.show(this, true); - widgetSheet.mayUpdateTitleAndDescription(mTitle, mDescription); - widgetSheet.disableNavBarScrim(true); - widgetSheet.addOnCloseListener(this::finish); + mWidgetSheet = WidgetsFullSheet.show(this, true); + mWidgetSheet.mayUpdateTitleAndDescription(mTitle, mDescription); + mWidgetSheet.disableNavBarScrim(true); + mWidgetSheet.addOnCloseListener(this::finish); }); } @@ -317,6 +335,51 @@ public class WidgetPickerActivity extends BaseActivity { } } + /** + * Animation callback for different predictive back animation states for the widget picker. + */ + private class BackAnimationCallback implements OnBackAnimationCallback { + @Nullable + OnBackAnimationCallback mActiveOnBackAnimationCallback; + + @Override + public void onBackStarted(@NonNull BackEvent backEvent) { + if (mActiveOnBackAnimationCallback != null) { + mActiveOnBackAnimationCallback.onBackCancelled(); + } + if (mWidgetSheet != null) { + mActiveOnBackAnimationCallback = mWidgetSheet; + mActiveOnBackAnimationCallback.onBackStarted(backEvent); + } + } + + @Override + public void onBackInvoked() { + if (mActiveOnBackAnimationCallback == null) { + return; + } + mActiveOnBackAnimationCallback.onBackInvoked(); + mActiveOnBackAnimationCallback = null; + } + + @Override + public void onBackProgressed(@NonNull BackEvent backEvent) { + if (mActiveOnBackAnimationCallback == null) { + return; + } + mActiveOnBackAnimationCallback.onBackProgressed(backEvent); + } + + @Override + public void onBackCancelled() { + if (mActiveOnBackAnimationCallback == null) { + return; + } + mActiveOnBackAnimationCallback.onBackCancelled(); + mActiveOnBackAnimationCallback = null; + } + }; + private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget) { final AppWidgetProviderInfo info = widget.widgetInfo; if (info == null) { From 7e7afac7c0cf36824924c9bd80c0b3aa741f8b8d Mon Sep 17 00:00:00 2001 From: Uwais Ashraf Date: Wed, 10 Jul 2024 18:50:42 +0000 Subject: [PATCH 7/8] Change drawing in TTV to use Views rather than Canvas Fix: 352332386 Test: Presubmit, Manual Flag: com.android.launcher3.enable_refactor_task_thumbnail Change-Id: Ia1b7b6dc3d093d431ce022e793f40e61cec38ac6 --- quickstep/res/layout/task.xml | 2 +- quickstep/res/layout/task_grouped.xml | 4 +- quickstep/res/layout/task_thumbnail.xml | 27 +++++- .../res/layout/task_thumbnail_deprecated.xml | 20 +++++ .../quickstep/task/thumbnail/LiveTileView.kt | 46 ++++++++++ .../task/thumbnail/TaskThumbnailView.kt | 86 ++++++++----------- .../quickstep/views/DesktopTaskView.kt | 35 ++++---- .../com/android/quickstep/views/TaskView.kt | 6 +- 8 files changed, 151 insertions(+), 75 deletions(-) create mode 100644 quickstep/res/layout/task_thumbnail_deprecated.xml create mode 100644 quickstep/src/com/android/quickstep/task/thumbnail/LiveTileView.kt diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml index cc3b30e9dc..46c1332651 100644 --- a/quickstep/res/layout/task.xml +++ b/quickstep/res/layout/task.xml @@ -28,7 +28,7 @@ launcher:focusBorderColor="?androidprv:attr/materialColorOutline" launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary"> - + diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml index 87a0f7073f..708aa3cbd6 100644 --- a/quickstep/res/layout/task_grouped.xml +++ b/quickstep/res/layout/task_grouped.xml @@ -33,9 +33,9 @@ launcher:focusBorderColor="?androidprv:attr/materialColorOutline" launcher:hoverBorderColor="?androidprv:attr/materialColorPrimary"> - + - - \ No newline at end of file + android:layout_height="match_parent"> + + + + + + + + \ No newline at end of file diff --git a/quickstep/res/layout/task_thumbnail_deprecated.xml b/quickstep/res/layout/task_thumbnail_deprecated.xml new file mode 100644 index 0000000000..f1a3d62021 --- /dev/null +++ b/quickstep/res/layout/task_thumbnail_deprecated.xml @@ -0,0 +1,20 @@ + + \ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/LiveTileView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/LiveTileView.kt new file mode 100644 index 0000000000..45b368709a --- /dev/null +++ b/quickstep/src/com/android/quickstep/task/thumbnail/LiveTileView.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep.task.thumbnail + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.PorterDuffXfermode +import android.util.AttributeSet +import android.view.View + +class LiveTileView : View { + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + ) : super(context, attrs, defStyleAttr) + + override fun onDraw(canvas: Canvas) { + canvas.drawRect(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat(), CLEAR_PAINT) + } + + companion object { + private val CLEAR_PAINT = + Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) } + } +} diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt index 22d49c12fe..c71b9e74dc 100644 --- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt +++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt @@ -18,17 +18,17 @@ package com.android.quickstep.task.thumbnail import android.content.Context import android.content.res.Configuration -import android.graphics.Canvas import android.graphics.Color import android.graphics.Outline -import android.graphics.Paint -import android.graphics.PorterDuff -import android.graphics.PorterDuffXfermode import android.graphics.Rect import android.util.AttributeSet import android.view.View import android.view.ViewOutlineProvider +import android.widget.FrameLayout +import android.widget.ImageView import androidx.annotation.ColorInt +import androidx.core.view.isVisible +import com.android.launcher3.R import com.android.launcher3.Utilities import com.android.launcher3.util.ViewPool import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly @@ -43,7 +43,7 @@ import com.android.systemui.shared.system.QuickStepContract import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch -class TaskThumbnailView : View, ViewPool.Reusable { +class TaskThumbnailView : FrameLayout, ViewPool.Reusable { // TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped // to [TaskView], and also shared between [TaskView] and [TaskThumbnailView] // This is using a lazy for now because the dependencies cannot be obtained without DI. @@ -59,12 +59,12 @@ class TaskThumbnailView : View, ViewPool.Reusable { ) } - private var uiState: TaskThumbnailUiState = Uninitialized - private var inheritedScale: Float = 1f - private var dimProgress: Float = 0f + private val scrimView: View by lazy { findViewById(R.id.task_thumbnail_scrim) } + private val liveTileView: LiveTileView by lazy { findViewById(R.id.task_thumbnail_live_tile) } + private val thumbnail: ImageView by lazy { findViewById(R.id.task_thumbnail) } + + private var inheritedScale: Float = 1f - private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG) - private val scrimPaint = Paint().apply { color = Color.BLACK } private val _measuredBounds = Rect() private val measuredBounds: Rect get() { @@ -75,12 +75,12 @@ class TaskThumbnailView : View, ViewPool.Reusable { private var overviewCornerRadius: Float = TaskCornerRadius.get(context) private var fullscreenCornerRadius: Float = QuickStepContract.getWindowCornerRadius(context) - constructor(context: Context?) : super(context) + constructor(context: Context) : super(context) - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor( - context: Context?, + context: Context, attrs: AttributeSet?, defStyleAttr: Int, ) : super(context, attrs, defStyleAttr) @@ -90,15 +90,19 @@ class TaskThumbnailView : View, ViewPool.Reusable { // TODO(b/335396935) replace MainScope with shorter lifecycle. MainScope().launch { viewModel.uiState.collect { viewModelUiState -> - uiState = viewModelUiState - invalidate() + resetViews() + when (viewModelUiState) { + is Uninitialized -> {} + is LiveTile -> drawLiveWindow() + is Snapshot -> drawSnapshot(viewModelUiState) + is BackgroundOnly -> drawBackground(viewModelUiState.backgroundColor) + } } } MainScope().launch { viewModel.dimProgress.collect { dimProgress -> // TODO(b/348195366) Add fade in/out for scrim - this@TaskThumbnailView.dimProgress = dimProgress - invalidate() + scrimView.alpha = dimProgress * MAX_SCRIM_ALPHA } } MainScope().launch { viewModel.cornerRadiusProgress.collect { invalidateOutline() } } @@ -120,25 +124,6 @@ class TaskThumbnailView : View, ViewPool.Reusable { override fun onRecycle() { // Do nothing - uiState = Uninitialized - } - - override fun onDraw(canvas: Canvas) { - when (val uiStateVal = uiState) { - is Uninitialized -> drawBackgroundOnly(canvas, Color.BLACK) - is LiveTile -> drawTransparentUiState(canvas) - is Snapshot -> drawSnapshotState(canvas, uiStateVal) - is BackgroundOnly -> drawBackgroundOnly(canvas, uiStateVal.backgroundColor) - } - - if (dimProgress > 0) { - drawScrim(canvas) - } - } - - private fun drawBackgroundOnly(canvas: Canvas, @ColorInt backgroundColor: Int) { - backgroundPaint.color = backgroundColor - canvas.drawRect(measuredBounds, backgroundPaint) } override fun onConfigurationChanged(newConfig: Configuration?) { @@ -149,18 +134,25 @@ class TaskThumbnailView : View, ViewPool.Reusable { invalidateOutline() } - private fun drawTransparentUiState(canvas: Canvas) { - canvas.drawRect(measuredBounds, CLEAR_PAINT) + private fun resetViews() { + liveTileView.isVisible = false + thumbnail.isVisible = false + scrimView.alpha = 0f + setBackgroundColor(Color.BLACK) } - private fun drawSnapshotState(canvas: Canvas, snapshot: Snapshot) { - drawBackgroundOnly(canvas, snapshot.backgroundColor) - canvas.drawBitmap(snapshot.bitmap, snapshot.drawnRect, measuredBounds, null) + private fun drawBackground(@ColorInt background: Int) { + setBackgroundColor(background) } - private fun drawScrim(canvas: Canvas) { - scrimPaint.alpha = (dimProgress * MAX_SCRIM_ALPHA).toInt() - canvas.drawRect(measuredBounds, scrimPaint) + private fun drawLiveWindow() { + liveTileView.isVisible = true + } + + private fun drawSnapshot(snapshot: Snapshot) { + drawBackground(snapshot.backgroundColor) + thumbnail.setImageBitmap(snapshot.bitmap) + thumbnail.isVisible = true } private fun getCurrentCornerRadius() = @@ -170,9 +162,7 @@ class TaskThumbnailView : View, ViewPool.Reusable { fullscreenCornerRadius ) / inheritedScale - companion object { - private val CLEAR_PAINT = - Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) } - private const val MAX_SCRIM_ALPHA = (0.4f * 255).toInt() + private companion object { + const val MAX_SCRIM_ALPHA = 0.4f } } diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt index 4333c8b905..9ce2277b14 100644 --- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt +++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt @@ -24,6 +24,7 @@ import android.graphics.drawable.ShapeDrawable import android.graphics.drawable.shapes.RoundRectShape import android.util.AttributeSet import android.util.Log +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.updateLayoutParams @@ -36,7 +37,6 @@ import com.android.launcher3.util.ViewPool import com.android.launcher3.util.rects.set import com.android.quickstep.BaseContainerInterface import com.android.quickstep.TaskOverlayFactory -import com.android.quickstep.task.thumbnail.TaskThumbnailView import com.android.quickstep.util.RecentsOrientedState import com.android.systemui.shared.recents.model.Task @@ -54,7 +54,7 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu ViewPool( context, this, - R.layout.task_thumbnail, + R.layout.task_thumbnail_deprecated, VIEW_POOL_MAX_SIZE, VIEW_POOL_INITIAL_SIZE ) @@ -108,22 +108,21 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu tasks.map { task -> val snapshotView = if (enableRefactorTaskThumbnail()) { - TaskThumbnailView(context) - } else { - taskThumbnailViewDeprecatedPool.view - } - .also { snapshotView -> - addView( - snapshotView, - // Add snapshotView to the front after initial views e.g. icon and - // background. - childCountAtInflation, - LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - ) - } + LayoutInflater.from(context).inflate(R.layout.task_thumbnail, this, false) + } else { + taskThumbnailViewDeprecatedPool.view + } + + addView( + snapshotView, + // Add snapshotView to the front after initial views e.g. icon and + // background. + childCountAtInflation, + LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + ) TaskContainer( this, task, diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt index 5c95aaa4c0..3209fab479 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.kt +++ b/quickstep/src/com/android/quickstep/views/TaskView.kt @@ -31,6 +31,7 @@ import android.util.AttributeSet import android.util.FloatProperty import android.util.Log import android.view.Display +import android.view.LayoutInflater import android.view.MotionEvent import android.view.View import android.view.View.OnClickListener @@ -665,9 +666,8 @@ constructor( if (enableRefactorTaskThumbnail()) { thumbnailViewDeprecated.visibility = GONE val indexOfSnapshotView = indexOfChild(thumbnailViewDeprecated) - TaskThumbnailView(context).apply { - layoutParams = thumbnailViewDeprecated.layoutParams - addView(this, indexOfSnapshotView) + LayoutInflater.from(context).inflate(R.layout.task_thumbnail, this, false).also { + addView(it, indexOfSnapshotView, thumbnailViewDeprecated.layoutParams) } } else { thumbnailViewDeprecated From cf55a30dcf4e6cc677fe133a22bc73f6d8f4faff Mon Sep 17 00:00:00 2001 From: fbaron Date: Thu, 11 Jul 2024 10:48:48 -0700 Subject: [PATCH 8/8] Add screenrecord to testDragIconToPage2 Flag: TEST_ONLY Bug: 352130094 Test: TaplTwoPanelWorkspaceTest#testDragIconToPage2 Change-Id: I0358e4da22afc19a8d976d529c68f09529247ca7 --- .../launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java index bc26c00c47..2df5509945 100644 --- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java +++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java @@ -169,6 +169,7 @@ public class TaplTwoPanelWorkspaceTest extends AbstractLauncherUiTest @Test @PortraitLandscape + @ScreenRecordRule.ScreenRecord // b/352130094 public void testDragIconToPage2() { Workspace workspace = mLauncher.getWorkspace();