Merge "Stop mouse long press from opening workspace menu" into main
diff --git a/Android.bp b/Android.bp
index 5b986ab..1e1e0ad 100644
--- a/Android.bp
+++ b/Android.bp
@@ -452,6 +452,7 @@
"AndroidManifest-common.xml",
],
lint: {
+ extra_check_modules: ["Launcher3LintChecker"],
baseline_filename: "lint-baseline.xml",
},
}
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 5186740..72b3c27 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -65,13 +65,6 @@
}
flag {
- name: "enable_taskbar_connected_displays"
- namespace: "launcher"
- description: "Enables connected displays in taskbar."
- bug: "362720616"
-}
-
-flag {
name: "enable_taskbar_customization"
namespace: "launcher"
description: "Enables taskbar customization framework."
diff --git a/checks/Android.bp b/checks/Android.bp
new file mode 100644
index 0000000..dfd701e
--- /dev/null
+++ b/checks/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library_host {
+ name: "Launcher3LintChecker",
+ srcs: ["src/**/*.kt"],
+ plugins: ["auto_service_plugin"],
+ libs: [
+ "auto_service_annotations",
+ "lint_api",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
+
+java_test_host {
+ name: "Launcher3LintCheckerTest",
+ defaults: ["AndroidLintCheckerTestDefaults"],
+ srcs: ["tests/**/*.kt"],
+ data: [
+ ":androidx.annotation_annotation",
+ ":dagger2",
+ ":kotlinx-coroutines-core",
+ ],
+ device_common_data: [
+ ":framework",
+ ],
+ static_libs: [
+ "Launcher3LintChecker",
+ ],
+}
diff --git a/checks/src/com/android/internal/launcher3/lint/CustomDialogDetector.kt b/checks/src/com/android/internal/launcher3/lint/CustomDialogDetector.kt
new file mode 100644
index 0000000..37358bb
--- /dev/null
+++ b/checks/src/com/android/internal/launcher3/lint/CustomDialogDetector.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UClass
+
+/** Detector to identify custom usage of Android's Dialog within the Launcher3 codebase. */
+class CustomDialogDetector : Detector(), SourceCodeScanner {
+
+ override fun applicableSuperClasses(): List<String> {
+ return listOf(DIALOG_CLASS_NAME)
+ }
+
+ override fun visitClass(context: JavaContext, declaration: UClass) {
+ val superTypeClassNames = declaration.superTypes.mapNotNull { it.resolve()?.qualifiedName }
+ if (superTypeClassNames.contains(DIALOG_CLASS_NAME)) {
+ context.report(
+ ISSUE,
+ declaration,
+ context.getNameLocation(declaration),
+ "Class implements Dialog",
+ )
+ }
+ }
+
+ companion object {
+ private const val DIALOG_CLASS_NAME = "android.app.Dialog"
+
+ @JvmField
+ val ISSUE =
+ Issue.create(
+ id = "IllegalUseOfCustomDialog",
+ briefDescription = "dialogs should not be used in Launcher",
+ explanation =
+ """
+ Don't use custom Dialogs within the launcher code base, instead consider utilizing
+ AbstractFloatingView to display content that should float above the launcher where
+ it can be correctly managed for dismissal.
+ """
+ .trimIndent(),
+ category = Category.CORRECTNESS,
+ priority = 10,
+ severity = Severity.ERROR,
+ implementation =
+ Implementation(CustomDialogDetector::class.java, Scope.JAVA_FILE_SCOPE),
+ )
+ }
+}
diff --git a/checks/src/com/android/internal/launcher3/lint/Launcher3IssueRegistry.kt b/checks/src/com/android/internal/launcher3/lint/Launcher3IssueRegistry.kt
new file mode 100644
index 0000000..c77c42b
--- /dev/null
+++ b/checks/src/com/android/internal/launcher3/lint/Launcher3IssueRegistry.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.launcher3.lint
+
+import CustomDialogDetector
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.android.tools.lint.detector.api.Issue
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+@Suppress("UnstableApiUsage")
+class Launcher3IssueRegistry : IssueRegistry() {
+ override val issues: List<Issue>
+ get() = listOf(CustomDialogDetector.ISSUE)
+
+ override val api: Int
+ get() = CURRENT_API
+
+ override val minApi: Int
+ get() = 8
+
+ override val vendor: Vendor =
+ Vendor(
+ vendorName = "Android",
+ feedbackUrl = "http://b/issues/new?component=78010",
+ contact = "abegovic@google.com",
+ )
+}
diff --git a/checks/tests/com/android/internal/launcher3/lint/CustomDialogDetectorTest.kt b/checks/tests/com/android/internal/launcher3/lint/CustomDialogDetectorTest.kt
new file mode 100644
index 0000000..2a37953
--- /dev/null
+++ b/checks/tests/com/android/internal/launcher3/lint/CustomDialogDetectorTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.launcher3.lint
+
+import CustomDialogDetector
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+/** Test for [CustomDialogDetector]. */
+class CustomDialogDetectorTest : Launcher3LintDetectorTest() {
+ override fun getDetector(): Detector = CustomDialogDetector()
+
+ override fun getIssues(): List<Issue> = listOf(CustomDialogDetector.ISSUE)
+
+ @Test
+ fun classDoesNotExtendDialog_noViolation() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package test.pkg
+
+ class SomeClass
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ )
+ .issues(CustomDialogDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun classDoesExtendDialog_violation() {
+ lint()
+ .files(
+ TestFiles.kotlin(
+ """
+ package test.pkg
+
+ import android.app.Dialog
+
+ class SomeClass(context: Context) : Dialog(context)
+ """
+ .trimIndent()
+ ),
+ *androidStubs,
+ )
+ .issues(CustomDialogDetector.ISSUE)
+ .run()
+ .expect(
+ ("""
+ src/test/pkg/SomeClass.kt:5: Error: Class implements Dialog [IllegalUseOfCustomDialog]
+ class SomeClass(context: Context) : Dialog(context)
+ ~~~~~~~~~
+ 1 errors, 0 warnings
+ """)
+ .trimIndent()
+ )
+ }
+}
diff --git a/checks/tests/com/android/internal/launcher3/lint/Launcher3LintDetectorTest.kt b/checks/tests/com/android/internal/launcher3/lint/Launcher3LintDetectorTest.kt
new file mode 100644
index 0000000..09085c7
--- /dev/null
+++ b/checks/tests/com/android/internal/launcher3/lint/Launcher3LintDetectorTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.launcher3.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import java.io.File
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/**
+ * Abstract class that should be used by any test for launcher 3 lint detectors.
+ *
+ * When you write your test, ensure that you pass [androidStubs] as part of your [TestFiles]
+ * definition.
+ */
+@RunWith(JUnit4::class)
+abstract class Launcher3LintDetectorTest : LintDetectorTest() {
+
+ /**
+ * Customize the lint task to disable SDK usage completely. This ensures that running the tests
+ * in Android Studio has the same result as running the tests in atest
+ */
+ override fun lint(): TestLintTask =
+ super.lint().allowMissingSdk(true).sdkHome(File("/dev/null"))
+
+ companion object {
+ private val libraryNames =
+ arrayOf(
+ "androidx.annotation_annotation.jar",
+ "dagger2.jar",
+ "framework.jar",
+ "kotlinx-coroutines-core.jar",
+ )
+
+ /**
+ * This file contains stubs of framework APIs and System UI classes for testing purposes
+ * only. The stubs are not used in the lint detectors themselves.
+ */
+ val androidStubs =
+ libraryNames
+ .map { TestFiles.LibraryReferenceTestFile(File(it).canonicalFile) }
+ .toTypedArray()
+ }
+}
diff --git a/quickstep/res/drawable/ic_close_option.xml b/quickstep/res/drawable/ic_close_option.xml
new file mode 100644
index 0000000..5681cb5
--- /dev/null
+++ b/quickstep/res/drawable/ic_close_option.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z" />
+</vector>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index d2a7029..8e70a2b 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -30,6 +30,8 @@
<string name="recent_task_option_desktop">Desktop</string>
<!-- Title and content description for an option to move app to external display. -->
<string name="recent_task_option_external_display">Move to external display</string>
+ <!-- Title and content description for an option to close the app [CHAR LIMIT=30] -->
+ <string name="recent_task_option_close">Close</string>
<!-- Title and content description for Desktop tile in Recents screen that contains apps opened inside desktop windowing mode [CHAR LIMIT=NONE] -->
<string name="recent_task_desktop">Desktop</string>
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 3736e6d..23065b5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -233,7 +233,8 @@
}
private boolean shouldExcludeTask(GroupTask task, Set<Integer> taskIdsToExclude) {
- return Flags.taskbarOverflow() && taskIdsToExclude.contains(task.task1.key.id);
+ return Flags.taskbarOverflow() && task.getTasks().stream().anyMatch(
+ t -> taskIdsToExclude.contains(t.key.id));
}
private void processLoadedTasks(List<GroupTask> tasks, Set<Integer> taskIdsToExclude) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 306443e..4581119 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -51,6 +51,9 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import java.util.HashMap;
@@ -255,17 +258,24 @@
layoutInflater,
previousTaskView);
- final boolean firstTaskIsLeftTopTask =
- groupTask.mSplitBounds == null
- || groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id
- || groupTask.task2 == null;
+ Task task1;
+ Task task2;
+ if (groupTask instanceof SplitTask splitTask) {
+ task1 = splitTask.getTopLeftTask();
+ task2 = splitTask.getBottomRightTask();
+ } else if (groupTask instanceof SingleTask singleTask) {
+ task1 = singleTask.getTask();
+ task2 = null;
+ } else {
+ continue;
+ }
currentTaskView.setThumbnailsForSplitTasks(
- firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2,
- firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1,
+ task1,
+ task2,
updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
updateTasks ? mViewCallbacks::updateIconInBackground : null,
- groupTask.mSplitBounds);
+ groupTask instanceof SplitTask splitTask ? splitTask.getSplitBounds() : null);
previousTaskView = currentTaskView;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 8cb43d2..5af7ff8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -44,6 +44,7 @@
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
import com.android.quickstep.util.SlideInRemoteTransition;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -281,9 +282,10 @@
return -1;
}
RemoteTransition remoteTransition = slideInTransition;
- if (mOnDesktop
- && mControllers.taskbarActivityContext.canUnminimizeDesktopTask(task.task1.key.id)
- ) {
+ boolean canUnminimizeDesktopTask = task instanceof SingleTask singleTask
+ && mControllers.taskbarActivityContext.canUnminimizeDesktopTask(
+ singleTask.getTask().key.id);
+ if (mOnDesktop && canUnminimizeDesktopTask) {
// This app is being unminimized - use our own transition runner.
remoteTransition = new RemoteTransition(
new DesktopAppLaunchTransition(
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 5a8fba6..4143157 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -49,7 +49,7 @@
import com.android.quickstep.HomeVisibilityState;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -480,8 +480,8 @@
@Override
public void launchSplitTasks(
- @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
- mLauncher.launchSplitTasks(groupTask, remoteTransition);
+ @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) {
+ mLauncher.launchSplitTasks(splitTask, remoteTransition);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 8a06b11..3963d40 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -156,6 +156,8 @@
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -1294,11 +1296,13 @@
mControllers.keyboardQuickSwitchController.closeQuickSwitchView(false);
- if (tag instanceof GroupTask groupTask) {
+ // TODO: b/316004172, b/343289567: Handle `DesktopTask` and `SplitTask`.
+ if (tag instanceof SingleTask singleTask) {
RemoteTransition remoteTransition =
- (areDesktopTasksVisible() && canUnminimizeDesktopTask(groupTask.task1.key.id))
+ (areDesktopTasksVisible() && canUnminimizeDesktopTask(
+ singleTask.getTask().key.id))
? createDesktopAppLaunchRemoteTransition(AppLaunchType.UNMINIMIZE,
- Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
+ Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
: null;
if (areDesktopTasksVisible() && mControllers.uiController.isInOverviewUi()) {
RunnableList runnableList = recents.launchRunningDesktopTaskView();
@@ -1306,12 +1310,12 @@
// launch.
if (runnableList != null) {
runnableList.add(() -> UI_HELPER_EXECUTOR.execute(
- () -> handleGroupTaskLaunch(groupTask, remoteTransition,
+ () -> handleGroupTaskLaunch(singleTask, remoteTransition,
areDesktopTasksVisible(),
DesktopTaskToFrontReason.TASKBAR_TAP)));
}
} else {
- handleGroupTaskLaunch(groupTask, remoteTransition, areDesktopTasksVisible(),
+ handleGroupTaskLaunch(singleTask, remoteTransition, areDesktopTasksVisible(),
DesktopTaskToFrontReason.TASKBAR_TAP);
}
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
@@ -1481,13 +1485,13 @@
remoteTransition));
return;
}
- if (onDesktop) {
- boolean useRemoteTransition = canUnminimizeDesktopTask(task.task1.key.id);
+ if (onDesktop && task instanceof SingleTask singleTask) {
+ boolean useRemoteTransition = canUnminimizeDesktopTask(singleTask.getTask().key.id);
UI_HELPER_EXECUTOR.execute(() -> {
if (onStartCallback != null) {
onStartCallback.run();
}
- SystemUiProxy.INSTANCE.get(this).showDesktopApp(task.task1.key.id,
+ SystemUiProxy.INSTANCE.get(this).showDesktopApp(singleTask.getTask().key.id,
useRemoteTransition ? remoteTransition : null, toFrontReason);
if (onFinishCallback != null) {
onFinishCallback.run();
@@ -1495,18 +1499,19 @@
});
return;
}
- if (task.task2 == null) {
+ if (task instanceof SingleTask singleTask) {
UI_HELPER_EXECUTOR.execute(() -> {
ActivityOptions activityOptions =
makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED).options;
activityOptions.setRemoteTransition(remoteTransition);
ActivityManagerWrapper.getInstance().startActivityFromRecents(
- task.task1.key, activityOptions);
+ singleTask.getTask().key, activityOptions);
});
return;
}
- mControllers.uiController.launchSplitTasks(task, remoteTransition);
+ assert task instanceof SplitTask;
+ mControllers.uiController.launchSplitTasks((SplitTask) task, remoteTransition);
}
/** Returns whether the given task is minimized and can be unminimized. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index a9e8d6d..3a83db2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -80,9 +80,9 @@
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.views.BubbleTextHolder;
-import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LogUtils;
import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.quickstep.util.SingleTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.shared.draganddrop.DragAndDropConstants;
@@ -433,8 +433,8 @@
null, item.user));
}
intent.putExtra(Intent.EXTRA_USER, item.user);
- } else if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
- Task task = groupTask.task1;
+ } else if (tag instanceof SingleTask singleTask) {
+ Task task = singleTask.getTask();
clipDescription = new ClipDescription(task.titleDescription,
new String[] {
ClipDescription.MIMETYPE_APPLICATION_TASK
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index 6047999..417ef7e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -76,12 +76,14 @@
var shownTasks: List<GroupTask> = emptyList()
private set
+ val shownTaskIds: List<Int>
+ get() = shownTasks.flatMap { shownTask -> shownTask.tasks }.map { it.key.id }
+
/**
- * The task-state of an app, i.e. whether the app has a task and what state
- * that task is in.
+ * The task-state of an app, i.e. whether the app has a task and what state that task is in.
*
- * @property taskId The ID of the task if one exists (i.e. if the state is
- * RUNNING or MINIMIZED), null otherwise (NOT_RUNNING).
+ * @property taskId The ID of the task if one exists (i.e. if the state is RUNNING or
+ * MINIMIZED), null otherwise (NOT_RUNNING).
*/
data class TaskState(val runningAppState: RunningAppState, val taskId: Int? = null)
@@ -214,9 +216,9 @@
return shownHotseatItems.toTypedArray()
}
- private fun getOrderedAndWrappedDesktopTasks(): List<GroupTask> {
+ private fun getOrderedAndWrappedDesktopTasks(): List<SingleTask> {
val tasks = desktopTask?.tasks ?: emptyList()
- // Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
+ // We wrap each task in the Desktop as a `SingleTask`.
val orderFromId = orderedRunningTaskIds.withIndex().associate { (index, id) -> id to index }
val sortedTasks = tasks.sortedWith(compareBy(nullsLast()) { orderFromId[it.key.id] })
return sortedTasks.map { SingleTask(it) }
@@ -286,7 +288,7 @@
}
private fun updateOrderedRunningTaskIds(): MutableList<Int> {
- val desktopTasksAsList = getOrderedAndWrappedDesktopTasks().flatMap { it.tasks }
+ val desktopTasksAsList = getOrderedAndWrappedDesktopTasks().map { it.task }
val desktopTaskIds = desktopTasksAsList.map { it.key.id }
var newOrder =
orderedRunningTaskIds
@@ -311,42 +313,43 @@
val newShownTasks =
if (Flags.enableMultiInstanceMenuTaskbar()) {
val deduplicatedDesktopTasks =
- desktopTasks.distinctBy { Pair(it.task1.key.packageName, it.task1.key.userId) }
+ desktopTasks.distinctBy { Pair(it.task.key.packageName, it.task.key.userId) }
shownTasks
.filter {
- !it.supportsMultipleTasks() &&
- it.task1.key.id in deduplicatedDesktopTasks.map { it.task1.key.id }
+ it is SingleTask &&
+ it.task.key.id in deduplicatedDesktopTasks.map { it.task.key.id }
}
.toMutableList()
.apply {
addAll(
deduplicatedDesktopTasks.filter { currentTask ->
- val currentTaskKey = currentTask.task1.key
- currentTaskKey.id !in shownTasks.map { it.task1.key.id } &&
+ val currentTaskKey = currentTask.task.key
+ currentTaskKey.id !in shownTaskIds &&
shownHotseatItems.none { hotseatItem ->
- hotseatItem.targetPackage == currentTaskKey.packageName &&
- hotseatItem.user.identifier == currentTaskKey.userId
+ currentTask.containsPackage(
+ hotseatItem.targetPackage,
+ hotseatItem.user.identifier,
+ )
}
}
)
}
} else {
- val desktopTaskIds = desktopTasks.map { it.task1.key.id }
+ val desktopTaskIds = desktopTasks.map { it.task.key.id }
val shownHotseatItemTaskIds =
shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
shownTasks
- .filter { !it.supportsMultipleTasks() && it.task1.key.id in desktopTaskIds }
+ .filter { it is SingleTask && it.task.key.id in desktopTaskIds }
.toMutableList()
.apply {
addAll(
desktopTasks.filter { desktopTask ->
- desktopTask.task1.key.id !in
- shownTasks.map { shownTask -> shownTask.task1.key.id }
+ desktopTask.task.key.id !in shownTaskIds
}
)
- removeAll { it.task1.key.id in shownHotseatItemTaskIds }
+ removeAll { it is SingleTask && it.task.key.id in shownHotseatItemTaskIds }
}
}
@@ -371,21 +374,28 @@
groupTasks: List<GroupTask>,
shownHotseatItems: List<ItemInfo>,
): List<GroupTask> {
+ // TODO: b/393476333 - Check the behavior of the Taskbar recents section when empty desks
+ // become supported.
return if (Flags.enableMultiInstanceMenuTaskbar()) {
groupTasks.filter { groupTask ->
- val taskKey = groupTask.task1.key
// Keep tasks that are group tasks or unique package name/user combinations
- groupTask.hasMultipleTasks() ||
- shownHotseatItems.none {
- it.targetPackage == taskKey.packageName &&
- it.user.identifier == taskKey.userId
- }
+ when (groupTask) {
+ is SingleTask ->
+ shownHotseatItems.none {
+ groupTask.containsPackage(it.targetPackage, it.user.identifier)
+ }
+
+ else -> true
+ }
}
} else {
val hotseatPackages = shownHotseatItems.map { it.targetPackage }
groupTasks.filter { groupTask ->
- groupTask.hasMultipleTasks() ||
- !hotseatPackages.contains(groupTask.task1.key.packageName)
+ when (groupTask) {
+ is SingleTask -> hotseatPackages.none { groupTask.containsPackage(it) }
+
+ else -> true
+ }
}
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index f29f95d..e5d642d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -39,7 +39,7 @@
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
@@ -332,7 +332,7 @@
* Launches the given task in split-screen.
*/
public void launchSplitTasks(
- @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) { }
+ @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) { }
/**
* Returns the matching view (if any) in the taskbar.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 457ba3d..a59c9e3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -26,8 +26,6 @@
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
-import static java.util.function.Predicate.not;
-
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
@@ -68,6 +66,7 @@
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
import com.android.quickstep.views.TaskViewType;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -398,7 +397,7 @@
.filter(Objects::nonNull)
.toArray(ItemInfo[]::new);
// TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
- recentTasks = recentTasks.stream().filter(not(GroupTask::supportsMultipleTasks)).toList();
+ recentTasks = recentTasks.stream().filter(it -> it instanceof SingleTask).toList();
if (taskbarRecentsLayoutTransition()) {
updateItemsWithLayoutTransition(hotseatItemInfos, recentTasks);
@@ -636,9 +635,10 @@
final Set<GroupTask> recentTasksSet = new ArraySet<>(recentTasks);
for (GroupTask task : recentTasks) {
if (mTaskbarOverflowView != null && overflownTasks != null
- && overflownTasks.size() < itemsToAddToOverflow) {
+ && overflownTasks.size() < itemsToAddToOverflow
+ && task instanceof SingleTask singleTask) {
// TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
- overflownTasks.add(task.task1);
+ overflownTasks.add(singleTask.getTask());
if (overflownTasks.size() == itemsToAddToOverflow) {
mTaskbarOverflowView.setItems(overflownTasks);
}
@@ -648,7 +648,7 @@
// Replace any Recent views with the appropriate type if it's not already that type.
final int expectedLayoutResId;
boolean isCollection = false;
- if (task.supportsMultipleTasks()) {
+ if (!(task instanceof SingleTask)) {
if (task.taskViewType == TaskViewType.DESKTOP) {
// TODO(b/316004172): use Desktop tile layout.
expectedLayoutResId = -1;
@@ -712,18 +712,22 @@
&& tagClass.isInstance(getChildAt(mNextViewIndex).getTag());
}
- /** Binds the GroupTask to the BubbleTextView to be ready to present to the user. */
+ /** Binds the SingleTask to the BubbleTextView to be ready to present to the user. */
public void applyGroupTaskToBubbleTextView(BubbleTextView btv, GroupTask groupTask) {
- // TODO(b/343289567): support app pairs.
- Task task1 = groupTask.task1;
+ if (!(groupTask instanceof SingleTask singleTask)) {
+ // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
+ return;
+ }
+
+ Task task = singleTask.getTask();
// TODO(b/344038728): use FastBitmapDrawable instead of Drawable, to get disabled state
// while dragging.
- Drawable taskIcon = groupTask.task1.icon;
+ Drawable taskIcon = task.icon;
if (taskIcon != null) {
taskIcon = taskIcon.getConstantState().newDrawable().mutate();
}
- btv.applyIconAndLabel(taskIcon, task1.titleDescription);
- btv.setTag(groupTask);
+ btv.applyIconAndLabel(taskIcon, task.titleDescription);
+ btv.setTag(singleTask);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 0f05887..cbc5d3d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -92,6 +92,7 @@
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -739,9 +740,9 @@
return mControllers.taskbarRecentAppsController.getRunningAppState(
itemInfo.getTaskId());
}
- if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
+ if (tag instanceof SingleTask singleTask) {
return mControllers.taskbarRecentAppsController.getRunningAppState(
- groupTask.task1.key.id);
+ singleTask.getTask().key.id);
}
return BubbleTextView.RunningAppState.NOT_RUNNING;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index f672840..690dec4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -65,7 +65,6 @@
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -178,10 +177,10 @@
import com.android.quickstep.TouchInteractionService.TISBinder;
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
import com.android.quickstep.util.AsyncClockEventDelegate;
-import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LauncherUnfoldAnimationController;
import com.android.quickstep.util.QuickstepOnboardingPrefs;
import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.util.SplitToWorkspaceController;
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
import com.android.quickstep.util.TISBindHelper;
@@ -1386,33 +1385,20 @@
}
/**
- * Launches the given {@link GroupTask} in splitscreen.
+ * Launches the given {@link SplitTask} in splitscreen.
*/
public void launchSplitTasks(
- @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
- // SplitBounds can be null if coming from Taskbar launch.
- final boolean firstTaskIsLeftTopTask = isFirstTaskLeftTopTask(groupTask);
- // task2 should never be null when calling this method. Allow a crash to catch invalid calls
- Task task1 = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
- Task task2 = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
- mSplitSelectStateController.launchExistingSplitPair(
- null /* launchingTaskView */,
- task1.key.id,
- task2.key.id,
+ @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) {
+ mSplitSelectStateController.launchExistingSplitPair(null /* launchingTaskView */,
+ splitTask.getTopLeftTask().key.id,
+ splitTask.getBottomRightTask().key.id,
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
/* callback= */ success -> mSplitSelectStateController.resetState(),
/* freezeTaskList= */ false,
- groupTask.mSplitBounds == null
- ? SNAP_TO_2_50_50
- : groupTask.mSplitBounds.snapPosition,
+ splitTask.getSplitBounds().snapPosition,
remoteTransition);
}
- private static boolean isFirstTaskLeftTopTask(@NonNull GroupTask groupTask) {
- return groupTask.mSplitBounds == null
- || groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
- }
-
/**
* Launches two apps as an app pair.
*/
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 2b296c8..f582324 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -149,9 +149,9 @@
mOverviewPanel.setFullscreenProgress(progress);
if (progress > UPDATE_SYSUI_FLAGS_THRESHOLD) {
int sysuiFlags = 0;
- TaskView tv = mOverviewPanel.getFirstTaskView();
- if (tv != null) {
- sysuiFlags = tv.getSysUiStatusNavFlags();
+ TaskView firstTaskView = mOverviewPanel.getFirstTaskView();
+ if (firstTaskView != null) {
+ sysuiFlags = firstTaskView.getSysUiStatusNavFlags();
}
mLauncher.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, sysuiFlags);
} else {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 78e543c..5f8b89a 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -120,6 +120,7 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulContainer;
import com.android.launcher3.taskbar.TaskbarThresholdUtils;
@@ -1527,8 +1528,9 @@
.withInputType(mGestureState.isTrackpadGesture()
? SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TRACKPAD
: SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TOUCH);
- if (targetTask != null) {
- logger.withItemInfo(targetTask.getFirstItemInfo());
+ ItemInfo itemInfo;
+ if (targetTask != null && (itemInfo = targetTask.getFirstItemInfo()) != null) {
+ logger.withItemInfo(itemInfo);
}
int pageIndex = endTarget == LAST_TASK || mRecentsView == null
@@ -2369,9 +2371,6 @@
ActiveGestureLog.CompoundString nextTaskLog =
ActiveGestureLog.CompoundString.newEmptyString();
for (TaskContainer container : nextTask.getTaskContainers()) {
- if (container == null) {
- continue;
- }
nextTaskLog.append("[id: %d, pkg: %s] | ",
container.getTask().key.id,
container.getTask().key.getPackageName());
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index c60d3e8..e1e9c99 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -56,6 +56,7 @@
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.animation.TransitionAnimator;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.InputConsumerController;
import java.util.Collections;
@@ -300,7 +301,9 @@
// Disable if swiping to PIP
return null;
}
- if (sourceTaskView == null || sourceTaskView.getFirstTask().key.getComponent() == null) {
+ Task firstTask;
+ if (sourceTaskView == null || ((firstTask = sourceTaskView.getFirstTask()) == null)
+ || firstTask.key.getComponent() == null) {
// Disable if it's an invalid task
return null;
}
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index ff9c9f6..a594e49 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -120,7 +120,8 @@
TaskShortcutFactory.WELLBEING,
TaskShortcutFactory.SAVE_APP_PAIR,
TaskShortcutFactory.SCREENSHOT,
- TaskShortcutFactory.MODAL
+ TaskShortcutFactory.MODAL,
+ TaskShortcutFactory.CLOSE,
};
/**
@@ -233,7 +234,7 @@
RecentsView overviewPanel = mTaskContainer.getTaskView().getRecentsView();
// Task has already been dismissed
if (overviewPanel == null) return;
- overviewPanel.initiateSplitSelect(mTaskContainer.getTaskView());
+ overviewPanel.initiateSplitSelect(mTaskContainer);
}
protected void saveAppPair() {
@@ -369,7 +370,7 @@
@Override
public void onClick(View view) {
- saveScreenshot(mTaskContainer.getTaskView().getFirstTask());
+ saveScreenshot(mTaskContainer.getTask());
dismissTaskMenuView();
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index ab5e830..2dbd5e9 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -21,6 +21,7 @@
import static android.view.Surface.ROTATION_0;
import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -48,6 +49,7 @@
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.popup.SystemShortcut.AppInfo;
import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
@@ -128,20 +130,28 @@
};
class SplitSelectSystemShortcut extends SystemShortcut {
- private final TaskView mTaskView;
+ private final TaskContainer mTaskContainer;
private final SplitPositionOption mSplitPositionOption;
- public SplitSelectSystemShortcut(RecentsViewContainer container, TaskView taskView,
+ public SplitSelectSystemShortcut(RecentsViewContainer container,
+ TaskContainer taskContainer, TaskView taskView,
SplitPositionOption option) {
super(option.iconResId, option.textResId, container, taskView.getFirstItemInfo(),
taskView);
- mTaskView = taskView;
+ mTaskContainer = taskContainer;
mSplitPositionOption = option;
}
@Override
public void onClick(View view) {
- mTaskView.initiateSplitSelect(mSplitPositionOption);
+ RecentsView recentsView = mTaskContainer.getTaskView().getRecentsView();
+ if (recentsView != null) {
+ recentsView.initiateSplitSelect(
+ mTaskContainer,
+ mSplitPositionOption.stagePosition,
+ SplitConfigurationOptions.getLogEventForPosition(
+ mSplitPositionOption.stagePosition));
+ }
}
}
@@ -152,7 +162,6 @@
class SaveAppPairSystemShortcut extends SystemShortcut<RecentsViewContainer> {
private final GroupedTaskView mTaskView;
-
public SaveAppPairSystemShortcut(RecentsViewContainer container, GroupedTaskView taskView,
int iconResId) {
super(iconResId, R.string.save_app_pair, container, taskView.getFirstItemInfo(),
@@ -202,14 +211,14 @@
}
private void startActivity() {
- final Task.TaskKey taskKey = mTaskView.getFirstTask().key;
- final int taskId = taskKey.id;
final ActivityOptions options = makeLaunchOptions(mTarget);
- if (options != null) {
- options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ if (options == null) {
+ return;
}
- if (options != null
- && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
+ final Task.TaskKey taskKey = mTaskContainer.getTask().key;
+ final int taskId = taskKey.id;
+ options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+ if (ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
options)) {
final Runnable animStartedListener = () -> {
// Hide the task view and wait for the window to be resized
@@ -252,8 +261,8 @@
overridePendingAppTransitionMultiThumbFuture(
future, animStartedListener, mHandler, true /* scaleUp */,
taskKey.displayId);
- mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getFirstItemInfo())
- .log(mLauncherEvent);
+ mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo())
+ .log(mLauncherEvent);
}
}
@@ -289,6 +298,29 @@
}
}
+ class CloseSystemShortcut extends SystemShortcut {
+ private final TaskContainer mTaskContainer;
+
+ public CloseSystemShortcut(int iconResId, int textResId, RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ super(iconResId, textResId, container, taskContainer.getTaskView().getFirstItemInfo(),
+ taskContainer.getTaskView());
+ mTaskContainer = taskContainer;
+ }
+
+ @Override
+ public void onClick(View view) {
+ TaskView taskView = mTaskContainer.getTaskView();
+ RecentsView<?, ?> recentsView = taskView.getRecentsView();
+ if (recentsView != null) {
+ dismissTaskMenuView();
+ recentsView.dismissTask(taskView, true, true);
+ mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo())
+ .log(LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP);
+ }
+ }
+ }
+
/**
* Does NOT add split options in the following scenarios:
* * 1. Taskbar is not present AND aren't at least 2 tasks in overview to show split options for
@@ -327,7 +359,8 @@
return orientationHandler.getSplitPositionOptions(deviceProfile)
.stream()
.map((Function<SplitPositionOption, SystemShortcut>) option ->
- new SplitSelectSystemShortcut(container, taskView, option))
+ new SplitSelectSystemShortcut(container, taskContainer, taskView,
+ option))
.collect(Collectors.toList());
}
};
@@ -420,24 +453,24 @@
private static final String TAG = "PinSystemShortcut";
- private final TaskView mTaskView;
+ private final TaskContainer mTaskContainer;
public PinSystemShortcut(RecentsViewContainer target,
TaskContainer taskContainer) {
super(R.drawable.ic_pin, R.string.recent_task_option_pin, target,
taskContainer.getItemInfo(), taskContainer.getTaskView());
- mTaskView = taskContainer.getTaskView();
+ mTaskContainer = taskContainer;
}
@Override
public void onClick(View view) {
- if (mTaskView.launchAsStaticTile() != null) {
+ if (mTaskContainer.getTaskView().launchAsStaticTile() != null) {
SystemUiProxy.INSTANCE.get(mTarget.asContext()).startScreenPinning(
- mTaskView.getFirstTask().key.id);
+ mTaskContainer.getTask().key.id);
}
dismissTaskMenuView();
- mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getFirstItemInfo())
- .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
+ mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo())
+ .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
}
}
@@ -508,4 +541,24 @@
return createSingletonShortcutList(modalStateSystemShortcut);
}
};
+
+ TaskShortcutFactory CLOSE = new TaskShortcutFactory() {
+ @Override
+ public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+ TaskContainer taskContainer) {
+ return Collections.singletonList(new CloseSystemShortcut(
+ R.drawable.ic_close_option,
+ R.string.recent_task_option_close, container, taskContainer));
+ }
+
+ @Override
+ public boolean showForGroupedTask() {
+ return true;
+ }
+
+ @Override
+ public boolean showForDesktopTask() {
+ return true;
+ }
+ };
}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 3133907..e47223b 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -123,8 +123,9 @@
int userId = itemInfo.user.getIdentifier();
if (componentName != null) {
for (TaskView taskView : recentsView.getTaskViews()) {
- if (recentsView.isTaskViewVisible(taskView)) {
- Task.TaskKey key = taskView.getFirstTask().key;
+ Task firstTask = taskView.getFirstTask();
+ if (firstTask != null && recentsView.isTaskViewVisible(taskView)) {
+ Task.TaskKey key = firstTask.key;
if (componentName.equals(key.getComponent()) && userId == key.userId) {
return taskView;
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index fff7e9b..8d010e2 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -52,6 +52,7 @@
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
+import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
@@ -239,10 +240,10 @@
}
@Override
- public void initiateSplitSelect(TaskView taskView,
+ public void initiateSplitSelect(TaskContainer taskContainer,
@SplitConfigurationOptions.StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent) {
- super.initiateSplitSelect(taskView, stagePosition, splitEvent);
+ super.initiateSplitSelect(taskContainer, stagePosition, splitEvent);
mContainer.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
}
diff --git a/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt b/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt
index 63bd03d..a1ff0ce 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt
+++ b/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt
@@ -30,7 +30,7 @@
init {
inputManager.registerInputDeviceListener(this, Executors.UI_HELPER_EXECUTOR.handler)
- inputManager.inputDeviceIds.forEach { deviceId -> onInputDeviceAdded(deviceId) }
+ inputManager.inputDeviceIds.filter(this::isTrackpadDevice).forEach(this::add)
}
override fun onInputDeviceAdded(deviceId: Int) {
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 6b8650f..f20d7a5 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -128,26 +128,25 @@
& ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0));
if (!taskView.containsMultipleTasks()
|| hasUnpinnableApp
- || !(taskView instanceof GroupedTaskView)) {
+ || !(taskView instanceof GroupedTaskView groupedTaskView)) {
return false;
}
- GroupedTaskView gtv = (GroupedTaskView) taskView;
- List<TaskContainer> containers = gtv.getTaskContainers();
- ComponentKey taskKey1 = TaskUtils.getLaunchComponentKeyForTask(
- containers.get(0).getTask().key);
- ComponentKey taskKey2 = TaskUtils.getLaunchComponentKeyForTask(
- containers.get(1).getTask().key);
- AppInfo app1 = resolveAppInfoByComponent(taskKey1);
- AppInfo app2 = resolveAppInfoByComponent(taskKey2);
+ ComponentKey leftTopComponentKey = TaskUtils.getLaunchComponentKeyForTask(
+ groupedTaskView.getLeftTopTaskContainer().getTask().key);
+ ComponentKey rightBottomComponentKey = TaskUtils.getLaunchComponentKeyForTask(
+ groupedTaskView.getRightBottomTaskContainer().getTask().key);
+ AppInfo leftTopAppInfo = resolveAppInfoByComponent(leftTopComponentKey);
+ AppInfo rightBottomAppInfo = resolveAppInfoByComponent(rightBottomComponentKey);
- if (app1 == null || app2 == null) {
+ if (leftTopAppInfo == null || rightBottomAppInfo == null) {
// Disallow saving app pairs for apps that don't have a front-door in Launcher
return false;
}
- if (PackageManagerHelper.isSameAppForMultiInstance(app1, app2)) {
- if (!app1.supportsMultiInstance() || !app2.supportsMultiInstance()) {
+ if (PackageManagerHelper.isSameAppForMultiInstance(leftTopAppInfo, rightBottomAppInfo)) {
+ if (!leftTopAppInfo.supportsMultiInstance()
+ || !rightBottomAppInfo.supportsMultiInstance()) {
return false;
}
}
@@ -183,9 +182,8 @@
return;
}
- List<TaskContainer> containers = gtv.getTaskContainers();
List<TaskViewItemInfo> recentsInfos =
- containers.stream().map(TaskContainer::getItemInfo).toList();
+ gtv.getTaskContainers().stream().map(TaskContainer::getItemInfo).toList();
List<WorkspaceItemInfo> apps =
recentsInfos.stream().map(this::resolveAppPairWorkspaceInfo).toList();
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.kt b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
index 5463cf7..53ea022 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.kt
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
@@ -20,16 +20,9 @@
/**
* A [Task] container that can contain N number of tasks that are part of the desktop in recent
- * tasks list.
+ * tasks list. Note that desktops can be empty with no tasks in them.
*/
-class DesktopTask(override val tasks: List<Task>) :
- GroupTask(tasks[0], null, null, TaskViewType.DESKTOP) {
-
- override fun containsTask(taskId: Int) = tasks.any { it.key.id == taskId }
-
- override fun hasMultipleTasks() = tasks.size > 1
-
- override fun supportsMultipleTasks() = true
+class DesktopTask(tasks: List<Task>) : GroupTask(tasks, TaskViewType.DESKTOP) {
override fun copy() = DesktopTask(tasks)
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.kt b/quickstep/src/com/android/quickstep/util/GroupTask.kt
index d5bbcd3..49c37dc 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.kt
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.kt
@@ -15,7 +15,6 @@
*/
package com.android.quickstep.util
-import androidx.annotation.VisibleForTesting
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
@@ -25,47 +24,26 @@
* An abstract class for creating [Task] containers that can be [SingleTask]s, [SplitTask]s, or
* [DesktopTask]s in the recent tasks list.
*/
-abstract class GroupTask
-@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
-constructor(
- @Deprecated("Prefer using `getTasks()` instead") @JvmField val task1: Task,
- @Deprecated("Prefer using `getTasks()` instead") @JvmField val task2: Task?,
- @JvmField val mSplitBounds: SplitConfigurationOptions.SplitBounds?,
- @JvmField val taskViewType: TaskViewType,
-) {
- protected constructor(
- task1: Task,
- task2: Task?,
- splitBounds: SplitConfigurationOptions.SplitBounds?,
- ) : this(
- task1,
- task2,
- splitBounds,
- if (task2 != null) TaskViewType.GROUPED else TaskViewType.SINGLE,
- )
-
- open fun containsTask(taskId: Int) =
- task1.key.id == taskId || (task2 != null && task2.key.id == taskId)
+abstract class GroupTask(val tasks: List<Task>, @JvmField val taskViewType: TaskViewType) {
+ fun containsTask(taskId: Int) = tasks.any { it.key.id == taskId }
/**
* Returns true if a task in this group has a package name that matches the given `packageName`.
*/
- fun containsPackage(packageName: String) = tasks.any { it.key.packageName == packageName }
+ fun containsPackage(packageName: String?) = tasks.any { it.key.packageName == packageName }
- open fun hasMultipleTasks() = task2 != null
+ /**
+ * Returns true if a task in this group has a package name that matches the given `packageName`,
+ * and its user ID matches the given `userId`.
+ */
+ fun containsPackage(packageName: String?, userId: Int) =
+ tasks.any { it.key.packageName == packageName && it.key.userId == userId }
- /** Returns whether this task supports multiple tasks or not. */
- open fun supportsMultipleTasks() = taskViewType == TaskViewType.GROUPED
-
- /** Returns a List of all the Tasks in this GroupTask */
- open val tasks: List<Task>
- get() = listOfNotNull(task1, task2)
+ fun isEmpty() = tasks.isEmpty()
/** Creates a copy of this instance */
abstract fun copy(): GroupTask
- override fun toString() = "type=$taskViewType task1=$task1 task2=$task2"
-
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is GroupTask) return false
@@ -76,11 +54,14 @@
}
/** A [Task] container that must contain exactly one task in the recent tasks list. */
-class SingleTask(task: Task) :
- GroupTask(task, task2 = null, mSplitBounds = null, TaskViewType.SINGLE) {
- override fun copy() = SingleTask(task1)
+class SingleTask(task: Task) : GroupTask(listOf(task), TaskViewType.SINGLE) {
- override fun toString() = "type=$taskViewType task=$task1"
+ val task: Task
+ get() = tasks[0]
+
+ override fun copy() = SingleTask(task)
+
+ override fun toString() = "type=$taskViewType task=$task"
override fun equals(o: Any?): Boolean {
if (this === o) return true
@@ -93,19 +74,26 @@
* A [Task] container that must contain exactly two tasks and split bounds to represent an app-pair
* in the recent tasks list.
*/
-class SplitTask(task1: Task, task2: Task, splitBounds: SplitConfigurationOptions.SplitBounds) :
- GroupTask(task1, task2, splitBounds, TaskViewType.GROUPED) {
+class SplitTask(task1: Task, task2: Task, val splitBounds: SplitConfigurationOptions.SplitBounds) :
+ GroupTask(listOf(task1, task2), TaskViewType.GROUPED) {
- override fun copy() = SplitTask(task1, task2!!, mSplitBounds!!)
+ val topLeftTask: Task
+ get() = if (splitBounds.leftTopTaskId == tasks[0].key.id) tasks[0] else tasks[1]
- override fun toString() = "type=$taskViewType task1=$task1 task2=$task2"
+ val bottomRightTask: Task
+ get() = if (topLeftTask == tasks[0]) tasks[1] else tasks[0]
+
+ override fun copy() = SplitTask(tasks[0], tasks[1], splitBounds)
+
+ override fun toString() =
+ "type=$taskViewType topLeftTask=$topLeftTask bottomRightTask=$bottomRightTask"
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is SplitTask) return false
- if (mSplitBounds!! != o.mSplitBounds!!) return false
+ if (splitBounds != o.splitBounds) return false
return super.equals(o)
}
- override fun hashCode() = Objects.hash(super.hashCode(), mSplitBounds)
+ override fun hashCode() = Objects.hash(super.hashCode(), splitBounds)
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 5f8b4d9..fd8b356 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -71,7 +71,6 @@
import com.android.launcher3.R;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.apppairs.AppPairIcon;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
@@ -260,7 +259,7 @@
GroupTask groupTask = taskGroups.get(i);
if (isInstanceOfAppPair(
groupTask, componentKeys.get(0), componentKeys.get(1))) {
- lastActiveTasks[0] = groupTask.task1;
+ lastActiveTasks[0] = ((SplitTask) groupTask).getTopLeftTask();
break;
}
}
@@ -314,11 +313,15 @@
*/
public boolean isInstanceOfAppPair(GroupTask groupTask, @NonNull ComponentKey componentKey1,
@NonNull ComponentKey componentKey2) {
- return ((isInstanceOfComponent(groupTask.task1, componentKey1)
- && isInstanceOfComponent(groupTask.task2, componentKey2))
- ||
- (isInstanceOfComponent(groupTask.task1, componentKey2)
- && isInstanceOfComponent(groupTask.task2, componentKey1)));
+ if (groupTask instanceof SplitTask splitTask) {
+ return ((isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey1)
+ && isInstanceOfComponent(splitTask.getBottomRightTask(), componentKey2))
+ ||
+ (isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey2)
+ && isInstanceOfComponent(splitTask.getBottomRightTask(),
+ componentKey1)));
+ }
+ return false;
}
/**
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 38ffe50..229c8f5 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -55,6 +55,12 @@
private val MINIMUM_RATIO_TO_SHOW_ICON = 0.2f
+ val leftTopTaskContainer: TaskContainer
+ get() = taskContainers[0]
+
+ val rightBottomTaskContainer: TaskContainer
+ get() = taskContainers[1]
+
// TODO(b/336612373): Support new TTV for GroupedTaskView
var splitBoundsConfig: SplitConfigurationOptions.SplitBounds? = null
private set
@@ -72,8 +78,8 @@
val splitBoundsConfig = splitBoundsConfig ?: return
val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
- taskContainers[0].snapshotView,
- taskContainers[1].snapshotView,
+ leftTopTaskContainer.snapshotView,
+ rightBottomTaskContainer.snapshotView,
widthSize,
heightSize,
splitBoundsConfig,
@@ -165,10 +171,10 @@
val iconMargins = (iconViewMarginStart + iconViewBackgroundMarginStart) * 2
// setMaxWidth() needs to be called before mIconView.setIconOrientation which is
// called in the super below.
- (taskContainers[0].iconView as IconAppChipView).setMaxWidth(
+ (leftTopTaskContainer.iconView as IconAppChipView).setMaxWidth(
groupedTaskViewSizes.first.x - iconMargins
)
- (taskContainers[1].iconView as IconAppChipView).setMaxWidth(
+ (rightBottomTaskContainer.iconView as IconAppChipView).setMaxWidth(
groupedTaskViewSizes.second.x - iconMargins
)
}
@@ -189,16 +195,12 @@
if (deviceProfile.isLeftRightSplit) splitBoundsConfig.leftTaskPercent
else splitBoundsConfig.topTaskPercent
val bottomRightTaskPercent = 1 - topLeftTaskPercent
- taskContainers[0]
- .iconView
- .setFlexSplitAlpha(
- if (topLeftTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
- )
- taskContainers[1]
- .iconView
- .setFlexSplitAlpha(
- if (bottomRightTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
- )
+ leftTopTaskContainer.iconView.setFlexSplitAlpha(
+ if (topLeftTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
+ )
+ rightBottomTaskContainer.iconView.setFlexSplitAlpha(
+ if (bottomRightTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
+ )
}
if (enableOverviewIconMenu()) {
@@ -210,8 +212,8 @@
layoutParams.height,
)
pagedOrientationHandler.setSplitIconParams(
- taskContainers[0].iconView.asView(),
- taskContainers[1].iconView.asView(),
+ leftTopTaskContainer.iconView.asView(),
+ rightBottomTaskContainer.iconView.asView(),
taskIconHeight,
groupedTaskViewSizes.first.x,
groupedTaskViewSizes.first.y,
@@ -224,11 +226,11 @@
)
} else {
pagedOrientationHandler.setSplitIconParams(
- taskContainers[0].iconView.asView(),
- taskContainers[1].iconView.asView(),
+ leftTopTaskContainer.iconView.asView(),
+ rightBottomTaskContainer.iconView.asView(),
taskIconHeight,
- taskContainers[0].snapshotView.measuredWidth,
- taskContainers[0].snapshotView.measuredHeight,
+ leftTopTaskContainer.snapshotView.measuredWidth,
+ leftTopTaskContainer.snapshotView.measuredHeight,
measuredHeight,
measuredWidth,
isRtl,
@@ -288,8 +290,8 @@
recentsView?.let {
it.splitSelectController.launchExistingSplitPair(
if (launchingExistingTaskView) this else null,
- taskContainers[0].task.key.id,
- taskContainers[1].task.key.id,
+ leftTopTaskContainer.task.key.id,
+ rightBottomTaskContainer.task.key.id,
STAGE_POSITION_TOP_OR_LEFT,
callback,
isQuickSwitch,
@@ -319,14 +321,14 @@
// checks below aren't reliable since both of those views may be gone/transformed
val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
if (initSplitTaskId != INVALID_TASK_ID) {
- return if (initSplitTaskId == taskContainers[0].task.key.id) 1 else 0
+ return if (initSplitTaskId == leftTopTaskContainer.task.key.id) 1 else 0
}
}
// Check which of the two apps was selected
if (
- taskContainers[1].iconView.asView().containsPoint(lastTouchDownPosition) ||
- taskContainers[1].snapshotView.containsPoint(lastTouchDownPosition)
+ rightBottomTaskContainer.iconView.asView().containsPoint(lastTouchDownPosition) ||
+ rightBottomTaskContainer.snapshotView.containsPoint(lastTouchDownPosition)
) {
return 1
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 9be462c..c6bd677 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -36,7 +36,6 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.statehandlers.DepthController;
@@ -127,9 +126,11 @@
// If Launcher needs to return to split select state, do it now, after the icon has updated.
if (mContainer.hasPendingSplitSelectInfo()) {
PendingSplitSelectInfo recoveryData = mContainer.getPendingSplitSelectInfo();
- if (recoveryData.getStagedTaskId() == taskId) {
+ TaskContainer taskContainer;
+ if (recoveryData != null && recoveryData.getStagedTaskId() == taskId && (taskContainer =
+ mUtils.getTaskContainerById(taskId)) != null) {
initiateSplitSelect(
- getTaskViewByTaskId(recoveryData.getStagedTaskId()),
+ taskContainer,
recoveryData.getStagePosition(), recoveryData.getSource()
);
mContainer.finishSplitSelectRecovery();
@@ -240,10 +241,10 @@
}
@Override
- public void initiateSplitSelect(TaskView taskView,
+ public void initiateSplitSelect(TaskContainer taskContainer,
@SplitConfigurationOptions.StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent) {
- super.initiateSplitSelect(taskView, stagePosition, splitEvent);
+ super.initiateSplitSelect(taskContainer, stagePosition, splitEvent);
getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 1c8299d..9c8b249 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -213,9 +213,11 @@
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
import com.android.quickstep.util.RecentsOrientedState;
+import com.android.quickstep.util.SingleTask;
import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps;
import com.android.quickstep.util.SplitAnimationTimings;
import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.SplitTask;
import com.android.quickstep.util.SurfaceTransaction;
import com.android.quickstep.util.SurfaceTransactionApplier;
import com.android.quickstep.util.TaskGridNavHelper;
@@ -652,13 +654,13 @@
return;
}
- TaskView taskView = getTaskViewByTaskId(taskId);
- if (taskView == null) {
- Log.d(TAG, "onTaskRemoved: " + taskId + ", no associated TaskView");
+ TaskContainer taskContainer = mUtils.getTaskContainerById(taskId);
+ if (taskContainer == null) {
+ Log.d(TAG, "onTaskRemoved: " + taskId + ", no associated Task");
return;
}
Log.d(TAG, "onTaskRemoved: " + taskId);
- Task.TaskKey taskKey = taskView.getFirstTask().key;
+ Task.TaskKey taskKey = taskContainer.getTask().key;
UI_HELPER_EXECUTOR.execute(new CancellableTask<>(
() -> PackageManagerWrapper.getInstance()
.getActivityInfo(taskKey.getComponent(), taskKey.userId) == null,
@@ -847,7 +849,7 @@
private final RecentsViewModel mRecentsViewModel;
private final RecentsViewModelHelper mHelper;
- private final RecentsViewUtils mUtils = new RecentsViewUtils(this);
+ protected final RecentsViewUtils mUtils = new RecentsViewUtils(this);
private final Matrix mTmpMatrix = new Matrix();
@@ -1117,7 +1119,7 @@
TaskView taskView = getTaskViewByTaskId(taskId);
if (taskView != null) {
for (TaskContainer container : taskView.getTaskContainers()) {
- if (container == null || taskId != container.getTask().key.id) {
+ if (taskId != container.getTask().key.id) {
continue;
}
container.getThumbnailViewDeprecated().setThumbnail(container.getTask(),
@@ -1132,9 +1134,10 @@
@Override
public void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) {
for (TaskView taskView : getTaskViews()) {
- Task task = taskView.getFirstTask();
- if (pkg.equals(task.key.getPackageName()) && task.key.userId == user.getIdentifier()) {
- task.icon = null;
+ Task firstTask = taskView.getFirstTask();
+ if (firstTask != null && pkg.equals(firstTask.key.getPackageName())
+ && firstTask.key.userId == user.getIdentifier()) {
+ firstTask.icon = null;
if (taskView.getTaskContainers().stream().anyMatch(
container -> container.getIconView().getDrawable() != null)) {
taskView.onTaskListVisibilityChanged(true /* visible */);
@@ -1951,7 +1954,7 @@
GroupTask groupTask = taskGroups.get(i);
boolean containsStagedTask = stagedTaskIdToBeRemoved != INVALID_TASK_ID
&& groupTask.containsTask(stagedTaskIdToBeRemoved);
- boolean shouldSkipGroupTask = containsStagedTask && !groupTask.hasMultipleTasks();
+ boolean shouldSkipGroupTask = containsStagedTask && groupTask instanceof SingleTask;
if ((isSplitSelectionActive() && groupTask.taskViewType == TaskViewType.DESKTOP)
|| shouldSkipGroupTask) {
@@ -1965,25 +1968,27 @@
// to be a temporary container for the remaining task.
TaskView taskView = getTaskViewFromPool(
containsStagedTask ? TaskViewType.SINGLE : groupTask.taskViewType);
- if (taskView instanceof GroupedTaskView) {
- boolean firstTaskIsLeftTopTask =
- groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
- Task leftTopTask = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
- Task rightBottomTask = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
- ((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
- mTaskOverlayFactory, groupTask.mSplitBounds);
- } else if (taskView instanceof DesktopTaskView) {
+ if (taskView instanceof GroupedTaskView groupedTaskView) {
+ var splitTask = (SplitTask) groupTask;
+ groupedTaskView.bind(splitTask.getTopLeftTask(),
+ splitTask.getBottomRightTask(), mOrientationState,
+ mTaskOverlayFactory, splitTask.getSplitBounds());
+ } else if (taskView instanceof DesktopTaskView desktopTaskView) {
// Minimized tasks should not be shown in Overview
List<Task> nonMinimizedTasks =
groupTask.getTasks().stream()
.filter(task -> !task.isMinimized)
.toList();
- ((DesktopTaskView) taskView).bind(nonMinimizedTasks, mOrientationState,
+ desktopTaskView.bind(nonMinimizedTasks, mOrientationState,
mTaskOverlayFactory);
- } else {
- Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2
- : groupTask.task1;
+ } else if (groupTask instanceof SplitTask splitTask) {
+ Task task = splitTask.getTopLeftTask().key.id == stagedTaskIdToBeRemoved
+ ? splitTask.getBottomRightTask()
+ : splitTask.getTopLeftTask();
taskView.bind(task, mOrientationState, mTaskOverlayFactory);
+ } else {
+ taskView.bind(((SingleTask) groupTask).getTask(), mOrientationState,
+ mTaskOverlayFactory);
}
addView(taskView);
@@ -4084,6 +4089,7 @@
} else {
removeTaskInternal(dismissedTaskView);
}
+ // TODO(b/391918297): Logging when the TaskView does not have tasks as well.
mContainer.getStatsLogManager().logger()
.withItemInfo(dismissedTaskView.getFirstItemInfo())
.log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
@@ -5171,18 +5177,20 @@
* Primarily used by overview actions to initiate split from focused task, logs the source
* of split invocation as such.
*/
- public void initiateSplitSelect(TaskView taskView) {
+ public void initiateSplitSelect(TaskContainer taskContainer) {
int defaultSplitPosition = getPagedOrientationHandler()
.getDefaultSplitPosition(mContainer.getDeviceProfile());
- initiateSplitSelect(taskView, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT);
+ initiateSplitSelect(taskContainer, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT);
}
/** TODO(b/266477929): Consolidate this call w/ the one below */
- public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition,
+ public void initiateSplitSelect(TaskContainer taskContainer,
+ @StagePosition int stagePosition,
StatsLogManager.EventEnum splitEvent) {
+ TaskView taskView = taskContainer.getTaskView();
mSplitHiddenTaskView = taskView;
mSplitSelectStateController.setInitialTaskSelect(null /*intent*/, stagePosition,
- taskView.getFirstItemInfo(), splitEvent, taskView.getFirstTask().key.id);
+ taskContainer.getItemInfo(), splitEvent, taskContainer.getTask().key.id);
mSplitSelectStateController.setAnimateCurrentTaskDismissal(
true /*animateCurrentTaskDismissal*/);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
@@ -5273,15 +5281,16 @@
boolean isInitiatingTaskViewSplitPair =
mSplitSelectStateController.isDismissingFromSplitPair();
if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair
- && mSplitHiddenTaskView instanceof GroupedTaskView) {
+ && mSplitHiddenTaskView instanceof GroupedTaskView groupedTaskView) {
// Splitting from Overview for split pair task
createInitialSplitSelectAnimation(builder);
// Animate pair thumbnail into full thumbnail
- boolean primaryTaskSelected = mSplitHiddenTaskView.getTaskIds()[0]
+ boolean primaryTaskSelected = groupedTaskView.getLeftTopTaskContainer().getTask().key.id
== mSplitSelectStateController.getInitialTaskId();
- TaskContainer taskContainer = mSplitHiddenTaskView
- .getTaskContainers().get(primaryTaskSelected ? 1 : 0);
+ TaskContainer taskContainer =
+ primaryTaskSelected ? groupedTaskView.getRightBottomTaskContainer()
+ : groupedTaskView.getLeftTopTaskContainer();
mSplitSelectStateController.getSplitAnimationController()
.addInitialSplitFromPair(taskContainer, builder,
mContainer.getDeviceProfile(),
@@ -5767,8 +5776,12 @@
} else {
taskView.launchWithoutAnimation(this::onTaskLaunchAnimationEnd);
}
- mContainer.getStatsLogManager().logger().withItemInfo(taskView.getFirstItemInfo())
- .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
+ // TODO(b/391918297): Logging when there is no associated task.
+ ItemInfo firstItemInfo = taskView.getFirstItemInfo();
+ if (firstItemInfo != null) {
+ mContainer.getStatsLogManager().logger().withItemInfo(firstItemInfo)
+ .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
+ }
} else {
onTaskLaunchAnimationEnd(false);
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index bce5a5e..94e8c03 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -224,6 +224,9 @@
/** Returns true if there are at least one TaskView has been added to the RecentsView. */
fun hasTaskViews() = taskViews.any()
+ fun getTaskContainerById(taskId: Int) =
+ taskViews.firstNotNullOfOrNull { it.getTaskContainerById(taskId) }
+
private fun getRowRect(firstView: View?, lastView: View?, outRowRect: Rect) {
outRowRect.setEmpty()
firstView?.let {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 20a385f..ce0efb6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -33,11 +33,9 @@
import android.view.Display
import android.view.MotionEvent
import android.view.View
-import android.view.View.OnClickListener
import android.view.ViewGroup
import android.view.ViewStub
import android.view.accessibility.AccessibilityNodeInfo
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.FrameLayout
import android.widget.Toast
import androidx.annotation.IntDef
@@ -64,9 +62,7 @@
import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
import com.android.launcher3.util.MultiValueAlpha
import com.android.launcher3.util.RunnableList
-import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
import com.android.launcher3.util.TraceHelper
import com.android.launcher3.util.TransformingTouchDelegate
@@ -163,14 +159,15 @@
val pagedOrientationHandler: RecentsPagedOrientationHandler
get() = orientedState.orientationHandler
- @get:Deprecated("Use [taskContainers] instead.")
- val firstTask: Task
- /** Returns the first task bound to this TaskView. */
- get() = taskContainers[0].task
+ val firstTaskContainer: TaskContainer?
+ get() = taskContainers.firstOrNull()
- @get:Deprecated("Use [taskContainers] instead.")
- val firstItemInfo: ItemInfo
- get() = taskContainers[0].itemInfo
+ val firstTask: Task?
+ /** Returns the first task bound to this TaskView. */
+ get() = firstTaskContainer?.task
+
+ val firstItemInfo: ItemInfo?
+ get() = firstTaskContainer?.itemInfo
protected val container: RecentsViewContainer =
RecentsViewContainer.containerFromContext(context)
@@ -671,13 +668,6 @@
val shouldPopulateAccessibilityMenu =
modalness == 0f && recentsView?.isSplitSelectionActive == false
if (shouldPopulateAccessibilityMenu) {
- addAction(
- AccessibilityAction(
- R.id.action_close,
- context.getText(R.string.accessibility_close),
- )
- )
-
taskContainers.forEach {
TraceHelper.allowIpcs("TV.a11yInfo") {
TaskOverlayFactory.getEnabledShortcuts(this@TaskView, it).forEach { shortcut
@@ -708,11 +698,6 @@
override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean {
// TODO(b/343708271): Add support for multiple tasks per action.
- if (action == R.id.action_close) {
- recentsView?.dismissTask(this, true /*animateTaskView*/, true /*removeTask*/)
- return true
- }
-
taskContainers.forEach {
if (it.digitalWellBeingToast?.handleAccessibilityAction(action) == true) {
return true
@@ -942,7 +927,7 @@
protected open fun updateThumbnailSize() {
// TODO(b/271468547), we should default to setting translations only on the snapshot instead
// of a hybrid of both margins and translations
- taskContainers[0].snapshotView.updateLayoutParams<LayoutParams> {
+ firstTaskContainer?.snapshotView?.updateLayoutParams<LayoutParams> {
topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
}
taskContainers.forEach { it.digitalWellBeingToast?.setupLayout() }
@@ -1106,10 +1091,13 @@
}
}
Log.d("b/310064698", "${taskIds.contentToString()} - onClick - callbackList: $callbackList")
- container.statsLogManager
- .logger()
- .withItemInfo(firstItemInfo)
- .log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP)
+ // TODO(b/391918297): Logging when there is no associated task.
+ firstItemInfo?.let {
+ container.statsLogManager
+ .logger()
+ .withItemInfo(it)
+ .log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP)
+ }
}
/** Launch of the current task (both live and inactive tasks) with an animation. */
@@ -1212,6 +1200,7 @@
* @return CompletionStage to indicate the animation completion or null if the launch failed.
*/
open fun launchAsStaticTile(): RunnableList? {
+ val firstTaskContainer = firstTaskContainer ?: return null
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN,
"startActivityFromRecentsAsync",
@@ -1223,7 +1212,7 @@
}
if (
ActivityManagerWrapper.getInstance()
- .startActivityFromRecents(taskContainers[0].task.key, opts.options)
+ .startActivityFromRecents(firstTaskContainer.task.key, opts.options)
) {
Log.d(
TAG,
@@ -1262,18 +1251,18 @@
isQuickSwitch: Boolean = false,
callback: (launched: Boolean) -> Unit,
) {
+ val firstTaskContainer = firstTaskContainer ?: return
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN,
"startActivityFromRecentsAsync",
taskIds.contentToString(),
)
- val firstContainer = taskContainers[0]
val failureListener = TaskRemovedDuringLaunchListener(context.applicationContext)
if (isQuickSwitch) {
// We only listen for failures to launch in quickswitch because the during this
// gesture launcher is in the background state, vs other launches which are in
// the actual overview state
- failureListener.register(container, firstContainer.task.key.id) {
+ failureListener.register(container, firstTaskContainer.task.key.id) {
notifyTaskLaunchFailed("launchWithoutAnimation")
recentsView?.let {
// Disable animations for now, as it is an edge case and the app usually
@@ -1305,12 +1294,12 @@
if (isQuickSwitch) {
setFreezeRecentTasksReordering()
}
- disableStartingWindow = firstContainer.shouldShowSplashView
+ disableStartingWindow = firstTaskContainer.shouldShowSplashView
}
Executors.UI_HELPER_EXECUTOR.execute {
if (
!ActivityManagerWrapper.getInstance()
- .startActivityFromRecents(firstContainer.task.key, opts)
+ .startActivityFromRecents(firstTaskContainer.task.key, opts)
) {
// If the call to start activity failed, then post the result immediately,
// otherwise, wait for the animation start callback from the activity options
@@ -1337,14 +1326,6 @@
Toast.makeText(context, R.string.activity_not_available, Toast.LENGTH_SHORT).show()
}
- fun initiateSplitSelect(splitPositionOption: SplitPositionOption) {
- recentsView?.initiateSplitSelect(
- this,
- splitPositionOption.stagePosition,
- SplitConfigurationOptions.getLogEventForPosition(splitPositionOption.stagePosition),
- )
- }
-
/**
* Returns `true` if user is already in split select mode and this tap was to choose the second
* app. `false` otherwise
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt
new file mode 100644
index 0000000..b4c236e
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.hardware.input.InputManager
+import android.view.InputDevice
+import android.view.InputDevice.SOURCE_MOUSE
+import android.view.InputDevice.SOURCE_TOUCHPAD
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.IntArray
+import com.android.launcher3.util.SandboxApplication
+import com.android.launcher3.util.TestUtil
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class ActiveTrackpadListTest {
+
+ @get:Rule val context = SandboxApplication()
+
+ private val inputDeviceIds = IntArray()
+ private lateinit var inputManager: InputManager
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ inputManager = context.spyService(InputManager::class.java)
+ doAnswer { inputDeviceIds.toArray() }.whenever(inputManager).inputDeviceIds
+
+ doReturn(null).whenever(inputManager).getInputDevice(eq(1))
+ doReturn(mockDevice(SOURCE_MOUSE or SOURCE_TOUCHPAD))
+ .whenever(inputManager)
+ .getInputDevice(eq(2))
+ doReturn(mockDevice(SOURCE_MOUSE or SOURCE_TOUCHPAD))
+ .whenever(inputManager)
+ .getInputDevice(eq(3))
+ doReturn(mockDevice(SOURCE_MOUSE)).whenever(inputManager).getInputDevice(eq(4))
+ }
+
+ @Test
+ fun `initialize correct devices`() {
+ inputDeviceIds.addAll(IntArray.wrap(1, 2, 3, 4))
+
+ val list = ActiveTrackpadList(context) {}
+ assertEquals(2, list.size())
+ assertTrue(list.contains(2))
+ assertTrue(list.contains(3))
+ }
+
+ @Test
+ fun `update callback not called in constructor`() {
+ inputDeviceIds.addAll(IntArray.wrap(2, 3))
+
+ var updateCalled = false
+ val list = ActiveTrackpadList(context) { updateCalled = true }
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+
+ assertEquals(2, list.size())
+ assertFalse(updateCalled)
+ }
+
+ @Test
+ fun `update called on add only once`() {
+ var updateCalled = false
+ val list = ActiveTrackpadList(context) { updateCalled = true }
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+
+ assertFalse(updateCalled)
+ assertEquals(0, list.size())
+
+ list.onInputDeviceAdded(1)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertFalse(updateCalled)
+ assertEquals(0, list.size())
+
+ list.onInputDeviceAdded(2)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertTrue(updateCalled)
+ assertEquals(1, list.size())
+
+ updateCalled = false
+ list.onInputDeviceAdded(3)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertFalse(updateCalled)
+ assertEquals(2, list.size())
+ }
+
+ @Test
+ fun `update called on remove only once`() {
+ var updateCalled = false
+ inputDeviceIds.addAll(IntArray.wrap(1, 2, 3, 4))
+ val list = ActiveTrackpadList(context) { updateCalled = true }
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertEquals(2, list.size())
+
+ list.onInputDeviceRemoved(2)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertEquals(1, list.size())
+ assertFalse(updateCalled)
+
+ list.onInputDeviceRemoved(3)
+ TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+ assertEquals(0, list.size())
+ assertTrue(updateCalled)
+ }
+
+ private fun mockDevice(sources: Int) =
+ mock(InputDevice::class.java).apply { doReturn(sources).whenever(this).sources }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 0491c07..e4bdba5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -102,12 +102,12 @@
fun activeTasks_noMatchingTasks() {
val nonMatchingComponent = ComponentKey(ComponentName("no", "match"), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName("pumpkin", "pie"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("hotdog", "juice"),
ComponentName("personal", "computer"),
)
@@ -143,12 +143,12 @@
val matchingComponent =
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
ComponentName("pomegranate", "juice"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pumpkin", "pie"),
ComponentName("personal", "computer"),
)
@@ -170,7 +170,7 @@
it[0].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[0], groupTask1.task1)
+ assertEquals(it[0], groupTask1.topLeftTask)
}
// Capture callback from recentsModel#getTasks()
@@ -196,12 +196,12 @@
val nonPrimaryUserComponent =
ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
ComponentName("pomegranate", "juice"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pumpkin", "pie"),
ComponentName("personal", "computer"),
)
@@ -237,14 +237,14 @@
val nonPrimaryUserComponent =
ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
nonPrimaryUserHandle,
ComponentName("pomegranate", "juice"),
nonPrimaryUserHandle,
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pumpkin", "pie"),
ComponentName("personal", "computer"),
)
@@ -267,7 +267,7 @@
matchingClass,
)
assertEquals("userId mismatched", it[0].key.userId, nonPrimaryUserHandle.identifier)
- assertEquals(it[0], groupTask1.task1)
+ assertEquals(it[0], groupTask1.topLeftTask)
}
// Capture callback from recentsModel#getTasks()
@@ -293,12 +293,12 @@
val matchingComponent =
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
ComponentName("pumpkin", "pie"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName(matchingPackage, matchingClass),
)
@@ -320,7 +320,7 @@
it[0].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[0], groupTask1.task1)
+ assertEquals(it[0], groupTask1.topLeftTask)
}
// Capture callback from recentsModel#getTasks()
@@ -348,9 +348,9 @@
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+ generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName(matchingPackage, matchingClass),
)
@@ -374,7 +374,7 @@
it[1].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[1], groupTask2.task2)
+ assertEquals(it[1], groupTask2.bottomRightTask)
}
// Capture callback from recentsModel#getTasks()
@@ -401,9 +401,9 @@
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+ generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName(matchingPackage, matchingClass),
)
@@ -426,7 +426,7 @@
it[0].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[0], groupTask2.task2)
+ assertEquals(it[0], groupTask2.bottomRightTask)
assertNull("No tasks should have matched", it[1] /*task*/)
}
@@ -454,12 +454,12 @@
ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
val groupTask1 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage, matchingClass),
ComponentName("pumpkin", "pie"),
)
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("pomegranate", "juice"),
ComponentName(matchingPackage, matchingClass),
)
@@ -482,7 +482,7 @@
it[0].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[0], groupTask1.task1)
+ assertEquals(it[0], groupTask1.topLeftTask)
assertEquals(
"ComponentName package mismatched",
it[1].key.baseIntent.component?.packageName,
@@ -493,7 +493,7 @@
it[1].key.baseIntent.component?.className,
matchingClass,
)
- assertEquals(it[1], groupTask2.task2)
+ assertEquals(it[1], groupTask2.bottomRightTask)
}
// Capture callback from recentsModel#getTasks()
@@ -524,14 +524,14 @@
ComponentKey(ComponentName(matchingPackage2, matchingClass2), primaryUserHandle)
val groupTask1 =
- generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+ generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
val groupTask2 =
- generateGroupTask(
+ generateSplitTask(
ComponentName(matchingPackage2, matchingClass2),
ComponentName(matchingPackage, matchingClass),
)
val groupTask3 =
- generateGroupTask(
+ generateSplitTask(
ComponentName("hotdog", "pie"),
ComponentName(matchingPackage, matchingClass),
)
@@ -545,7 +545,7 @@
val taskConsumer =
Consumer<Array<Task>> {
assertEquals("Expected array length 2", 2, it.size)
- assertEquals("Found wrong task", it[0], groupTask2.task1)
+ assertEquals("Found wrong task", it[0], groupTask2.topLeftTask)
}
// Capture callback from recentsModel#getTasks()
@@ -640,11 +640,11 @@
verify(recentsView, times(0)).resetDesktopTaskFromSplitSelectState()
}
- // Generate GroupTask with default userId.
- private fun generateGroupTask(
+ /** Generates a [SplitTask] with default userId. */
+ private fun generateSplitTask(
task1ComponentName: ComponentName,
task2ComponentName: ComponentName,
- ): GroupTask {
+ ): SplitTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
taskInfo.taskId = getUniqueId()
@@ -666,20 +666,20 @@
SplitConfigurationOptions.SplitBounds(
/* leftTopBounds = */ Rect(),
/* rightBottomBounds = */ Rect(),
- /* leftTopTaskId = */ -1,
- /* rightBottomTaskId = */ -1,
+ /* leftTopTaskId = */ task1.key.id,
+ /* rightBottomTaskId = */ task2.key.id,
/* snapPosition = */ SNAP_TO_2_50_50,
),
)
}
- // Generate GroupTask with custom user handles.
- private fun generateGroupTask(
+ /** Generates a [SplitTask] with custom user handles. */
+ private fun generateSplitTask(
task1ComponentName: ComponentName,
userHandle1: UserHandle,
task2ComponentName: ComponentName,
userHandle2: UserHandle,
- ): GroupTask {
+ ): SplitTask {
val task1 = Task()
var taskInfo = ActivityManager.RunningTaskInfo()
taskInfo.taskId = getUniqueId()
@@ -704,8 +704,8 @@
SplitConfigurationOptions.SplitBounds(
/* leftTopBounds = */ Rect(),
/* rightBottomBounds = */ Rect(),
- /* leftTopTaskId = */ -1,
- /* rightBottomTaskId = */ -1,
+ /* leftTopTaskId = */ task1.key.id,
+ /* rightBottomTaskId = */ task2.key.id,
/* snapPosition = */ SNAP_TO_2_50_50,
),
)
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index f923142..c78fe1c 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.PendingIntent;
@@ -93,7 +94,8 @@
final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
return getFromLauncher(launcher -> {
- TaskContainer taskContainer = task.getTaskContainers().get(0);
+ TaskContainer taskContainer = task.getFirstTaskContainer();
+ assertNotNull(taskContainer);
assertTrue("Latest task is not Calculator", calculatorPackage.equals(
taskContainer.getTask().getTopComponent().getPackageName()));
return taskContainer.getDigitalWellBeingToast();
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index b4a24f1..813d8f1 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -25,7 +25,6 @@
import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.Utilities.pxFromSp;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
@@ -52,6 +51,7 @@
import com.android.launcher3.CellLayout.ContainerType;
import com.android.launcher3.DevicePaddings.DevicePadding;
+import com.android.launcher3.folder.ClippedFolderIconLayoutRule;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.icons.DotRenderer;
import com.android.launcher3.icons.IconNormalizer;
@@ -1228,7 +1228,7 @@
}
private int getIconSizeWithOverlap(int iconSize) {
- return (int) Math.ceil(iconSize * ICON_OVERLAP_FACTOR);
+ return (int) Math.ceil(iconSize * ClippedFolderIconLayoutRule.getIconOverlapFactor());
}
/**
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 8cd91d3..cf5150a 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -1,5 +1,7 @@
package com.android.launcher3.folder;
+import com.android.launcher3.Flags;
+
public class ClippedFolderIconLayoutRule {
public static final int MAX_NUM_ITEMS_IN_PREVIEW = 4;
@@ -7,9 +9,12 @@
private static final float MIN_SCALE = 0.44f;
private static final float MAX_SCALE = 0.51f;
+ // TODO: figure out exact radius for different icons
+ private static final float MAX_RADIUS_DILATION_SHAPES = 0.15f;
private static final float MAX_RADIUS_DILATION = 0.25f;
// The max amount of overlap the preview items can go outside of the background bounds.
public static final float ICON_OVERLAP_FACTOR = 1 + (MAX_RADIUS_DILATION / 2f);
+ public static final float ICON_OVERLAP_FACTOR_SHAPES = 1f;
private static final float ITEM_RADIUS_SCALE_FACTOR = 1.15f;
public static final int EXIT_INDEX = -2;
@@ -28,7 +33,7 @@
mRadius = ITEM_RADIUS_SCALE_FACTOR * availableSpace / 2f;
mIconSize = intrinsicIconSize;
mIsRtl = rtl;
- mBaselineIconScale = availableSpace / (intrinsicIconSize * 1f);
+ mBaselineIconScale = availableSpace / intrinsicIconSize;
}
public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
@@ -84,6 +89,7 @@
result[1] = top + (row * dy);
}
+ // b/392610664 TODO: Change positioning from circular geometry to square / grid-based.
private void getPosition(int index, int curNumItems, float[] result) {
// The case of two items is homomorphic to the case of one.
curNumItems = Math.max(curNumItems, 2);
@@ -113,8 +119,10 @@
}
// We bump the radius up between 0 and MAX_RADIUS_DILATION % as the number of items increase
- float radius = mRadius * (1 + MAX_RADIUS_DILATION * (curNumItems -
- MIN_NUM_ITEMS_IN_PREVIEW) / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW));
+ float radiusDilation = Flags.enableLauncherIconShapes() ? MAX_RADIUS_DILATION_SHAPES
+ : MAX_RADIUS_DILATION;
+ float radius = mRadius * (1 + radiusDilation * (curNumItems - MIN_NUM_ITEMS_IN_PREVIEW)
+ / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW));
double theta = theta0 + index * (2 * Math.PI / curNumItems) * direction;
float halfIconSize = (mIconSize * scaleForItem(curNumItems)) / 2;
@@ -130,7 +138,7 @@
public float scaleForItem(int numItems) {
// Scale is determined by the number of items in the preview.
final float scale;
- if (numItems <= 3) {
+ if (numItems <= 3 && !Flags.enableLauncherIconShapes()) {
scale = MAX_SCALE;
} else {
scale = MIN_SCALE;
@@ -141,4 +149,15 @@
public float getIconSize() {
return mIconSize;
}
+
+ /**
+ * Gets correct constant for icon overlap.
+ */
+ public static float getIconOverlapFactor() {
+ if (Flags.enableLauncherIconShapes()) {
+ return ICON_OVERLAP_FACTOR_SHAPES;
+ } else {
+ return ICON_OVERLAP_FACTOR;
+ }
+ }
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index b7b378e..0ed8787 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -17,7 +17,6 @@
package com.android.launcher3.folder;
import static com.android.launcher3.Flags.enableCursorHoverStates;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
@@ -248,7 +247,8 @@
mPreviewItemManager.recomputePreviewDrawingParams();
mBackground.getBounds(outBounds);
// The preview items go outside of the bounds of the background.
- Utilities.scaleRectAboutCenter(outBounds, ICON_OVERLAP_FACTOR);
+ Utilities.scaleRectAboutCenter(outBounds,
+ ClippedFolderIconLayoutRule.getIconOverlapFactor());
}
public float getBackgroundStrokeWidth() {
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index d9c60db..77fa355 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -18,7 +18,6 @@
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.animation.Animator;
@@ -373,7 +372,7 @@
public Path getClipPath() {
mPath.reset();
- float radius = getScaledRadius() * ICON_OVERLAP_FACTOR;
+ float radius = getScaledRadius() * ClippedFolderIconLayoutRule.getIconOverlapFactor();
// Find the difference in radius so that the clip path remains centered.
float radiusDifference = radius - getRadius();
float offsetX = basePreviewOffsetX - radiusDifference;
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 5a2864d..c9e688b 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -47,7 +47,7 @@
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.shapes.IconShapeModel;
-import com.android.launcher3.shapes.IconShapesProvider;
+import com.android.launcher3.shapes.ShapesProvider;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
@@ -145,7 +145,7 @@
KEY_SHAPE_KEY, KEY_SHAPE_TITLE, KEY_PATH, KEY_IS_DEFAULT});
String currentShapePath =
ThemeManager.INSTANCE.get(context).getIconState().getIconMask();
- for (IconShapeModel shape : IconShapesProvider.INSTANCE.getShapes().values()) {
+ for (IconShapeModel shape : ShapesProvider.INSTANCE.getIconShapes().values()) {
cursor.newRow()
.add(KEY_SHAPE_KEY, shape.getKey())
.add(KEY_SHAPE_TITLE, shape.getTitle())
diff --git a/src/com/android/launcher3/graphics/ThemeManager.kt b/src/com/android/launcher3/graphics/ThemeManager.kt
index f24c2ab..9f35e4a 100644
--- a/src/com/android/launcher3/graphics/ThemeManager.kt
+++ b/src/com/android/launcher3/graphics/ThemeManager.kt
@@ -27,7 +27,7 @@
import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.icons.IconThemeController
import com.android.launcher3.icons.mono.MonoIconThemeController
-import com.android.launcher3.shapes.IconShapesProvider
+import com.android.launcher3.shapes.ShapesProvider
import com.android.launcher3.util.DaggerSingletonObject
import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
@@ -94,7 +94,7 @@
private fun parseIconState(): IconState {
val shapeModel =
prefs.get(PREF_ICON_SHAPE).let { shapeOverride ->
- IconShapesProvider.shapes.values.firstOrNull { it.key == shapeOverride }
+ ShapesProvider.iconShapes.values.firstOrNull { it.key == shapeOverride }
}
val iconMask =
when {
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 6eb02ab..2d30466 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -225,9 +225,12 @@
@UiEvent(doc = "User tapped on desktop icon on a task menu.")
LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP(1706),
- @UiEvent(doc = "Use tapped on external display icon on a task menu,")
+ @UiEvent(doc = "User tapped on external display icon on a task menu,")
LAUNCHER_SYSTEM_SHORTCUT_EXTERNAL_DISPLAY_TAP(1957),
+ @UiEvent(doc = "User tapped on close app on a task menu,")
+ LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP(2081),
+
@UiEvent(doc = "User tapped on pause app system shortcut.")
LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP(521),
diff --git a/src/com/android/launcher3/model/GridSizeMigrationDBController.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
index d256d1b..b291421 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationDBController.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
@@ -17,7 +17,6 @@
package com.android.launcher3.model;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
-import static com.android.launcher3.Flags.oneGridSpecs;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
@@ -39,6 +38,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
@@ -127,7 +127,7 @@
return true;
}
- boolean shouldMigrateToStrictlyTallerGrid = isDestNewDb
+ boolean shouldMigrateToStrictlyTallerGrid = (Flags.oneGridSpecs() || isDestNewDb)
&& srcDeviceState.getColumns().equals(destDeviceState.getColumns())
&& srcDeviceState.getRows() < destDeviceState.getRows();
if (shouldMigrateToStrictlyTallerGrid) {
@@ -142,7 +142,7 @@
if (shouldMigrateToStrictlyTallerGrid) {
// We want to add the extra row(s) to the top of the screen, so we shift the grid
// down.
- if (oneGridSpecs()) {
+ if (Flags.oneGridSpecs()) {
shiftWorkspaceByXCells(
target.getWritableDatabase(),
(destDeviceState.getRows() - srcDeviceState.getRows()),
diff --git a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
index 876919a..9586bf3 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
+++ b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
@@ -21,7 +21,6 @@
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.launcher3.Flags
-import com.android.launcher3.Flags.oneGridSpecs
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.get
import com.android.launcher3.LauncherPrefs.Companion.getPrefs
@@ -81,7 +80,7 @@
// down.
if (shouldMigrateToStrtictlyTallerGrid) {
Log.d(TAG, "Migrating to strictly taller grid")
- if (oneGridSpecs()) {
+ if (Flags.oneGridSpecs()) {
shiftWorkspaceByXCells(
target.writableDatabase,
(destDeviceState.rows - srcDeviceState.rows),
@@ -374,7 +373,7 @@
srcDeviceState: DeviceGridState,
destDeviceState: DeviceGridState,
): Boolean {
- return isDestNewDb &&
+ return (Flags.oneGridSpecs() || isDestNewDb) &&
srcDeviceState.columns == destDeviceState.columns &&
srcDeviceState.rows < destDeviceState.rows
}
diff --git a/src/com/android/launcher3/shapes/IconShapesProvider.kt b/src/com/android/launcher3/shapes/IconShapesProvider.kt
deleted file mode 100644
index 8608437..0000000
--- a/src/com/android/launcher3/shapes/IconShapesProvider.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.shapes
-
-import com.android.launcher3.Flags as LauncherFlags
-import com.android.systemui.shared.Flags
-
-object IconShapesProvider {
- val shapes =
- if (Flags.newCustomizationPickerUi() && LauncherFlags.enableLauncherIconShapes()) {
- mapOf(
- "arch" to
- IconShapeModel(
- key = "arch",
- title = "arch",
- pathString =
- "M50 0C77.614 0 100 22.386 100 50C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116 .884 93.916 .1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0Z",
- ),
- "4_sided_cookie" to
- IconShapeModel(
- key = "4_sided_cookie",
- title = "4 sided cookie",
- pathString =
- "M39.888,4.517C46.338 7.319 53.662 7.319 60.112 4.517L63.605 3C84.733 -6.176 106.176 15.268 97 36.395L95.483 39.888C92.681 46.338 92.681 53.662 95.483 60.112L97 63.605C106.176 84.732 84.733 106.176 63.605 97L60.112 95.483C53.662 92.681 46.338 92.681 39.888 95.483L36.395 97C15.267 106.176 -6.176 84.732 3 63.605L4.517 60.112C7.319 53.662 7.319 46.338 4.517 39.888L3 36.395C -6.176 15.268 15.267 -6.176 36.395 3Z",
- ),
- "seven_sided_cookie" to
- IconShapeModel(
- key = "seven_sided_cookie",
- title = "7 sided cookie",
- pathString =
- "M35.209 4.878C36.326 3.895 36.884 3.404 37.397 3.006 44.82 -2.742 55.18 -2.742 62.603 3.006 63.116 3.404 63.674 3.895 64.791 4.878 65.164 5.207 65.351 5.371 65.539 5.529 68.167 7.734 71.303 9.248 74.663 9.932 74.902 9.981 75.147 10.025 75.637 10.113 77.1 10.375 77.831 10.506 78.461 10.66 87.573 12.893 94.032 21.011 94.176 30.412 94.186 31.062 94.151 31.805 94.08 33.293 94.057 33.791 94.045 34.04 94.039 34.285 93.958 37.72 94.732 41.121 96.293 44.18 96.404 44.399 96.522 44.618 96.759 45.056 97.467 46.366 97.821 47.021 98.093 47.611 102.032 56.143 99.727 66.266 92.484 72.24 91.983 72.653 91.381 73.089 90.177 73.961 89.774 74.254 89.572 74.4 89.377 74.548 86.647 76.626 84.477 79.353 83.063 82.483 82.962 82.707 82.865 82.936 82.671 83.395 82.091 84.766 81.8 85.451 81.51 86.033 77.31 94.44 67.977 98.945 58.801 96.994 58.166 96.859 57.451 96.659 56.019 96.259 55.54 96.125 55.3 96.058 55.063 95.998 51.74 95.154 48.26 95.154 44.937 95.998 44.699 96.058 44.46 96.125 43.981 96.259 42.549 96.659 41.834 96.859 41.199 96.994 32.023 98.945 22.69 94.44 18.49 86.033 18.2 85.451 17.909 84.766 17.329 83.395 17.135 82.936 17.038 82.707 16.937 82.483 15.523 79.353 13.353 76.626 10.623 74.548 10.428 74.4 10.226 74.254 9.823 73.961 8.619 73.089 8.017 72.653 7.516 72.24 .273 66.266 -2.032 56.143 1.907 47.611 2.179 47.021 2.533 46.366 3.241 45.056 3.478 44.618 3.596 44.399 3.707 44.18 5.268 41.121 6.042 37.72 5.961 34.285 5.955 34.04 5.943 33.791 5.92 33.293 5.849 31.805 5.814 31.062 5.824 30.412 5.968 21.011 12.427 12.893 21.539 10.66 22.169 10.506 22.9 10.375 24.363 10.113 24.853 10.025 25.098 9.981 25.337 9.932 28.697 9.248 31.833 7.734 34.461 5.529 34.649 5.371 34.836 5.207 35.209 4.878Z",
- ),
- "sunny" to
- IconShapeModel(
- key = "sunny",
- title = "sunny",
- pathString =
- "M42.846 4.873C46.084 -.531 53.916 -.531 57.154 4.873L60.796 10.951C62.685 14.103 66.414 15.647 69.978 14.754L76.851 13.032C82.962 11.5 88.5 17.038 86.968 23.149L85.246 30.022C84.353 33.586 85.897 37.315 89.049 39.204L95.127 42.846C100.531 46.084 100.531 53.916 95.127 57.154L89.049 60.796C85.897 62.685 84.353 66.414 85.246 69.978L86.968 76.851C88.5 82.962 82.962 88.5 76.851 86.968L69.978 85.246C66.414 84.353 62.685 85.898 60.796 89.049L57.154 95.127C53.916 100.531 46.084 100.531 42.846 95.127L39.204 89.049C37.315 85.898 33.586 84.353 30.022 85.246L23.149 86.968C17.038 88.5 11.5 82.962 13.032 76.851L14.754 69.978C15.647 66.414 14.103 62.685 10.951 60.796L4.873 57.154C -.531 53.916 -.531 46.084 4.873 42.846L10.951 39.204C14.103 37.315 15.647 33.586 14.754 30.022L13.032 23.149C11.5 17.038 17.038 11.5 23.149 13.032L30.022 14.754C33.586 15.647 37.315 14.103 39.204 10.951L42.846 4.873Z",
- ),
- "circle" to
- IconShapeModel(
- key = "circle",
- title = "circle",
- pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
- ),
- "square" to
- IconShapeModel(
- key = "square",
- title = "square",
- pathString =
- "M53.689 0.82 L53.689 .82 C67.434 .82 74.306 .82 79.758 2.978 87.649 6.103 93.897 12.351 97.022 20.242 99.18 25.694 99.18 32.566 99.18 46.311 V53.689 C99.18 67.434 99.18 74.306 97.022 79.758 93.897 87.649 87.649 93.897 79.758 97.022 74.306 99.18 67.434 99.18 53.689 99.18 H46.311 C32.566 99.18 25.694 99.18 20.242 97.022 12.351 93.897 6.103 87.649 2.978 79.758 .82 74.306 .82 67.434 .82 53.689 L.82 46.311 C.82 32.566 .82 25.694 2.978 20.242 6.103 12.351 12.351 6.103 20.242 2.978 25.694 .82 32.566 .82 46.311 .82Z",
- ),
- )
- } else {
- mapOf(
- "circle" to
- IconShapeModel(
- key = "circle",
- title = "circle",
- pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
- )
- )
- }
-}
diff --git a/src/com/android/launcher3/shapes/ShapesProvider.kt b/src/com/android/launcher3/shapes/ShapesProvider.kt
new file mode 100644
index 0000000..dfb7793
--- /dev/null
+++ b/src/com/android/launcher3/shapes/ShapesProvider.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shapes
+
+import com.android.launcher3.Flags as LauncherFlags
+import com.android.systemui.shared.Flags
+
+object ShapesProvider {
+ val folderShapes =
+ if (LauncherFlags.enableLauncherIconShapes()) {
+ mapOf(
+ "clover" to
+ "M 39.616 4" +
+ "C 46.224 6.87 53.727 6.87 60.335 4" +
+ "L 63.884 2.459" +
+ "C 85.178 -6.789 106.789 14.822 97.541 36.116" +
+ "L 96 39.665" +
+ "C 93.13 46.273 93.13 53.776 96 60.384" +
+ "L 97.541 63.934" +
+ "C 106.789 85.227 85.178 106.839 63.884 97.591" +
+ "L 60.335 96.049" +
+ "C 53.727 93.179 46.224 93.179 39.616 96.049" +
+ "L 36.066 97.591" +
+ "C 14.773 106.839 -6.839 85.227 2.409 63.934" +
+ "L 3.951 60.384" +
+ "C 6.821 53.776 6.821 46.273 3.951 39.665" +
+ "L 2.409 36.116" +
+ "C -6.839 14.822 14.773 -6.789 36.066 2.459" +
+ "Z",
+ "complexClover" to
+ "M 49.85 6.764" +
+ "L 50.013 6.971" +
+ "L 50.175 6.764" +
+ "C 53.422 2.635 58.309 0.207 63.538 0.207" +
+ "C 65.872 0.207 68.175 0.692 70.381 1.648" +
+ "L 71.79 2.264" +
+ "L 71.792 2.265" +
+ "A 3.46 3.46 0 0 0 74.515 2.265" +
+ "L 74.517 2.264" +
+ "L 75.926 1.652" +
+ "A 17.1 17.1 0 0 1 82.769 0.207" +
+ "C 88.495 0.207 93.824 3.117 97.022 7.989" +
+ "C 100.21 12.848 100.697 18.712 98.36 24.087" +
+ "L 97.749 25.496" +
+ "V 25.497" +
+ "A 3.45 3.45 0 0 0 97.749 28.222" +
+ "V 28.223" +
+ "L 98.36 29.632" +
+ "C 100.697 35.007 100.207 40.871 97.022 45.73" +
+ "A 17.5 17.5 0 0 1 93.264 49.838" +
+ "L 93.06 50" +
+ "L 93.264 50.162" +
+ "A 17.5 17.5 0 0 1 97.022 54.27" +
+ "C 100.21 59.129 100.697 64.993 98.36 70.368" +
+ "V 71.778" +
+ "A 3.45 3.45 0 0 0 97.749 74.503" +
+ "V 74.504" +
+ "L 98.36 75.913" +
+ "C 100.697 81.288 100.207 87.152 97.022 92.011" +
+ "C 93.824 96.883 88.495 99.793 82.769 99.793" +
+ "C 80.435 99.793 78.132 99.308 75.926 98.348" +
+ "L 74.517 97.736" +
+ "H 74.515" +
+ "A 3.5 3.5 0 0 0 73.153 97.455" +
+ "C 72.682 97.455 72.225 97.552 71.792 97.736" +
+ "H 71.79" +
+ "L 70.381 98.348" +
+ "A 17.1 17.1 0 0 1 63.538 99.793" +
+ "C 58.309 99.793 53.422 97.365 50.175 93.236" +
+ "L 50.013 93.029" +
+ "L 49.85 93.236" +
+ "C 46.603 97.365 41.717 99.793 36.488 99.793" +
+ "C 34.154 99.793 31.851 99.308 29.645 98.348" +
+ "L 28.236 97.736" +
+ "H 28.234" +
+ "A 3.5 3.5 0 0 0 26.872 97.455" +
+ "C 26.401 97.455 25.944 97.552 25.511 97.736" +
+ "H 25.509" +
+ "L 24.1 98.348" +
+ "A 17.1 17.1 0 0 1 17.257 99.793" +
+ "C 11.53 99.793 6.202 96.883 3.004 92.011" +
+ "C -0.181 87.152 -0.671 81.288 1.661 75.913" +
+ "L 2.277 74.504" +
+ "V 74.503" +
+ "A 3.45 3.45 0 0 0 2.277 71.778" +
+ "V 71.777" +
+ "L 1.665 70.368" +
+ "C -0.671 64.993 -0.181 59.129 3.004 54.274" +
+ "A 17.5 17.5 0 0 1 6.761 50.162" +
+ "L 6.965 50" +
+ "L 6.761 49.838" +
+ "A 17.5 17.5 0 0 1 3.004 45.73" +
+ "C -0.181 40.871 -0.671 35.007 1.665 29.632" +
+ "L 2.277 28.223" +
+ "V 28.222" +
+ "A 3.45 3.45 0 0 0 2.277 25.497" +
+ "V 25.496" +
+ "L 1.665 24.087" +
+ "C -0.671 18.712 -0.181 12.848 3.004 7.994" +
+ "V 7.993" +
+ "C 6.202 3.117 11.53 0.207 17.257 0.207" +
+ "C 19.591 0.207 21.894 0.692 24.1 1.652" +
+ "L 25.509 2.264" +
+ "L 25.511 2.265" +
+ "A 3.46 3.46 0 0 0 28.234 2.265" +
+ "L 28.236 2.264" +
+ "L 29.645 1.652" +
+ "A 17.1 17.1 0 0 1 36.488 0.207" +
+ "C 41.717 0.207 46.603 2.635 49.85 6.764" +
+ "Z",
+ "arch" to
+ "M 50 0" +
+ "L 72.5 0" +
+ "A 27.5 27.5 0 0 1 100 27.5" +
+ "L 100 86.67" +
+ "A 13.33 13.33 0 0 1 86.67 100" +
+ "L 13.33 100" +
+ "A 13.33 13.33 0 0 1 0 86.67" +
+ "L 0 27.5" +
+ "A 27.5 27.5 0 0 1 27.5 0" +
+ "Z",
+ "square" to
+ "M 50 0" +
+ "L 83.4 0" +
+ "A 16.6 16.6 0 0 1 100 16.6" +
+ "L 100 83.4" +
+ "A 16.6 16.6 0 0 1 83.4 100" +
+ "L 16.6 100" +
+ "A 16.6 16.6 0 0 1 0 83.4" +
+ "L 0 16.6" +
+ "A 16.6 16.6 0 0 1 16.6 0" +
+ "Z",
+ )
+ } else {
+ mapOf("circle" to "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0")
+ }
+
+ val iconShapes =
+ if (Flags.newCustomizationPickerUi() && LauncherFlags.enableLauncherIconShapes()) {
+ mapOf(
+ "arch" to
+ IconShapeModel(
+ key = "arch",
+ title = "arch",
+ pathString =
+ "M50 0C77.614 0 100 22.386 100 50C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116 .884 93.916 .1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0Z",
+ folderPathString = folderShapes["arch"]!!,
+ ),
+ "four_sided_cookie" to
+ IconShapeModel(
+ key = "four_sided_cookie",
+ title = "4 sided cookie",
+ pathString =
+ "M39.888,4.517C46.338 7.319 53.662 7.319 60.112 4.517L63.605 3C84.733 -6.176 106.176 15.268 97 36.395L95.483 39.888C92.681 46.338 92.681 53.662 95.483 60.112L97 63.605C106.176 84.732 84.733 106.176 63.605 97L60.112 95.483C53.662 92.681 46.338 92.681 39.888 95.483L36.395 97C15.267 106.176 -6.176 84.732 3 63.605L4.517 60.112C7.319 53.662 7.319 46.338 4.517 39.888L3 36.395C -6.176 15.268 15.267 -6.176 36.395 3Z",
+ folderPathString = folderShapes["complexClover"]!!,
+ ),
+ "seven_sided_cookie" to
+ IconShapeModel(
+ key = "seven_sided_cookie",
+ title = "7 sided cookie",
+ pathString =
+ "M35.209 4.878C36.326 3.895 36.884 3.404 37.397 3.006 44.82 -2.742 55.18 -2.742 62.603 3.006 63.116 3.404 63.674 3.895 64.791 4.878 65.164 5.207 65.351 5.371 65.539 5.529 68.167 7.734 71.303 9.248 74.663 9.932 74.902 9.981 75.147 10.025 75.637 10.113 77.1 10.375 77.831 10.506 78.461 10.66 87.573 12.893 94.032 21.011 94.176 30.412 94.186 31.062 94.151 31.805 94.08 33.293 94.057 33.791 94.045 34.04 94.039 34.285 93.958 37.72 94.732 41.121 96.293 44.18 96.404 44.399 96.522 44.618 96.759 45.056 97.467 46.366 97.821 47.021 98.093 47.611 102.032 56.143 99.727 66.266 92.484 72.24 91.983 72.653 91.381 73.089 90.177 73.961 89.774 74.254 89.572 74.4 89.377 74.548 86.647 76.626 84.477 79.353 83.063 82.483 82.962 82.707 82.865 82.936 82.671 83.395 82.091 84.766 81.8 85.451 81.51 86.033 77.31 94.44 67.977 98.945 58.801 96.994 58.166 96.859 57.451 96.659 56.019 96.259 55.54 96.125 55.3 96.058 55.063 95.998 51.74 95.154 48.26 95.154 44.937 95.998 44.699 96.058 44.46 96.125 43.981 96.259 42.549 96.659 41.834 96.859 41.199 96.994 32.023 98.945 22.69 94.44 18.49 86.033 18.2 85.451 17.909 84.766 17.329 83.395 17.135 82.936 17.038 82.707 16.937 82.483 15.523 79.353 13.353 76.626 10.623 74.548 10.428 74.4 10.226 74.254 9.823 73.961 8.619 73.089 8.017 72.653 7.516 72.24 .273 66.266 -2.032 56.143 1.907 47.611 2.179 47.021 2.533 46.366 3.241 45.056 3.478 44.618 3.596 44.399 3.707 44.18 5.268 41.121 6.042 37.72 5.961 34.285 5.955 34.04 5.943 33.791 5.92 33.293 5.849 31.805 5.814 31.062 5.824 30.412 5.968 21.011 12.427 12.893 21.539 10.66 22.169 10.506 22.9 10.375 24.363 10.113 24.853 10.025 25.098 9.981 25.337 9.932 28.697 9.248 31.833 7.734 34.461 5.529 34.649 5.371 34.836 5.207 35.209 4.878Z",
+ folderPathString = folderShapes["clover"]!!,
+ ),
+ "sunny" to
+ IconShapeModel(
+ key = "sunny",
+ title = "sunny",
+ pathString =
+ "M42.846 4.873C46.084 -.531 53.916 -.531 57.154 4.873L60.796 10.951C62.685 14.103 66.414 15.647 69.978 14.754L76.851 13.032C82.962 11.5 88.5 17.038 86.968 23.149L85.246 30.022C84.353 33.586 85.897 37.315 89.049 39.204L95.127 42.846C100.531 46.084 100.531 53.916 95.127 57.154L89.049 60.796C85.897 62.685 84.353 66.414 85.246 69.978L86.968 76.851C88.5 82.962 82.962 88.5 76.851 86.968L69.978 85.246C66.414 84.353 62.685 85.898 60.796 89.049L57.154 95.127C53.916 100.531 46.084 100.531 42.846 95.127L39.204 89.049C37.315 85.898 33.586 84.353 30.022 85.246L23.149 86.968C17.038 88.5 11.5 82.962 13.032 76.851L14.754 69.978C15.647 66.414 14.103 62.685 10.951 60.796L4.873 57.154C -.531 53.916 -.531 46.084 4.873 42.846L10.951 39.204C14.103 37.315 15.647 33.586 14.754 30.022L13.032 23.149C11.5 17.038 17.038 11.5 23.149 13.032L30.022 14.754C33.586 15.647 37.315 14.103 39.204 10.951L42.846 4.873Z",
+ folderPathString = folderShapes["clover"]!!,
+ ),
+ "circle" to
+ IconShapeModel(
+ key = "circle",
+ title = "circle",
+ pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
+ folderPathString = folderShapes["clover"]!!,
+ ),
+ "square" to
+ IconShapeModel(
+ key = "square",
+ title = "square",
+ pathString =
+ "M53.689 0.82 L53.689 .82 C67.434 .82 74.306 .82 79.758 2.978 87.649 6.103 93.897 12.351 97.022 20.242 99.18 25.694 99.18 32.566 99.18 46.311 V53.689 C99.18 67.434 99.18 74.306 97.022 79.758 93.897 87.649 87.649 93.897 79.758 97.022 74.306 99.18 67.434 99.18 53.689 99.18 H46.311 C32.566 99.18 25.694 99.18 20.242 97.022 12.351 93.897 6.103 87.649 2.978 79.758 .82 74.306 .82 67.434 .82 53.689 L.82 46.311 C.82 32.566 .82 25.694 2.978 20.242 6.103 12.351 12.351 6.103 20.242 2.978 25.694 .82 32.566 .82 46.311 .82Z",
+ folderShapes["square"]!!,
+ ),
+ )
+ } else {
+ mapOf(
+ "default" to
+ IconShapeModel(
+ key = "default",
+ title = "circle",
+ pathString = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0",
+ )
+ )
+ }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/shapes/IconShapesProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
similarity index 63%
rename from tests/multivalentTests/src/com/android/launcher3/shapes/IconShapesProviderTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
index 234e050..2b8896e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/shapes/IconShapesProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
@@ -30,14 +30,14 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
-class IconShapesProviderTest {
+class ShapesProviderTest {
@get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path arch`() {
- IconShapesProvider.shapes["arch"]?.apply {
+ ShapesProvider.iconShapes["arch"]?.apply {
GenericPathShape(pathString)
PathParser.createPathFromPathData(pathString)
}
@@ -46,7 +46,7 @@
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path 4_sided_cookie`() {
- IconShapesProvider.shapes["4_sided_cookie"]?.apply {
+ ShapesProvider.iconShapes["4_sided_cookie"]?.apply {
GenericPathShape(pathString)
PathParser.createPathFromPathData(pathString)
}
@@ -55,7 +55,7 @@
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path seven_sided_cookie`() {
- IconShapesProvider.shapes["seven_sided_cookie"]?.apply {
+ ShapesProvider.iconShapes["seven_sided_cookie"]?.apply {
GenericPathShape(pathString)
PathParser.createPathFromPathData(pathString)
}
@@ -64,7 +64,7 @@
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path sunny`() {
- IconShapesProvider.shapes["sunny"]?.apply {
+ ShapesProvider.iconShapes["sunny"]?.apply {
GenericPathShape(pathString)
PathParser.createPathFromPathData(pathString)
}
@@ -73,7 +73,7 @@
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path circle`() {
- IconShapesProvider.shapes["circle"]?.apply {
+ ShapesProvider.iconShapes["circle"]?.apply {
GenericPathShape(pathString)
PathParser.createPathFromPathData(pathString)
}
@@ -82,7 +82,43 @@
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path square`() {
- IconShapesProvider.shapes["square"]?.apply {
+ ShapesProvider.iconShapes["square"]?.apply {
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid folder path clover`() {
+ ShapesProvider.folderShapes["clover"]?.let { pathString ->
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid folder path complexClover`() {
+ ShapesProvider.folderShapes["complexClover"]?.let { pathString ->
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid folder path arch`() {
+ ShapesProvider.folderShapes["arch"]?.let { pathString ->
+ GenericPathShape(pathString)
+ PathParser.createPathFromPathData(pathString)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ fun `verify valid folder path square`() {
+ ShapesProvider.folderShapes["square"]?.let { pathString ->
GenericPathShape(pathString)
PathParser.createPathFromPathData(pathString)
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index faf6b91..5c326f9 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -27,7 +27,6 @@
import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING;
import static com.android.launcher3.Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS;
import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
-import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -81,8 +80,6 @@
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
-import java.util.concurrent.CountDownLatch;
-
/**
* Unit tests for testing modifyTitleToSupportMultiLine() in BubbleTextView.java
* This class tests a couple of strings and uses the getMaxLines() to determine if the test passes.
@@ -518,14 +515,12 @@
// Icon is replaced with a non pending icon when download finishes
mItemInfoWithIcon.setProgressLevel(100, PackageInstallInfo.STATUS_INSTALLED);
- CountDownLatch animWait = new CountDownLatch(1);
TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () -> {
mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon);
assertThat(mBubbleTextView.getIcon()).isSameInstanceAs(oldIcon);
assertThat(oldIcon.getActiveAnimation()).isNotNull();
- oldIcon.getActiveAnimation().addListener(forEndCallback(animWait::countDown));
+ oldIcon.getActiveAnimation().end();
});
- animWait.await();
// Assert that the icon is replaced with a non-pending icon
assertThat(mBubbleTextView.getIcon()).isNotInstanceOf(PreloadIconDrawable.class);