Snap for 11889377 from 92b73a4f09 to 24Q3-release
Change-Id: I012fe5c7ede6a9c3ee72f46a7836d2cc738c08cd
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
+41
-13
@@ -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;
|
||||
|
||||
+33
@@ -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
|
||||
}
|
||||
}
|
||||
+52
@@ -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
|
||||
}
|
||||
}
|
||||
+150
@@ -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))
|
||||
}
|
||||
+9
-10
@@ -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
|
||||
+35
-6
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
multivalentTests
|
||||
Reference in New Issue
Block a user