Snap for 11889377 from 92b73a4f09 to 24Q3-release

Change-Id: I012fe5c7ede6a9c3ee72f46a7836d2cc738c08cd
This commit is contained in:
Android Build Coastguard Worker
2024-05-25 03:21:32 +00:00
32 changed files with 656 additions and 79 deletions
-7
View File
@@ -284,10 +284,3 @@ flag {
description: "Enables folders in all apps"
bug: "341582436"
}
flag {
name: "enable_tiny_taskbar"
namespace: "launcher"
description: "Enables Taskbar on phones"
bug: "341784466"
}
+3
View File
@@ -358,6 +358,9 @@
<dimen name="taskbar_running_app_indicator_height">4dp</dimen>
<dimen name="taskbar_running_app_indicator_width">14dp</dimen>
<dimen name="taskbar_running_app_indicator_top_margin">2dp</dimen>
<dimen name="taskbar_minimized_app_indicator_height">2dp</dimen>
<dimen name="taskbar_minimized_app_indicator_width">12dp</dimen>
<dimen name="taskbar_minimized_app_indicator_top_margin">2dp</dimen>
<!-- Transient taskbar -->
<dimen name="transient_taskbar_padding">12dp</dimen>
@@ -47,6 +47,7 @@ class DesktopTaskbarRunningAppsController(
private var apps: Array<AppInfo>? = null
private var allRunningDesktopAppInfos: List<AppInfo>? = null
private var allMinimizedDesktopAppInfos: List<AppInfo>? = null
private val desktopVisibilityController: DesktopVisibilityController?
get() = desktopVisibilityControllerProvider()
@@ -95,6 +96,13 @@ class DesktopTaskbarRunningAppsController(
return allRunningDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet()
}
override fun getMinimizedApps(): Set<String> {
if (!isInDesktopMode) {
return emptySet()
}
return allMinimizedDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet()
}
@VisibleForTesting
public override fun updateRunningApps() {
if (!isInDesktopMode) {
@@ -102,10 +110,34 @@ class DesktopTaskbarRunningAppsController(
mControllers.taskbarViewController.commitRunningAppsToUI()
return
}
allRunningDesktopAppInfos = getRunningDesktopAppInfos()
val runningTasks = getDesktopRunningTasks()
val runningAppInfo = getAppInfosFromRunningTasks(runningTasks)
allRunningDesktopAppInfos = runningAppInfo
updateMinimizedApps(runningTasks, runningAppInfo)
mControllers.taskbarViewController.commitRunningAppsToUI()
}
private fun updateMinimizedApps(
runningTasks: List<RunningTaskInfo>,
runningAppInfo: List<AppInfo>,
) {
val allRunningAppTasks =
runningAppInfo
.mapNotNull { appInfo -> appInfo.targetPackage?.let { appInfo to it } }
.associate { (appInfo, targetPackage) ->
appInfo to
runningTasks
.filter { it.realActivity?.packageName == targetPackage }
.map { it.taskId }
}
val minimizedTaskIds = runningTasks.associate { it.taskId to !it.isVisible }
allMinimizedDesktopAppInfos =
allRunningAppTasks
.filterValues { taskIds -> taskIds.all { minimizedTaskIds[it] ?: false } }
.keys
.toList()
}
private fun getRunningDesktopAppInfosExceptHotseatApps(
allRunningDesktopAppInfos: List<AppInfo>,
hotseatItems: List<ItemInfo>
@@ -116,15 +148,10 @@ class DesktopTaskbarRunningAppsController(
.map { WorkspaceItemInfo(it) }
}
private fun getRunningDesktopAppInfos(): List<AppInfo> {
return getAppInfosFromRunningTasks(
recentsModel.runningTasks
.filter { taskInfo: RunningTaskInfo ->
taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
}
.toList()
)
}
private fun getDesktopRunningTasks(): List<RunningTaskInfo> =
recentsModel.runningTasks.filter { taskInfo: RunningTaskInfo ->
taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
}
// TODO(b/335398876) fetch app icons from Tasks instead of AppInfos
private fun getAppInfosFromRunningTasks(tasks: List<RunningTaskInfo>): List<AppInfo> {
@@ -138,9 +165,10 @@ class DesktopTaskbarRunningAppsController(
.filterNotNull()
}
private fun <E> SparseArray<E>.toList(): List<E> {
return valueIterator().asSequence().toList()
}
private fun getAppInfosFromRunningTask(task: RunningTaskInfo): AppInfo? =
apps?.firstOrNull { it.targetPackage == task.realActivity?.packageName }
private fun <E> SparseArray<E>.toList(): List<E> = valueIterator().asSequence().toList()
companion object {
private const val TAG = "TabletDesktopTaskbarRunningAppsController"
@@ -148,7 +148,7 @@ public final class KeyboardQuickSwitchController implements
});
}
private void processLoadedTasks(ArrayList<GroupTask> tasks) {
private void processLoadedTasks(List<GroupTask> tasks) {
// Only store MAX_TASK tasks, from most to least recent
Collections.reverse(tasks);
mTasks = tasks.stream()
@@ -157,7 +157,7 @@ public final class KeyboardQuickSwitchController implements
mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS);
}
private void processLoadedTasksOnDesktop(ArrayList<GroupTask> tasks) {
private void processLoadedTasksOnDesktop(List<GroupTask> tasks) {
// Find the single desktop task that contains a grouping of desktop tasks
DesktopTask desktopTask = findDesktopTask(tasks);
@@ -173,7 +173,7 @@ public final class KeyboardQuickSwitchController implements
}
@Nullable
private DesktopTask findDesktopTask(ArrayList<GroupTask> tasks) {
private DesktopTask findDesktopTask(List<GroupTask> tasks) {
return (DesktopTask) tasks.stream()
.filter(t -> t instanceof DesktopTask)
.findFirst()
@@ -43,6 +43,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
import static com.android.wm.shell.Flags.enableTinyTaskbar;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
@@ -416,7 +417,9 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
* single window for taskbar and navbar.
*/
public boolean isPhoneMode() {
return ENABLE_TASKBAR_NAVBAR_UNIFICATION && mDeviceProfile.isPhone;
return ENABLE_TASKBAR_NAVBAR_UNIFICATION
&& mDeviceProfile.isPhone
&& !mDeviceProfile.isTaskbarPresent;
}
/**
@@ -433,6 +436,11 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
return isPhoneMode() && !isThreeButtonNav();
}
/** Returns {@code true} iff a tiny version of taskbar is shown on phone. */
public boolean isTinyTaskbar() {
return enableTinyTaskbar() && mDeviceProfile.isPhone && mDeviceProfile.isTaskbarPresent;
}
/**
* Returns if software keyboard is docked or input toolbar is placed at the taskbar area
*/
@@ -981,7 +989,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
public int getDefaultTaskbarWindowSize() {
Resources resources = getResources();
if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && mDeviceProfile.isPhone) {
if (isPhoneMode()) {
return isThreeButtonNav() ?
resources.getDimensionPixelSize(R.dimen.taskbar_phone_size) :
resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
@@ -81,7 +81,11 @@ open class TaskbarEduTooltipController(context: Context) :
protected val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
open val shouldShowSearchEdu = false
private val isTooltipEnabled: Boolean
get() = !Utilities.isRunningInTestHarness() && !activityContext.isPhoneMode
get() {
return !Utilities.isRunningInTestHarness() &&
!activityContext.isPhoneMode &&
!activityContext.isTinyTaskbar
}
private val isOpen: Boolean
get() = tooltip?.isOpen ?: false
val isBeforeTooltipFeaturesStep: Boolean
@@ -51,6 +51,7 @@ import com.android.launcher3.anim.AlphaUpdateListener
import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION
import com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
import com.android.launcher3.testing.shared.ResourceUtils
import com.android.launcher3.util.DisplayController
import java.io.PrintWriter
import kotlin.jvm.optionals.getOrNull
@@ -231,8 +232,24 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
val contentHeight = controllers.taskbarStashController.contentHeightToReportToApps
val tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
val res = context.resources
if (provider.type == navigationBars() || provider.type == mandatorySystemGestures()) {
if (provider.type == navigationBars()) {
provider.insetsSize = getInsetsForGravityWithCutout(contentHeight, gravity, endRotation)
} else if (provider.type == mandatorySystemGestures()) {
if (context.isThreeButtonNav) {
provider.insetsSize = Insets.of(0, 0, 0, 0)
} else {
val gestureHeight =
ResourceUtils.getNavbarSize(
ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
context.resources)
val isPinnedTaskbar = context.deviceProfile.isTaskbarPresent
&& !context.deviceProfile.isTransientTaskbar
val mandatoryGestureHeight =
if (isPinnedTaskbar) contentHeight
else gestureHeight
provider.insetsSize = getInsetsForGravityWithCutout(mandatoryGestureHeight, gravity,
endRotation)
}
} else if (provider.type == tappableElement()) {
provider.insetsSize = getInsetsForGravity(tappableHeight, gravity)
} else if (provider.type == systemGestures() && provider.index == INDEX_LEFT) {
@@ -519,7 +519,7 @@ public class TaskbarManager {
}
}
private static boolean isTaskbarEnabled(DeviceProfile deviceProfile) {
private boolean isTaskbarEnabled(DeviceProfile deviceProfile) {
return ENABLE_TASKBAR_NAVBAR_UNIFICATION || deviceProfile.isTaskbarPresent;
}
@@ -68,7 +68,7 @@ public class TaskbarModelCallbacks implements
// Used to defer any UI updates during the SUW unstash animation.
private boolean mDeferUpdatesForSUW;
private Runnable mDeferredUpdates;
private DesktopVisibilityController.DesktopVisibilityListener mDesktopVisibilityListener =
private final DesktopVisibilityController.DesktopVisibilityListener mDesktopVisibilityListener =
visible -> updateRunningApps();
public TaskbarModelCallbacks(
@@ -235,20 +235,23 @@ public class TaskbarModelCallbacks implements
hotseatItemInfos = mControllers.taskbarRecentAppsController
.updateHotseatItemInfos(hotseatItemInfos);
Set<String> runningPackages = mControllers.taskbarRecentAppsController.getRunningApps();
Set<String> minimizedPackages = mControllers.taskbarRecentAppsController.getMinimizedApps();
if (mDeferUpdatesForSUW) {
ItemInfo[] finalHotseatItemInfos = hotseatItemInfos;
mDeferredUpdates = () ->
commitHotseatItemUpdates(finalHotseatItemInfos, runningPackages);
commitHotseatItemUpdates(finalHotseatItemInfos, runningPackages,
minimizedPackages);
} else {
commitHotseatItemUpdates(hotseatItemInfos, runningPackages);
commitHotseatItemUpdates(hotseatItemInfos, runningPackages, minimizedPackages);
}
}
private void commitHotseatItemUpdates(
ItemInfo[] hotseatItemInfos, Set<String> runningPackages) {
private void commitHotseatItemUpdates(ItemInfo[] hotseatItemInfos, Set<String> runningPackages,
Set<String> minimizedPackages) {
mContainer.updateHotseatItems(hotseatItemInfos);
mControllers.taskbarViewController.updateIconViewsRunningStates(runningPackages);
mControllers.taskbarViewController.updateIconViewsRunningStates(runningPackages,
minimizedPackages);
}
/**
@@ -69,4 +69,9 @@ public class TaskbarRecentAppsController {
public Set<String> getRunningApps() {
return emptySet();
}
/** Returns the set of apps whose tasks are all minimized. */
public Set<String> getMinimizedApps() {
return emptySet();
}
}
@@ -510,14 +510,30 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
}
/** Updates which icons are marked as running given the Set of currently running packages. */
public void updateIconViewsRunningStates(Set<String> runningPackages) {
public void updateIconViewsRunningStates(Set<String> runningPackages,
Set<String> minimizedPackages) {
for (View iconView : getIconViews()) {
if (iconView instanceof BubbleTextView btv) {
btv.updateRunningState(runningPackages.contains(btv.getTargetPackageName()));
btv.updateRunningState(
getRunningAppState(btv.getTargetPackageName(), runningPackages,
minimizedPackages));
}
}
}
private BubbleTextView.RunningAppState getRunningAppState(
String packageName,
Set<String> runningPackages,
Set<String> minimizedPackages) {
if (minimizedPackages.contains(packageName)) {
return BubbleTextView.RunningAppState.MINIMIZED;
}
if (runningPackages.contains(packageName)) {
return BubbleTextView.RunningAppState.RUNNING;
}
return BubbleTextView.RunningAppState.NOT_RUNNING;
}
/**
* Defers any updates to the UI for the setup wizard animation.
*/
@@ -31,6 +31,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.util.SparseBooleanArray;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.util.LooperExecutor;
@@ -44,6 +45,7 @@ import com.android.wm.shell.util.GroupedRecentTaskInfo;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -137,7 +139,7 @@ public class RecentTasksList {
* @return The change id of the current task list
*/
public synchronized int getTasks(boolean loadKeysOnly,
Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter) {
@Nullable Consumer<List<GroupTask>> callback, Predicate<GroupTask> filter) {
final int requestLoadId = mChangeId;
if (mResultsUi.isValidForRequest(requestLoadId, loadKeysOnly)) {
// The list is up to date, send the callback on the next frame,
@@ -33,6 +33,7 @@ import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.icons.IconProvider;
@@ -40,6 +41,7 @@ import com.android.launcher3.icons.IconProvider.IconChangeListener;
import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SafeCloseable;
import com.android.quickstep.recents.data.RecentTasksDataSource;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.TaskVisualsChangeListener;
import com.android.systemui.shared.recents.model.Task;
@@ -60,8 +62,8 @@ import java.util.function.Predicate;
* Singleton class to load and manage recents model.
*/
@TargetApi(Build.VERSION_CODES.O)
public class RecentsModel implements IconChangeListener, TaskStackChangeListener,
TaskVisualsChangeListener, SafeCloseable {
public class RecentsModel implements RecentTasksDataSource, IconChangeListener,
TaskStackChangeListener, TaskVisualsChangeListener, SafeCloseable {
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
@@ -141,7 +143,8 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener
* always called on the UI thread.
* @return the request id associated with this call.
*/
public int getTasks(Consumer<ArrayList<GroupTask>> callback) {
@Override
public int getTasks(@Nullable Consumer<List<GroupTask>> callback) {
return mTaskList.getTasks(false /* loadKeysOnly */, callback,
RecentsFilterState.DEFAULT_FILTER);
}
@@ -155,7 +158,7 @@ public class RecentsModel implements IconChangeListener, TaskStackChangeListener
* callback.
* @return the request id associated with this call.
*/
public int getTasks(Consumer<ArrayList<GroupTask>> callback, Predicate<GroupTask> filter) {
public int getTasks(@Nullable Consumer<List<GroupTask>> callback, Predicate<GroupTask> filter) {
return mTaskList.getTasks(false /* loadKeysOnly */, callback, filter);
}
@@ -21,11 +21,13 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.content.Context;
import android.content.res.Resources;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource;
import com.android.quickstep.util.TaskKeyByLastActiveTimeCache;
import com.android.quickstep.util.TaskKeyCache;
import com.android.quickstep.util.TaskKeyLruCache;
@@ -38,7 +40,7 @@ import java.util.ArrayList;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
public class TaskThumbnailCache {
public class TaskThumbnailCache implements TaskThumbnailDataSource {
private final Executor mBgExecutor;
private final TaskKeyCache<ThumbnailData> mCache;
@@ -148,8 +150,9 @@ public class TaskThumbnailCache {
* @param callback The callback to receive the task after its data has been populated.
* @return A cancelable handle to the request
*/
@Override
public CancellableTask<ThumbnailData> updateThumbnailInBackground(
Task task, Consumer<ThumbnailData> callback) {
Task task, @NonNull Consumer<ThumbnailData> callback) {
Preconditions.assertUIThread();
boolean lowResolution = !mHighResLoadingState.isEnabled();
@@ -54,6 +54,7 @@ import com.android.systemui.shared.recents.model.Task;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsState>
implements StateListener<RecentsState> {
@@ -179,7 +180,7 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsSta
}
@Override
protected void applyLoadPlan(ArrayList<GroupTask> taskGroups) {
protected void applyLoadPlan(List<GroupTask> taskGroups) {
// When quick-switching on 3p-launcher, we add a "stub" tile corresponding to Launcher
// as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
// track the index of the next task appropriately, as if we are switching on any other app.
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.recents.data
import com.android.quickstep.util.GroupTask
import java.util.function.Consumer
interface RecentTasksDataSource {
fun getTasks(callback: Consumer<List<GroupTask>>?): Int
}
@@ -0,0 +1,114 @@
/*
* 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 com.android.quickstep.TaskIconCache
import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource
import com.android.quickstep.util.GroupTask
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
import kotlin.coroutines.resume
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.suspendCancellableCoroutine
@OptIn(ExperimentalCoroutinesApi::class)
class TasksRepository(
private val recentsModel: RecentTasksDataSource,
private val taskThumbnailDataSource: TaskThumbnailDataSource,
private val taskIconCache: TaskIconCache,
) {
private val groupedTaskData = MutableStateFlow(emptyList<GroupTask>())
private val _taskData =
groupedTaskData.map { groupTaskList -> groupTaskList.flatMap { it.tasks } }
private val visibleTaskIds = MutableStateFlow(emptySet<Int>())
private val taskData: Flow<List<Task>> =
combine(_taskData, getThumbnailQueryResults()) { tasks, results ->
tasks.forEach { task ->
// Add retrieved thumbnails + remove unnecessary thumbnails
task.thumbnail = results[task.key.id]
}
tasks
}
fun getAllTaskData(forceRefresh: Boolean = false): Flow<List<Task>> {
if (forceRefresh) {
recentsModel.getTasks { groupedTaskData.value = it }
}
return taskData
}
fun getTaskDataById(taskId: Int): Flow<Task?> =
taskData.map { taskList -> taskList.firstOrNull { it.key.id == taskId } }
fun setVisibleTasks(visibleTaskIdList: List<Int>) {
this.visibleTaskIds.value = visibleTaskIdList.toSet()
}
/** Flow wrapper for [TaskThumbnailDataSource.updateThumbnailInBackground] api */
private fun getThumbnailDataRequest(task: Task): ThumbnailDataRequest =
flow {
emit(task.key.id to task.thumbnail)
val thumbnailDataResult: ThumbnailData? =
suspendCancellableCoroutine { continuation ->
val cancellableTask =
taskThumbnailDataSource.updateThumbnailInBackground(task) {
continuation.resume(it)
}
continuation.invokeOnCancellation { cancellableTask?.cancel() }
}
emit(task.key.id to thumbnailDataResult)
}
.distinctUntilChanged()
/**
* This is a Flow that makes a query for thumbnail data to the [taskThumbnailDataSource] for
* each visible task. It then collects the responses and returns them in a Map as soon as they
* are available.
*/
private fun getThumbnailQueryResults(): Flow<Map<Int, ThumbnailData?>> {
val visibleTasks =
combine(_taskData, visibleTaskIds) { tasks, visibleIds ->
tasks.filter { it.key.id in visibleIds }
}
val visibleThumbnailDataRequests: Flow<List<ThumbnailDataRequest>> =
visibleTasks.map {
it.map { visibleTask ->
val taskCopy = Task(visibleTask).apply { thumbnail = visibleTask.thumbnail }
getThumbnailDataRequest(taskCopy)
}
}
return visibleThumbnailDataRequests.flatMapLatest {
thumbnailRequestFlows: List<ThumbnailDataRequest> ->
if (thumbnailRequestFlows.isEmpty()) {
flowOf(emptyMap())
} else {
combine(thumbnailRequestFlows) { it.toMap() }
}
}
}
}
typealias ThumbnailDataRequest = Flow<Pair<Int, ThumbnailData?>>
@@ -0,0 +1,29 @@
/*
* 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.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
import java.util.function.Consumer
interface TaskThumbnailDataSource {
fun updateThumbnailInBackground(
task: Task,
callback: Consumer<ThumbnailData>
): CancellableTask<ThumbnailData>?
}
@@ -21,7 +21,7 @@ import androidx.annotation.NonNull;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import java.util.ArrayList;
import java.util.List;
/**
* A {@link Task} container that can contain N number of tasks that are part of the desktop in
@@ -30,9 +30,9 @@ import java.util.ArrayList;
public class DesktopTask extends GroupTask {
@NonNull
public final ArrayList<Task> tasks;
public final List<Task> tasks;
public DesktopTask(@NonNull ArrayList<Task> tasks) {
public DesktopTask(@NonNull List<Task> tasks) {
super(tasks.get(0), null, null, TaskView.Type.DESKTOP);
this.tasks = tasks;
}
@@ -52,6 +52,12 @@ public class DesktopTask extends GroupTask {
return true;
}
@Override
@NonNull
public List<Task> getTasks() {
return tasks;
}
@Override
public DesktopTask copy() {
return new DesktopTask(tasks);
@@ -23,6 +23,10 @@ import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* A {@link Task} container that can contain one or two tasks, depending on if the two tasks
* are represented as an app-pair in the recents task list.
@@ -61,6 +65,17 @@ public class GroupTask {
return task2 != null;
}
/**
* Returns a List of all the Tasks in this GroupTask
*/
public List<Task> getTasks() {
if (task2 == null) {
return Collections.singletonList(task1);
} else {
return Arrays.asList(task1, task2);
}
}
/**
* Create a copy of this instance
*/
@@ -1141,6 +1141,7 @@ public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewCo
if (FeatureFlags.enableSplitContextually()) {
mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener);
}
reset();
}
@Override
@@ -1715,7 +1716,7 @@ public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewCo
return super.isPageScrollsInitialized() && mLoadPlanEverApplied;
}
protected void applyLoadPlan(ArrayList<GroupTask> taskGroups) {
protected void applyLoadPlan(List<GroupTask> taskGroups) {
if (mPendingAnimation != null) {
mPendingAnimation.addEndListener(success -> applyLoadPlan(taskGroups));
return;
@@ -0,0 +1,33 @@
/*
* 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 com.android.quickstep.util.GroupTask
import java.util.function.Consumer
class FakeRecentTasksDataSource : RecentTasksDataSource {
var taskList: List<GroupTask> = listOf()
override fun getTasks(callback: Consumer<List<GroupTask>>?): Int {
callback?.accept(taskList)
return 0
}
fun seedTasks(tasks: List<GroupTask>) {
taskList = tasks
}
}
@@ -0,0 +1,52 @@
/*
* 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.Bitmap
import com.android.launcher3.util.CancellableTask
import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
import java.util.function.Consumer
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
class FakeTaskThumbnailDataSource : TaskThumbnailDataSource {
val taskIdToBitmap: Map<Int, Bitmap> = (0..10).associateWith { mock() }
val taskIdToUpdatingTask: MutableMap<Int, () -> Unit> = mutableMapOf()
var shouldLoadSynchronously: Boolean = true
/** Retrieves and sets a thumbnail on [task] from [taskIdToBitmap]. */
override fun updateThumbnailInBackground(
task: Task,
callback: Consumer<ThumbnailData>
): CancellableTask<ThumbnailData>? {
val thumbnailData = mock<ThumbnailData>()
whenever(thumbnailData.thumbnail).thenReturn(taskIdToBitmap[task.key.id])
val wrappedCallback = {
task.thumbnail = thumbnailData
callback.accept(thumbnailData)
}
if (shouldLoadSynchronously) {
wrappedCallback()
} else {
taskIdToUpdatingTask[task.key.id] = wrappedCallback
}
return null
}
}
@@ -0,0 +1,150 @@
/*
* 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.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
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.toList
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 {
private val tasks = (0..5).map(::createTaskWithId)
private val defaultTaskList =
listOf(
GroupTask(tasks[0]),
GroupTask(tasks[1], tasks[2], null),
DesktopTask(tasks.subList(3, 6))
)
private val recentsModel = FakeRecentTasksDataSource()
private val taskThumbnailDataSource = FakeTaskThumbnailDataSource()
private val taskIconCache = mock<TaskIconCache>()
private val systemUnderTest =
TasksRepository(recentsModel, taskThumbnailDataSource, taskIconCache)
@Test
fun getAllTaskDataReturnsFlattenedListOfTasks() = runTest {
recentsModel.seedTasks(defaultTaskList)
assertThat(systemUnderTest.getAllTaskData(forceRefresh = true).first()).isEqualTo(tasks)
}
@Test
fun getTaskDataByIdReturnsSpecificTask() = runTest {
recentsModel.seedTasks(defaultTaskList)
systemUnderTest.getAllTaskData(forceRefresh = true)
assertThat(systemUnderTest.getTaskDataById(2).first()).isEqualTo(tasks[2])
}
@Test
fun setVisibleTasksPopulatesThumbnails() = runTest {
recentsModel.seedTasks(defaultTaskList)
val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
systemUnderTest.getAllTaskData(forceRefresh = true)
systemUnderTest.setVisibleTasks(listOf(1, 2))
// .drop(1) to ignore initial null content before from thumbnail was loaded.
assertThat(systemUnderTest.getTaskDataById(1).drop(1).first()!!.thumbnail!!.thumbnail)
.isEqualTo(bitmap1)
assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail)
.isEqualTo(bitmap2)
}
@Test
fun changingVisibleTasksContainsAlreadyPopulatedThumbnails() = runTest {
recentsModel.seedTasks(defaultTaskList)
val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
systemUnderTest.getAllTaskData(forceRefresh = true)
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)
// Prevent new loading of Bitmaps
taskThumbnailDataSource.shouldLoadSynchronously = false
systemUnderTest.setVisibleTasks(listOf(2, 3))
assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail)
.isEqualTo(bitmap2)
}
@Test
fun retrievedThumbnailsAreDiscardedWhenTaskBecomesInvisible() = runTest {
recentsModel.seedTasks(defaultTaskList)
val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
systemUnderTest.getAllTaskData(forceRefresh = true)
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)
// Prevent new loading of Bitmaps
taskThumbnailDataSource.shouldLoadSynchronously = false
systemUnderTest.setVisibleTasks(listOf(0, 1))
assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail).isNull()
}
@Test
fun retrievedThumbnailsCauseEmissionOnTaskDataFlow() = runTest {
// Setup fakes
recentsModel.seedTasks(defaultTaskList)
val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
taskThumbnailDataSource.shouldLoadSynchronously = false
// Setup TasksRepository
systemUnderTest.getAllTaskData(forceRefresh = true)
systemUnderTest.setVisibleTasks(listOf(1, 2))
// Assert there is no bitmap in first emission
val taskFlow = systemUnderTest.getTaskDataById(2)
val taskFlowValuesList = mutableListOf<Task?>()
backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
taskFlow.toList(taskFlowValuesList)
}
assertThat(taskFlowValuesList[0]!!.thumbnail).isNull()
// Simulate bitmap loading after first emission
taskThumbnailDataSource.taskIdToUpdatingTask.getValue(2).invoke()
// Check for second emission
assertThat(taskFlowValuesList[1]!!.thumbnail!!.thumbnail).isEqualTo(bitmap2)
}
private fun createTaskWithId(taskId: Int) =
Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000))
}
@@ -31,7 +31,6 @@ import com.android.launcher3.logging.StatsLogManager.StatsLogger
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.statehandlers.DepthController
import com.android.launcher3.statemanager.StateManager
import com.android.launcher3.statemanager.StatefulActivity
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.quickstep.RecentsModel
@@ -121,7 +120,7 @@ class SplitSelectStateControllerTest {
// Capture callback from recentsModel#getTasks()
val consumer =
argumentCaptor<Consumer<ArrayList<GroupTask>>> {
argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonMatchingComponent),
false /* findExactPairMatch */,
@@ -174,7 +173,7 @@ class SplitSelectStateControllerTest {
// Capture callback from recentsModel#getTasks()
val consumer =
argumentCaptor<Consumer<ArrayList<GroupTask>>> {
argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent),
false /* findExactPairMatch */,
@@ -215,7 +214,7 @@ class SplitSelectStateControllerTest {
// Capture callback from recentsModel#getTasks()
val consumer =
argumentCaptor<Consumer<ArrayList<GroupTask>>> {
argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonPrimaryUserComponent),
false /* findExactPairMatch */,
@@ -271,7 +270,7 @@ class SplitSelectStateControllerTest {
// Capture callback from recentsModel#getTasks()
val consumer =
argumentCaptor<Consumer<ArrayList<GroupTask>>> {
argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonPrimaryUserComponent),
false /* findExactPairMatch */,
@@ -324,7 +323,7 @@ class SplitSelectStateControllerTest {
// Capture callback from recentsModel#getTasks()
val consumer =
argumentCaptor<Consumer<ArrayList<GroupTask>>> {
argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent),
false /* findExactPairMatch */,
@@ -378,7 +377,7 @@ class SplitSelectStateControllerTest {
// Capture callback from recentsModel#getTasks()
val consumer =
argumentCaptor<Consumer<ArrayList<GroupTask>>> {
argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(nonMatchingComponent, matchingComponent),
false /* findExactPairMatch */,
@@ -431,7 +430,7 @@ class SplitSelectStateControllerTest {
// Capture callback from recentsModel#getTasks()
val consumer =
argumentCaptor<Consumer<ArrayList<GroupTask>>> {
argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent, matchingComponent),
false /* findExactPairMatch */,
@@ -497,7 +496,7 @@ class SplitSelectStateControllerTest {
// Capture callback from recentsModel#getTasks()
val consumer =
argumentCaptor<Consumer<ArrayList<GroupTask>>> {
argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent, matchingComponent),
false /* findExactPairMatch */,
@@ -549,7 +548,7 @@ class SplitSelectStateControllerTest {
// Capture callback from recentsModel#getTasks()
val consumer =
argumentCaptor<Consumer<ArrayList<GroupTask>>> {
argumentCaptor<Consumer<List<GroupTask>>> {
splitSelectStateController.findLastActiveTasksAndRunCallback(
listOf(matchingComponent2, matchingComponent),
true /* findExactPairMatch */,
@@ -1 +0,0 @@
./multivalentTests
@@ -86,7 +86,8 @@ class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() {
val newHotseatItems =
taskbarRunningAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
assertThat(newHotseatItems.map { it?.targetPackage }).isEqualTo(hotseatPackages)
assertThat(newHotseatItems.map { it?.targetPackage })
.containsExactlyElementsIn(hotseatPackages)
}
@Test
@@ -119,7 +120,8 @@ class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() {
RUNNING_APP_PACKAGE_1,
RUNNING_APP_PACKAGE_2,
)
assertThat(newHotseatItems.map { it?.targetPackage }).isEqualTo(expectedPackages)
assertThat(newHotseatItems.map { it?.targetPackage })
.containsExactlyElementsIn(expectedPackages)
}
@Test
@@ -144,7 +146,8 @@ class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() {
RUNNING_APP_PACKAGE_1,
RUNNING_APP_PACKAGE_2,
)
assertThat(newHotseatItems.map { it?.targetPackage }).isEqualTo(expectedPackages)
assertThat(newHotseatItems.map { it?.targetPackage })
.containsExactlyElementsIn(expectedPackages)
}
@Test
@@ -155,7 +158,8 @@ class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() {
whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
taskbarRunningAppsController.updateRunningApps()
assertThat(taskbarRunningAppsController.runningApps).isEqualTo(emptySet<String>())
assertThat(taskbarRunningAppsController.runningApps).isEmpty()
assertThat(taskbarRunningAppsController.minimizedApps).isEmpty()
}
@Test
@@ -167,7 +171,28 @@ class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() {
taskbarRunningAppsController.updateRunningApps()
assertThat(taskbarRunningAppsController.runningApps)
.isEqualTo(setOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
.containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
assertThat(taskbarRunningAppsController.minimizedApps).isEmpty()
}
@Test
fun getMinimizedApps_inDesktopMode_returnsAllAppsRunningAndInvisibleAppsMinimized() {
setInDesktopMode(true)
val runningTasks =
ArrayList(
listOf(
createDesktopTaskInfo(RUNNING_APP_PACKAGE_1) { isVisible = true },
createDesktopTaskInfo(RUNNING_APP_PACKAGE_2) { isVisible = true },
createDesktopTaskInfo(RUNNING_APP_PACKAGE_3) { isVisible = false },
)
)
whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
taskbarRunningAppsController.updateRunningApps()
assertThat(taskbarRunningAppsController.runningApps)
.containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3)
assertThat(taskbarRunningAppsController.minimizedApps)
.containsExactly(RUNNING_APP_PACKAGE_3)
}
private fun createHotseatItemsFromPackageNames(packageNames: List<String>): List<ItemInfo> {
@@ -180,11 +205,15 @@ class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() {
return ArrayList(packageNames.map { createDesktopTaskInfo(packageName = it) })
}
private fun createDesktopTaskInfo(packageName: String): RunningTaskInfo {
private fun createDesktopTaskInfo(
packageName: String,
init: RunningTaskInfo.() -> Unit = { isVisible = true },
): RunningTaskInfo {
return RunningTaskInfo().apply {
taskId = nextTaskId++
configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
realActivity = ComponentName(packageName, "TestActivity")
init()
}
}
+3
View File
@@ -416,6 +416,9 @@
<dimen name="taskbar_running_app_indicator_height">0dp</dimen>
<dimen name="taskbar_running_app_indicator_width">0dp</dimen>
<dimen name="taskbar_running_app_indicator_top_margin">0dp</dimen>
<dimen name="taskbar_minimized_app_indicator_height">0dp</dimen>
<dimen name="taskbar_minimized_app_indicator_width">0dp</dimen>
<dimen name="taskbar_minimized_app_indicator_top_margin">0dp</dimen>
<!-- Transient taskbar (placeholders to compile in Launcher3 without Quickstep) -->
<dimen name="transient_taskbar_padding">0dp</dimen>
+31 -11
View File
@@ -186,9 +186,20 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
// These fields, related to showing running apps, are only used for Taskbar.
private final Size mRunningAppIndicatorSize;
private final int mRunningAppIndicatorTopMargin;
private final Size mMinimizedAppIndicatorSize;
private final int mMinimizedAppIndicatorTopMargin;
private final Paint mRunningAppIndicatorPaint;
private final Rect mRunningAppIconBounds = new Rect();
private boolean mIsRunning;
private RunningAppState mRunningAppState;
/**
* Various options for the running state of an app.
*/
public enum RunningAppState {
NOT_RUNNING,
RUNNING,
MINIMIZED,
}
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mStayPressed;
@@ -259,9 +270,16 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
mRunningAppIndicatorSize = new Size(
getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_width),
getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_height));
mMinimizedAppIndicatorSize = new Size(
getResources().getDimensionPixelSize(R.dimen.taskbar_minimized_app_indicator_width),
getResources().getDimensionPixelSize(
R.dimen.taskbar_minimized_app_indicator_height));
mRunningAppIndicatorTopMargin =
getResources().getDimensionPixelSize(
R.dimen.taskbar_running_app_indicator_top_margin);
mMinimizedAppIndicatorTopMargin =
getResources().getDimensionPixelSize(
R.dimen.taskbar_minimized_app_indicator_top_margin);
mRunningAppIndicatorPaint = new Paint();
mRunningAppIndicatorPaint.setColor(getResources().getColor(
R.color.taskbar_running_app_indicator_color, context.getTheme()));
@@ -414,8 +432,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
/** Updates whether the app this view represents is currently running. */
@UiThread
public void updateRunningState(boolean isRunning) {
mIsRunning = isRunning;
public void updateRunningState(RunningAppState runningAppState) {
mRunningAppState = runningAppState;
}
protected void setItemInfo(ItemInfoWithIcon itemInfo) {
@@ -667,18 +685,20 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
/** Draws a line under the app icon if this is representing a running app in Desktop Mode. */
protected void drawRunningAppIndicatorIfNecessary(Canvas canvas) {
if (!mIsRunning || mDisplay != DISPLAY_TASKBAR) {
if (mRunningAppState == RunningAppState.NOT_RUNNING || mDisplay != DISPLAY_TASKBAR) {
return;
}
getIconBounds(mRunningAppIconBounds);
// TODO(b/333872717): update color, shape, and size of indicator
int indicatorTop = mRunningAppIconBounds.bottom + mRunningAppIndicatorTopMargin;
canvas.drawRect(
mRunningAppIconBounds.centerX() - mRunningAppIndicatorSize.getWidth() / 2,
indicatorTop,
mRunningAppIconBounds.centerX() + mRunningAppIndicatorSize.getWidth() / 2,
indicatorTop + mRunningAppIndicatorSize.getHeight(),
mRunningAppIndicatorPaint);
boolean isMinimized = mRunningAppState == RunningAppState.MINIMIZED;
int indicatorTop =
mRunningAppIconBounds.bottom + (isMinimized ? mMinimizedAppIndicatorTopMargin
: mRunningAppIndicatorTopMargin);
final Size indicatorSize =
isMinimized ? mMinimizedAppIndicatorSize : mRunningAppIndicatorSize;
canvas.drawRect(mRunningAppIconBounds.centerX() - indicatorSize.getWidth() / 2,
indicatorTop, mRunningAppIconBounds.centerX() + indicatorSize.getWidth() / 2,
indicatorTop + indicatorSize.getHeight(), mRunningAppIndicatorPaint);
}
@Override
+2 -1
View File
@@ -32,6 +32,7 @@ import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTO
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
import static com.android.launcher3.testing.shared.ResourceUtils.roundPxValueFromFloat;
import static com.android.wm.shell.Flags.enableTinyTaskbar;
import android.annotation.SuppressLint;
import android.content.Context;
@@ -353,7 +354,7 @@ public class DeviceProfile {
isTablet = info.isTablet(windowBounds);
isPhone = !isTablet;
isTwoPanels = isTablet && isMultiDisplay;
isTaskbarPresent = isTablet
isTaskbarPresent = (isTablet || (enableTinyTaskbar() && isGestureMode))
&& WindowManagerProxy.INSTANCE.get(context).isTaskbarDrawnInProcess();
// Some more constants.
@@ -109,6 +109,13 @@ public class RecyclerViewFastScroller extends View {
private float mLastTouchY;
private boolean mIsDragging;
/**
* Tracks whether a keyboard hide request has been sent due to downward scrolling.
* <p>
* Set to true when scrolling down and reset when scrolling up to prevents redundant hide
* requests during continuous downward scrolls.
*/
private boolean mRequestedHideKeyboard;
private boolean mIsThumbDetached;
private final boolean mCanThumbDetach;
private boolean mIgnoreDragGesture;
@@ -241,6 +248,7 @@ public class RecyclerViewFastScroller extends View {
public boolean handleTouchEvent(MotionEvent ev, Point offset) {
int x = (int) ev.getX() - offset.x;
int y = (int) ev.getY() - offset.y;
ActivityContext activityContext = ActivityContext.lookupContext(getContext());
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
@@ -248,6 +256,7 @@ public class RecyclerViewFastScroller extends View {
mDownX = x;
mDownY = mLastY = y;
mDownTimeStampMillis = ev.getDownTime();
mRequestedHideKeyboard = false;
if ((Math.abs(mDy) < mDeltaThreshold &&
mRv.getScrollState() != SCROLL_STATE_IDLE)) {
@@ -260,6 +269,15 @@ public class RecyclerViewFastScroller extends View {
}
break;
case MotionEvent.ACTION_MOVE:
if (y > mLastY) {
if (!mRequestedHideKeyboard) {
activityContext.hideKeyboard();
}
mRequestedHideKeyboard = true;
} else {
mRequestedHideKeyboard = false;
}
mLastY = y;
int absDeltaY = Math.abs(y - mDownY);
int absDeltaX = Math.abs(x - mDownX);
@@ -294,7 +312,6 @@ public class RecyclerViewFastScroller extends View {
}
private void calcTouchOffsetAndPrepToFastScroll(int downY, int lastY) {
ActivityContext.lookupContext(getContext()).hideKeyboard();
mIsDragging = true;
if (mCanThumbDetach) {
mIsThumbDetached = true;
-1
View File
@@ -1 +0,0 @@
multivalentTests