Merge "Revert "Not refresh predicted app in right click"" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 4f5b1a0..c564594 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -629,3 +629,27 @@
description: "Enable Alt + Tab KQS view to show apps in flattened structure"
bug: "382769617"
}
+
+flag {
+ name: "enable_gesture_nav_on_connected_displays"
+ namespace: "lse_desktop_experience"
+ description: "Enables gesture navigation handling on connected displays"
+ bug: "382130680"
+}
+
+flag {
+ name: "enable_taskbar_behind_shade"
+ namespace: "lse_desktop_experience"
+ description: "Keeps taskbar behind notification shade when its pulled down"
+ bug: "343194358"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "enable_scalability_for_desktop_experience"
+ namespace: "launcher"
+ description: "Enable more grid scale options on the launcher for desktop experience"
+ bug: "375491272"
+}
diff --git a/aconfig/launcher_growth.aconfig b/aconfig/launcher_growth.aconfig
index 35a91d7..a880538 100644
--- a/aconfig/launcher_growth.aconfig
+++ b/aconfig/launcher_growth.aconfig
@@ -1,5 +1,5 @@
package: "com.android.launcher3"
-container: "system"
+container: "system_ext"
flag {
name: "enable_growth_nudge"
diff --git a/go/quickstep/src/com/android/launcher3/util/MainThreadInitializedObject.java b/go/quickstep/src/com/android/launcher3/util/MainThreadInitializedObject.java
new file mode 100644
index 0000000..e1f3508
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 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.util;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.os.Looper;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Utility class for defining singletons which are initiated on main thread.
+ */
+public class MainThreadInitializedObject<T extends SafeCloseable> {
+
+ private final ObjectProvider<T> mProvider;
+ private T mValue;
+
+ public MainThreadInitializedObject(ObjectProvider<T> provider) {
+ mProvider = provider;
+ }
+
+ public T get(Context context) {
+ Context app = context.getApplicationContext();
+ if (app instanceof ObjectSandbox sc) {
+ return sc.getObject(this);
+ }
+
+ if (mValue == null) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ mValue = TraceHelper.allowIpcs("main.thread.object", () -> mProvider.get(app));
+ } else {
+ try {
+ return MAIN_EXECUTOR.submit(() -> get(context)).get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return mValue;
+ }
+
+ public interface ObjectProvider<T> {
+
+ T get(Context context);
+ }
+
+ /** Sandbox for isolating {@link MainThreadInitializedObject} instances from Launcher. */
+ public interface ObjectSandbox {
+
+ /**
+ * Find a cached object from mObjectMap if we have already created one. If not, generate
+ * an object using the provider.
+ */
+ <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object);
+ }
+}
diff --git a/quickstep/res/color/keyboard_quick_switch_scroll_button_bg.xml b/quickstep/res/color/keyboard_quick_switch_scroll_button_bg.xml
new file mode 100644
index 0000000..1592055
--- /dev/null
+++ b/quickstep/res/color/keyboard_quick_switch_scroll_button_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="@color/materialColorPrimary"
+ android:alpha="0.38"/>
+ <item android:color="@color/materialColorPrimary"/>
+</selector>
\ No newline at end of file
diff --git a/quickstep/res/color/keyboard_quick_switch_scroll_button_fg.xml b/quickstep/res/color/keyboard_quick_switch_scroll_button_fg.xml
new file mode 100644
index 0000000..051c18f
--- /dev/null
+++ b/quickstep/res/color/keyboard_quick_switch_scroll_button_fg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="true"
+ android:state_pressed="true"
+ android:color="@color/materialColorOnPrimary"
+ android:alpha="0.15"/>
+ <item android:state_enabled="true"
+ android:state_hovered="true"
+ android:color="@color/materialColorOnPrimary"
+ android:alpha="0.11" />
+ <item android:color="@android:color/transparent" />
+</selector>
\ No newline at end of file
diff --git a/quickstep/res/color/keyboard_quick_switch_scroll_button_icon.xml b/quickstep/res/color/keyboard_quick_switch_scroll_button_icon.xml
new file mode 100644
index 0000000..74df84b
--- /dev/null
+++ b/quickstep/res/color/keyboard_quick_switch_scroll_button_icon.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="@color/materialColorOnPrimary"
+ android:alpha="0.38"/>
+ <item android:color="@color/materialColorOnPrimary"/>
+</selector>
diff --git a/quickstep/res/drawable/bg_keyboard_quick_switch_scroll_button.xml b/quickstep/res/drawable/bg_keyboard_quick_switch_scroll_button.xml
new file mode 100644
index 0000000..7067f13
--- /dev/null
+++ b/quickstep/res/drawable/bg_keyboard_quick_switch_scroll_button.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <solid android:color="@color/keyboard_quick_switch_scroll_button_bg" />
+ <corners android:radius="@dimen/keyboard_quick_switch_scroll_button_corner_radius" />
+</shape>
diff --git a/quickstep/res/drawable/fg_keyboard_quick_switch_scroll_button.xml b/quickstep/res/drawable/fg_keyboard_quick_switch_scroll_button.xml
new file mode 100644
index 0000000..dd63f54
--- /dev/null
+++ b/quickstep/res/drawable/fg_keyboard_quick_switch_scroll_button.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+ <solid android:color="@color/keyboard_quick_switch_scroll_button_fg" />
+ <corners android:radius="@dimen/keyboard_quick_switch_scroll_button_corner_radius" />
+</shape>
diff --git a/quickstep/res/drawable/ic_chevron_end.xml b/quickstep/res/drawable/ic_chevron_end.xml
new file mode 100644
index 0000000..9ca4f3a
--- /dev/null
+++ b/quickstep/res/drawable/ic_chevron_end.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:autoMirrored="true"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M504,480L320,296L376,240L616,480L376,720L320,664L504,480Z" />
+</vector>
diff --git a/quickstep/res/drawable/ic_chevron_start.xml b/quickstep/res/drawable/ic_chevron_start.xml
new file mode 100644
index 0000000..913da02
--- /dev/null
+++ b/quickstep/res/drawable/ic_chevron_start.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:autoMirrored="true"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M560,720L320,480L560,240L616,296L432,480L616,664L560,720Z" />
+</vector>
diff --git a/quickstep/res/layout/keyboard_quick_switch_view.xml b/quickstep/res/layout/keyboard_quick_switch_view.xml
index 345b97c..885bdb9 100644
--- a/quickstep/res/layout/keyboard_quick_switch_view.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_view.xml
@@ -67,6 +67,24 @@
</androidx.constraintlayout.widget.ConstraintLayout>
+ <ImageButton
+ android:id="@+id/scroll_button_start"
+ android:src="@drawable/ic_chevron_start"
+ android:contentDescription="@string/quick_switch_scroll_arrow_left"
+ android:background="@drawable/bg_keyboard_quick_switch_scroll_button"
+ android:foreground="@drawable/fg_keyboard_quick_switch_scroll_button"
+ android:tint="@color/keyboard_quick_switch_scroll_button_icon"
+ android:layout_width="@dimen/keyboard_quick_switch_scroll_button_width"
+ android:layout_height="@dimen/keyboard_quick_switch_scroll_button_height"
+ android:paddingHorizontal="@dimen/keyboard_quick_switch_scroll_button_horizontal_padding"
+ android:paddingVertical="@dimen/keyboard_quick_switch_scroll_button_vertical_padding"
+ android:layout_marginStart="@dimen/keyboard_quick_switch_view_spacing"
+ android:visibility="gone"
+
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"/>
+
<HorizontalScrollView
android:id="@+id/scroll_view"
android:layout_width="wrap_content"
@@ -76,9 +94,12 @@
android:alpha="0"
android:visibility="gone"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constrainedWidth="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent">
+ app:layout_constraintStart_toEndOf="@id/scroll_button_start"
+ app:layout_constraintEnd_toStartOf="@id/scroll_button_end">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
@@ -89,4 +110,22 @@
</HorizontalScrollView>
+ <ImageButton
+ android:id="@+id/scroll_button_end"
+ android:src="@drawable/ic_chevron_end"
+ android:contentDescription="@string/quick_switch_scroll_arrow_right"
+ android:background="@drawable/bg_keyboard_quick_switch_scroll_button"
+ android:foreground="@drawable/fg_keyboard_quick_switch_scroll_button"
+ android:tint="@color/keyboard_quick_switch_scroll_button_icon"
+ android:layout_width="@dimen/keyboard_quick_switch_scroll_button_width"
+ android:layout_height="@dimen/keyboard_quick_switch_scroll_button_height"
+ android:paddingHorizontal="@dimen/keyboard_quick_switch_scroll_button_horizontal_padding"
+ android:paddingVertical="@dimen/keyboard_quick_switch_scroll_button_vertical_padding"
+ android:layout_marginEnd="@dimen/keyboard_quick_switch_view_spacing"
+ android:visibility="gone"
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
</com.android.launcher3.taskbar.KeyboardQuickSwitchView>
diff --git a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
index 7530c28..8ca59c4 100644
--- a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
@@ -137,6 +137,7 @@
android:layout_above="@id/gesture_tutorial_fragment_action_button"
android:layout_centerHorizontal="true"
android:background="@android:color/transparent"
+ android:screenReaderFocusable="true"
android:paddingTop="24dp"
android:paddingHorizontal="24dp"
android:layout_marginBottom="16dp">
@@ -146,8 +147,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="104dp"
- android:accessibilityHeading="true"
- android:accessibilityTraversalBefore="@id/gesture_tutorial_fragment_feedback_subtitle"
android:gravity="top"
android:lineSpacingExtra="-1sp"
android:textAppearance="@style/TextAppearance.GestureTutorial.MainTitle"
@@ -162,8 +161,6 @@
android:layout_marginTop="24dp"
android:lineSpacingExtra="4sp"
android:textAppearance="@style/TextAppearance.GestureTutorial.MainSubtitle"
- android:accessibilityTraversalAfter="@id/gesture_tutorial_fragment_feedback_title"
- android:accessibilityTraversalBefore="@id/gesture_tutorial_fragment_action_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_fragment_feedback_title" />
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index d699cdf..e69fa4d 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -56,6 +56,7 @@
<!-- Accessibility actions -->
<item type="id" name="action_move_to_top_or_left" />
<item type="id" name="action_move_to_bottom_or_right" />
+ <item type="id" name="action_create_application_bubble" />
<!-- The max scale for the wallpaper when it's zoomed in -->
<item name="config_wallpaperMaxScale" format="float" type="dimen">
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 52ebdae..6196be4 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -530,6 +530,11 @@
<dimen name="keyboard_quick_switch_text_button_radius">360dp</dimen>
<dimen name="keyboard_quick_switch_text_button_horizontal_padding">16dp</dimen>
<dimen name="keyboard_quick_switch_text_button_fade_edge_length">20dp</dimen>
+ <dimen name="keyboard_quick_switch_scroll_button_width">36dp</dimen>
+ <dimen name="keyboard_quick_switch_scroll_button_height">56dp</dimen>
+ <dimen name="keyboard_quick_switch_scroll_button_horizontal_padding">12dp</dimen>
+ <dimen name="keyboard_quick_switch_scroll_button_vertical_padding">32dp</dimen>
+ <dimen name="keyboard_quick_switch_scroll_button_corner_radius">18dp</dimen>
<!-- Digital Wellbeing -->
<dimen name="digital_wellbeing_toast_height">48dp</dimen>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 8e70a2b..65f4b3c 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -322,6 +322,8 @@
<string name="move_drop_target_top_or_left">Move to top/left</string>
<!-- Label for moving drop target to the bottom or right side of the screen, depending on orientation (from the Taskbar only). -->
<string name="move_drop_target_bottom_or_right">Move to bottom/right</string>
+ <!-- Label for creating an application bubble (from the Taskbar only). -->
+ <string name="open_app_as_a_bubble">Open app as a bubble</string>
<!-- Label for quick switch tile showing how many more apps are available. The number will be displayed above this text. [CHAR LIMIT=NONE] -->
<string name="quick_switch_overflow">{count, plural,
@@ -335,6 +337,13 @@
<!-- Accessibility label for quick switch tiles showing split tasks [CHAR LIMIT=NONE] -->
<string name="quick_switch_split_task"><xliff:g id="app_name_1" example="Chrome">%1$s</xliff:g> and <xliff:g id="app_name_2" example="Gmail">%2$s</xliff:g></string>
+ <!-- Accessibility label for an arrow button within quick switch UI that scrolls the quick switch content left
+ TODO(b/397975686): Make these translatable when verified by UX. -->
+ <string name="quick_switch_scroll_arrow_left" translatable="false">Scroll left</string>
+ <!-- Accessibility label for an arrow button within quick switch UI that scrolls the quick switch content right
+ TODO(b/397975686): Make these translatable when verified by UX. -->
+ <string name="quick_switch_scroll_arrow_right" translatable="false">Scroll right</string>
+
<!-- Strings for bubble bar -->
<!-- Fallback name for a bubble if it does have a title [CHAR_LIMIT=none] -->
<string name="bubble_bar_bubble_fallback_description">Bubble</string>
@@ -366,4 +375,9 @@
<string name="header_default_app_title">App title</string>
<!-- Content description for the header close button. [CHAR LIMIT=NONE] -->
<string name="header_close_icon_description">Close button</string>
+
+ <!-- Label for pinning an item to the taskbar. [CHAR_LIMIT=20] -->
+ <string name="pin_to_taskbar">Pin to taskbar</string>
+ <!-- Label for unpinning an item from the taskbar. [CHAR_LIMIT=20] -->
+ <string name="unpin_from_taskbar">Unpin from taskbar</string>
</resources>
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index f992913..7f3e615 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -25,7 +25,6 @@
import static java.lang.Math.max;
import static java.lang.Math.min;
-import static java.util.Collections.emptyList;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
@@ -48,7 +47,6 @@
import com.android.launcher3.model.StringCache;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.WidgetPredictionsRequester;
-import com.android.launcher3.model.WidgetsFilterDataProvider;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.PackageItemInfo;
@@ -121,9 +119,7 @@
private LauncherAppState mApp;
private StringCache mStringCache;
private WidgetPredictionsRequester mWidgetPredictionsRequester;
- private final WidgetPickerDataProvider mWidgetPickerDataProvider =
- new WidgetPickerDataProvider();
- private WidgetsFilterDataProvider mWidgetsFilterDataProvider;
+ private WidgetPickerDataProvider mWidgetPickerDataProvider;
private int mDesiredWidgetWidth;
private int mDesiredWidgetHeight;
@@ -170,7 +166,7 @@
InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
mDeviceProfile = idp.getDeviceProfile(this);
mModel = new WidgetsModel();
- mWidgetsFilterDataProvider = WidgetsFilterDataProvider.Companion.newInstance(this);
+ mWidgetPickerDataProvider = new WidgetPickerDataProvider(this);
setContentView(R.layout.widget_picker_activity);
mDragLayer = findViewById(R.id.drag_layer);
@@ -313,17 +309,13 @@
private void refreshAndBindWidgets() {
MODEL_EXECUTOR.execute(() -> {
LauncherAppState app = LauncherAppState.getInstance(this);
- // Don't have to setup filters - its setup when launcher loads
- // Just refresh filters with available cached info.
- mModel.updateWidgetFilters(mWidgetsFilterDataProvider);
mModel.update(app, null);
StringCache stringCache = new StringCache();
stringCache.loadStrings(this);
bindStringCache(stringCache);
- bindWidgets(mModel.getWidgetsByPackageItemForPicker(),
- mModel.getDefaultWidgetsFilter());
+ bindWidgets(mModel.getWidgetsByPackageItemForPicker());
// Open sheet once widgets are available, so that it doesn't interrupt the open
// animation.
openWidgetsSheet();
@@ -339,26 +331,19 @@
MAIN_EXECUTOR.execute(() -> mStringCache = stringCache);
}
- private void bindWidgets(Map<PackageItemInfo, List<WidgetItem>> widgets,
- @Nullable Predicate<WidgetItem> defaultWidgetsFilter) {
+ private void bindWidgets(Map<PackageItemInfo, List<WidgetItem>> widgets) {
WidgetsListBaseEntriesBuilder builder = new WidgetsListBaseEntriesBuilder(
mApp.getContext());
-
final List<WidgetsListBaseEntry> allWidgets = builder.build(widgets, mNoShortcutsFilter);
- // Default list is shown if either defaultWidgetsFilter exists or host has additionally
- // enforced size filtering.
+ // Default list is shown if host has additionally enforced size filtering.
@Nullable Predicate<WidgetItem> defaultListFilter =
hasHostSizeFilters() ? mHostSizeAndNoShortcutsFilter : null;
- if (defaultWidgetsFilter != null) {
- defaultListFilter = defaultListFilter != null ? defaultListFilter.and(
- defaultWidgetsFilter) : defaultWidgetsFilter;
- }
- final List<WidgetsListBaseEntry> defaultWidgets = defaultListFilter != null ? builder.build(
- widgets, defaultListFilter) : emptyList();
- MAIN_EXECUTOR.execute(
- () -> mWidgetPickerDataProvider.setWidgets(allWidgets, defaultWidgets));
+ MAIN_EXECUTOR.execute(() -> {
+ mWidgetPickerDataProvider.setHostSpecifiedDefaultWidgetsFilter(defaultListFilter);
+ mWidgetPickerDataProvider.setWidgets(allWidgets);
+ });
}
private void openWidgetsSheet() {
@@ -380,7 +365,7 @@
@Override
protected void onDestroy() {
super.onDestroy();
- MODEL_EXECUTOR.execute(() -> mWidgetsFilterDataProvider.destroy());
+ mWidgetPickerDataProvider.destroy();
if (mWidgetPredictionsRequester != null) {
mWidgetPredictionsRequester.clear();
}
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index 8bd2ba8..b732cba 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -45,7 +45,6 @@
import java.util.Map;
import java.util.Random;
import java.util.Set;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
/** Task to update model as a result of predicted widgets update */
@@ -68,8 +67,6 @@
@Override
public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
@NonNull AllAppsList apps) {
- Predicate<WidgetItem> predictedWidgetsFilter = enableTieredWidgetsByDefaultInPicker()
- ? dataModel.widgetsModel.getPredictedWidgetsFilter() : null;
Set<ComponentKey> widgetsInWorkspace = dataModel.itemsIdMap
.stream()
.filter(WIDGET_FILTER)
@@ -84,8 +81,6 @@
.stream()
.filter(entry -> entry.getValue().widgetInfo != null
&& !widgetsInWorkspace.contains(entry.getValue())
- && (predictedWidgetsFilter == null
- || predictedWidgetsFilter.test(entry.getValue()))
).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
Context context = taskController.getApp().getContext();
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index 5afc5ed..8555376 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.Flags.enableAltTabKqsOnConnectedDisplays;
+import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.view.MotionEvent;
@@ -354,6 +355,27 @@
}
}
+ @VisibleForTesting
+ boolean isShownFromTaskbar() {
+ return isShown() && mQuickSwitchViewController.wasOpenedFromTaskbar();
+ }
+
+ @VisibleForTesting
+ boolean isShown() {
+ return mQuickSwitchViewController != null
+ && !mQuickSwitchViewController.isCloseAnimationRunning();
+ }
+
+ @VisibleForTesting
+ List<Integer> shownTaskIds() {
+ if (!isShown()) {
+ return Collections.emptyList();
+ }
+
+ return mTasks.stream().flatMap(
+ groupTask -> groupTask.getTasks().stream().map(task -> task.key.id)).toList();
+ }
+
@Override
public void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "KeyboardQuickSwitchController:");
@@ -423,7 +445,13 @@
if (task == null) {
return false;
}
- int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().taskId;
+ ActivityManager.RunningTaskInfo runningTaskInfo =
+ ActivityManagerWrapper.getInstance().getRunningTask();
+ if (runningTaskInfo == null) {
+ return false;
+ }
+
+ int runningTaskId = runningTaskInfo.taskId;
return task.containsTask(runningTaskId);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 4b4d68d..336ef48 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -30,10 +30,12 @@
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
import android.widget.HorizontalScrollView;
+import android.widget.ImageButton;
import android.widget.TextView;
import android.window.OnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;
@@ -45,6 +47,7 @@
import com.android.app.animation.Interpolators;
import com.android.internal.jank.Cuj;
+import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
@@ -102,8 +105,12 @@
private HorizontalScrollView mScrollView;
private ConstraintLayout mContent;
- private int mTaskViewWidth;
- private int mTaskViewHeight;
+ private boolean mSupportsScrollArrows = false;
+ private ImageButton mStartScrollArrow;
+ private ImageButton mEndScrollArrow;
+
+ private int mTaskViewBorderWidth;
+ private int mTaskViewRadius;
private int mSpacing;
private int mSmallSpacing;
private int mOutlineRadius;
@@ -112,11 +119,13 @@
private int mOverviewTaskIndex = -1;
private int mDesktopTaskIndex = -1;
- @Nullable private AnimatorSet mOpenAnimation;
+ @Nullable
+ private AnimatorSet mOpenAnimation;
private boolean mIsBackCallbackRegistered = false;
- @Nullable private KeyboardQuickSwitchViewController.ViewCallbacks mViewCallbacks;
+ @Nullable
+ private KeyboardQuickSwitchViewController.ViewCallbacks mViewCallbacks;
public KeyboardQuickSwitchView(@NonNull Context context) {
this(context, null);
@@ -152,18 +161,35 @@
mNoRecentItemsPane = findViewById(R.id.no_recent_items_pane);
mScrollView = findViewById(R.id.scroll_view);
mContent = findViewById(R.id.content);
+ mStartScrollArrow = findViewById(R.id.scroll_button_start);
+ mEndScrollArrow = findViewById(R.id.scroll_button_end);
+
+ setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
Resources resources = getResources();
- mTaskViewWidth = resources.getDimensionPixelSize(
- R.dimen.keyboard_quick_switch_taskview_width);
- mTaskViewHeight = resources.getDimensionPixelSize(
- R.dimen.keyboard_quick_switch_taskview_height);
mSpacing = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_view_spacing);
mSmallSpacing = resources.getDimensionPixelSize(
R.dimen.keyboard_quick_switch_view_small_spacing);
mOutlineRadius = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_view_radius);
+ mTaskViewBorderWidth = resources.getDimensionPixelSize(
+ R.dimen.keyboard_quick_switch_border_width);
+ mTaskViewRadius = resources.getDimensionPixelSize(
+ R.dimen.keyboard_quick_switch_task_view_radius);
+
mIsRtl = Utilities.isRtl(resources);
+ if (Flags.taskbarOverflow()) {
+ initializeScrollArrows();
+
+ if (mIsRtl) {
+ mStartScrollArrow.setContentDescription(
+ resources.getString(R.string.quick_switch_scroll_arrow_right));
+ mEndScrollArrow.setContentDescription(
+ resources.getString(R.string.quick_switch_scroll_arrow_left));
+ }
+ }
+
+
TypefaceUtils.setTypeface(
mNoRecentItemsPane.findViewById(R.id.no_recent_items_text),
TypefaceUtils.FONT_FAMILY_LABEL_LARGE_BASELINE);
@@ -331,6 +357,78 @@
});
}
+ private void initializeScrollArrows() {
+ mSupportsScrollArrows = true;
+
+ mStartScrollArrow.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mIsRtl) {
+ runScrollCommand(false, () -> {
+ mScrollView.smoothScrollBy(mScrollView.getWidth(), 0);
+ });
+ } else {
+ runScrollCommand(false, () -> {
+ mScrollView.smoothScrollBy(-mScrollView.getWidth(), 0);
+ });
+ }
+ }
+ });
+
+ mEndScrollArrow.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mIsRtl) {
+ runScrollCommand(false, () -> {
+ mScrollView.smoothScrollBy(-mScrollView.getWidth(), 0);
+ });
+ } else {
+ runScrollCommand(false, () -> {
+ mScrollView.smoothScrollBy(mScrollView.getWidth(), 0);
+ });
+ }
+ }
+ });
+
+ // Add listeners to disable arrow buttons when the scroll view cannot be further scrolled in
+ // the associated direction.
+ mScrollView.setOnScrollChangeListener(new OnScrollChangeListener() {
+ @Override
+ public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX,
+ int oldScrollY) {
+ updateArrowButtonsEnabledState();
+ }
+ });
+
+ // Update scroll view outline to clip its contents with rounded corners.
+ mScrollView.setClipToOutline(true);
+ mScrollView.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ int spacingWithoutBorder = mSpacing - mTaskViewBorderWidth;
+ outline.setRoundRect(spacingWithoutBorder,
+ spacingWithoutBorder, view.getWidth() - spacingWithoutBorder,
+ view.getHeight() - spacingWithoutBorder,
+ mTaskViewRadius);
+ }
+ });
+ }
+
+ private void updateArrowButtonsEnabledState() {
+ if (!mDisplayingRecentTasks) {
+ return;
+ }
+
+ int scrollX = mScrollView.getScrollX();
+ if (mIsRtl) {
+ mEndScrollArrow.setEnabled(scrollX > 0);
+ mStartScrollArrow.setEnabled(scrollX < mContent.getWidth() - mScrollView.getWidth());
+ } else {
+ mStartScrollArrow.setEnabled(scrollX > 0);
+ mEndScrollArrow.setEnabled(scrollX < mContent.getWidth() - mScrollView.getWidth());
+ }
+ }
+
int getOverviewTaskIndex() {
return mOverviewTaskIndex;
}
@@ -346,6 +444,21 @@
mViewCallbacks = null;
}
+ private void animateDisplayedContentForClose(View view, AnimatorSet animator) {
+ Animator translationYAnimation = ObjectAnimator.ofFloat(
+ view,
+ TRANSLATION_Y,
+ 0, -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP));
+ translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
+ translationYAnimation.setInterpolator(CLOSE_TRANSLATION_Y_INTERPOLATOR);
+ animator.play(translationYAnimation);
+
+ Animator contentAlphaAnimation = ObjectAnimator.ofFloat(view, ALPHA, 1f, 0f);
+ contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
+ animator.play(contentAlphaAnimation);
+
+ }
+
protected Animator getCloseAnimation() {
AnimatorSet closeAnimation = new AnimatorSet();
@@ -360,17 +473,11 @@
closeAnimation.play(alphaAnimation);
View displayedContent = mDisplayingRecentTasks ? mScrollView : mNoRecentItemsPane;
- Animator translationYAnimation = ObjectAnimator.ofFloat(
- displayedContent,
- TRANSLATION_Y,
- 0, -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP));
- translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
- translationYAnimation.setInterpolator(CLOSE_TRANSLATION_Y_INTERPOLATOR);
- closeAnimation.play(translationYAnimation);
-
- Animator contentAlphaAnimation = ObjectAnimator.ofFloat(displayedContent, ALPHA, 1f, 0f);
- contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
- closeAnimation.play(contentAlphaAnimation);
+ animateDisplayedContentForClose(displayedContent, closeAnimation);
+ if (mSupportsScrollArrows) {
+ animateDisplayedContentForClose(mStartScrollArrow, closeAnimation);
+ animateDisplayedContentForClose(mEndScrollArrow, closeAnimation);
+ }
closeAnimation.addListener(new AnimatorListenerAdapter() {
@Override
@@ -385,6 +492,31 @@
return closeAnimation;
}
+ private void animateDisplayedContentForOpen(View view, AnimatorSet animator) {
+ Animator translationXAnimation = ObjectAnimator.ofFloat(
+ view,
+ TRANSLATION_X,
+ -Utilities.dpToPx(CONTENT_START_TRANSLATION_X_DP), 0);
+ translationXAnimation.setDuration(CONTENT_TRANSLATION_X_ANIMATION_DURATION_MS);
+ translationXAnimation.setInterpolator(OPEN_TRANSLATION_X_INTERPOLATOR);
+ animator.play(translationXAnimation);
+
+ Animator translationYAnimation = ObjectAnimator.ofFloat(
+ view,
+ TRANSLATION_Y,
+ -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP), 0);
+ translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
+ translationYAnimation.setInterpolator(OPEN_TRANSLATION_Y_INTERPOLATOR);
+ animator.play(translationYAnimation);
+
+ view.setAlpha(0.0f);
+ Animator contentAlphaAnimation = ObjectAnimator.ofFloat(view, ALPHA, 0f,
+ 1f);
+ contentAlphaAnimation.setStartDelay(CONTENT_ALPHA_ANIMATION_START_DELAY_MS);
+ contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
+ animator.play(contentAlphaAnimation);
+ }
+
protected void animateOpen(int currentFocusIndexOverride) {
if (mOpenAnimation != null) {
// Restart animation since currentFocusIndexOverride can change the initial scroll.
@@ -407,26 +539,12 @@
mOpenAnimation.play(alphaAnimation);
View displayedContent = mDisplayingRecentTasks ? mScrollView : mNoRecentItemsPane;
- Animator translationXAnimation = ObjectAnimator.ofFloat(
- displayedContent,
- TRANSLATION_X,
- -Utilities.dpToPx(CONTENT_START_TRANSLATION_X_DP), 0);
- translationXAnimation.setDuration(CONTENT_TRANSLATION_X_ANIMATION_DURATION_MS);
- translationXAnimation.setInterpolator(OPEN_TRANSLATION_X_INTERPOLATOR);
- mOpenAnimation.play(translationXAnimation);
+ animateDisplayedContentForOpen(displayedContent, mOpenAnimation);
+ if (mSupportsScrollArrows) {
+ animateDisplayedContentForOpen(mStartScrollArrow, mOpenAnimation);
+ animateDisplayedContentForOpen(mEndScrollArrow, mOpenAnimation);
+ }
- Animator translationYAnimation = ObjectAnimator.ofFloat(
- displayedContent,
- TRANSLATION_Y,
- -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP), 0);
- translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
- translationYAnimation.setInterpolator(OPEN_TRANSLATION_Y_INTERPOLATOR);
- mOpenAnimation.play(translationYAnimation);
-
- Animator contentAlphaAnimation = ObjectAnimator.ofFloat(displayedContent, ALPHA, 0f, 1f);
- contentAlphaAnimation.setStartDelay(CONTENT_ALPHA_ANIMATION_START_DELAY_MS);
- contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
- mOpenAnimation.play(contentAlphaAnimation);
ViewOutlineProvider outlineProvider = getOutlineProvider();
mOpenAnimation.addListener(new AnimatorListenerAdapter() {
@@ -461,6 +579,27 @@
OPEN_OUTLINE_INTERPOLATOR));
}
});
+
+ if (mSupportsScrollArrows) {
+ mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (mScrollView.getWidth() == 0) {
+ return;
+ }
+
+ if (mContent.getWidth() > mScrollView.getWidth()) {
+ mStartScrollArrow.setVisibility(VISIBLE);
+ mEndScrollArrow.setVisibility(VISIBLE);
+ updateArrowButtonsEnabledState();
+ }
+ mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(
+ this);
+ }
+ });
+ }
+
animateFocusMove(-1, Math.min(
getTaskCount() - 1,
currentFocusIndexOverride == -1 ? 1 : currentFocusIndexOverride));
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 3f3700b..2272d11 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -467,10 +467,15 @@
}
@Override
- protected boolean canToggleHomeAllApps() {
- return mLauncher.isResumed()
+ protected void toggleAllApps(boolean focusSearch) {
+ boolean canToggleHomeAllApps = mLauncher.isResumed()
&& !mTaskbarLauncherStateController.isInOverviewUi()
&& !mLauncher.areDesktopTasksVisible();
+ if (canToggleHomeAllApps) {
+ mLauncher.toggleAllApps(focusSearch);
+ return;
+ }
+ super.toggleAllApps(focusSearch);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/PinToTaskbarShortcut.kt b/quickstep/src/com/android/launcher3/taskbar/PinToTaskbarShortcut.kt
new file mode 100644
index 0000000..b9a211d
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/PinToTaskbarShortcut.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.taskbar
+
+import android.content.Context
+import android.view.View
+import com.android.launcher3.R
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.popup.SystemShortcut
+import com.android.launcher3.views.ActivityContext
+
+/**
+ * A single menu item shortcut to allow users to pin an item to the taskbar and unpin an item from
+ * the taskbar.
+ */
+class PinToTaskbarShortcut<T>(target: T, itemInfo: ItemInfo?, originalView: View, isPin: Boolean) :
+ SystemShortcut<T>(
+ if (isPin) R.drawable.ic_pin else R.drawable.ic_unpin,
+ if (isPin) R.string.pin_to_taskbar else R.string.unpin_from_taskbar,
+ target,
+ itemInfo,
+ originalView,
+ ) where T : Context?, T : ActivityContext? {
+
+ override fun onClick(v: View?) {
+ // TODO(b/375648361): Pin/Unpin the item here.
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 57bcc14..cfbddbd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -81,6 +81,7 @@
import android.widget.Toast;
import android.window.DesktopExperienceFlags;
import android.window.DesktopModeFlags;
+import android.window.DesktopModeFlags.DesktopModeFlag;
import android.window.RemoteTransition;
import androidx.annotation.NonNull;
@@ -94,15 +95,16 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.BubbleTextView.RunningAppState;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
+import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.desktop.DesktopAppLaunchTransition;
import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType;
-import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.logger.LauncherAtom;
@@ -149,6 +151,7 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.RunnableList;
@@ -196,6 +199,9 @@
private static final String WINDOW_TITLE = "Taskbar";
+ private static final DesktopModeFlag ENABLE_TASKBAR_BEHIND_SHADE = new DesktopModeFlag(
+ Flags::enableTaskbarBehindShade, false);
+
private final @Nullable Context mNavigationBarPanelContext;
private final TaskbarDragLayer mDragLayer;
@@ -229,7 +235,6 @@
private boolean mIsDestroyed = false;
// The flag to know if the window is excluded from magnification region computation.
private boolean mIsExcludeFromMagnificationRegion = false;
- private boolean mBindingItems = false;
private boolean mAddedWindow = false;
// The bounds of the taskbar items relative to TaskbarDragLayer
@@ -608,16 +613,6 @@
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(false);
}
- /** Toggles Taskbar All Apps overlay. */
- public void toggleAllApps() {
- mControllers.taskbarAllAppsController.toggle();
- }
-
- /** Toggles Taskbar All Apps overlay with keyboard ready for search. */
- public void toggleAllAppsSearch() {
- mControllers.taskbarAllAppsController.toggleSearch();
- }
-
@Override
public DeviceProfile getDeviceProfile() {
return mDeviceProfile;
@@ -878,32 +873,29 @@
}
}
- @Override
- public DotInfo getDotInfoForItem(ItemInfo info) {
- return getPopupDataProvider().getDotInfoForItem(info);
- }
-
@NonNull
@Override
public PopupDataProvider getPopupDataProvider() {
return mControllers.taskbarPopupController.getPopupDataProvider();
}
+ @NonNull
+ @Override
+ public LauncherBindableItemsContainer getContent() {
+ return mControllers.taskbarViewController.getContent();
+ }
+
+ @Override
+ public ActivityAllAppsContainerView<?> getAppsView() {
+ return mControllers.taskbarAllAppsController.getAppsView();
+ }
+
@Override
public View.AccessibilityDelegate getAccessibilityDelegate() {
return mAccessibilityDelegate;
}
@Override
- public boolean isBindingItems() {
- return mBindingItems;
- }
-
- public void setBindingItems(boolean bindingItems) {
- mBindingItems = bindingItems;
- }
-
- @Override
public void onDragStart() {
setTaskbarWindowFullscreen(true);
}
@@ -2049,8 +2041,6 @@
"%s\tmIsUserSetupComplete=%b", prefix, mIsUserSetupComplete));
pw.println(String.format(
"%s\tmWindowLayoutParams.height=%dpx", prefix, mWindowLayoutParams.height));
- pw.println(String.format(
- "%s\tmBindInProgress=%b", prefix, mBindingItems));
mControllers.dumpLogs(prefix + "\t", pw);
mDeviceProfile.dump(this, prefix, pw);
}
@@ -2075,10 +2065,6 @@
mControllers.keyboardQuickSwitchController.closeQuickSwitchView(false);
}
- boolean canToggleHomeAllApps() {
- return mControllers.uiController.canToggleHomeAllApps();
- }
-
boolean isIconAlignedWithHotseat() {
return mControllers.uiController.isIconAlignedWithHotseat();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index d531e2c..1b516be 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -68,6 +68,7 @@
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.folder.Folder;
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logging.StatsLogManager;
@@ -116,6 +117,7 @@
private int mRegistrationY;
private boolean mIsSystemDragInProgress;
+ private boolean mIsDropHandledByDropTarget;
// Animation for the drag shadow back into position after an unsuccessful drag
private ValueAnimator mReturnAnimator;
@@ -252,7 +254,8 @@
/* originalView = */ btv,
dragLayerX + dragOffset.x,
dragLayerY + dragOffset.y,
- (View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */,
+ (View target, DropTarget.DragObject d, boolean success) ->
+ mIsDropHandledByDropTarget = success /* DragSource */,
btv.getTag() instanceof ItemInfo itemInfo ? itemInfo : null,
dragRect,
scale * iconScale,
@@ -561,7 +564,7 @@
@Override
protected void endDrag() {
- if (mDisallowGlobalDrag) {
+ if (mDisallowGlobalDrag && !mIsDropHandledByDropTarget) {
// We need to explicitly set deferDragViewCleanupPostAnimation to true here so the
// super call doesn't remove it from the drag layer before the animation completes.
// This variable gets set in to false in super.dispatchDropComplete() because it
@@ -765,8 +768,11 @@
@Override
public void addDropTarget(DropTarget target) {
- // No-op as Taskbar currently doesn't support any drop targets internally.
- // Note: if we do add internal DropTargets, we'll still need to ignore Folder.
+ if (target instanceof Folder) {
+ // we need to ignore Folder.
+ return;
+ }
+ super.addDropTarget(target);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 10eb64a..b510e7e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -191,6 +191,8 @@
private boolean mIsQsbInline;
+ private RecentsAnimationCallbacks mRecentsAnimationCallbacks;
+
private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
new DeviceProfile.OnDeviceProfileChangeListener() {
@Override
@@ -295,6 +297,11 @@
mIsDestroyed = true;
mCanSyncViews = false;
+ if (mRecentsAnimationCallbacks != null) {
+ mRecentsAnimationCallbacks.removeListener(mTaskBarRecentsAnimationListener);
+ mRecentsAnimationCallbacks = null;
+ }
+
mIconAlignment.finishAnimation();
mLauncher.getHotseat().setIconsAlpha(1f, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
@@ -315,6 +322,7 @@
// If going to overview, stash the task bar
// If going home, align the icons to hotseat
AnimatorSet animatorSet = new AnimatorSet();
+ mRecentsAnimationCallbacks = callbacks;
// Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly.
TaskbarStashController stashController = mControllers.taskbarStashController;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 943c44e..19e528a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -511,14 +511,14 @@
/**
* Toggles All Apps for Taskbar or Launcher depending on the current state.
*/
- public void toggleAllApps() {
+ public void toggleAllAppsSearch() {
TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
- if (taskbar == null || taskbar.canToggleHomeAllApps()) {
+ if (taskbar == null) {
// Home All Apps should be toggled from this class, because the controllers are not
// initialized when Taskbar is disabled (i.e. TaskbarActivityContext is null).
- if (mActivity instanceof Launcher l) l.toggleAllAppsSearch();
+ if (mActivity instanceof Launcher l) l.toggleAllApps(true);
} else {
- taskbar.toggleAllAppsSearch();
+ taskbar.getControllers().uiController.toggleAllApps(true);
}
}
@@ -756,13 +756,14 @@
}
}
- public void onSystemUiFlagsChanged(@SystemUiStateFlags long systemUiStateFlags) {
+ /** Called when the SysUI flags for a given display change. */
+ public void onSystemUiFlagsChanged(@SystemUiStateFlags long systemUiStateFlags, int displayId) {
if (DEBUG) {
Log.d(TAG, "SysUI flags changed: " + formatFlagChange(systemUiStateFlags,
mSharedState.sysuiStateFlags, QuickStepContract::getSystemUiStateString));
}
mSharedState.sysuiStateFlags = systemUiStateFlags;
- TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+ TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
if (taskbar != null) {
taskbar.updateSysuiStateFlags(systemUiStateFlags, false /* fromInit */);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 6815f97..d4ad555 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -61,6 +61,7 @@
// Used to defer any UI updates during the SUW unstash animation.
private boolean mDeferUpdatesForSUW;
private Runnable mDeferredUpdates;
+ private boolean mBindingItems = false;
public TaskbarModelCallbacks(
TaskbarActivityContext context, TaskbarView container) {
@@ -74,14 +75,14 @@
@Override
public void startBinding() {
- mContext.setBindingItems(true);
+ mBindingItems = true;
mHotseatItems.clear();
mPredictedItems = Collections.emptyList();
}
@Override
public void finishBindingItems(IntSet pagesBoundFirst) {
- mContext.setBindingItems(false);
+ mBindingItems = false;
commitItemsToUI();
}
@@ -167,7 +168,7 @@
}
private void commitItemsToUI() {
- if (mContext.isBindingItems()) {
+ if (mBindingItems) {
return;
}
@@ -203,6 +204,7 @@
ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
mContainer.updateItems(hotseatItemInfos, recentTasks);
mControllers.taskbarViewController.updateIconViewsRunningStates();
+ mControllers.taskbarPopupController.setHotseatInfosList(mHotseatItems);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 5d8b821..e81563e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -16,18 +16,20 @@
package com.android.launcher3.taskbar;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
-import static com.android.launcher3.popup.SystemShortcut.PIN_UNPIN_ITEM;
import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.graphics.Point;
import android.util.Pair;
+import android.util.SparseArray;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
import com.android.launcher3.AbstractFloatingView;
@@ -35,11 +37,7 @@
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
-import com.android.launcher3.dot.FolderDotInfo;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationListener;
@@ -49,8 +47,6 @@
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.splitscreen.SplitShortcut;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.LauncherBindableItemsContainer;
-import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.views.ActivityContext;
@@ -65,7 +61,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -88,12 +83,14 @@
private TaskbarControllers mControllers;
private boolean mAllowInitialSplitSelection;
private AppInfo[] mAppInfosList;
+ // Saves the ItemInfos in the hotseat without the predicted items.
+ private SparseArray<ItemInfo> mHotseatInfosList;
private ManageWindowsTaskbarShortcut<BaseTaskbarContext> mManageWindowsTaskbarShortcut;
public TaskbarPopupController(TaskbarActivityContext context) {
mContext = context;
- mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
+ mPopupDataProvider = new PopupDataProvider(mContext);
}
public void init(TaskbarControllers controllers) {
@@ -132,39 +129,6 @@
mAllowInitialSplitSelection = allowInitialSplitSelection;
}
- private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- final PackageUserKey packageUserKey = new PackageUserKey(null, null);
- Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
- || updatedDots.test(packageUserKey);
-
- LauncherBindableItemsContainer.ItemOperator op = (info, v) -> {
- if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
- if (matcher.test(info)) {
- ((BubbleTextView) v).applyDotState(info, true /* animate */);
- }
- } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
- FolderInfo fi = (FolderInfo) info;
- if (fi.anyMatch(matcher)) {
- FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (ItemInfo si : fi.getContents()) {
- folderDotInfo.addDotInfo(mPopupDataProvider.getDotInfoForItem(si));
- }
- ((FolderIcon) v).setDotInfo(folderDotInfo);
- }
- }
-
- // process all the shortcuts
- return false;
- };
-
- mControllers.taskbarViewController.mapOverItems(op);
- Folder folder = Folder.getOpen(mContext);
- if (folder != null) {
- folder.iterateOverItems(op);
- }
- mControllers.taskbarAllAppsController.updateNotificationDots(updatedDots);
- }
-
/**
* Shows the notifications and deep shortcuts associated with a Taskbar {@param icon}.
* @return the container if shown or null.
@@ -189,6 +153,14 @@
.filter(Objects::nonNull)
.collect(Collectors.toList());
+ // TODO(b/375648361): Revisit to see if this can be implemented within getSystemShortcuts().
+ if (Flags.enablePinningAppWithContextMenu()) {
+ SystemShortcut shortcut = createPinShortcut(context, item, icon);
+ if (shortcut != null) {
+ systemShortcuts.add(0, shortcut);
+ }
+ }
+
container = (PopupContainerWithArrow) context.getLayoutInflater().inflate(
R.layout.popup_container, context.getDragLayer(), false);
container.populateAndShowRows(icon, deepShortcutCount, systemShortcuts);
@@ -212,9 +184,6 @@
// append split options to APP_INFO shortcut if not in Desktop Windowing mode, the order
// here will reflect in the popup
ArrayList<SystemShortcut.Factory> shortcuts = new ArrayList<>();
- if (Flags.enablePinningAppWithContextMenu()) {
- shortcuts.add(PIN_UNPIN_ITEM);
- }
shortcuts.add(APP_INFO);
if (!mControllers.taskbarDesktopModeController
.isInDesktopModeAndNotInOverview(mContext.getDisplayId())) {
@@ -233,6 +202,24 @@
return shortcuts.stream();
}
+ @Nullable
+ private SystemShortcut createPinShortcut(BaseTaskbarContext target, ItemInfo itemInfo,
+ BubbleTextView originalView) {
+ // Predicted items use {@code HotseatPredictionController.PinPrediction} shortcut to pin.
+ if (itemInfo.isPredictedItem()) {
+ return null;
+ }
+ if (itemInfo.container == CONTAINER_HOTSEAT) {
+ return new PinToTaskbarShortcut<>(target, itemInfo, originalView, false);
+ }
+ if (mHotseatInfosList.size()
+ < mContext.getTaskbarSpecsEvaluator().getNumShownHotseatIcons()) {
+ return new PinToTaskbarShortcut<>(target, itemInfo, originalView, true);
+ }
+
+ return null;
+ }
+
@Override
public void dumpLogs(String prefix, PrintWriter pw) {
pw.println(prefix + "TaskbarPopupController:");
@@ -316,6 +303,10 @@
return index < 0 ? null : mAppInfosList[index];
}
+ public void setHotseatInfosList(SparseArray<ItemInfo> info) {
+ mHotseatInfosList = info;
+ }
+
/**
* Returns a stream of Multi Instance menu options if an app supports it.
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
index 25db960..94cff0b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.View;
@@ -38,6 +39,7 @@
import com.android.launcher3.util.ShortcutUtil;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.LogUtils;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import java.util.List;
@@ -50,6 +52,7 @@
public static final int MOVE_TO_TOP_OR_LEFT = R.id.action_move_to_top_or_left;
public static final int MOVE_TO_BOTTOM_OR_RIGHT = R.id.action_move_to_bottom_or_right;
+ public static final int CREATE_APPLICATION_BUBBLE = R.id.action_create_application_bubble;
private final LauncherApps mLauncherApps;
private final StatsLogManager mStatsLogManager;
@@ -67,6 +70,9 @@
MOVE_TO_BOTTOM_OR_RIGHT,
R.string.move_drop_target_bottom_or_right,
KeyEvent.KEYCODE_R));
+ mActions.put(CREATE_APPLICATION_BUBBLE, new LauncherAction(
+ CREATE_APPLICATION_BUBBLE, R.string.open_app_as_a_bubble,
+ KeyEvent.KEYCODE_L));
}
@Override
@@ -76,11 +82,27 @@
}
out.add(mActions.get(MOVE_TO_TOP_OR_LEFT));
out.add(mActions.get(MOVE_TO_BOTTOM_OR_RIGHT));
+ if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
+ out.add(mActions.get(CREATE_APPLICATION_BUBBLE));
+ }
}
@Override
protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
- if (item instanceof ItemInfoWithIcon
+ if (action == DEEP_SHORTCUTS) {
+ mContext.showPopupMenuForIcon((BubbleTextView) host);
+ return true;
+ } else if (action == CREATE_APPLICATION_BUBBLE) {
+ if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+ && item instanceof WorkspaceItemInfo) {
+ ShortcutInfo shortcutInfo = ((WorkspaceItemInfo) item).getDeepShortcutInfo();
+ SystemUiProxy.INSTANCE.get(mContext).showShortcutBubble(shortcutInfo);
+ return true;
+ } else if (item.getIntent() != null && item.getIntent().getPackage() != null) {
+ SystemUiProxy.INSTANCE.get(mContext).showAppBubble(item.getIntent(), item.user);
+ return true;
+ }
+ } else if (item instanceof ItemInfoWithIcon
&& (action == MOVE_TO_TOP_OR_LEFT || action == MOVE_TO_BOTTOM_OR_RIGHT)) {
ItemInfoWithIcon info = (ItemInfoWithIcon) item;
int side = action == MOVE_TO_TOP_OR_LEFT
@@ -112,10 +134,6 @@
instanceIds.first);
}
return true;
- } else if (action == DEEP_SHORTCUTS) {
- mContext.showPopupMenuForIcon((BubbleTextView) host);
-
- return true;
}
return false;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 89bcb41..ea0b81e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -207,9 +207,18 @@
return false;
}
- /** Returns {@code true} if Home All Apps available instead of Taskbar All Apps. */
- protected boolean canToggleHomeAllApps() {
- return false;
+
+ /**
+ * Toggles all apps UI. Default implementation opens Taskbar All Apps, but may be overridden to
+ * open different Alls Apps variant depending on the context.
+ * @param focusSearch indicates whether All Apps should be opened with search input focused.
+ */
+ protected void toggleAllApps(boolean focusSearch) {
+ if (focusSearch) {
+ mControllers.taskbarAllAppsController.toggleSearch();
+ } else {
+ mControllers.taskbarAllAppsController.toggle();
+ }
}
@CallSuper
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index c92f20b..e589c87 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -62,7 +62,6 @@
import com.android.launcher3.taskbar.customization.TaskbarDividerContainer;
import com.android.launcher3.uioverrides.PredictedAppIcon;
import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.util.GroupTask;
@@ -478,7 +477,7 @@
}
// Recents divider takes priority.
- if (!mAddedDividerForRecents) {
+ if (!mAddedDividerForRecents && !mActivityContext.areDesktopTasksVisible()) {
updateAllAppsDivider();
}
}
@@ -1098,20 +1097,6 @@
}
/**
- * Maps {@code op} over all the child views.
- */
- public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
- // map over all the shortcuts on the taskbar
- for (int i = 0; i < getChildCount(); i++) {
- View item = getChildAt(i);
- // TODO(b/344657629): Support GroupTask as well for notification dots/popup
- if (item.getTag() instanceof ItemInfo itemInfo && op.evaluate(itemInfo, item)) {
- return;
- }
- }
- }
-
- /**
* Finds the first icon to match one of the given matchers, from highest to lowest priority.
*
* @return The first match, or All Apps button if no match was found.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index c7ef960..066d4df 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -66,7 +66,16 @@
InteractionJankMonitorWrapper.begin(v, Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS,
/* tag= */ "TASKBAR_BUTTON");
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP);
- mControllers.taskbarAllAppsController.toggle();
+ if (DisplayController.showLockedTaskbarOnHome(mActivity)
+ || DisplayController.showDesktopTaskbarForFreeformDisplay(mActivity)) {
+ // If the taskbar can be shown on the home screen, use mAllAppsToggler to toggle all
+ // apps, which will toggle the launcher activity all apps when on home screen.
+ // TODO(b/395913143): Reconsider this if a gap in taskbar all apps functionality that
+ // prevents users to drag items to workspace is addressed.
+ mControllers.uiController.toggleAllApps(false);
+ } else {
+ mControllers.taskbarAllAppsController.toggle();
+ }
}
/** Trigger All Apps button long click action. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 384468c..6ae13d4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -83,14 +83,16 @@
import com.android.launcher3.model.data.TaskItemInfo;
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
+import com.android.launcher3.taskbar.customization.TaskbarAllAppsButtonContainer;
+import com.android.launcher3.taskbar.customization.TaskbarDividerContainer;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LauncherBindableItemsContainer;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.SandboxContext;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.SingleTask;
import com.android.systemui.shared.recents.model.Task;
@@ -735,10 +737,21 @@
for (View iconView : getIconViews()) {
if (iconView instanceof BubbleTextView btv) {
btv.updateRunningState(getRunningAppState(btv));
+ if (shouldUpdateIconContentDescription(btv)) {
+ btv.setContentDescription(
+ btv.getContentDescription() + " " + btv.getIconStateDescription());
+ }
}
}
}
+ private boolean shouldUpdateIconContentDescription(BubbleTextView btv) {
+ boolean isInDesktopMode = mControllers.taskbarDesktopModeController.isInDesktopMode();
+ boolean isAllAppsButton = btv instanceof TaskbarAllAppsButtonContainer;
+ boolean isDividerButton = btv instanceof TaskbarDividerContainer;
+ return isInDesktopMode && !isAllAppsButton && !isDividerButton;
+ }
+
/**
* @return A set of Task ids of running apps that are pinned in the taskbar.
*/
@@ -1167,11 +1180,8 @@
mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY());
}
- /**
- * Maps the given operator to all the top-level children of TaskbarView.
- */
- public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
- mTaskbarView.mapOverItems(op);
+ public LauncherBindableItemsContainer getContent() {
+ return mModelCallbacks;
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index ddbf3b7..6c55b28 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -35,7 +35,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.function.Predicate;
/**
* Handles the all apps overlay window initialization, updates, and its data.
* <p>
@@ -120,13 +119,6 @@
mZeroStateSearchSuggestions = zeroStateSearchSuggestions;
}
- /** Updates the current notification dots. */
- public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- if (mAppsView != null) {
- mAppsView.getAppsStore().updateNotificationDots(updatedDots);
- }
- }
-
/** Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay window. */
public void toggle() {
toggle(false);
@@ -218,6 +210,11 @@
mAppsView = null;
}
+ @Nullable
+ public TaskbarAllAppsContainerView getAppsView() {
+ return mAppsView;
+ }
+
@VisibleForTesting
public int getTaskbarAllAppsTopPadding() {
// Allow null-pointer since this should only be null if the apps view is not showing.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarLocationDropTarget.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarLocationDropTarget.kt
new file mode 100644
index 0000000..383f4d2
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarLocationDropTarget.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.taskbar.bubbles
+
+import android.graphics.Rect
+import android.view.View
+import com.android.launcher3.DropTarget
+import com.android.launcher3.dragndrop.DragOptions
+import com.android.launcher3.model.data.ItemInfo
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+
+/**
+ * Implementation of the {@link DropTarget} that handles drag and drop events over the bubble bar
+ * locations.
+ */
+class BubbleBarLocationDropTarget(
+ private val bubbleBarLocation: BubbleBarLocation,
+ private val bubbleBarDragListener: BubbleBarDragListener,
+) : DropTarget {
+
+ /** Controller that takes care of the bubble bar drag events inside launcher process. */
+ interface BubbleBarDragListener {
+
+ /** Called when the drag event is over the bubble bar drop zone. */
+ fun onLauncherItemDraggedOverBubbleBarDragZone(location: BubbleBarLocation)
+
+ /** Called when the drag event leaves the bubble bar drop zone. */
+ fun onLauncherItemDraggedOutsideBubbleBarDropZone()
+
+ /** Called when the drop event happens over the bubble bar drop zone. */
+ fun onLauncherItemDroppedOverBubbleBarDragZone(
+ location: BubbleBarLocation,
+ itemInfo: ItemInfo,
+ )
+
+ /** Gets the hit [rect][android.graphics.Rect] of the bubble bar location. */
+ fun getBubbleBarLocationHitRect(bubbleBarLocation: BubbleBarLocation, outRect: Rect)
+
+ /** Provides the view that will accept the drop. */
+ fun getDropView(): View
+ }
+
+ private var isShowingDropTarget = false
+
+ override fun isDropEnabled(): Boolean = true
+
+ override fun onDrop(dragObject: DropTarget.DragObject, options: DragOptions) {
+ val itemInfo = dragObject.dragInfo ?: return
+ // TODO(b/397459664) : fix task bar icon animation after drop
+ // TODO(b/397459664) : update bubble bar location
+ bubbleBarDragListener.onLauncherItemDroppedOverBubbleBarDragZone(
+ bubbleBarLocation,
+ itemInfo,
+ )
+ }
+
+ override fun onDragEnter(dragObject: DropTarget.DragObject) {}
+
+ override fun onDragOver(dragObject: DropTarget.DragObject) {
+ if (isShowingDropTarget) return
+ isShowingDropTarget = true
+ bubbleBarDragListener.onLauncherItemDraggedOverBubbleBarDragZone(bubbleBarLocation)
+ }
+
+ override fun onDragExit(dragObject: DropTarget.DragObject) {
+ // TODO(b/397459664) : fix the issue for no bubbles, when moving task bar icon out of
+ // the bubble bar drag zone drag ends and swipes gesture swipes the overview
+ if (!isShowingDropTarget) return
+ isShowingDropTarget = false
+ bubbleBarDragListener.onLauncherItemDraggedOutsideBubbleBarDropZone()
+ }
+
+ override fun acceptDrop(dragObject: DropTarget.DragObject): Boolean = true
+
+ override fun prepareAccessibilityDrop() {}
+
+ override fun getHitRectRelativeToDragLayer(outRect: Rect) {
+ bubbleBarDragListener.getBubbleBarLocationHitRect(bubbleBarLocation, outRect)
+ }
+
+ override fun getDropView(): View = bubbleBarDragListener.getDropView()
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index b90a5b0..1f5c541 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -24,6 +24,8 @@
import android.animation.Animator;
import android.animation.AnimatorSet;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
@@ -43,11 +45,15 @@
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.taskbar.TaskbarDragController;
import com.android.launcher3.taskbar.TaskbarInsetsController;
import com.android.launcher3.taskbar.TaskbarSharedState;
import com.android.launcher3.taskbar.TaskbarStashController;
+import com.android.launcher3.taskbar.bubbles.BubbleBarLocationDropTarget.BubbleBarDragListener;
import com.android.launcher3.taskbar.bubbles.animation.BubbleBarViewAnimator;
import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutController;
import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutPositioner;
@@ -59,6 +65,7 @@
import com.android.quickstep.SystemUiProxy;
import com.android.wm.shell.Flags;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.DeviceConfig;
import java.io.PrintWriter;
import java.util.List;
@@ -117,6 +124,61 @@
updateTranslationY();
setBubbleBarScaleAndPadding(pinningProgress);
});
+ private final BubbleBarDragListener mDragListener = new BubbleBarDragListener() {
+
+ @NonNull
+ @Override
+ public void getBubbleBarLocationHitRect(@NonNull BubbleBarLocation bubbleBarLocation,
+ Rect outRect) {
+ Point screenSize = DisplayController.INSTANCE.get(mActivity).getInfo().currentSize;
+ outRect.top = screenSize.y - mBubbleBarDropTargetSize;
+ outRect.bottom = screenSize.y;
+ if (bubbleBarLocation.isOnLeft(mBarView.isLayoutRtl())) {
+ outRect.left = 0;
+ outRect.right = mBubbleBarDropTargetSize;
+ } else {
+ outRect.left = screenSize.x - mBubbleBarDropTargetSize;
+ outRect.right = screenSize.x;
+ }
+ }
+
+ @Override
+ public void onLauncherItemDroppedOverBubbleBarDragZone(@NonNull BubbleBarLocation location,
+ @NonNull ItemInfo itemInfo) {
+ //TODO(b/397459664) : fix drag interruption when there are no bubbles
+ //TODO(b/397459664) : update bubble bar location
+ ShortcutInfo shortcutInfo = null;
+ if (itemInfo instanceof WorkspaceItemInfo) {
+ shortcutInfo = ((WorkspaceItemInfo) itemInfo).getDeepShortcutInfo();
+ }
+ Intent itemIntent = itemInfo.getIntent();
+ SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(mActivity);
+ if (shortcutInfo != null) {
+ systemUiProxy.showShortcutBubble(shortcutInfo);
+ } else if (itemIntent != null && itemIntent.getComponent() != null) {
+ systemUiProxy.showAppBubble(itemIntent, itemInfo.user);
+ }
+ }
+
+ @Override
+ public void onLauncherItemDraggedOutsideBubbleBarDropZone() {
+ //TODO(b/397459664) : hide expanded view drop target
+ onItemDraggedOutsideBubbleBarDropZone();
+ }
+
+ @Override
+ public void onLauncherItemDraggedOverBubbleBarDragZone(
+ @NonNull BubbleBarLocation location) {
+ //TODO(b/397459664) : show expanded view drop target
+ onDragItemOverBubbleBarDragZone(location);
+ }
+
+ @NonNull
+ @Override
+ public View getDropView() {
+ return mBarView;
+ }
+ };
// Modified when swipe up is happening on the bubble bar or task bar.
private float mBubbleBarSwipeUpTranslationY;
@@ -139,8 +201,12 @@
private BubbleBarFlyoutController mBubbleBarFlyoutController;
private BubbleBarPinController mBubbleBarPinController;
private TaskbarSharedState mTaskbarSharedState;
+ private TaskbarDragController mTaskbarDragController;
+ private final BubbleBarLocationDropTarget mBubbleBarLeftDropTarget;
+ private final BubbleBarLocationDropTarget mBubbleBarRightDropTarget;
private final TimeSource mTimeSource = System::currentTimeMillis;
private final int mTaskbarTranslationDelta;
+ private final int mBubbleBarDropTargetSize;
@Nullable
private BubbleBarBoundsChangeListener mBoundsChangeListener;
@@ -158,11 +224,21 @@
R.dimen.bubblebar_transient_taskbar_min_distance);
mDragElevation = res.getDimensionPixelSize(R.dimen.bubblebar_drag_elevation);
mTaskbarTranslationDelta = getBubbleBarTranslationDeltaForTaskbar(activity);
+ if (DeviceConfig.isSmallTablet(mActivity)) {
+ mBubbleBarDropTargetSize = res.getDimensionPixelSize(R.dimen.drag_zone_bubble_fold);
+ } else {
+ mBubbleBarDropTargetSize = res.getDimensionPixelSize(R.dimen.drag_zone_bubble_tablet);
+ }
+ mBubbleBarLeftDropTarget = new BubbleBarLocationDropTarget(BubbleBarLocation.LEFT,
+ mDragListener);
+ mBubbleBarRightDropTarget = new BubbleBarLocationDropTarget(BubbleBarLocation.RIGHT,
+ mDragListener);
}
/** Initializes controller. */
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers,
TaskbarViewPropertiesProvider taskbarViewPropertiesProvider) {
+ mTaskbarDragController = controllers.taskbarDragController;
mTaskbarSharedState = controllers.getSharedState();
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleBarController = bubbleControllers.bubbleBarController;
@@ -264,6 +340,8 @@
mBubbleBarController.updateBubbleBarLocation(location, source);
}
};
+ mTaskbarDragController.addDropTarget(mBubbleBarLeftDropTarget);
+ mTaskbarDragController.addDropTarget(mBubbleBarRightDropTarget);
}
/** Returns animated float property responsible for pinning transition animation. */
@@ -542,7 +620,9 @@
*/
public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) {
mBarView.showDropTarget(/* isDropTarget = */ true);
- mIsLocationUpdatedForDropTarget = getBubbleBarLocation() != bubbleBarLocation;
+ boolean isRtl = mBarView.isLayoutRtl();
+ mIsLocationUpdatedForDropTarget = getBubbleBarLocation().isOnLeft(isRtl)
+ != bubbleBarLocation.isOnLeft(isRtl);
if (mIsLocationUpdatedForDropTarget) {
animateBubbleBarLocation(bubbleBarLocation);
}
@@ -1278,6 +1358,8 @@
/** Called when the controller is destroyed. */
public void onDestroy() {
adjustTaskbarAndHotseatToBubbleBarState(/*isBubbleBarExpanded = */false);
+ mTaskbarDragController.removeDropTarget(mBubbleBarLeftDropTarget);
+ mTaskbarDragController.removeDropTarget(mBubbleBarRightDropTarget);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
index 822ca64..f1ed6c5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
@@ -26,6 +26,8 @@
numColumns: Int = taskbarActivityContext.deviceProfile.inv.numColumns,
) {
var taskbarIconSize: TaskbarIconSize = getIconSizeByGrid(numColumns, numRows)
+ val numShownHotseatIcons
+ get() = taskbarActivityContext.deviceProfile.numShownHotseatIcons
// TODO(b/341146605) : initialize it to taskbar container in later cl.
private var taskbarContainer: List<TaskbarContainer> = emptyList()
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
index 64cc47c..636d89b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
@@ -18,12 +18,11 @@
import android.content.Context;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
-import com.android.launcher3.dot.DotInfo;
-import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.taskbar.BaseTaskbarContext;
import com.android.launcher3.taskbar.TaskbarActivityContext;
@@ -116,11 +115,6 @@
}
@Override
- public boolean isBindingItems() {
- return mTaskbarContext.isBindingItems();
- }
-
- @Override
public View.OnClickListener getItemOnClickListener() {
return mTaskbarContext.getItemOnClickListener();
}
@@ -130,6 +124,7 @@
return mDragController::startDragOnLongClick;
}
+ @NonNull
@Override
public PopupDataProvider getPopupDataProvider() {
return mTaskbarContext.getPopupDataProvider();
@@ -141,11 +136,6 @@
}
@Override
- public DotInfo getDotInfoForItem(ItemInfo info) {
- return mTaskbarContext.getDotInfoForItem(info);
- }
-
- @Override
public void onDragStart() {}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index aab8ad1..484978c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -48,7 +48,6 @@
import static com.android.launcher3.popup.SystemShortcut.BUBBLE_SHORTCUT;
import static com.android.launcher3.popup.SystemShortcut.DONT_SUGGEST_APP;
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
-import static com.android.launcher3.popup.SystemShortcut.PIN_UNPIN_ITEM;
import static com.android.launcher3.popup.SystemShortcut.PRIVATE_PROFILE_INSTALL;
import static com.android.launcher3.popup.SystemShortcut.UNINSTALL_APP;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
@@ -475,9 +474,6 @@
List<SystemShortcut.Factory> shortcuts = new ArrayList(Arrays.asList(
APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController));
- if (Flags.enablePinningAppWithContextMenu()) {
- shortcuts.add(0, PIN_UNPIN_ITEM);
- }
shortcuts.addAll(getSplitShortcuts());
shortcuts.add(WIDGETS);
shortcuts.add(INSTALL);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
index 04e1905..79328df 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
@@ -36,8 +36,8 @@
import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X
import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y
import com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW
-import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
import com.android.quickstep.util.AnimUtils
+import com.android.quickstep.views.AddDesktopButton
import com.android.quickstep.views.ClearAllButton
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET
@@ -303,8 +303,8 @@
)
recentsView.addDeskButton?.let {
propertySetter.setFloat(
- it.visibilityAlphaProperty,
- MULTI_PROPERTY_VALUE,
+ it,
+ AddDesktopButton.VISIBILITY_ALPHA,
if (state.areElementsVisible(launcher, LauncherState.ADD_DESK_BUTTON)) 1f else 0f,
LINEAR,
)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
index 88b7155..454a307 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
@@ -36,7 +36,6 @@
import com.android.quickstep.views.TaskView
import com.google.android.msdl.data.model.MSDLToken
import kotlin.math.abs
-import kotlin.math.sign
/** Touch controller for handling task view card dismiss swipes */
class TaskViewDismissTouchController<CONTAINER>(
@@ -53,6 +52,8 @@
recentsView.pagedOrientationHandler.upDownSwipeDirection,
)
private val isRtl = isRtl(container.resources)
+ private val upDirection: Int = recentsView.pagedOrientationHandler.getUpDirection(isRtl)
+
private val tempTaskThumbnailBounds = Rect()
private var taskBeingDragged: TaskView? = null
@@ -96,7 +97,11 @@
}
onControllerTouchEvent(ev)
- return detector.isDraggingState && detector.wasInitialTouchPositive()
+ val upDirectionIsPositive = upDirection == SingleAxisSwipeDetector.DIRECTION_POSITIVE
+ val wasInitialTouchUp =
+ (upDirectionIsPositive && detector.wasInitialTouchPositive()) ||
+ (!upDirectionIsPositive && !detector.wasInitialTouchPositive())
+ return detector.isDraggingState && wasInitialTouchUp
}
override fun onControllerTouchEvent(ev: MotionEvent?): Boolean = detector.onTouchEvent(ev)
@@ -107,25 +112,27 @@
if (!canInterceptTouch(ev)) {
return false
}
-
taskBeingDragged =
recentsView.taskViews
.firstOrNull {
recentsView.isTaskViewVisible(it) && container.dragLayer.isEventOverView(it, ev)
}
?.also {
+ val secondaryLayerDimension =
+ recentsView.pagedOrientationHandler.getSecondaryDimension(
+ container.dragLayer
+ )
// Dismiss length as bottom of task so it is fully off screen when dismissed.
it.getThumbnailBounds(tempTaskThumbnailBounds, relativeToDragLayer = true)
- dismissLength = tempTaskThumbnailBounds.bottom
+ dismissLength =
+ recentsView.pagedOrientationHandler.getTaskDismissLength(
+ secondaryLayerDimension,
+ tempTaskThumbnailBounds,
+ )
verticalFactor =
- recentsView.pagedOrientationHandler.secondaryTranslationDirectionFactor
+ recentsView.pagedOrientationHandler.getTaskDismissVerticalDirection()
}
-
- detector.setDetectableScrollConditions(
- recentsView.pagedOrientationHandler.getUpDirection(isRtl),
- /* ignoreSlop = */ false,
- )
-
+ detector.setDetectableScrollConditions(upDirection, /* ignoreSlop= */ false)
return true
}
@@ -148,8 +155,8 @@
boundToRange(abs(currentDisplacement), 0f, dismissLength.toFloat())
// When swiping below origin, allow slight undershoot to simulate resisting the movement.
val totalDisplacement =
- if (isDisplacementPositiveDirection(currentDisplacement))
- boundedDisplacement * sign(currentDisplacement)
+ if (recentsView.pagedOrientationHandler.isGoingUp(currentDisplacement, isRtl))
+ boundedDisplacement * verticalFactor
else
mapToRange(
boundedDisplacement,
@@ -158,7 +165,7 @@
0f,
container.resources.getDimension(R.dimen.task_dismiss_max_undershoot),
DECELERATE,
- )
+ ) * -verticalFactor
taskBeingDragged.secondaryDismissTranslationProperty.setValue(
taskBeingDragged,
totalDisplacement,
@@ -207,8 +214,9 @@
}
val isBeyondDismissThreshold =
abs(currentDisplacement) > abs(DISMISS_THRESHOLD_FRACTION * dismissLength)
- val isFlingingTowardsDismiss = detector.isFling(velocity) && velocity < 0
- val isFlingingTowardsRestState = detector.isFling(velocity) && velocity > 0
+ val velocityIsGoingUp = recentsView.pagedOrientationHandler.isGoingUp(velocity, isRtl)
+ val isFlingingTowardsDismiss = detector.isFling(velocity) && velocityIsGoingUp
+ val isFlingingTowardsRestState = detector.isFling(velocity) && !velocityIsGoingUp
val isDismissing =
isFlingingTowardsDismiss || (isBeyondDismissThreshold && !isFlingingTowardsRestState)
springAnimation =
@@ -232,10 +240,6 @@
}
}
- // Returns if the current task being dragged is towards "positive" (e.g. dismissal).
- private fun isDisplacementPositiveDirection(displacement: Float): Boolean =
- sign(displacement) == sign(verticalFactor.toFloat())
-
private fun clearState() {
detector.finishedScrolling()
detector.setDetectableScrollConditions(0, false)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
index c740dad..8ee552d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
@@ -53,6 +53,7 @@
recentsView.pagedOrientationHandler.upDownSwipeDirection,
)
private val isRtl = isRtl(container.resources)
+ private val downDirection = recentsView.pagedOrientationHandler.getDownDirection(isRtl)
private var taskBeingDragged: TaskView? = null
private var launchEndDisplacement: Float = 0f
@@ -104,7 +105,11 @@
}
}
onControllerTouchEvent(ev)
- return detector.isDraggingState && !detector.wasInitialTouchPositive()
+ val downDirectionIsNegative = downDirection == SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+ val wasInitialTouchDown =
+ (downDirectionIsNegative && !detector.wasInitialTouchPositive()) ||
+ (!downDirectionIsNegative && detector.wasInitialTouchPositive())
+ return detector.isDraggingState && wasInitialTouchDown
}
override fun onControllerTouchEvent(ev: MotionEvent) = detector.onTouchEvent(ev)
@@ -120,15 +125,12 @@
}
?.also {
verticalFactor =
- recentsView.pagedOrientationHandler.secondaryTranslationDirectionFactor
+ recentsView.pagedOrientationHandler.getTaskDragDisplacementFactor(isRtl)
}
if (!canTaskLaunchTaskView(taskBeingDragged)) {
return false
}
- detector.setDetectableScrollConditions(
- recentsView.pagedOrientationHandler.getDownDirection(isRtl),
- /* ignoreSlop = */ false,
- )
+ detector.setDetectableScrollConditions(downDirection, /* ignoreSlop= */ false)
return true
}
@@ -143,7 +145,10 @@
recentsView.createTaskLaunchAnimation(taskBeingDragged, maxDuration, ZOOM_IN)
// Since the thumbnail is what is filling the screen, based the end displacement on it.
taskBeingDragged.getThumbnailBounds(tempRect, /* relativeToDragLayer= */ true)
- launchEndDisplacement = (secondaryLayerDimension - tempRect.bottom).toFloat()
+ launchEndDisplacement =
+ recentsView.pagedOrientationHandler
+ .getTaskLaunchLength(secondaryLayerDimension, tempRect)
+ .toFloat() * verticalFactor
playbackController =
pendingAnimation.createPlaybackController()?.apply {
taskViewRecentsTouchContext.onUserControlledAnimationCreated(this)
@@ -163,8 +168,9 @@
val isBeyondLaunchThreshold =
abs(playbackController.progressFraction) > abs(LAUNCH_THRESHOLD_FRACTION)
- val isFlingingTowardsLaunch = detector.isFling(velocity) && velocity > 0
- val isFlingingTowardsRestState = detector.isFling(velocity) && velocity < 0
+ val velocityIsNegative = !recentsView.pagedOrientationHandler.isGoingUp(velocity, isRtl)
+ val isFlingingTowardsLaunch = detector.isFling(velocity) && velocityIsNegative
+ val isFlingingTowardsRestState = detector.isFling(velocity) && !velocityIsNegative
val isLaunching =
isFlingingTowardsLaunch || (isBeyondLaunchThreshold && !isFlingingTowardsRestState)
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index c51f659..23b8e36 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -102,6 +102,7 @@
import android.window.TransitionInfo;
import android.window.WindowAnimationState;
+import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
@@ -221,6 +222,7 @@
private final Runnable mLauncherOnDestroyCallback = () -> {
ActiveGestureProtoLogProxy.logLauncherDestroyed();
+ mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
mRecentsView = null;
mContainer = null;
mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
@@ -313,7 +315,7 @@
*/
private static final int LOG_NO_OP_PAGE_INDEX = -1;
- protected final TaskAnimationManager mTaskAnimationManager;
+ protected TaskAnimationManager mTaskAnimationManager;
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
private RunningWindowAnim[] mRunningWindowAnim;
// Possible second animation running at the same time as mRunningWindowAnim
@@ -1114,9 +1116,6 @@
public void onGestureEnded(float endVelocityPxPerMs, PointF velocityPxPerMs) {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_speed);
- Log.d(TAG, "onGestureEnded: mGestureStarted=" + mGestureStarted
- + ", mIsMotionPaused=" + mIsMotionPaused
- + ", flingThresholdPassed=" + (Math.abs(endVelocityPxPerMs) > flingThreshold));
boolean isFling = mGestureStarted && !mIsMotionPaused
&& Math.abs(endVelocityPxPerMs) > flingThreshold;
mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
@@ -1265,12 +1264,12 @@
dpiFromPx(velocityPxPerMs.x),
dpiFromPx(velocityPxPerMs.y),
Math.toDegrees(Math.atan2(-velocityPxPerMs.y, velocityPxPerMs.x)));
+
if (mGestureState.isHandlingAtomicEvent()) {
// Button mode, this is only used to go to recents.
return RECENTS;
}
- Log.d(TAG, "calculateEndTarget: isCancel=" + isCancel + ", isFlingY=" + isFlingY);
GestureEndTarget endTarget;
if (isCancel) {
endTarget = LAST_TASK;
@@ -1280,7 +1279,6 @@
endTarget = calculateEndTargetForNonFling(velocityPxPerMs);
}
- Log.d(TAG, "calculateEndTarget: endTarget(1)=" + endTarget);
if (mDeviceState.isOverviewDisabled() && endTarget == RECENTS) {
return LAST_TASK;
}
@@ -1299,7 +1297,6 @@
return LAST_TASK;
}
}
- Log.d(TAG, "calculateEndTarget: endTarget(2)=" + endTarget);
return endTarget;
}
@@ -1308,12 +1305,9 @@
final boolean willGoToNewTask =
isScrollingToNewTask() && Math.abs(velocity.x) > Math.abs(endVelocity);
final boolean isSwipeUp = endVelocity < 0;
- Log.d(TAG, "calculateEndTargetForFlingY: willGoToNewTask=" + willGoToNewTask
- + ", isSwipeUp=" + isSwipeUp);
if (!isSwipeUp) {
final boolean isCenteredOnNewTask = mRecentsView != null
&& mRecentsView.getDestinationPage() != mRecentsView.getRunningTaskIndex();
- Log.d(TAG, "calculateEndTargetForFlingY: isCenteredOnNewTask=" + isCenteredOnNewTask);
return willGoToNewTask || isCenteredOnNewTask ? NEW_TASK : LAST_TASK;
}
@@ -1326,9 +1320,6 @@
// Fully gestural mode.
final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_speed);
- Log.d(TAG, "calculateEndTargetForNonFling: isScrollingToNewTask=" + isScrollingToNewTask
- + ", isFlingX=" + isFlingX
- + ", mIsMotionPaused=" + mIsMotionPaused);
if (isScrollingToNewTask && isFlingX) {
// Flinging towards new task takes precedence over mIsMotionPaused (which only
// checks y-velocity).
@@ -1338,7 +1329,6 @@
} else if (isScrollingToNewTask) {
return NEW_TASK;
}
- Log.d(TAG, "calculateEndTargetForNonFling: mCanSlowSwipeGoHome=" + mCanSlowSwipeGoHome);
return velocity.y < 0 && mCanSlowSwipeGoHome ? HOME : LAST_TASK;
}
@@ -1382,7 +1372,7 @@
&& mIsTransientTaskbar
&& mContainerInterface.getTaskbarController() != null) {
mContainerInterface.getTaskbarController()
- .setUserIsNotGoingHome(endTarget != GestureState.GestureEndTarget.HOME);
+ .setUserIsNotGoingHome(endTarget != HOME);
}
float endShift = endTarget.isLauncher ? 1 : 0;
@@ -2070,7 +2060,7 @@
* specific edge case: if we switch from A to B, and back to A before B appears, we need to
* start A again to ensure it stays on top.
*/
- @androidx.annotation.CallSuper
+ @CallSuper
protected void onRestartPreviouslyAppearedTask() {
// Finish the controller here, since we won't get onTaskAppeared() for a task that already
// appeared.
diff --git a/quickstep/src/com/android/quickstep/DisplayModel.kt b/quickstep/src/com/android/quickstep/DisplayModel.kt
index cbc2f7d..ac94375 100644
--- a/quickstep/src/com/android/quickstep/DisplayModel.kt
+++ b/quickstep/src/com/android/quickstep/DisplayModel.kt
@@ -20,28 +20,28 @@
import android.hardware.display.DisplayManager
import android.util.Log
import android.util.SparseArray
+import android.view.Display
import androidx.core.util.valueIterator
+import com.android.launcher3.util.Executors
import com.android.quickstep.DisplayModel.DisplayResource
+import java.io.PrintWriter
/** data model for managing resources with lifecycles that match that of the connected display */
abstract class DisplayModel<RESOURCE_TYPE : DisplayResource>(val context: Context) {
companion object {
- private const val TAG = "DisplayViewModel"
+ private const val TAG = "DisplayModel"
private const val DEBUG = false
}
- protected val displayManager =
- context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+ private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
protected val displayResourceArray = SparseArray<RESOURCE_TYPE>()
- abstract fun createDisplayResource(displayId: Int)
-
- protected val displayListener: DisplayManager.DisplayListener =
+ private val displayListener: DisplayManager.DisplayListener =
(object : DisplayManager.DisplayListener {
override fun onDisplayAdded(displayId: Int) {
if (DEBUG) Log.d(TAG, "onDisplayAdded: displayId=$displayId")
- createDisplayResource(displayId)
+ storeDisplayResource(displayId)
}
override fun onDisplayRemoved(displayId: Int) {
@@ -54,6 +54,17 @@
}
})
+ protected abstract fun createDisplayResource(display: Display): RESOURCE_TYPE
+
+ protected fun registerDisplayListener() {
+ displayManager.registerDisplayListener(displayListener, Executors.MAIN_EXECUTOR.handler)
+ // In the scenario where displays were added before this display listener was
+ // registered, we should store the DisplayResources for those displays directly.
+ displayManager.displays
+ .filter { getDisplayResource(it.displayId) == null }
+ .forEach { storeDisplayResource(it.displayId) }
+ }
+
fun destroy() {
displayResourceArray.valueIterator().forEach { displayResource ->
displayResource.cleanup()
@@ -73,7 +84,36 @@
displayResourceArray.remove(displayId)
}
- abstract class DisplayResource() {
+ fun storeDisplayResource(displayId: Int) {
+ if (DEBUG) Log.d(TAG, "store: displayId=$displayId")
+ getDisplayResource(displayId)?.let {
+ return
+ }
+ val display = displayManager.getDisplay(displayId)
+ if (display == null) {
+ if (DEBUG)
+ Log.w(
+ TAG,
+ "storeDisplayResource: could not create display for displayId=$displayId",
+ Exception(),
+ )
+ return
+ }
+ displayResourceArray[displayId] = createDisplayResource(display)
+ }
+
+ fun dump(prefix: String, writer: PrintWriter) {
+ writer.println("${prefix}${this::class.simpleName}: display resources=[")
+
+ displayResourceArray.valueIterator().forEach { displayResource ->
+ displayResource.dump("${prefix}\t", writer)
+ }
+ writer.println("${prefix}]")
+ }
+
+ abstract class DisplayResource {
abstract fun cleanup()
+
+ abstract fun dump(prefix: String, writer: PrintWriter)
}
}
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index e1d4536..74aa8e2 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -30,6 +30,7 @@
import android.content.Intent;
import android.os.SystemClock;
+import android.view.Display;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.window.TransitionInfo;
@@ -158,6 +159,7 @@
private final BaseContainerInterface mContainerInterface;
private final MultiStateCallback mStateCallback;
private final int mGestureId;
+ private final int mDisplayId;
public enum TrackpadGestureType {
NONE,
@@ -190,16 +192,18 @@
private boolean mHandlingAtomicEvent;
private boolean mIsInExtendedSlopRegion;
- public GestureState(OverviewComponentObserver componentObserver, int gestureId) {
+ public GestureState(OverviewComponentObserver componentObserver, int displayId, int gestureId) {
+ mDisplayId = displayId;
mHomeIntent = componentObserver.getHomeIntent();
mOverviewIntent = componentObserver.getOverviewIntent();
- mContainerInterface = componentObserver.getContainerInterface();
+ mContainerInterface = componentObserver.getContainerInterface(displayId);
mStateCallback = new MultiStateCallback(
STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState);
mGestureId = gestureId;
}
public GestureState(GestureState other) {
+ mDisplayId = other.mDisplayId;
mHomeIntent = other.mHomeIntent;
mOverviewIntent = other.mOverviewIntent;
mContainerInterface = other.mContainerInterface;
@@ -214,6 +218,7 @@
public GestureState() {
// Do nothing, only used for initializing the gesture state prior to user unlock
+ mDisplayId = Display.DEFAULT_DISPLAY;
mHomeIntent = new Intent();
mOverviewIntent = new Intent();
mContainerInterface = null;
@@ -285,6 +290,13 @@
}
/**
+ * @return the id for the display this particular gesture was performed on.
+ */
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ /**
* Sets if the gesture is is from the trackpad, if so, whether 3-finger, or 4-finger
*/
public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) {
@@ -545,6 +557,7 @@
public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "GestureState:");
+ pw.println(prefix + "\tdisplayID=" + mDisplayId);
pw.println(prefix + "\tgestureID=" + mGestureId);
pw.println(prefix + "\trunningTask=" + mRunningTask);
pw.println(prefix + "\tendTarget=" + mEndTarget);
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 0185737..081ed9d 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -17,6 +17,7 @@
import android.annotation.TargetApi;
import android.os.Build;
+import android.view.Display;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -66,6 +67,10 @@
int getType();
+ default int getDisplayId() {
+ return Display.DEFAULT_DISPLAY;
+ }
+
/**
* Returns true if the user has crossed the threshold for it to be an explicit action.
*/
diff --git a/quickstep/src/com/android/quickstep/InputConsumerUtils.kt b/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
index c340c92..cd3ac12 100644
--- a/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
+++ b/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
@@ -76,7 +76,12 @@
val bubbleControllers = tac?.bubbleControllers
if (bubbleControllers != null && BubbleBarInputConsumer.isEventOnBubbles(tac, event)) {
val consumer: InputConsumer =
- BubbleBarInputConsumer(context, bubbleControllers, inputMonitorCompat)
+ BubbleBarInputConsumer(
+ context,
+ gestureState.displayId,
+ bubbleControllers,
+ inputMonitorCompat,
+ )
logInputConsumerSelectionReason(
consumer,
newCompoundString("event is on bubbles, creating new input consumer"),
@@ -285,7 +290,13 @@
"%ssystem dialog is showing, using SysUiOverlayInputConsumer",
SUBSTRING_PREFIX,
)
- base = SysUiOverlayInputConsumer(context, deviceState, inputMonitorCompat)
+ base =
+ SysUiOverlayInputConsumer(
+ context,
+ gestureState.displayId,
+ deviceState,
+ inputMonitorCompat,
+ )
}
if (
@@ -299,7 +310,13 @@
"%sTrackpad 3-finger gesture, using TrackpadStatusBarInputConsumer",
SUBSTRING_PREFIX,
)
- base = TrackpadStatusBarInputConsumer(context, base, inputMonitorCompat)
+ base =
+ TrackpadStatusBarInputConsumer(
+ context,
+ gestureState.displayId,
+ base,
+ inputMonitorCompat,
+ )
}
if (deviceState.isScreenPinningActive) {
@@ -322,7 +339,14 @@
reasonPrefix,
SUBSTRING_PREFIX,
)
- base = OneHandedModeInputConsumer(context, deviceState, base, inputMonitorCompat)
+ base =
+ OneHandedModeInputConsumer(
+ context,
+ gestureState.displayId,
+ deviceState,
+ base,
+ inputMonitorCompat,
+ )
}
if (deviceState.isAccessibilityMenuAvailable) {
@@ -332,7 +356,14 @@
reasonPrefix,
SUBSTRING_PREFIX,
)
- base = AccessibilityInputConsumer(context, deviceState, base, inputMonitorCompat)
+ base =
+ AccessibilityInputConsumer(
+ context,
+ gestureState.displayId,
+ deviceState,
+ base,
+ inputMonitorCompat,
+ )
}
} else {
val reasonPrefix = "device is not in gesture navigation mode"
@@ -354,7 +385,14 @@
reasonPrefix,
SUBSTRING_PREFIX,
)
- base = OneHandedModeInputConsumer(context, deviceState, base, inputMonitorCompat)
+ base =
+ OneHandedModeInputConsumer(
+ context,
+ gestureState.displayId,
+ deviceState,
+ base,
+ inputMonitorCompat,
+ )
}
}
logInputConsumerSelectionReason(base, reasonString)
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index afdb403..6bd3400 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -22,6 +22,7 @@
import android.os.SystemClock
import android.os.Trace
import android.util.Log
+import android.view.Display.DEFAULT_DISPLAY
import android.view.View
import android.window.TransitionInfo
import androidx.annotation.BinderThread
@@ -91,9 +92,11 @@
*/
private var keyboardTaskFocusIndex = -1
+ // TODO (b/397942185): get per-display interface
private val containerInterface: BaseContainerInterface<*, *>
- get() = overviewComponentObserver.containerInterface
+ get() = overviewComponentObserver.getContainerInterface(DEFAULT_DISPLAY)
+ // TODO (b/397942185): get per-display RecentsView
private val visibleRecentsView: RecentsView<*, *>?
get() = containerInterface.getVisibleRecentsView<RecentsView<*, *>>()
@@ -399,6 +402,7 @@
val gestureState =
touchInteractionService.createGestureState(
+ focusedDisplayId,
GestureState.DEFAULT_STATE,
GestureState.TrackpadGestureType.NONE,
)
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index bc3de41..7eacef3 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -21,6 +21,7 @@
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.launcher3.Flags.enableOverviewOnConnectedDisplays;
import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
@@ -87,7 +88,7 @@
new CopyOnWriteArrayList<>();
private String mUpdateRegisteredPackage;
- private BaseContainerInterface mContainerInterface;
+ private BaseContainerInterface mDefaultDisplayContainerInterface;
private Intent mOverviewIntent;
private boolean mIsHomeAndOverviewSame;
private boolean mIsDefaultHome;
@@ -175,8 +176,8 @@
// Set assistant visibility to 0 from launcher's perspective, ensures any elements that
// launcher made invisible become visible again before the new activity control helper
// becomes active.
- if (mContainerInterface != null) {
- mContainerInterface.onAssistantVisibilityChanged(0.f);
+ if (mDefaultDisplayContainerInterface != null) {
+ mDefaultDisplayContainerInterface.onAssistantVisibilityChanged(0.f);
}
if (SEPARATE_RECENTS_ACTIVITY.get() || Flags.enableLauncherOverviewInWindow()) {
@@ -193,7 +194,7 @@
if (!mIsHomeDisabled && (defaultHome == null || mIsDefaultHome)) {
// User default home is same as out home app. Use Overview integrated in Launcher.
- mContainerInterface = LauncherActivityInterface.INSTANCE;
+ mDefaultDisplayContainerInterface = LauncherActivityInterface.INSTANCE;
mIsHomeAndOverviewSame = true;
mOverviewIntent = mMyHomeIntent;
mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
@@ -202,12 +203,11 @@
unregisterOtherHomeAppUpdateReceiver();
} else {
// The default home app is a different launcher. Use the fallback Overview instead.
-
if (Flags.enableLauncherOverviewInWindow() || Flags.enableFallbackOverviewInWindow()) {
- mContainerInterface =
+ mDefaultDisplayContainerInterface =
mRecentsDisplayModel.getFallbackWindowInterface(DEFAULT_DISPLAY);
} else {
- mContainerInterface = FallbackActivityInterface.INSTANCE;
+ mDefaultDisplayContainerInterface = FallbackActivityInterface.INSTANCE;
}
mIsHomeAndOverviewSame = false;
mOverviewIntent = mFallbackIntent;
@@ -296,12 +296,16 @@
}
/**
- * Get the current control helper for managing interactions to the overview container.
+ * Get the current control helper for managing interactions to the overview container for
+ * the given displayId.
*
- * @return the current control helper
+ * @param displayId The display id
+ * @return the control helper for the given display
*/
- public BaseContainerInterface<?,?> getContainerInterface() {
- return mContainerInterface;
+ public BaseContainerInterface<?, ?> getContainerInterface(int displayId) {
+ return (enableOverviewOnConnectedDisplays() && displayId != DEFAULT_DISPLAY)
+ ? mRecentsDisplayModel.getFallbackWindowInterface(displayId)
+ : mDefaultDisplayContainerInterface;
}
public void dump(PrintWriter pw) {
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index f47937c..e9f7024 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -1,5 +1,7 @@
package com.android.quickstep;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.launcher3.taskbar.TaskbarThresholdUtils.getFromNavThreshold;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -210,8 +212,9 @@
}
private RecentsViewContainer getRecentsViewContainer() {
+ // TODO (b/400647896): support per-display container in e2e tests
return OverviewComponentObserver.INSTANCE.get(mContext)
- .getContainerInterface().getCreatedContainer();
+ .getContainerInterface(DEFAULT_DISPLAY).getCreatedContainer();
}
@Override
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 2c4c2f9..f506039 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -353,7 +353,7 @@
TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
- int numVisibleTasks = 0;
+ boolean isFirstVisibleTaskFound = false;
for (GroupedTaskInfo rawTask : rawTasks) {
if (rawTask.isBaseType(TYPE_DESK)) {
// TYPE_DESK tasks is only created when desktop mode can be entered,
@@ -362,6 +362,14 @@
List<DesktopTask> desktopTasks = createDesktopTasks(
rawTask.getBaseGroupedTask());
allTasks.addAll(desktopTasks);
+
+ // If any task in desktop group task is visible, set isFirstVisibleTaskFound to
+ // true. This way if there is a transparent task in the list later on, it does
+ // not get its own tile in Overview.
+ if (rawTask.getBaseGroupedTask().getTaskInfoList().stream().anyMatch(
+ taskInfo -> taskInfo.isVisible)) {
+ isFirstVisibleTaskFound = true;
+ }
}
continue;
}
@@ -402,7 +410,7 @@
tmpLockedUsers.get(task2Key.userId) /* isLocked */);
} else {
// Is fullscreen task
- if (numVisibleTasks > 0) {
+ if (isFirstVisibleTaskFound) {
boolean isExcluded = (taskInfo1.baseIntent.getFlags()
& FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
if (taskInfo1.isTopActivityTransparent && isExcluded) {
@@ -413,7 +421,7 @@
}
}
if (taskInfo1.isVisible) {
- numVisibleTasks++;
+ isFirstVisibleTaskFound = true;
}
if (task2 != null) {
Objects.requireNonNull(rawTask.getSplitBounds());
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 090ccdc..6710096 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -88,6 +88,9 @@
import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import javax.inject.Inject;
@@ -119,6 +122,7 @@
InputMethodService.canImeRenderGesturalNavButtons();
private @SystemUiStateFlags long mSystemUiStateFlags = QuickStepContract.SYSUI_STATE_AWAKE;
+ private final Map<Integer, Long> mSysUIStateFlagsPerDisplay = new ConcurrentHashMap<>();
private NavigationMode mMode = THREE_BUTTONS;
private NavBarPosition mNavBarPosition;
@@ -321,13 +325,6 @@
}
/**
- * @return the display id for the display that Launcher is running on.
- */
- public int getDisplayId() {
- return DEFAULT_DISPLAY;
- }
-
- /**
* @return whether the user has completed setup wizard
*/
public boolean isUserSetupComplete() {
@@ -353,22 +350,51 @@
}
/**
- * Updates the system ui state flags from SystemUI.
+ * Updates the system ui state flags from SystemUI for a specific display.
+ *
+ * @param stateFlags the current {@link SystemUiStateFlags} for the display.
+ * @param displayId the display's ID.
*/
- public void setSystemUiFlags(@SystemUiStateFlags long stateFlags) {
- mSystemUiStateFlags = stateFlags;
+ public void setSysUIStateFlagsForDisplay(@SystemUiStateFlags long stateFlags,
+ int displayId) {
+ mSysUIStateFlagsPerDisplay.put(displayId, stateFlags);
}
/**
- * @return the system ui state flags.
+ * Clears the system ui state flags for a specific display. This is called when the display is
+ * destroyed.
+ *
+ * @param displayId the display's ID.
+ */
+ public void clearSysUIStateFlagsForDisplay(int displayId) {
+ mSysUIStateFlagsPerDisplay.remove(displayId);
+ }
+
+ /**
+ * @return the system ui state flags for the default display.
*/
// TODO(141886704): See if we can remove this
@SystemUiStateFlags
- public long getSystemUiStateFlags() {
- return mSystemUiStateFlags;
+ public long getSysuiStateFlag() {
+ return getSystemUiStateFlags(DEFAULT_DISPLAY);
}
/**
+ * @return the system ui state flags for a given display ID.
+ */
+ @SystemUiStateFlags
+ public long getSystemUiStateFlags(int displayId) {
+ return mSysUIStateFlagsPerDisplay.getOrDefault(displayId,
+ QuickStepContract.SYSUI_STATE_AWAKE);
+ }
+
+ /**
+ * @return the display ids that have sysui state.
+ */
+ public Set<Integer> getDisplaysWithSysUIState() {
+ return mSysUIStateFlagsPerDisplay.keySet();
+ }
+ /**
* Sets the flag that indicates whether a predictive back-to-home animation is in progress
*/
public void setPredictiveBackToHomeInProgress(boolean isInProgress) {
@@ -386,8 +412,8 @@
* @return whether SystemUI is in a state where we can start a system gesture.
*/
public boolean canStartSystemGesture() {
- boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
- || (mSystemUiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0
+ boolean canStartWithNavHidden = (getSysuiStateFlag() & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
+ || (getSysuiStateFlag() & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0
|| mRotationTouchHelper.isTaskListFrozen();
return canStartWithNavHidden && canStartAnyGesture();
}
@@ -399,7 +425,7 @@
*/
public boolean canStartTrackpadGesture() {
boolean trackpadGesturesEnabled =
- (mSystemUiStateFlags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) == 0;
+ (getSysuiStateFlag() & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) == 0;
return trackpadGesturesEnabled && canStartAnyGesture();
}
@@ -407,8 +433,8 @@
* Common logic to determine if either trackpad or finger gesture can be started
*/
private boolean canStartAnyGesture() {
- boolean homeOrOverviewEnabled = (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
- || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
+ boolean homeOrOverviewEnabled = (getSysuiStateFlag() & SYSUI_STATE_HOME_DISABLED) == 0
+ || (getSysuiStateFlag() & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
long gestureDisablingStates = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED
@@ -416,7 +442,7 @@
| SYSUI_STATE_DEVICE_DREAMING
| SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION
| SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
- return (gestureDisablingStates & mSystemUiStateFlags) == 0 && homeOrOverviewEnabled;
+ return (gestureDisablingStates & getSysuiStateFlag()) == 0 && homeOrOverviewEnabled;
}
/**
@@ -424,35 +450,35 @@
* (like camera or maps)
*/
public boolean isKeyguardShowingOccluded() {
- return (mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0;
+ return (getSysuiStateFlag() & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0;
}
/**
* @return whether screen pinning is enabled and active
*/
public boolean isScreenPinningActive() {
- return (mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
+ return (getSysuiStateFlag() & SYSUI_STATE_SCREEN_PINNING) != 0;
}
/**
* @return whether assistant gesture is constraint
*/
public boolean isAssistantGestureIsConstrained() {
- return (mSystemUiStateFlags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0;
+ return (getSysuiStateFlag() & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0;
}
/**
* @return whether the bubble stack is expanded
*/
public boolean isBubblesExpanded() {
- return (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
+ return (getSysuiStateFlag() & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
}
/**
* @return whether the global actions dialog is showing
*/
public boolean isSystemUiDialogShowing() {
- return (mSystemUiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0;
+ return (getSysuiStateFlag() & SYSUI_STATE_DIALOG_SHOWING) != 0;
}
/**
@@ -466,35 +492,35 @@
* @return whether the accessibility menu is available.
*/
public boolean isAccessibilityMenuAvailable() {
- return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
+ return (getSysuiStateFlag() & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
}
/**
* @return whether the accessibility menu shortcut is available.
*/
public boolean isAccessibilityMenuShortcutAvailable() {
- return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
+ return (getSysuiStateFlag() & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
}
/**
* @return whether home is disabled (either by SUW/SysUI/device policy)
*/
public boolean isHomeDisabled() {
- return (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
+ return (getSysuiStateFlag() & SYSUI_STATE_HOME_DISABLED) != 0;
}
/**
* @return whether overview is disabled (either by SUW/SysUI/device policy)
*/
public boolean isOverviewDisabled() {
- return (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
+ return (getSysuiStateFlag() & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
}
/**
* @return whether one-handed mode is enabled and active
*/
public boolean isOneHandedModeActive() {
- return (mSystemUiStateFlags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
+ return (getSysuiStateFlag() & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
}
/**
@@ -557,7 +583,7 @@
*/
public boolean canTriggerAssistantAction(MotionEvent ev) {
return mAssistantAvailable
- && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
+ && !QuickStepContract.isAssistantGestureDisabled(getSysuiStateFlag())
&& mRotationTouchHelper.touchInAssistantRegion(ev)
&& !isTrackpadScroll(ev)
&& !isLockToAppActive();
@@ -597,7 +623,7 @@
/** Returns whether IME is rendering nav buttons, and IME is currently showing. */
public boolean isImeRenderingNavButtons() {
return mCanImeRenderGesturalNavButtons && mMode == NO_BUTTON
- && ((mSystemUiStateFlags & SYSUI_STATE_IME_VISIBLE) != 0);
+ && ((getSysuiStateFlag() & SYSUI_STATE_IME_VISIBLE) != 0);
}
/**
@@ -629,24 +655,37 @@
return touchSlop * touchSlop;
}
+ /** Returns a string representation of the system ui state flags for the default display. */
public String getSystemUiStateString() {
- return QuickStepContract.getSystemUiStateString(mSystemUiStateFlags);
+ return getSystemUiStateString(getSysuiStateFlag());
+ }
+
+ /** Returns a string representation of the system ui state flags. */
+ public String getSystemUiStateString(long flags) {
+ return QuickStepContract.getSystemUiStateString(flags);
}
public void dump(PrintWriter pw) {
pw.println("DeviceState:");
pw.println(" canStartSystemGesture=" + canStartSystemGesture());
- pw.println(" systemUiFlags=" + mSystemUiStateFlags);
+ pw.println(" systemUiFlagsForDefaultDisplay=" + getSysuiStateFlag());
pw.println(" systemUiFlagsDesc=" + getSystemUiStateString());
pw.println(" assistantAvailable=" + mAssistantAvailable);
pw.println(" assistantDisabled="
- + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
+ + QuickStepContract.isAssistantGestureDisabled(getSysuiStateFlag()));
pw.println(" isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
pw.println(" isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
pw.println(" deferredGestureRegion=" + mDeferredGestureRegion.getBounds());
pw.println(" exclusionRegion=" + mExclusionRegion.getBounds());
pw.println(" pipIsActive=" + mPipIsActive);
pw.println(" predictiveBackToHomeInProgress=" + mIsPredictiveBackToHomeInProgress);
+ for (int displayId : mSysUIStateFlagsPerDisplay.keySet()) {
+ pw.println(" systemUiFlagsForDisplay" + displayId + "=" + getSystemUiStateFlags(
+ displayId));
+ pw.println(" systemUiFlagsForDisplay" + displayId + "Desc=" + getSystemUiStateString(
+ getSystemUiStateFlags(displayId)));
+ }
+ pw.println(" RotationTouchHelper:");
mRotationTouchHelper.dump(pw);
}
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
index 1f3eb2a..0f6649b 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -655,20 +655,28 @@
* Tells SysUI to show a shortcut bubble.
*
* @param info the shortcut info used to create or identify the bubble.
+ * @param bubbleBarLocation the optional location of the bubble bar.
*/
- fun showShortcutBubble(info: ShortcutInfo?) =
+ @JvmOverloads
+ fun showShortcutBubble(info: ShortcutInfo?, bubbleBarLocation: BubbleBarLocation? = null) =
executeWithErrorLog({ "Failed call showShortcutBubble" }) {
- bubbles?.showShortcutBubble(info)
+ bubbles?.showShortcutBubble(info, bubbleBarLocation)
}
/**
* Tells SysUI to show a bubble of an app.
*
* @param intent the intent used to create the bubble.
+ * @param bubbleBarLocation the optional location of the bubble bar.
*/
- fun showAppBubble(intent: Intent?, user: UserHandle) =
+ @JvmOverloads
+ fun showAppBubble(
+ intent: Intent?,
+ user: UserHandle,
+ bubbleBarLocation: BubbleBarLocation? = null,
+ ) =
executeWithErrorLog({ "Failed call showAppBubble" }) {
- bubbles?.showAppBubble(intent, user)
+ bubbles?.showAppBubble(intent, user, bubbleBarLocation)
}
/** Tells SysUI to show the expanded view. */
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index cb11afa..64a8c25 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -318,7 +318,7 @@
mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent, options,
mCallbacks, gestureState.useSyntheticRecentsTransition());
RecentsDisplayModel.getINSTANCE().get(mCtx)
- .getRecentsWindowManager(mDeviceState.getDisplayId())
+ .getRecentsWindowManager(gestureState.getDisplayId())
.startRecentsWindow(mCallbacks);
} else {
mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent,
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index ba4c65a..ba662c4 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
@@ -40,6 +41,7 @@
import android.app.PendingIntent;
import android.app.Service;
+import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
@@ -53,9 +55,11 @@
import android.os.SystemClock;
import android.util.Log;
import android.view.Choreographer;
+import android.view.Display;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.MotionEvent;
+import android.window.DesktopModeFlags;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
@@ -89,6 +93,7 @@
import com.android.quickstep.OverviewCommandHelper.CommandType;
import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener;
import com.android.quickstep.fallback.window.RecentsDisplayModel;
+import com.android.quickstep.fallback.window.RecentsDisplayModel.RecentsDisplayResource;
import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
@@ -124,6 +129,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
+import java.util.Locale;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -139,6 +145,9 @@
private static final ConstantItem<Boolean> HAS_ENABLED_QUICKSTEP_ONCE = backedUpItem(
"launcher.has_enabled_quickstep_once", false, EncryptionType.ENCRYPTED);
+ private static final DesktopModeFlags.DesktopModeFlag ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS =
+ new DesktopModeFlags.DesktopModeFlag(Flags::enableGestureNavOnConnectedDisplays, false);
+
private final TISBinder mTISBinder = new TISBinder(this);
/**
@@ -274,11 +283,12 @@
}
@BinderThread
- public void onSystemUiStateChanged(@SystemUiStateFlags long stateFlags) {
+ public void onSystemUiStateChanged(@SystemUiStateFlags long stateFlags, int displayId) {
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
- long lastFlags = tis.mDeviceState.getSystemUiStateFlags();
- tis.mDeviceState.setSystemUiFlags(stateFlags);
- tis.onSystemUiFlagsChanged(lastFlags);
+ // Last flags is only used for the default display case.
+ long lastFlags = tis.mDeviceState.getSysuiStateFlag();
+ tis.mDeviceState.setSysUIStateFlagsForDisplay(stateFlags, displayId);
+ tis.onSystemUiFlagsChanged(lastFlags, displayId);
}));
}
@@ -292,8 +302,9 @@
@Override
public void enterStageSplitFromRunningApp(boolean leftOrTop) {
executeForTouchInteractionService(tis -> {
+ // TODO (b/397942185): support external displays
RecentsViewContainer container = tis.mOverviewComponentObserver
- .getContainerInterface().getCreatedContainer();
+ .getContainerInterface(DEFAULT_DISPLAY).getCreatedContainer();
if (container != null) {
container.enterStageSplitFromRunningApp(leftOrTop);
}
@@ -312,6 +323,9 @@
public void onDisplayRemoved(int displayId) {
executeForTaskbarManager(taskbarManager ->
taskbarManager.onDisplayRemoved(displayId));
+ executeForTouchInteractionService(tis -> {
+ tis.mDeviceState.clearSysUIStateFlagsForDisplay(displayId);
+ });
}
@BinderThread
@@ -550,6 +564,7 @@
private @Nullable ResetGestureInputConsumer mResetGestureInputConsumer;
private GestureState mGestureState = DEFAULT_STATE;
+ private InputMonitorDisplayModel mInputMonitorDisplayModel;
private InputMonitorCompat mInputMonitorCompat;
private InputEventReceiver mInputEventReceiver;
@@ -564,6 +579,8 @@
private DisplayController.DisplayInfoChangeListener mDisplayInfoChangeListener;
+ private RecentsDisplayModel mRecentsDisplayModel;
+
@Override
public void onCreate() {
super.onCreate();
@@ -574,6 +591,7 @@
mMainChoreographer = Choreographer.getInstance();
mDeviceState = RecentsAnimationDeviceState.INSTANCE.get(this);
mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(this);
+ mRecentsDisplayModel = RecentsDisplayModel.getINSTANCE().get(this);
mAllAppsActionManager = new AllAppsActionManager(
this, UI_HELPER_EXECUTOR, this::createAllAppsPendingIntent);
mTrackpadsConnected = new ActiveTrackpadList(this, () -> {
@@ -586,7 +604,7 @@
});
mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager, mNavCallbacks,
- RecentsDisplayModel.getINSTANCE().get(this));
+ mRecentsDisplayModel);
mDesktopAppLaunchTransitionManager =
new DesktopAppLaunchTransitionManager(this, SystemUiProxy.INSTANCE.get(this));
mDesktopAppLaunchTransitionManager.registerTransitions();
@@ -599,9 +617,34 @@
ScreenOnTracker.INSTANCE.get(this).addListener(mScreenOnListener);
}
+ @Nullable
+ private InputEventReceiver getInputEventReceiver(int displayId) {
+ if (ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS.isTrue()) {
+ InputMonitorResource inputMonitorResource = mInputMonitorDisplayModel == null
+ ? null : mInputMonitorDisplayModel.getDisplayResource(displayId);
+ return inputMonitorResource == null ? null : inputMonitorResource.inputEventReceiver;
+ }
+ return mInputEventReceiver;
+ }
+
+ @Nullable
+ private InputMonitorCompat getInputMonitorCompat(int displayId) {
+ if (ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS.isTrue()) {
+ InputMonitorResource inputMonitorResource = mInputMonitorDisplayModel == null
+ ? null : mInputMonitorDisplayModel.getDisplayResource(displayId);
+ return inputMonitorResource == null ? null : inputMonitorResource.inputMonitorCompat;
+ }
+ return mInputMonitorCompat;
+ }
+
private void disposeEventHandlers(String reason) {
Log.d(TAG, "disposeEventHandlers: Reason: " + reason
+ " instance=" + System.identityHashCode(this));
+ if (ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS.isTrue()) {
+ if (mInputMonitorDisplayModel == null) return;
+ mInputMonitorDisplayModel.destroy();
+ return;
+ }
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
@@ -620,10 +663,13 @@
&& (mTrackpadsConnected.isEmpty())) {
return;
}
-
- mInputMonitorCompat = new InputMonitorCompat("swipe-up", mDeviceState.getDisplayId());
- mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
- mMainChoreographer, this::onInputEvent);
+ if (ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS.isTrue()) {
+ mInputMonitorDisplayModel = new InputMonitorDisplayModel(this);
+ } else {
+ mInputMonitorCompat = new InputMonitorCompat("swipe-up", Display.DEFAULT_DISPLAY);
+ mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
+ mMainChoreographer, this::onInputEvent);
+ }
mRotationTouchHelper.updateGestureTouchRegions();
}
@@ -643,13 +689,14 @@
mTaskAnimationManager = new TaskAnimationManager(this, mDeviceState);
mOverviewComponentObserver = OverviewComponentObserver.INSTANCE.get(this);
mOverviewCommandHelper = new OverviewCommandHelper(this,
- mOverviewComponentObserver, mTaskAnimationManager,
- RecentsDisplayModel.getINSTANCE().get(this),
+ mOverviewComponentObserver, mTaskAnimationManager, mRecentsDisplayModel,
SystemUiProxy.INSTANCE.get(this).getFocusState(), mTaskbarManager);
mResetGestureInputConsumer = new ResetGestureInputConsumer(
mTaskAnimationManager, mTaskbarManager::getCurrentActivityContext);
mInputConsumer.registerInputConsumer();
- onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags());
+ for (int displayId : mDeviceState.getDisplaysWithSysUIState()) {
+ onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags(displayId), displayId);
+ }
onAssistantVisibilityChanged();
// Initialize the task tracker
@@ -687,8 +734,11 @@
private void onOverviewTargetChanged(boolean isHomeAndOverviewSame) {
mAllAppsActionManager.setHomeAndOverviewSame(isHomeAndOverviewSame);
+ // TODO (b/399089118): how will this work with per-display Taskbars? Is using the
+ // default-display container ok?
RecentsViewContainer newOverviewContainer =
- mOverviewComponentObserver.getContainerInterface().getCreatedContainer();
+ mOverviewComponentObserver.getContainerInterface(
+ DEFAULT_DISPLAY).getCreatedContainer();
if (newOverviewContainer != null) {
if (newOverviewContainer instanceof StatefulActivity activity) {
// This will also call setRecentsViewContainer() internally.
@@ -705,26 +755,33 @@
public void send(int code, Intent intent, String resolvedType,
IBinder allowlistToken, IIntentReceiver finishedReceiver,
String requiredPermission, Bundle options) {
- MAIN_EXECUTOR.execute(() -> mTaskbarManager.toggleAllApps());
+ MAIN_EXECUTOR.execute(() -> mTaskbarManager.toggleAllAppsSearch());
}
});
}
@UiThread
- private void onSystemUiFlagsChanged(@SystemUiStateFlags long lastSysUIFlags) {
+ private void onSystemUiFlagsChanged(@SystemUiStateFlags long lastSysUIFlags, int displayId) {
if (LockedUserState.get(this).isUserUnlocked()) {
- long systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
- SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
- mOverviewComponentObserver.setHomeDisabled(mDeviceState.isHomeDisabled());
- mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags);
- mTaskAnimationManager.onSystemUiFlagsChanged(lastSysUIFlags, systemUiStateFlags);
+ long systemUiStateFlags = mDeviceState.getSystemUiStateFlags(displayId);
+ mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags, displayId);
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ // The following don't care about non-default displays, at least for now. If they
+ // ever will, they should be taken care of.
+ SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
+ mOverviewComponentObserver.setHomeDisabled(mDeviceState.isHomeDisabled());
+ // TODO b/399371607 - Propagate to taskAnimationManager once overview is multi
+ // display.
+ mTaskAnimationManager.onSystemUiFlagsChanged(lastSysUIFlags, systemUiStateFlags);
+ }
}
}
@UiThread
private void onAssistantVisibilityChanged() {
if (LockedUserState.get(this).isUserUnlocked()) {
- mOverviewComponentObserver.getContainerInterface().onAssistantVisibilityChanged(
+ mOverviewComponentObserver.getContainerInterface(
+ DEFAULT_DISPLAY).onAssistantVisibilityChanged(
mDeviceState.getAssistantVisibility());
}
}
@@ -774,8 +831,9 @@
}
private void onInputEvent(InputEvent ev) {
+ int displayId = ev.getDisplayId();
if (!(ev instanceof MotionEvent)) {
- ActiveGestureProtoLogProxy.logUnknownInputEvent(ev.toString());
+ ActiveGestureProtoLogProxy.logUnknownInputEvent(displayId, ev.toString());
return;
}
MotionEvent event = (MotionEvent) ev;
@@ -784,19 +842,19 @@
TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
if (!LockedUserState.get(this).isUserUnlocked()) {
- ActiveGestureProtoLogProxy.logOnInputEventUserLocked();
+ ActiveGestureProtoLogProxy.logOnInputEventUserLocked(displayId);
return;
}
NavigationMode currentNavMode = mDeviceState.getMode();
if (mGestureStartNavMode != null && mGestureStartNavMode != currentNavMode) {
ActiveGestureProtoLogProxy.logOnInputEventNavModeSwitched(
- mGestureStartNavMode.name(), currentNavMode.name());
+ displayId, mGestureStartNavMode.name(), currentNavMode.name());
event.setAction(ACTION_CANCEL);
} else if (mDeviceState.isButtonNavMode()
&& !mDeviceState.supportsAssistantGestureInButtonNav()
&& !isTrackpadMotionEvent(event)) {
- ActiveGestureProtoLogProxy.logOnInputEventThreeButtonNav();
+ ActiveGestureProtoLogProxy.logOnInputEventThreeButtonNav(displayId);
return;
}
@@ -812,12 +870,15 @@
}
if (mTaskAnimationManager.shouldIgnoreMotionEvents()) {
if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
- ActiveGestureProtoLogProxy.logOnInputIgnoringFollowingEvents();
+ ActiveGestureProtoLogProxy.logOnInputIgnoringFollowingEvents(displayId);
}
return;
}
}
+ InputMonitorCompat inputMonitorCompat = getInputMonitorCompat(displayId);
+ InputEventReceiver inputEventReceiver = getInputEventReceiver(displayId);
+
if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
mGestureStartNavMode = currentNavMode;
} else if (action == ACTION_UP || action == ACTION_CANCEL) {
@@ -844,10 +905,14 @@
if (mDeviceState.canTriggerAssistantAction(event)) {
reasonString.append(" and event can trigger assistant action, "
+ "consuming gesture for assistant action");
- mGestureState =
- createGestureState(mGestureState, getTrackpadGestureType(event));
+ mGestureState = createGestureState(
+ displayId, mGestureState, getTrackpadGestureType(event));
mUncheckedConsumer = tryCreateAssistantInputConsumer(
- this, mDeviceState, mInputMonitorCompat, mGestureState, event);
+ this,
+ mDeviceState,
+ inputMonitorCompat,
+ mGestureState,
+ event);
} else {
reasonString.append(" but event cannot trigger Assistant, "
+ "consuming gesture as no-op");
@@ -862,8 +927,8 @@
// Clone the previous gesture state since onConsumerAboutToBeSwitched might trigger
// onConsumerInactive and wipe the previous gesture state
GestureState prevGestureState = new GestureState(mGestureState);
- GestureState newGestureState = createGestureState(mGestureState,
- getTrackpadGestureType(event));
+ GestureState newGestureState = createGestureState(
+ displayId, mGestureState, getTrackpadGestureType(event));
mConsumer.onConsumerAboutToBeSwitched();
mGestureState = newGestureState;
mConsumer = newConsumer(
@@ -874,10 +939,10 @@
prevGestureState,
mGestureState,
mTaskAnimationManager,
- mInputMonitorCompat,
+ inputMonitorCompat,
getSwipeUpHandlerFactory(),
this::onConsumerInactive,
- mInputEventReceiver,
+ inputEventReceiver,
mTaskbarManager,
mSwipeUpProxyProvider,
mOverviewCommandHelper,
@@ -890,18 +955,19 @@
+ "consuming gesture for assistant action"
: "event is a trackpad multi-finger swipe and event can trigger assistant "
+ "action, consuming gesture for assistant action");
- mGestureState = createGestureState(mGestureState, getTrackpadGestureType(event));
+ mGestureState = createGestureState(
+ displayId, mGestureState, getTrackpadGestureType(event));
// Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we
// should not interrupt it. QuickSwitch assumes that interruption can only
// happen if the next gesture is also quick switch.
mUncheckedConsumer = tryCreateAssistantInputConsumer(
- this, mDeviceState, mInputMonitorCompat, mGestureState, event);
+ this, mDeviceState, inputMonitorCompat, mGestureState, event);
} else if (mDeviceState.canTriggerOneHandedAction(event)) {
reasonString.append("event can trigger one-handed action, "
+ "consuming gesture for one-handed action");
// Consume gesture event for triggering one handed feature.
- mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
- InputConsumer.NO_OP, mInputMonitorCompat);
+ mUncheckedConsumer = new OneHandedModeInputConsumer(
+ this, displayId, mDeviceState, InputConsumer.NO_OP, inputMonitorCompat);
} else {
mUncheckedConsumer = InputConsumer.NO_OP;
}
@@ -916,25 +982,28 @@
if (mUncheckedConsumer != InputConsumer.NO_OP) {
switch (action) {
case ACTION_DOWN:
- ActiveGestureProtoLogProxy.logOnInputEventActionDown(reasonString);
+ ActiveGestureProtoLogProxy.logOnInputEventActionDown(displayId, reasonString);
// fall through
case ACTION_UP:
ActiveGestureProtoLogProxy.logOnInputEventActionUp(
(int) event.getRawX(),
(int) event.getRawY(),
action,
- MotionEvent.classificationToString(event.getClassification()));
+ MotionEvent.classificationToString(event.getClassification()),
+ displayId);
break;
case ACTION_MOVE:
ActiveGestureProtoLogProxy.logOnInputEventActionMove(
MotionEvent.actionToString(action),
MotionEvent.classificationToString(event.getClassification()),
- event.getPointerCount());
+ event.getPointerCount(),
+ displayId);
break;
default: {
ActiveGestureProtoLogProxy.logOnInputEventGenericAction(
MotionEvent.actionToString(action),
- MotionEvent.classificationToString(event.getClassification()));
+ MotionEvent.classificationToString(event.getClassification()),
+ displayId);
}
}
}
@@ -958,7 +1027,7 @@
}
if (cleanUpConsumer) {
- reset();
+ reset(displayId);
}
traceToken.close();
}
@@ -977,13 +1046,15 @@
return event.isHoverEvent() && event.getSource() == InputDevice.SOURCE_MOUSE;
}
- public GestureState createGestureState(GestureState previousGestureState,
+ public GestureState createGestureState(
+ int displayId,
+ GestureState previousGestureState,
GestureState.TrackpadGestureType trackpadGestureType) {
final GestureState gestureState;
TopTaskTracker.CachedTaskInfo taskInfo;
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
- gestureState = new GestureState(mOverviewComponentObserver,
- ActiveGestureLog.INSTANCE.getLogId());
+ gestureState = new GestureState(
+ mOverviewComponentObserver, displayId, ActiveGestureLog.INSTANCE.getLogId());
TopTaskTracker.CachedTaskInfo previousTaskInfo = previousGestureState.getRunningTask();
// previousTaskInfo can be null iff previousGestureState == GestureState.DEFAULT_STATE
taskInfo = previousTaskInfo != null
@@ -994,7 +1065,9 @@
gestureState.updatePreviouslyAppearedTaskIds(
previousGestureState.getPreviouslyAppearedTaskIds());
} else {
- gestureState = new GestureState(mOverviewComponentObserver,
+ gestureState = new GestureState(
+ mOverviewComponentObserver,
+ displayId,
ActiveGestureLog.INSTANCE.incrementLogId());
taskInfo = TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false);
gestureState.updateRunningTask(taskInfo);
@@ -1022,17 +1095,18 @@
*/
private void onConsumerInactive(InputConsumer caller) {
if (mConsumer != null && mConsumer.getActiveConsumerInHierarchy() == caller) {
- reset();
+ reset(caller.getDisplayId());
}
}
- private void reset() {
+ private void reset(int displayId) {
mConsumer = mUncheckedConsumer = getDefaultInputConsumer();
mGestureState = DEFAULT_STATE;
// By default, use batching of the input events, but check receiver before using in the rare
// case that the monitor was disposed before the swipe settled
- if (mInputEventReceiver != null) {
- mInputEventReceiver.setBatchingEnabled(true);
+ InputEventReceiver inputEventReceiver = getInputEventReceiver(displayId);
+ if (inputEventReceiver != null) {
+ inputEventReceiver.setBatchingEnabled(true);
}
}
@@ -1064,8 +1138,10 @@
if (!LockedUserState.get(this).isUserUnlocked()) {
return;
}
+ // TODO (b/399094853): handle config updates for all connected displays (relevant only for
+ // gestures on external displays)
final BaseContainerInterface containerInterface =
- mOverviewComponentObserver.getContainerInterface();
+ mOverviewComponentObserver.getContainerInterface(DEFAULT_DISPLAY);
final RecentsViewContainer container = containerInterface.getCreatedContainer();
if (container == null || container.isStarted()) {
// We only care about the existing background activity.
@@ -1112,23 +1188,33 @@
pw.println("Input state:");
pw.println("\tmInputMonitorCompat=" + mInputMonitorCompat);
pw.println("\tmInputEventReceiver=" + mInputEventReceiver);
+ if (mInputMonitorDisplayModel == null) {
+ pw.println("\tmInputMonitorDisplayModel=null");
+ } else {
+ mInputMonitorDisplayModel.dump("\t", pw);
+ }
DisplayController.INSTANCE.get(this).dump(pw);
- pw.println("TouchState:");
- RecentsViewContainer createdOverviewContainer = mOverviewComponentObserver == null ? null
- : mOverviewComponentObserver.getContainerInterface().getCreatedContainer();
- boolean resumed = mOverviewComponentObserver != null
- && mOverviewComponentObserver.getContainerInterface().isResumed();
- pw.println("\tcreatedOverviewActivity=" + createdOverviewContainer);
- pw.println("\tresumed=" + resumed);
+ for (RecentsDisplayResource resource : mRecentsDisplayModel.getActiveDisplayResources()) {
+ int displayId = resource.getDisplayId();
+ pw.println(String.format(Locale.ENGLISH, "TouchState (displayId %d):", displayId));
+ RecentsViewContainer createdOverviewContainer =
+ mOverviewComponentObserver == null ? null
+ : mOverviewComponentObserver.getContainerInterface(
+ displayId).getCreatedContainer();
+ boolean resumed = mOverviewComponentObserver != null
+ && mOverviewComponentObserver.getContainerInterface(displayId).isResumed();
+ pw.println("\tcreatedOverviewActivity=" + createdOverviewContainer);
+ pw.println("\tresumed=" + resumed);
+ if (createdOverviewContainer != null) {
+ createdOverviewContainer.getDeviceProfile().dump(this, "", pw);
+ }
+ }
pw.println("\tmConsumer=" + mConsumer.getName());
ActiveGestureLog.INSTANCE.dump("", pw);
RecentsModel.INSTANCE.get(this).dump("", pw);
if (mTaskAnimationManager != null) {
mTaskAnimationManager.dump("", pw);
}
- if (createdOverviewContainer != null) {
- createdOverviewContainer.getDeviceProfile().dump(this, "", pw);
- }
mTaskbarManager.dumpLogs("", pw);
DesktopVisibilityController.INSTANCE.get(this).dumpLogs("", pw);
pw.println("ContextualSearchStateManager:");
@@ -1158,4 +1244,53 @@
gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
mInputConsumer, MSDLPlayerWrapper.INSTANCE.get(this));
}
+
+ /**
+ * Helper class that keeps track of external displays and prepares input monitors for each.
+ */
+ private class InputMonitorDisplayModel extends DisplayModel<InputMonitorResource> {
+
+ private InputMonitorDisplayModel(Context context) {
+ super(context);
+ registerDisplayListener();
+ }
+
+ @NonNull
+ @Override
+ public InputMonitorResource createDisplayResource(@NonNull Display display) {
+ return new InputMonitorResource(display.getDisplayId());
+ }
+ }
+
+ private class InputMonitorResource extends DisplayModel.DisplayResource {
+
+ private final int displayId;
+
+ private final InputMonitorCompat inputMonitorCompat;
+ private final InputEventReceiver inputEventReceiver;
+
+ private InputMonitorResource(int displayId) {
+ this.displayId = displayId;
+ inputMonitorCompat = new InputMonitorCompat("swipe-up", displayId);
+ inputEventReceiver = inputMonitorCompat.getInputReceiver(
+ Looper.getMainLooper(),
+ TouchInteractionService.this.mMainChoreographer,
+ TouchInteractionService.this::onInputEvent);
+ }
+
+ @Override
+ public void cleanup() {
+ inputEventReceiver.dispose();
+ inputMonitorCompat.dispose();
+ }
+
+ @Override
+ public void dump(String prefix , PrintWriter writer) {
+ writer.println(prefix + "InputMonitorResource:");
+
+ writer.println(prefix + "\tdisplayId=" + displayId);
+ writer.println(prefix + "\tinputMonitorCompat=" + inputMonitorCompat);
+ writer.println(prefix + "\tinputEventReceiver=" + inputEventReceiver);
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 537092f..2631efe 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -26,7 +26,6 @@
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.quickstep.fallback.RecentsState.OVERVIEW_SPLIT_SELECT;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.DESKTOP_CAROUSEL_DETACH_PROGRESS;
@@ -52,6 +51,7 @@
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
+import com.android.quickstep.views.AddDesktopButton;
import com.android.quickstep.views.ClearAllButton;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
@@ -98,12 +98,12 @@
private void setProperties(RecentsState state, StateAnimationConfig config,
PropertySetter setter) {
float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
- setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
- clearAllButtonAlpha, LINEAR);
+ setter.setFloat(mRecentsView.getClearAllButton(),
+ ClearAllButton.VISIBILITY_ALPHA, clearAllButtonAlpha, LINEAR);
if (mRecentsView.getAddDeskButton() != null) {
float addDeskButtonAlpha = state.hasAddDeskButton() ? 1 : 0;
- setter.setFloat(mRecentsView.getAddDeskButton().getVisibilityAlphaProperty(),
- MULTI_PROPERTY_VALUE, addDeskButtonAlpha, LINEAR);
+ setter.setFloat(mRecentsView.getAddDeskButton(), AddDesktopButton.VISIBILITY_ALPHA,
+ addDeskButtonAlpha, LINEAR);
}
float overviewButtonAlpha = state.hasOverviewActions() ? 1 : 0;
setter.setFloat(mRecentsViewContainer.getActionsView().getVisibilityAlpha(),
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
index 95a3ec2..58c6c50 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
@@ -17,19 +17,19 @@
package com.android.quickstep.fallback.window
import android.content.Context
-import android.util.Log
import android.view.Display
+import androidx.core.util.valueIterator
import com.android.launcher3.Flags
import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.util.DaggerSingletonObject
import com.android.launcher3.util.DaggerSingletonTracker
-import com.android.launcher3.util.Executors
import com.android.launcher3.util.WallpaperColorHints
import com.android.quickstep.DisplayModel
import com.android.quickstep.FallbackWindowInterface
import com.android.quickstep.dagger.QuickstepBaseAppComponent
import com.android.quickstep.fallback.window.RecentsDisplayModel.RecentsDisplayResource
+import java.io.PrintWriter
import javax.inject.Inject
@LauncherAppSingleton
@@ -58,42 +58,17 @@
init {
if (enableOverviewInWindow()) {
- displayManager.registerDisplayListener(displayListener, Executors.MAIN_EXECUTOR.handler)
- // In the scenario where displays were added before this display listener was
- // registered, we should store the RecentsDisplayResources for those displays
- // directly.
- displayManager.displays
- .filter { getDisplayResource(it.displayId) == null }
- .forEach { storeRecentsDisplayResource(it.displayId, it) }
+ registerDisplayListener()
tracker.addCloseable { destroy() }
}
}
- override fun createDisplayResource(displayId: Int) {
- if (DEBUG) Log.d(TAG, "createDisplayResource: displayId=$displayId")
- getDisplayResource(displayId)?.let {
- return
- }
- val display = displayManager.getDisplay(displayId)
- if (display == null) {
- if (DEBUG)
- Log.w(
- TAG,
- "createDisplayResource: could not create display for displayId=$displayId",
- Exception(),
- )
- return
- }
- storeRecentsDisplayResource(displayId, display)
- }
-
- private fun storeRecentsDisplayResource(displayId: Int, display: Display) {
- displayResourceArray[displayId] =
- RecentsDisplayResource(
- displayId,
- context.createDisplayContext(display),
- wallpaperColorHints.hints,
- )
+ override fun createDisplayResource(display: Display): RecentsDisplayResource {
+ return RecentsDisplayResource(
+ display.displayId,
+ context.createDisplayContext(display),
+ wallpaperColorHints.hints,
+ )
}
fun getRecentsWindowManager(displayId: Int): RecentsWindowManager? {
@@ -104,9 +79,15 @@
return getDisplayResource(displayId)?.fallbackWindowInterface
}
+ val activeDisplayResources: Iterable<RecentsDisplayResource>
+ get() =
+ object : Iterable<RecentsDisplayResource> {
+ override fun iterator() = displayResourceArray.valueIterator()
+ }
+
data class RecentsDisplayResource(
- var displayId: Int,
- var displayContext: Context,
+ val displayId: Int,
+ val displayContext: Context,
val wallpaperColorHints: Int,
) : DisplayResource() {
val recentsWindowManager = RecentsWindowManager(displayContext, wallpaperColorHints)
@@ -116,5 +97,15 @@
override fun cleanup() {
recentsWindowManager.destroy()
}
+
+ override fun dump(prefix: String, writer: PrintWriter) {
+ writer.println("${prefix}RecentsDisplayResource:")
+
+ writer.println("${prefix}\tdisplayId=${displayId}")
+ writer.println("${prefix}\tdisplayContext=${displayContext}")
+ writer.println("${prefix}\twallpaperColorHints=${wallpaperColorHints}")
+ writer.println("${prefix}\trecentsWindowManager=${recentsWindowManager}")
+ writer.println("${prefix}\tfallbackWindowInterface=${fallbackWindowInterface}")
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
index cd48136..9a408ad 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.graphics.PixelFormat
import android.view.ContextThemeWrapper
+import android.view.Display
import android.view.ViewGroup
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
@@ -62,7 +63,10 @@
}
fun initDeviceProfile() {
- deviceProfile = InvariantDeviceProfile.INSTANCE[this].getDeviceProfile(this)
+ deviceProfile =
+ if (displayId == Display.DEFAULT_DISPLAY)
+ InvariantDeviceProfile.INSTANCE[this].getDeviceProfile(this)
+ else InvariantDeviceProfile.INSTANCE[this].createDeviceProfileForSecondaryDisplay(this)
}
override fun getDeviceProfile(): DeviceProfile {
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
index 1f4961a..6a13927 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
@@ -81,7 +81,6 @@
import com.android.systemui.shared.recents.model.ThumbnailData
import com.android.systemui.shared.system.TaskStackChangeListener
import com.android.systemui.shared.system.TaskStackChangeListeners
-import java.util.function.Predicate
/**
* Class that will manage RecentsView lifecycle within a window and interface correctly where
@@ -135,7 +134,6 @@
// Callback array that corresponds to events defined in @ActivityEvent
private val eventCallbacks =
listOf(RunnableList(), RunnableList(), RunnableList(), RunnableList())
- private var onInitListener: Predicate<Boolean>? = null
private val animationToHomeFactory =
RemoteAnimationFactory {
@@ -336,10 +334,6 @@
return taskbarUIController
}
- fun registerInitListener(onInitListener: Predicate<Boolean>) {
- this.onInitListener = onInitListener
- }
-
override fun collectStateHandlers(out: MutableList<StateManager.StateHandler<RecentsState?>>?) {
out!!.add(FallbackRecentsStateController(this))
}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
index 5adc960..1d85feb 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
@@ -163,7 +163,7 @@
&& endTarget == GestureState.GestureEndTarget.HOME;
if (fromHomeToHome) {
RecentsWindowManager manager =
- mRecentsDisplayModel.getRecentsWindowManager(mDeviceState.getDisplayId());
+ mRecentsDisplayModel.getRecentsWindowManager(mGestureState.getDisplayId());
if (manager != null) {
manager.startHome(/* finishRecentsAnimation= */ false);
}
@@ -228,7 +228,7 @@
recentsCallback = () -> {
callback.run();
RecentsWindowManager manager =
- mRecentsDisplayModel.getRecentsWindowManager(mDeviceState.getDisplayId());
+ mRecentsDisplayModel.getRecentsWindowManager(mGestureState.getDisplayId());
if (manager != null) {
manager.startHome();
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index 4e5d037..365c80c 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -56,9 +56,13 @@
private float mDownY;
private float mTotalY;
- public AccessibilityInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
- InputConsumer delegate, InputMonitorCompat inputMonitor) {
- super(delegate, inputMonitor);
+ public AccessibilityInputConsumer(
+ Context context,
+ int displayId,
+ RecentsAnimationDeviceState deviceState,
+ InputConsumer delegate,
+ InputMonitorCompat inputMonitor) {
+ super(displayId, delegate, inputMonitor);
mContext = context;
mVelocityTracker = VelocityTracker.obtain();
mMinGestureDistance = context.getResources()
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index 222ccd3..365014d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -95,7 +95,7 @@
InputMonitorCompat inputMonitor,
RecentsAnimationDeviceState deviceState,
MotionEvent startEvent) {
- super(delegate, inputMonitor);
+ super(gestureState.getDisplayId(), delegate, inputMonitor);
final Resources res = context.getResources();
mContext = context;
mDragDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
index b2e7015..86d7190 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
@@ -57,12 +57,19 @@
private final int mTouchSlop;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
+
+ private final int mDisplayId;
+
private long mDownTime;
private final long mTimeForLongPress;
private int mActivePointerId = INVALID_POINTER_ID;
- public BubbleBarInputConsumer(Context context, BubbleControllers bubbleControllers,
+ public BubbleBarInputConsumer(
+ Context context,
+ int displayId,
+ BubbleControllers bubbleControllers,
InputMonitorCompat inputMonitorCompat) {
+ mDisplayId = displayId;
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleBarSwipeController = bubbleControllers.bubbleBarSwipeController.orElse(null);
@@ -78,6 +85,11 @@
}
@Override
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ @Override
public void onMotionEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 4afd92a..0b1a6c4 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -17,15 +17,24 @@
protected final InputConsumer mDelegate;
protected final InputMonitorCompat mInputMonitor;
+ private final int mDisplayId;
+
protected int mState;
- public DelegateInputConsumer(InputConsumer delegate, InputMonitorCompat inputMonitor) {
+ public DelegateInputConsumer(
+ int displayId, InputConsumer delegate, InputMonitorCompat inputMonitor) {
+ mDisplayId = displayId;
mDelegate = delegate;
mInputMonitor = inputMonitor;
mState = STATE_INACTIVE;
}
@Override
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ @Override
public InputConsumer getActiveConsumerInHierarchy() {
if (mState == STATE_ACTIVE) {
return this;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 503b900..e192702 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -108,8 +108,11 @@
private RecentsAnimationController mRecentsAnimationController;
- public DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
- TaskAnimationManager taskAnimationManager, GestureState gestureState,
+ public DeviceLockedInputConsumer(
+ Context context,
+ RecentsAnimationDeviceState deviceState,
+ TaskAnimationManager taskAnimationManager,
+ GestureState gestureState,
InputMonitorCompat inputMonitorCompat) {
mContext = context;
mTaskAnimationManager = taskAnimationManager;
@@ -138,6 +141,11 @@
}
@Override
+ public int getDisplayId() {
+ return mGestureState.getDisplayId();
+ }
+
+ @Override
public void onMotionEvent(MotionEvent ev) {
if (mVelocityTracker == null) {
return;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index a703c23..baabde8 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -74,10 +74,14 @@
private MotionEvent mCurrentMotionEvent; // Most recent motion event.
private boolean mDeepPressLogged; // Whether deep press has been logged for the current touch.
- public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
- InputMonitorCompat inputMonitor, RecentsAnimationDeviceState deviceState,
- NavHandle navHandle, GestureState gestureState) {
- super(delegate, inputMonitor);
+ public NavHandleLongPressInputConsumer(
+ Context context,
+ InputConsumer delegate,
+ InputMonitorCompat inputMonitor,
+ RecentsAnimationDeviceState deviceState,
+ NavHandle navHandle,
+ GestureState gestureState) {
+ super(gestureState.getDisplayId(), delegate, inputMonitor);
mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
mDeepPressEnabled = DeviceConfigWrapper.get().getEnableLpnhDeepPress();
ContextualSearchStateManager contextualSearchStateManager =
@@ -254,13 +258,15 @@
Log.d(TAG, "cancelLongPress: " + reason);
}
// Log LPNH abandon latency if we didn't trigger but were still prepared to.
- long latencyMs = mCurrentMotionEvent.getEventTime() - mCurrentDownEvent.getEventTime();
- if (mState != STATE_ACTIVE && MAIN_EXECUTOR.getHandler().hasCallbacks(mTriggerLongPress)
- && latencyMs >= MIN_TIME_TO_LOG_ABANDON_MS) {
- mStatsLogManager.latencyLogger()
- .withInstanceId(new InstanceIdSequence().newInstanceId())
- .withLatency(latencyMs)
- .log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
+ if (mCurrentMotionEvent != null && mCurrentDownEvent != null) {
+ long latencyMs = mCurrentMotionEvent.getEventTime() - mCurrentDownEvent.getEventTime();
+ if (mState != STATE_ACTIVE && MAIN_EXECUTOR.getHandler().hasCallbacks(mTriggerLongPress)
+ && latencyMs >= MIN_TIME_TO_LOG_ABANDON_MS) {
+ mStatsLogManager.latencyLogger()
+ .withInstanceId(new InstanceIdSequence().newInstanceId())
+ .withLatency(latencyMs)
+ .log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
+ }
}
mGestureState.setIsInExtendedSlopRegion(false);
MAIN_EXECUTOR.getHandler().removeCallbacks(mTriggerLongPress);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
index 83b556d..67cb992 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -61,9 +61,13 @@
private boolean mPassedSlop;
private boolean mIsStopGesture;
- public OneHandedModeInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
- InputConsumer delegate, InputMonitorCompat inputMonitor) {
- super(delegate, inputMonitor);
+ public OneHandedModeInputConsumer(
+ Context context,
+ int displayId,
+ RecentsAnimationDeviceState deviceState,
+ InputConsumer delegate,
+ InputMonitorCompat inputMonitor) {
+ super(displayId, delegate, inputMonitor);
mContext = context;
mDeviceState = deviceState;
mDragDistThreshold = context.getResources().getDimensionPixelSize(
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index dd2b2be..5963a7c 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -126,11 +126,17 @@
// The callback called upon finishing the recents transition if it was force-canceled
private Runnable mForceFinishRecentsTransitionCallback;
- public OtherActivityInputConsumer(Context base, RecentsAnimationDeviceState deviceState,
- TaskAnimationManager taskAnimationManager, GestureState gestureState,
- boolean isDeferredDownTarget, Consumer<OtherActivityInputConsumer> onCompleteCallback,
- InputMonitorCompat inputMonitorCompat, InputEventReceiver inputEventReceiver,
- boolean disableHorizontalSwipe, Factory handlerFactory) {
+ public OtherActivityInputConsumer(
+ Context base,
+ RecentsAnimationDeviceState deviceState,
+ TaskAnimationManager taskAnimationManager,
+ GestureState gestureState,
+ boolean isDeferredDownTarget,
+ Consumer<OtherActivityInputConsumer> onCompleteCallback,
+ InputMonitorCompat inputMonitorCompat,
+ InputEventReceiver inputEventReceiver,
+ boolean disableHorizontalSwipe,
+ Factory handlerFactory) {
super(base);
mDeviceState = deviceState;
mNavBarPosition = mDeviceState.getNavBarPosition();
@@ -166,6 +172,11 @@
}
@Override
+ public int getDisplayId() {
+ return mGestureState.getDisplayId();
+ }
+
+ @Override
public boolean isConsumerDetachedFromGesture() {
return true;
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index a236eca..4658cb0 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -55,12 +55,16 @@
private final int[] mLocationOnScreen = new int[2];
private final boolean mStartingInActivityBounds;
+
private boolean mTargetHandledTouch;
private boolean mHasSetTouchModeForFirstDPadEvent;
private boolean mIsWaitingForAttachToWindow;
- public OverviewInputConsumer(GestureState gestureState, T container,
- @Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
+ public OverviewInputConsumer(
+ GestureState gestureState,
+ T container,
+ @Nullable InputMonitorCompat inputMonitor,
+ boolean startingInActivityBounds) {
mContainer = container;
mInputMonitor = inputMonitor;
mStartingInActivityBounds = startingInActivityBounds;
@@ -77,6 +81,11 @@
}
@Override
+ public int getDisplayId() {
+ return mGestureState.getDisplayId();
+ }
+
+ @Override
public boolean allowInterceptByParent() {
return !mTargetHandledTouch;
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index be47df9..7838e86 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -43,9 +43,12 @@
private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
private final GestureState mGestureState;
- public OverviewWithoutFocusInputConsumer(Context context,
- RecentsAnimationDeviceState deviceState, GestureState gestureState,
- InputMonitorCompat inputMonitor, boolean disableHorizontalSwipe) {
+ public OverviewWithoutFocusInputConsumer(
+ Context context,
+ RecentsAnimationDeviceState deviceState,
+ GestureState gestureState,
+ InputMonitorCompat inputMonitor,
+ boolean disableHorizontalSwipe) {
mContext = context;
mGestureState = gestureState;
mInputMonitor = inputMonitor;
@@ -59,6 +62,11 @@
}
@Override
+ public int getDisplayId() {
+ return mGestureState.getDisplayId();
+ }
+
+ @Override
public boolean allowInterceptByParent() {
return !mTriggerSwipeUpTracker.interceptedTouch();
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
index c91bebe..52aaa03 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
@@ -89,9 +89,12 @@
private RecentsAnimationController mRecentsAnimationController;
private Boolean mFlingEndsOnHome;
- public ProgressDelegateInputConsumer(Context context,
- TaskAnimationManager taskAnimationManager, GestureState gestureState,
- InputMonitorCompat inputMonitorCompat, AnimatedFloat progress) {
+ public ProgressDelegateInputConsumer(
+ Context context,
+ TaskAnimationManager taskAnimationManager,
+ GestureState gestureState,
+ InputMonitorCompat inputMonitorCompat,
+ AnimatedFloat progress) {
mContext = context;
mTaskAnimationManager = taskAnimationManager;
mGestureState = gestureState;
@@ -118,6 +121,11 @@
}
@Override
+ public int getDisplayId() {
+ return mGestureState.getDisplayId();
+ }
+
+ @Override
public void onMotionEvent(MotionEvent ev) {
if (mFlingEndsOnHome == null) {
mSwipeDetector.onTouchEvent(ev);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
index d73c23f..9dc27de 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
@@ -36,9 +36,12 @@
private final float mMotionPauseMinDisplacement;
private final MotionPauseDetector mMotionPauseDetector;
+ private final int mDisplayId;
+
private float mTouchDownY;
public ScreenPinnedInputConsumer(Context context, GestureState gestureState) {
+ mDisplayId = gestureState.getDisplayId();
mMotionPauseMinDisplacement = context.getResources().getDimension(
R.dimen.motion_pause_detector_min_displacement_from_app);
mMotionPauseDetector = new MotionPauseDetector(context, true /* makePauseHarderToTrigger*/);
@@ -61,6 +64,11 @@
}
@Override
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ @Override
public void onMotionEvent(MotionEvent ev) {
float y = ev.getY();
switch (ev.getAction()) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
index 871d075..ad1a01b 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
@@ -47,11 +47,15 @@
private final InputMonitorCompat mInputMonitor;
private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
+ private final int mDisplayId;
+
public SysUiOverlayInputConsumer(
Context context,
+ int displayId,
RecentsAnimationDeviceState deviceState,
InputMonitorCompat inputMonitor) {
mContext = context;
+ mDisplayId = displayId;
mInputMonitor = inputMonitor;
mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, true,
deviceState.getNavBarPosition(), this);
@@ -63,6 +67,11 @@
}
@Override
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
+ @Override
public boolean allowInterceptByParent() {
return !mTriggerSwipeUpTracker.interceptedTouch();
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index 49bff8d..dbe6e14 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -89,10 +89,14 @@
// Velocity defined as dp per s
private float mTaskbarSlowVelocityYThreshold;
- public TaskbarUnstashInputConsumer(Context context, InputConsumer delegate,
- InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext,
- OverviewCommandHelper overviewCommandHelper, GestureState gestureState) {
- super(delegate, inputMonitor);
+ public TaskbarUnstashInputConsumer(
+ Context context,
+ InputConsumer delegate,
+ InputMonitorCompat inputMonitor,
+ TaskbarActivityContext taskbarActivityContext,
+ OverviewCommandHelper overviewCommandHelper,
+ GestureState gestureState) {
+ super(gestureState.getDisplayId(), delegate, inputMonitor);
mTaskbarActivityContext = taskbarActivityContext;
mOverviewCommandHelper = overviewCommandHelper;
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java
index f3e21e1..a53a395 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java
@@ -35,9 +35,12 @@
private final PointF mDown = new PointF();
private boolean mHasPassedTouchSlop;
- public TrackpadStatusBarInputConsumer(Context context, InputConsumer delegate,
+ public TrackpadStatusBarInputConsumer(
+ Context context,
+ int displayId,
+ InputConsumer delegate,
InputMonitorCompat inputMonitor) {
- super(delegate, inputMonitor);
+ super(displayId, delegate, inputMonitor);
mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
mTouchSlop = 2 * ViewConfiguration.get(context).getScaledTouchSlop();
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index e265e61..c63cddf 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -34,6 +34,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.view.Display;
import android.view.View;
import android.view.ViewOutlineProvider;
@@ -84,8 +85,8 @@
SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
super(tutorialFragment, tutorialType);
- mTaskViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext,
- new GestureState(OverviewComponentObserver.INSTANCE.get(mContext), -1));
+ mTaskViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, new GestureState(
+ OverviewComponentObserver.INSTANCE.get(mContext), Display.DEFAULT_DISPLAY, -1));
DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext)
.getDeviceProfile(mContext)
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index e73fb3b..22227c9 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -36,7 +36,6 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
import android.widget.FrameLayout;
@@ -124,13 +123,10 @@
// These runnables should be used when posting callbacks to their views and cleared from their
// views before posting new callbacks.
- private final Runnable mTitleViewCallback;
- private final Runnable mSubtitleViewCallback;
@Nullable private Runnable mFeedbackViewCallback;
@Nullable private Runnable mFakeTaskViewCallback;
@Nullable private Runnable mFakeTaskbarViewCallback;
private final Runnable mShowFeedbackRunnable;
- private final AccessibilityManager mAccessibilityManager;
TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
mTutorialFragment = tutorialFragment;
@@ -187,17 +183,6 @@
outline.setRoundRect(mExitingAppRect, mExitingAppRadius);
}
});
-
- mAccessibilityManager = AccessibilityManager.getInstance(mContext);
- mTitleViewCallback = () -> {
- mFeedbackTitleView.requestFocus();
- mFeedbackTitleView.sendAccessibilityEvent(
- AccessibilityEvent.TYPE_VIEW_FOCUSED);
- };
- mSubtitleViewCallback = () -> {
- mFeedbackSubtitleView.requestFocus();
- mFeedbackSubtitleView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- };
mShowFeedbackRunnable = () -> {
mFeedbackView.setAlpha(0f);
mFeedbackView.setScaleX(0.95f);
@@ -216,14 +201,14 @@
mFeedbackViewCallback = mTutorialFragment::continueTutorial;
mFeedbackView.postDelayed(
mFeedbackViewCallback,
- mAccessibilityManager.getRecommendedTimeoutMillis(
- ADVANCE_TUTORIAL_TIMEOUT_MS,
- AccessibilityManager.FLAG_CONTENT_TEXT
+ AccessibilityManager.getInstance(mContext)
+ .getRecommendedTimeoutMillis(
+ ADVANCE_TUTORIAL_TIMEOUT_MS,
+ AccessibilityManager.FLAG_CONTENT_TEXT
| AccessibilityManager.FLAG_CONTENT_CONTROLS));
}
})
.start();
- mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS);
};
}
@@ -416,8 +401,6 @@
int titleResId,
int subtitleResId,
boolean isGestureSuccessful) {
- mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
- mFeedbackSubtitleView.removeCallbacks(mSubtitleViewCallback);
if (mFeedbackViewCallback != null) {
mFeedbackView.removeCallbacks(mFeedbackViewCallback);
mFeedbackViewCallback = null;
@@ -425,15 +408,6 @@
mFeedbackTitleView.setText(titleResId);
mFeedbackSubtitleView.setText(subtitleResId);
- mFeedbackTitleView.postDelayed(mTitleViewCallback, mAccessibilityManager
- .getRecommendedTimeoutMillis(
- FEEDBACK_ANIMATION_MS,
- AccessibilityManager.FLAG_CONTENT_TEXT));
- mFeedbackSubtitleView.postDelayed(mSubtitleViewCallback, mAccessibilityManager
- .getRecommendedTimeoutMillis(
- SUBTITLE_ANNOUNCE_DELAY_MS,
- AccessibilityManager.FLAG_CONTENT_TEXT));
-
if (isGestureSuccessful) {
if (mTutorialFragment.isAtFinalStep()) {
TypefaceUtils.setTypeface(
@@ -494,8 +468,6 @@
mFakeTaskbarView.removeCallbacks(mFakeTaskbarViewCallback);
mFakeTaskbarViewCallback = null;
}
- mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
- mFeedbackSubtitleView.removeCallbacks(mSubtitleViewCallback);
}
private void playFeedbackAnimation() {
@@ -588,13 +560,6 @@
mSkipButton.setVisibility(GONE);
mDoneButton.setVisibility(View.VISIBLE);
mDoneButton.setOnClickListener(this::onActionButtonClicked);
- mDoneButton.postDelayed(() -> {
- mDoneButton.requestFocus();
- mDoneButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
- }, mAccessibilityManager
- .getRecommendedTimeoutMillis(
- DONE_BUTTON_ANNOUNCE_DELAY_MS,
- AccessibilityManager.FLAG_CONTENT_CONTROLS));
}
void hideFakeTaskbar(boolean animateToHotseat) {
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index 17f861d..e3c9b2b 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -103,7 +103,7 @@
target: T,
action: Int2DAction<T>,
primaryParam: Int,
- secondaryParam: Int
+ secondaryParam: Int,
) = action.call(target, secondaryParam, primaryParam)
override fun getPrimaryDirection(event: MotionEvent, pointerIndex: Int): Float =
@@ -171,7 +171,7 @@
override fun getSplitTranslationDirectionFactor(
stagePosition: Int,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
override fun getTaskMenuX(
@@ -179,7 +179,7 @@
thumbnailView: View,
deviceProfile: DeviceProfile,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float = thumbnailView.measuredWidth + x - taskInsetMargin
override fun getTaskMenuY(
@@ -188,7 +188,7 @@
stagePosition: Int,
taskMenuView: View,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float {
val layoutParams = taskMenuView.layoutParams as BaseDragLayer.LayoutParams
var taskMenuY = y + taskInsetMargin
@@ -203,7 +203,7 @@
override fun getTaskMenuWidth(
thumbnailView: View,
deviceProfile: DeviceProfile,
- @StagePosition stagePosition: Int
+ @StagePosition stagePosition: Int,
): Int =
when {
Flags.enableOverviewIconMenu() ->
@@ -218,14 +218,14 @@
taskInsetMargin: Float,
deviceProfile: DeviceProfile,
taskMenuX: Float,
- taskMenuY: Float
+ taskMenuY: Float,
): Int = (taskMenuX - taskInsetMargin).toInt()
override fun setTaskOptionsMenuLayoutOrientation(
deviceProfile: DeviceProfile,
taskMenuLayout: LinearLayout,
dividerSpacing: Int,
- dividerDrawable: ShapeDrawable
+ dividerDrawable: ShapeDrawable,
) {
taskMenuLayout.orientation = LinearLayout.VERTICAL
dividerDrawable.intrinsicHeight = dividerSpacing
@@ -235,7 +235,7 @@
override fun setLayoutParamsForTaskMenuOptionItem(
lp: LinearLayout.LayoutParams,
viewGroup: LinearLayout,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
) {
// Phone fake landscape
viewGroup.orientation = LinearLayout.HORIZONTAL
@@ -250,7 +250,7 @@
deviceProfile: DeviceProfile,
snapshotViewWidth: Int,
snapshotViewHeight: Int,
- banner: View
+ banner: View,
) {
banner.pivotX = 0f
banner.pivotY = 0f
@@ -273,7 +273,7 @@
deviceProfile: DeviceProfile,
thumbnailViews: Array<View>,
desiredTaskId: Int,
- banner: View
+ banner: View,
): Pair<Float, Float> {
val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
val translationX = banner.height.toFloat()
@@ -311,13 +311,21 @@
override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) 1 else -1
+ override fun getTaskDismissVerticalDirection(): Int = 1
+
+ override fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ secondaryDimension - taskThumbnailBounds.left
+
+ override fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ taskThumbnailBounds.left
+
/* -------------------- */
override fun getChildBounds(
child: View,
childStart: Int,
pageCenter: Int,
- layoutChild: Boolean
+ layoutChild: Boolean,
): ChildBounds {
val childHeight = child.measuredHeight
val childWidth = child.measuredWidth
@@ -338,7 +346,7 @@
R.drawable.ic_split_horizontal,
R.string.recent_task_option_split_screen,
STAGE_POSITION_TOP_OR_LEFT,
- STAGE_TYPE_MAIN
+ STAGE_TYPE_MAIN,
)
)
@@ -347,7 +355,7 @@
placeholderInset: Int,
dp: DeviceProfile,
@StagePosition stagePosition: Int,
- out: Rect
+ out: Rect,
) {
// In fake land/seascape, the placeholder always needs to go to the "top" of the device,
// which is the same bounds as 0 rotation.
@@ -374,7 +382,7 @@
drawableWidth: Int,
drawableHeight: Int,
dp: DeviceProfile,
- @StagePosition stagePosition: Int
+ @StagePosition stagePosition: Int,
) {
val insetAdjustment = getPlaceholderSizeAdjustment(dp) / 2f
out.x = (onScreenRectCenterX / fullscreenScaleX - 1.0f * drawableWidth / 2)
@@ -393,7 +401,7 @@
out: View,
dp: DeviceProfile,
splitInstructionsHeight: Int,
- splitInstructionsWidth: Int
+ splitInstructionsWidth: Int,
) {
out.pivotX = 0f
out.pivotY = splitInstructionsHeight.toFloat()
@@ -421,7 +429,7 @@
dp: DeviceProfile,
@StagePosition stagePosition: Int,
out1: Rect,
- out2: Rect
+ out2: Rect,
) {
// In fake land/seascape, the window bounds are always top and bottom half
val screenHeight = dp.heightPx
@@ -434,7 +442,7 @@
dp: DeviceProfile,
outRect: Rect,
splitInfo: SplitBounds,
- desiredStagePosition: Int
+ desiredStagePosition: Int,
) {
val topLeftTaskPercent = splitInfo.leftTopTaskPercent
val dividerBarPercent = splitInfo.dividerPercent
@@ -448,7 +456,7 @@
/**
* @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. Currently this state is not reachable in fake landscape.
+ * split screen. Currently this state is not reachable in fake landscape.
*/
override fun measureGroupedTaskViewThumbnailBounds(
primarySnapshot: View,
@@ -458,7 +466,7 @@
splitBoundsConfig: SplitBounds,
dp: DeviceProfile,
isRtl: Boolean,
- inSplitSelection: Boolean
+ inSplitSelection: Boolean,
) {
val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
@@ -479,13 +487,13 @@
primarySnapshot.translationY = spaceAboveSnapshot.toFloat()
primarySnapshot.measure(
MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY)
+ MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY),
)
val translationY = taskViewFirst.y + spaceAboveSnapshot + dividerBar
secondarySnapshot.translationY = (translationY - spaceAboveSnapshot).toFloat()
secondarySnapshot.measure(
MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY)
+ MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY),
)
}
@@ -493,7 +501,7 @@
dp: DeviceProfile,
splitBoundsConfig: SplitBounds,
parentWidth: Int,
- parentHeight: Int
+ parentHeight: Int,
): Pair<Point, Point> {
val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
@@ -511,7 +519,7 @@
taskIconMargin: Int,
taskIconHeight: Int,
thumbnailTopMargin: Int,
- isRtl: Boolean
+ isRtl: Boolean,
) {
iconParams.gravity =
if (isRtl) {
@@ -527,7 +535,7 @@
override fun setIconAppChipChildrenParams(
iconParams: FrameLayout.LayoutParams,
- chipChildMarginStart: Int
+ chipChildMarginStart: Int,
) {
iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
iconParams.marginStart = chipChildMarginStart
@@ -538,7 +546,7 @@
iconAppChipView: IconAppChipView,
iconMenuParams: FrameLayout.LayoutParams,
iconMenuMargin: Int,
- thumbnailTopMargin: Int
+ thumbnailTopMargin: Int,
) {
val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
@@ -564,7 +572,7 @@
/**
* @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. Currently this state is not reachable in fake landscape.
+ * split screen. Currently this state is not reachable in fake landscape.
*/
override fun setSplitIconParams(
primaryIconView: View,
@@ -606,20 +614,20 @@
override fun <T> getSplitSelectTaskOffset(
primary: FloatProperty<T>,
secondary: FloatProperty<T>,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Pair<FloatProperty<T>, FloatProperty<T>> = Pair(primary, secondary)
override fun getFloatingTaskOffscreenTranslationTarget(
floatingTask: View,
onScreenRect: RectF,
@StagePosition stagePosition: Int,
- dp: DeviceProfile
+ dp: DeviceProfile,
): Float = floatingTask.translationY - onScreenRect.height()
override fun setFloatingTaskPrimaryTranslation(
floatingTask: View,
translation: Float,
- dp: DeviceProfile
+ dp: DeviceProfile,
) {
floatingTask.translationY = translation
}
@@ -660,12 +668,11 @@
} else {
if (oneIconHiddenDueToSmallWidth) {
// Center both icons
- val centerY = primarySnapshotHeight + overviewTaskMarginPx +
+ val centerY =
+ primarySnapshotHeight +
+ overviewTaskMarginPx +
((taskIconHeight + dividerSize) / 2)
- SplitIconPositions(
- topLeftY = centerY,
- bottomRightY = centerY,
- )
+ SplitIconPositions(topLeftY = centerY, bottomRightY = centerY)
} else {
val topLeftY = primarySnapshotHeight + overviewTaskMarginPx
SplitIconPositions(
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
deleted file mode 100644
index c4e82d6..0000000
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ /dev/null
@@ -1,872 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.orientation;
-
-import static android.view.Gravity.BOTTOM;
-import static android.view.Gravity.CENTER_HORIZONTAL;
-import static android.view.Gravity.END;
-import static android.view.Gravity.START;
-import static android.view.Gravity.TOP;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
-
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ShapeDrawable;
-import android.util.FloatProperty;
-import android.util.Pair;
-import android.view.Gravity;
-import android.view.Surface;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.touch.DefaultPagedViewHandler;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.quickstep.views.IconAppChipView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class PortraitPagedViewHandler extends DefaultPagedViewHandler implements
- RecentsPagedOrientationHandler {
-
- private final Matrix mTmpMatrix = new Matrix();
- private final RectF mTmpRectF = new RectF();
-
- @Override
- public <T> T getPrimaryValue(T x, T y) {
- return x;
- }
-
- @Override
- public <T> T getSecondaryValue(T x, T y) {
- return y;
- }
-
- @Override
- public boolean isLayoutNaturalToLauncher() {
- return true;
- }
-
- @Override
- public void adjustFloatingIconStartVelocity(PointF velocity) {
- //no-op
- }
-
- @Override
- public void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile) {
- if (outStartRect.left > deviceProfile.widthPx) {
- outStartRect.offsetTo(0, outStartRect.top);
- } else if (outStartRect.left < -deviceProfile.widthPx) {
- outStartRect.offsetTo(0, outStartRect.top);
- }
- }
-
- @Override
- public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
- action.call(target, 0, param);
- }
-
- @Override
- public <T> void set(T target, Int2DAction<T> action, int primaryParam,
- int secondaryParam) {
- action.call(target, primaryParam, secondaryParam);
- }
-
- @Override
- public int getPrimarySize(View view) {
- return view.getWidth();
- }
-
- @Override
- public float getPrimarySize(RectF rect) {
- return rect.width();
- }
-
- @Override
- public float getStart(RectF rect) {
- return rect.left;
- }
-
- @Override
- public float getEnd(RectF rect) {
- return rect.right;
- }
-
- @Override
- public void rotateInsets(@NonNull Rect insets, @NonNull Rect outInsets) {
- outInsets.set(insets);
- }
-
- @Override
- public int getClearAllSidePadding(View view, boolean isRtl) {
- return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2;
- }
-
- @Override
- public int getSecondaryDimension(View view) {
- return view.getHeight();
- }
-
- @Override
- public FloatProperty<View> getPrimaryViewTranslate() {
- return VIEW_TRANSLATE_X;
- }
-
- @Override
- public FloatProperty<View> getSecondaryViewTranslate() {
- return VIEW_TRANSLATE_Y;
- }
-
- @Override
- public float getDegreesRotated() {
- return 0;
- }
-
- @Override
- public int getRotation() {
- return Surface.ROTATION_0;
- }
-
- @Override
- public void setPrimaryScale(View view, float scale) {
- view.setScaleX(scale);
- }
-
- @Override
- public void setSecondaryScale(View view, float scale) {
- view.setScaleY(scale);
- }
-
- public int getSecondaryTranslationDirectionFactor() {
- return -1;
- }
-
- @Override
- public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
- if (deviceProfile.isLeftRightSplit && stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
- return -1;
- } else {
- return 1;
- }
- }
-
- @Override
- public float getTaskMenuX(float x, View thumbnailView,
- DeviceProfile deviceProfile, float taskInsetMargin, View taskViewIcon) {
- if (deviceProfile.isLandscape) {
- return x + taskInsetMargin
- + (thumbnailView.getMeasuredWidth() - thumbnailView.getMeasuredHeight()) / 2f;
- } else {
- return x + taskInsetMargin;
- }
- }
-
- @Override
- public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
- View taskMenuView, float taskInsetMargin, View taskViewIcon) {
- return y + taskInsetMargin;
- }
-
- @Override
- public int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
- @StagePosition int stagePosition) {
- if (enableOverviewIconMenu()) {
- return thumbnailView.getResources().getDimensionPixelSize(
- R.dimen.task_thumbnail_icon_menu_expanded_width);
- }
- int padding = thumbnailView.getResources()
- .getDimensionPixelSize(R.dimen.task_menu_edge_padding);
- return (deviceProfile.isLandscape && !deviceProfile.isTablet
- ? thumbnailView.getMeasuredHeight()
- : thumbnailView.getMeasuredWidth()) - (2 * padding);
- }
-
- @Override
- public int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile,
- float taskMenuX, float taskMenuY) {
- return (int) (deviceProfile.heightPx - deviceProfile.getInsets().top - taskMenuY
- - deviceProfile.getOverviewActionsClaimedSpaceBelow());
- }
-
- @Override
- public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile,
- LinearLayout taskMenuLayout, int dividerSpacing,
- ShapeDrawable dividerDrawable) {
- taskMenuLayout.setOrientation(LinearLayout.VERTICAL);
- dividerDrawable.setIntrinsicHeight(dividerSpacing);
- taskMenuLayout.setDividerDrawable(dividerDrawable);
- }
-
- @Override
- public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp,
- LinearLayout viewGroup, DeviceProfile deviceProfile) {
- viewGroup.setOrientation(LinearLayout.HORIZONTAL);
- lp.width = LinearLayout.LayoutParams.MATCH_PARENT;
- lp.height = WRAP_CONTENT;
- }
-
- @Override
- public void updateDwbBannerLayout(int taskViewWidth, int taskViewHeight,
- boolean isGroupedTaskView, @NonNull DeviceProfile deviceProfile,
- int snapshotViewWidth, int snapshotViewHeight, @NonNull View banner) {
- FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
- banner.setPivotX(0);
- banner.setPivotY(0);
- banner.setRotation(getDegreesRotated());
- if (isGroupedTaskView) {
- bannerParams.gravity =
- BOTTOM | (deviceProfile.isLeftRightSplit ? START : CENTER_HORIZONTAL);
- bannerParams.width = snapshotViewWidth;
- } else {
- bannerParams.width = MATCH_PARENT;
- bannerParams.gravity = BOTTOM | CENTER_HORIZONTAL;
- }
- banner.setLayoutParams(bannerParams);
- }
-
- @NonNull
- @Override
- public Pair<Float, Float> getDwbBannerTranslations(int taskViewWidth,
- int taskViewHeight, SplitBounds splitBounds, @NonNull DeviceProfile deviceProfile,
- @NonNull View[] thumbnailViews, int desiredTaskId, @NonNull View banner) {
- float translationX = 0;
- float translationY = 0;
- if (splitBounds != null) {
- if (deviceProfile.isLeftRightSplit) {
- if (desiredTaskId == splitBounds.rightBottomTaskId) {
- float leftTopTaskPercent = splitBounds.getLeftTopTaskPercent();
- float dividerThicknessPercent = splitBounds.getDividerPercent();
- translationX = ((taskViewWidth * leftTopTaskPercent)
- + (taskViewWidth * dividerThicknessPercent));
- }
- } else {
- if (desiredTaskId == splitBounds.leftTopTaskId) {
- FrameLayout.LayoutParams snapshotParams =
- (FrameLayout.LayoutParams) thumbnailViews[0]
- .getLayoutParams();
- float bottomRightTaskPlusDividerPercent =
- splitBounds.getRightBottomTaskPercent()
- + splitBounds.getDividerPercent();
- translationY = -((taskViewHeight - snapshotParams.topMargin)
- * bottomRightTaskPlusDividerPercent);
- }
- }
- }
- return new Pair<>(translationX, translationY);
- }
-
- /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
-
- @Override
- public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
- return VERTICAL;
- }
-
- @Override
- public int getUpDirection(boolean isRtl) {
- // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
- return SingleAxisSwipeDetector.DIRECTION_POSITIVE;
- }
-
- @Override
- public int getDownDirection(boolean isRtl) {
- // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
- return SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
- }
-
- @Override
- public boolean isGoingUp(float displacement, boolean isRtl) {
- // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
- return displacement < 0;
- }
-
- @Override
- public int getTaskDragDisplacementFactor(boolean isRtl) {
- // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
- return 1;
- }
-
- /* -------------------- */
- @Override
- public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
- return dp.heightPx - rect.bottom;
- }
-
- @Override
- public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
- if (dp.isTablet) {
- return Utilities.getSplitPositionOptions(dp);
- }
-
- List<SplitPositionOption> options = new ArrayList<>();
- if (dp.isSeascape()) {
- options.add(new SplitPositionOption(
- R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
- STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
- } else if (dp.isLeftRightSplit) {
- options.add(new SplitPositionOption(
- R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
- STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
- } else {
- // Only add top option
- options.add(new SplitPositionOption(
- R.drawable.ic_split_vertical, R.string.recent_task_option_split_screen,
- STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
- }
- return options;
- }
-
- @Override
- public void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset,
- DeviceProfile dp, @StagePosition int stagePosition, Rect out) {
- int screenWidth = dp.widthPx;
- int screenHeight = dp.heightPx;
- boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
- int insetSizeAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight);
-
- out.set(0, 0, screenWidth, placeholderHeight + insetSizeAdjustment);
- if (!dp.isLeftRightSplit) {
- // portrait, phone or tablet - spans width of screen, nothing else to do
- out.inset(placeholderInset, 0);
-
- // Adjust the top to account for content off screen. This will help to animate the view
- // in with rounded corners.
- int totalHeight = (int) (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset)
- / screenWidth);
- out.top -= (totalHeight - placeholderHeight);
- return;
- }
-
- // Now we rotate the portrait rect depending on what side we want pinned
-
- float postRotateScale = (float) screenHeight / screenWidth;
- mTmpMatrix.reset();
- mTmpMatrix.postRotate(pinToRight ? 90 : 270);
- mTmpMatrix.postTranslate(pinToRight ? screenWidth : 0, pinToRight ? 0 : screenWidth);
- // The placeholder height stays constant after rotation, so we don't change width scale
- mTmpMatrix.postScale(1, postRotateScale);
-
- mTmpRectF.set(out);
- mTmpMatrix.mapRect(mTmpRectF);
- mTmpRectF.inset(0, placeholderInset);
- mTmpRectF.roundOut(out);
-
- // Adjust the top to account for content off screen. This will help to animate the view in
- // with rounded corners.
- int totalWidth = (int) (1.0f * screenWidth / 2 * (screenHeight - 2 * placeholderInset)
- / screenHeight);
- int width = out.width();
- if (pinToRight) {
- out.right += totalWidth - width;
- } else {
- out.left -= totalWidth - width;
- }
- }
-
- @Override
- public void updateSplitIconParams(View out, float onScreenRectCenterX,
- float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
- int drawableWidth, int drawableHeight, DeviceProfile dp,
- @StagePosition int stagePosition) {
- boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
- float insetAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight) / 2f;
- if (!dp.isLeftRightSplit) {
- out.setX(onScreenRectCenterX / fullscreenScaleX
- - 1.0f * drawableWidth / 2);
- out.setY((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY
- - 1.0f * drawableHeight / 2);
- } else {
- if (pinToRight) {
- out.setX((onScreenRectCenterX - insetAdjustment) / fullscreenScaleX
- - 1.0f * drawableWidth / 2);
- } else {
- out.setX((onScreenRectCenterX + insetAdjustment) / fullscreenScaleX
- - 1.0f * drawableWidth / 2);
- }
- out.setY(onScreenRectCenterY / fullscreenScaleY
- - 1.0f * drawableHeight / 2);
- }
- }
-
- /**
- * The split placeholder comes with a default inset to buffer the icon from the top of the
- * screen. But if the device already has a large inset (from cutouts etc), use that instead.
- */
- private int getPlaceholderSizeAdjustment(DeviceProfile dp, boolean pinToRight) {
- int insetThickness;
- if (!dp.isLandscape) {
- insetThickness = dp.getInsets().top;
- } else {
- insetThickness = pinToRight ? dp.getInsets().right : dp.getInsets().left;
- }
- return Math.max(insetThickness - dp.splitPlaceholderInset, 0);
- }
-
- @Override
- public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
- int splitInstructionsWidth) {
- out.setPivotX(0);
- out.setPivotY(splitInstructionsHeight);
- out.setRotation(getDegreesRotated());
- int distanceToEdge;
- if (dp.isPhone) {
- if (dp.isLandscape) {
- distanceToEdge = out.getResources().getDimensionPixelSize(
- R.dimen.split_instructions_bottom_margin_phone_landscape);
- } else {
- distanceToEdge = out.getResources().getDimensionPixelSize(
- R.dimen.split_instructions_bottom_margin_phone_portrait);
- }
- } else {
- distanceToEdge = dp.getOverviewActionsClaimedSpaceBelow();
- }
-
- // Center the view in case of unbalanced insets on left or right of screen
- int insetCorrectionX = (dp.getInsets().right - dp.getInsets().left) / 2;
- // Adjust for any insets on the bottom edge
- int insetCorrectionY = dp.getInsets().bottom;
- out.setTranslationX(insetCorrectionX);
- out.setTranslationY(-distanceToEdge + insetCorrectionY);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
- lp.gravity = CENTER_HORIZONTAL | BOTTOM;
- out.setLayoutParams(lp);
- }
-
- @Override
- public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
- @StagePosition int stagePosition, Rect out1, Rect out2) {
- int screenHeight = dp.heightPx;
- int screenWidth = dp.widthPx;
- out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
- out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight);
- if (!dp.isLeftRightSplit) {
- // Portrait - the window bounds are always top and bottom half
- return;
- }
-
- // Now we rotate the portrait rect depending on what side we want pinned
- boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
- float postRotateScale = (float) screenHeight / screenWidth;
-
- mTmpMatrix.reset();
- mTmpMatrix.postRotate(pinToRight ? 90 : 270);
- mTmpMatrix.postTranslate(pinToRight ? screenHeight : 0, pinToRight ? 0 : screenWidth);
- mTmpMatrix.postScale(1 / postRotateScale, postRotateScale);
-
- mTmpRectF.set(out1);
- mTmpMatrix.mapRect(mTmpRectF);
- mTmpRectF.roundOut(out1);
-
- mTmpRectF.set(out2);
- mTmpMatrix.mapRect(mTmpRectF);
- mTmpRectF.roundOut(out2);
- }
-
- @Override
- public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
- SplitBounds splitInfo, int desiredStagePosition) {
- float topLeftTaskPercent = splitInfo.getLeftTopTaskPercent();
- float dividerBarPercent = splitInfo.getDividerPercent();
-
- int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight;
- float scale = (float) outRect.height() / (dp.availableHeightPx - taskbarHeight);
- float topTaskHeight = dp.availableHeightPx * topLeftTaskPercent;
- float scaledTopTaskHeight = topTaskHeight * scale;
- float dividerHeight = dp.availableHeightPx * dividerBarPercent;
- float scaledDividerHeight = dividerHeight * scale;
-
- if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
- if (dp.isLeftRightSplit) {
- outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent);
- } else {
- outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight);
- }
- } else {
- if (dp.isLeftRightSplit) {
- outRect.left += Math.round(outRect.width()
- * (topLeftTaskPercent + dividerBarPercent));
- } else {
- outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight);
- }
- }
- }
-
- /**
- * @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. If true, we have custom translations/scaling in place
- * for the remaining snapshot, so we'll skip setting translation/scale
- * here.
- */
- @Override
- public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
- int parentWidth, int parentHeight, SplitBounds splitBoundsConfig,
- DeviceProfile dp, boolean isRtl, boolean inSplitSelection) {
- int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
-
- FrameLayout.LayoutParams primaryParams =
- (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
- FrameLayout.LayoutParams secondaryParams =
- (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
-
- // Reset margins that aren't used in this method, but are used in other
- // `RecentsPagedOrientationHandler` variants.
- secondaryParams.topMargin = 0;
- primaryParams.topMargin = spaceAboveSnapshot;
-
- int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- float dividerScale = splitBoundsConfig.getDividerPercent();
- Pair<Point, Point> taskViewSizes =
- getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight);
- if (!inSplitSelection) {
- // Reset translations that aren't used in this method, but are used in other
- // `RecentsPagedOrientationHandler` variants.
- primarySnapshot.setTranslationY(0);
-
- if (dp.isLeftRightSplit) {
- int scaledDividerBar = Math.round(parentWidth * dividerScale);
- if (isRtl) {
- int translationX = taskViewSizes.second.x + scaledDividerBar;
- primarySnapshot.setTranslationX(-translationX);
- secondarySnapshot.setTranslationX(0);
- } else {
- int translationX = taskViewSizes.first.x + scaledDividerBar;
- secondarySnapshot.setTranslationX(translationX);
- primarySnapshot.setTranslationX(0);
- }
- secondarySnapshot.setTranslationY(spaceAboveSnapshot);
- } else {
- float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
- float translationY =
- taskViewSizes.first.y + spaceAboveSnapshot + finalDividerHeight;
- secondarySnapshot.setTranslationY(translationY);
-
- // Reset unused translations.
- secondarySnapshot.setTranslationX(0);
- primarySnapshot.setTranslationX(0);
- }
- }
-
- primarySnapshot.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY));
- secondarySnapshot.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y,
- View.MeasureSpec.EXACTLY));
- }
-
- @Override
- public Pair<Point, Point> getGroupedTaskViewSizes(
- DeviceProfile dp,
- SplitBounds splitBoundsConfig,
- int parentWidth,
- int parentHeight) {
- int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
- int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- float dividerScale = splitBoundsConfig.getDividerPercent();
- float taskPercent = splitBoundsConfig.getLeftTopTaskPercent();
-
- Point firstTaskViewSize = new Point();
- Point secondTaskViewSize = new Point();
-
- if (dp.isLeftRightSplit) {
- int scaledDividerBar = Math.round(parentWidth * dividerScale);
- firstTaskViewSize.x = Math.round(parentWidth * taskPercent);
- firstTaskViewSize.y = totalThumbnailHeight;
-
- secondTaskViewSize.x = parentWidth - firstTaskViewSize.x - scaledDividerBar;
- secondTaskViewSize.y = totalThumbnailHeight;
- } else {
- int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight;
- float scale = (float) totalThumbnailHeight / (dp.availableHeightPx - taskbarHeight);
- float topTaskHeight = dp.availableHeightPx * taskPercent;
- float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
- float scaledTopTaskHeight = topTaskHeight * scale;
- firstTaskViewSize.x = parentWidth;
- firstTaskViewSize.y = Math.round(scaledTopTaskHeight);
-
- secondTaskViewSize.x = parentWidth;
- secondTaskViewSize.y = Math.round(totalThumbnailHeight - firstTaskViewSize.y
- - finalDividerHeight);
- }
-
- return new Pair<>(firstTaskViewSize, secondTaskViewSize);
- }
-
- @Override
- public void setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin,
- int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
- iconParams.gravity = TOP | CENTER_HORIZONTAL;
- // Reset margins, since they may have been set on rotation
- iconParams.leftMargin = iconParams.rightMargin = 0;
- iconParams.topMargin = iconParams.bottomMargin = 0;
- }
-
- @Override
- public void setIconAppChipChildrenParams(FrameLayout.LayoutParams iconParams,
- int chipChildMarginStart) {
- iconParams.setMarginStart(chipChildMarginStart);
- iconParams.gravity = Gravity.START | Gravity.CENTER_VERTICAL;
- iconParams.topMargin = 0;
- }
-
- @Override
- public void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
- FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) {
- iconMenuParams.gravity = TOP | START;
- iconMenuParams.setMarginStart(iconMenuMargin);
- iconMenuParams.topMargin = thumbnailTopMargin;
- iconMenuParams.bottomMargin = 0;
- iconMenuParams.setMarginEnd(0);
-
- iconAppChipView.setPivotX(0);
- iconAppChipView.setPivotY(0);
- iconAppChipView.setSplitTranslationY(0);
- iconAppChipView.setRotation(getDegreesRotated());
- }
-
- /**
- * @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. If true, we have custom translations in place for the
- * remaining icon, so we'll skip setting translations here.
- */
- @Override
- public void setSplitIconParams(View primaryIconView, View secondaryIconView,
- int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
- int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
- DeviceProfile deviceProfile, SplitBounds splitConfig, boolean inSplitSelection,
- boolean oneIconHiddenDueToSmallWidth) {
- FrameLayout.LayoutParams primaryIconParams =
- (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
- FrameLayout.LayoutParams secondaryIconParams = enableOverviewIconMenu()
- ? (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams()
- : new FrameLayout.LayoutParams(primaryIconParams);
-
- if (enableOverviewIconMenu()) {
- IconAppChipView primaryAppChipView = (IconAppChipView) primaryIconView;
- IconAppChipView secondaryAppChipView = (IconAppChipView) secondaryIconView;
- primaryIconParams.gravity = TOP | START;
- secondaryIconParams.gravity = TOP | START;
- secondaryIconParams.topMargin = primaryIconParams.topMargin;
- secondaryIconParams.setMarginStart(primaryIconParams.getMarginStart());
- if (!inSplitSelection) {
- if (deviceProfile.isLeftRightSplit) {
- if (isRtl) {
- int secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth;
- primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth);
- } else {
- secondaryAppChipView.setSplitTranslationX(primarySnapshotWidth);
- }
- } else {
- primaryAppChipView.setSplitTranslationX(0);
- secondaryAppChipView.setSplitTranslationX(0);
- int dividerThickness = Math.min(splitConfig.visualDividerBounds.width(),
- splitConfig.visualDividerBounds.height());
- secondaryAppChipView.setSplitTranslationY(
- primarySnapshotHeight + (deviceProfile.isTablet ? 0
- : dividerThickness));
- }
- }
- } else if (deviceProfile.isLeftRightSplit) {
- // We calculate the "midpoint" of the thumbnail area, and place the icons there.
- // This is the place where the thumbnail area splits by default, in a near-50/50 split.
- // It is usually not exactly 50/50, due to insets/screen cutouts.
- int fullscreenInsetThickness = deviceProfile.isSeascape()
- ? deviceProfile.getInsets().right
- : deviceProfile.getInsets().left;
- int fullscreenMidpointFromBottom = ((deviceProfile.widthPx
- - fullscreenInsetThickness) / 2);
- float midpointFromEndPct = (float) fullscreenMidpointFromBottom
- / deviceProfile.widthPx;
- float insetPct = (float) fullscreenInsetThickness / deviceProfile.widthPx;
- int spaceAboveSnapshots = 0;
- int overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots;
- int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness
- * midpointFromEndPct);
- int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
-
- if (deviceProfile.isSeascape()) {
- primaryIconParams.gravity = TOP | (isRtl ? END : START);
- secondaryIconParams.gravity = TOP | (isRtl ? END : START);
- if (!inSplitSelection) {
- if (splitConfig.initiatedFromSeascape) {
- if (oneIconHiddenDueToSmallWidth) {
- // Center both icons
- float centerX = bottomToMidpointOffset - (taskIconHeight / 2f);
- primaryIconView.setTranslationX(centerX);
- secondaryIconView.setTranslationX(centerX);
- } else {
- // the task on the right (secondary) is slightly larger
- primaryIconView.setTranslationX(
- bottomToMidpointOffset - taskIconHeight);
- secondaryIconView.setTranslationX(bottomToMidpointOffset);
- }
- } else {
- if (oneIconHiddenDueToSmallWidth) {
- // Center both icons
- float centerX =
- bottomToMidpointOffset + insetOffset - (taskIconHeight / 2f);
- primaryIconView.setTranslationX(centerX);
- secondaryIconView.setTranslationX(centerX);
- } else {
- // the task on the left (primary) is slightly larger
- primaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset
- - taskIconHeight);
- secondaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset);
- }
- }
- }
- } else {
- primaryIconParams.gravity = TOP | (isRtl ? START : END);
- secondaryIconParams.gravity = TOP | (isRtl ? START : END);
- if (!inSplitSelection) {
- if (!splitConfig.initiatedFromSeascape) {
- if (oneIconHiddenDueToSmallWidth) {
- // Center both icons
- float centerX = -bottomToMidpointOffset + (taskIconHeight / 2f);
- primaryIconView.setTranslationX(centerX);
- secondaryIconView.setTranslationX(centerX);
- } else {
- // the task on the left (primary) is slightly larger
- primaryIconView.setTranslationX(-bottomToMidpointOffset);
- secondaryIconView.setTranslationX(
- -bottomToMidpointOffset + taskIconHeight);
- }
- } else {
- if (oneIconHiddenDueToSmallWidth) {
- // Center both icons
- float centerX =
- -bottomToMidpointOffset - insetOffset + (taskIconHeight / 2f);
- primaryIconView.setTranslationX(centerX);
- secondaryIconView.setTranslationX(centerX);
- } else {
- // the task on the right (secondary) is slightly larger
- primaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset);
- secondaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset
- + taskIconHeight);
- }
- }
- }
- }
- } else {
- primaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
- secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
- if (!inSplitSelection) {
- if (oneIconHiddenDueToSmallWidth) {
- // Center both icons
- primaryIconView.setTranslationX(0);
- secondaryIconView.setTranslationX(0);
- } else {
- // shifts icon half a width left (height is used here since icons are square)
- primaryIconView.setTranslationX(-(taskIconHeight / 2f));
- secondaryIconView.setTranslationX(taskIconHeight / 2f);
- }
- }
- }
- if (!enableOverviewIconMenu() && !inSplitSelection) {
- primaryIconView.setTranslationY(0);
- secondaryIconView.setTranslationY(0);
- }
-
-
- primaryIconView.setLayoutParams(primaryIconParams);
- secondaryIconView.setLayoutParams(secondaryIconParams);
- }
-
- @Override
- public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
- if (!deviceProfile.isTablet) {
- throw new IllegalStateException("Default position available only for large screens");
- }
- if (deviceProfile.isLeftRightSplit) {
- return STAGE_POSITION_BOTTOM_OR_RIGHT;
- } else {
- return STAGE_POSITION_TOP_OR_LEFT;
- }
- }
-
- @Override
- public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
- FloatProperty secondary, DeviceProfile deviceProfile) {
- if (deviceProfile.isLeftRightSplit) { // or seascape
- return new Pair<>(primary, secondary);
- } else {
- return new Pair<>(secondary, primary);
- }
- }
-
- @Override
- public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
- @StagePosition int stagePosition, DeviceProfile dp) {
- if (dp.isLeftRightSplit) {
- float currentTranslationX = floatingTask.getTranslationX();
- return stagePosition == STAGE_POSITION_TOP_OR_LEFT
- ? currentTranslationX - onScreenRect.width()
- : currentTranslationX + onScreenRect.width();
- } else {
- float currentTranslationY = floatingTask.getTranslationY();
- return currentTranslationY - onScreenRect.height();
- }
- }
-
- @Override
- public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation,
- DeviceProfile dp) {
- if (dp.isLeftRightSplit) {
- floatingTask.setTranslationX(translation);
- } else {
- floatingTask.setTranslationY(translation);
- }
-
- }
-
- @Override
- public float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
- return dp.isLeftRightSplit
- ? floatingTask.getTranslationX()
- : floatingTask.getTranslationY();
- }
-
- @NonNull
- @Override
- public LauncherAtom.TaskSwitcherContainer.OrientationHandler getHandlerTypeForLogging() {
- return LauncherAtom.TaskSwitcherContainer.OrientationHandler.PORTRAIT;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
new file mode 100644
index 0000000..1883649
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
@@ -0,0 +1,911 @@
+/*
+ * 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.orientation
+
+import android.graphics.Matrix
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.ShapeDrawable
+import android.util.FloatProperty
+import android.util.Pair
+import android.view.Gravity
+import android.view.Surface
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.core.view.updateLayoutParams
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.LauncherAnimUtils
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.touch.DefaultPagedViewHandler
+import com.android.launcher3.touch.PagedOrientationHandler.Float2DAction
+import com.android.launcher3.touch.PagedOrientationHandler.Int2DAction
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
+import com.android.quickstep.views.IconAppChipView
+import kotlin.math.max
+import kotlin.math.min
+
+class PortraitPagedViewHandler : DefaultPagedViewHandler(), RecentsPagedOrientationHandler {
+ private val tmpMatrix = Matrix()
+ private val tmpRectF = RectF()
+
+ override fun <T> getPrimaryValue(x: T, y: T): T = x
+
+ override fun <T> getSecondaryValue(x: T, y: T): T = y
+
+ override val isLayoutNaturalToLauncher: Boolean = true
+
+ override fun adjustFloatingIconStartVelocity(velocity: PointF) {
+ // no-op
+ }
+
+ override fun fixBoundsForHomeAnimStartRect(outStartRect: RectF, deviceProfile: DeviceProfile) {
+ if (outStartRect.left > deviceProfile.widthPx) {
+ outStartRect.offsetTo(0f, outStartRect.top)
+ } else if (outStartRect.left < -deviceProfile.widthPx) {
+ outStartRect.offsetTo(0f, outStartRect.top)
+ }
+ }
+
+ override fun <T> setSecondary(target: T, action: Float2DAction<T>, param: Float) =
+ action.call(target, 0f, param)
+
+ override fun <T> set(
+ target: T,
+ action: Int2DAction<T>,
+ primaryParam: Int,
+ secondaryParam: Int,
+ ) = action.call(target, primaryParam, secondaryParam)
+
+ override fun getPrimarySize(view: View): Int = view.width
+
+ override fun getPrimarySize(rect: RectF): Float = rect.width()
+
+ override fun getStart(rect: RectF): Float = rect.left
+
+ override fun getEnd(rect: RectF): Float = rect.right
+
+ override fun rotateInsets(insets: Rect, outInsets: Rect) = outInsets.set(insets)
+
+ override fun getClearAllSidePadding(view: View, isRtl: Boolean): Int =
+ (if (isRtl) view.paddingRight else -view.paddingLeft) / 2
+
+ override fun getSecondaryDimension(view: View): Int = view.height
+
+ override val primaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_X
+
+ override val secondaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_Y
+
+ override val degreesRotated: Float = 0f
+
+ override val rotation: Int = Surface.ROTATION_0
+
+ override fun setPrimaryScale(view: View, scale: Float) {
+ view.scaleX = scale
+ }
+
+ override fun setSecondaryScale(view: View, scale: Float) {
+ view.scaleY = scale
+ }
+
+ override val secondaryTranslationDirectionFactor: Int
+ get() = -1
+
+ override fun getSplitTranslationDirectionFactor(
+ stagePosition: Int,
+ deviceProfile: DeviceProfile,
+ ): Int =
+ if (
+ deviceProfile.isLeftRightSplit &&
+ stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ ) {
+ -1
+ } else {
+ 1
+ }
+
+ override fun getTaskMenuX(
+ x: Float,
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ taskInsetMargin: Float,
+ taskViewIcon: View,
+ ): Float =
+ if (deviceProfile.isLandscape) {
+ (x +
+ taskInsetMargin +
+ (thumbnailView.measuredWidth - thumbnailView.measuredHeight) / 2f)
+ } else {
+ x + taskInsetMargin
+ }
+
+ override fun getTaskMenuY(
+ y: Float,
+ thumbnailView: View,
+ stagePosition: Int,
+ taskMenuView: View,
+ taskInsetMargin: Float,
+ taskViewIcon: View,
+ ): Float = y + taskInsetMargin
+
+ override fun getTaskMenuWidth(
+ thumbnailView: View,
+ deviceProfile: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ ): Int =
+ when {
+ enableOverviewIconMenu() -> {
+ thumbnailView.resources.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_expanded_width
+ )
+ }
+
+ (deviceProfile.isLandscape && !deviceProfile.isTablet) -> {
+ val padding =
+ thumbnailView.resources.getDimensionPixelSize(R.dimen.task_menu_edge_padding)
+ thumbnailView.measuredHeight - (2 * padding)
+ }
+
+ else -> {
+ val padding =
+ thumbnailView.resources.getDimensionPixelSize(R.dimen.task_menu_edge_padding)
+ thumbnailView.measuredWidth - (2 * padding)
+ }
+ }
+
+ override fun getTaskMenuHeight(
+ taskInsetMargin: Float,
+ deviceProfile: DeviceProfile,
+ taskMenuX: Float,
+ taskMenuY: Float,
+ ): Int =
+ deviceProfile.heightPx -
+ deviceProfile.insets.top -
+ taskMenuY.toInt() -
+ deviceProfile.overviewActionsClaimedSpaceBelow
+
+ override fun setTaskOptionsMenuLayoutOrientation(
+ deviceProfile: DeviceProfile,
+ taskMenuLayout: LinearLayout,
+ dividerSpacing: Int,
+ dividerDrawable: ShapeDrawable,
+ ) {
+ taskMenuLayout.orientation = LinearLayout.VERTICAL
+ dividerDrawable.intrinsicHeight = dividerSpacing
+ taskMenuLayout.dividerDrawable = dividerDrawable
+ }
+
+ override fun setLayoutParamsForTaskMenuOptionItem(
+ lp: LinearLayout.LayoutParams,
+ viewGroup: LinearLayout,
+ deviceProfile: DeviceProfile,
+ ) {
+ viewGroup.orientation = LinearLayout.HORIZONTAL
+ lp.width = LinearLayout.LayoutParams.MATCH_PARENT
+ lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
+ }
+
+ override fun updateDwbBannerLayout(
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ isGroupedTaskView: Boolean,
+ deviceProfile: DeviceProfile,
+ snapshotViewWidth: Int,
+ snapshotViewHeight: Int,
+ banner: View,
+ ) {
+ banner.pivotX = 0f
+ banner.pivotY = 0f
+ banner.rotation = degreesRotated
+ banner.updateLayoutParams<FrameLayout.LayoutParams> {
+ if (isGroupedTaskView) {
+ gravity =
+ Gravity.BOTTOM or
+ (if (deviceProfile.isLeftRightSplit) Gravity.START
+ else Gravity.CENTER_HORIZONTAL)
+ width = snapshotViewWidth
+ } else {
+ width = ViewGroup.LayoutParams.MATCH_PARENT
+ gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ }
+ }
+ }
+
+ override fun getDwbBannerTranslations(
+ taskViewWidth: Int,
+ taskViewHeight: Int,
+ splitBounds: SplitConfigurationOptions.SplitBounds?,
+ deviceProfile: DeviceProfile,
+ thumbnailViews: Array<View>,
+ desiredTaskId: Int,
+ banner: View,
+ ): Pair<Float, Float> {
+ var translationX = 0f
+ var translationY = 0f
+ if (splitBounds != null) {
+ if (deviceProfile.isLeftRightSplit) {
+ if (desiredTaskId == splitBounds.rightBottomTaskId) {
+ val leftTopTaskPercent = splitBounds.leftTopTaskPercent
+ val dividerThicknessPercent = splitBounds.dividerPercent
+ translationX =
+ ((taskViewWidth * leftTopTaskPercent) +
+ (taskViewWidth * dividerThicknessPercent))
+ }
+ } else {
+ if (desiredTaskId == splitBounds.leftTopTaskId) {
+ val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
+ val bottomRightTaskPlusDividerPercent =
+ (splitBounds.rightBottomTaskPercent + splitBounds.dividerPercent)
+ translationY =
+ -((taskViewHeight - snapshotParams.topMargin) *
+ bottomRightTaskPlusDividerPercent)
+ }
+ }
+ }
+ return Pair(translationX, translationY)
+ }
+
+ /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
+
+ override val upDownSwipeDirection: SingleAxisSwipeDetector.Direction =
+ SingleAxisSwipeDetector.VERTICAL
+
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ override fun getUpDirection(isRtl: Boolean): Int = SingleAxisSwipeDetector.DIRECTION_POSITIVE
+
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ override fun getDownDirection(isRtl: Boolean): Int = SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean = displacement < 0
+
+ // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+ override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = 1
+
+ override fun getTaskDismissVerticalDirection(): Int = -1
+
+ override fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ taskThumbnailBounds.bottom
+
+ override fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ secondaryDimension - taskThumbnailBounds.bottom
+
+ /* -------------------- */
+
+ override fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int =
+ dp.heightPx - rect.bottom
+
+ override fun getSplitPositionOptions(dp: DeviceProfile): List<SplitPositionOption> =
+ when {
+ dp.isTablet -> {
+ Utilities.getSplitPositionOptions(dp)
+ }
+
+ dp.isSeascape -> {
+ listOf(
+ SplitPositionOption(
+ R.drawable.ic_split_horizontal,
+ R.string.recent_task_option_split_screen,
+ SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT,
+ SplitConfigurationOptions.STAGE_TYPE_MAIN,
+ )
+ )
+ }
+
+ dp.isLeftRightSplit -> {
+ listOf(
+ SplitPositionOption(
+ R.drawable.ic_split_horizontal,
+ R.string.recent_task_option_split_screen,
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+ SplitConfigurationOptions.STAGE_TYPE_MAIN,
+ )
+ )
+ }
+
+ else -> {
+ // Only add top option
+ listOf(
+ SplitPositionOption(
+ R.drawable.ic_split_vertical,
+ R.string.recent_task_option_split_screen,
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+ SplitConfigurationOptions.STAGE_TYPE_MAIN,
+ )
+ )
+ }
+ }
+
+ override fun getInitialSplitPlaceholderBounds(
+ placeholderHeight: Int,
+ placeholderInset: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out: Rect,
+ ) {
+ val screenWidth = dp.widthPx
+ val screenHeight = dp.heightPx
+ val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ val insetSizeAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight)
+
+ out.set(0, 0, screenWidth, placeholderHeight + insetSizeAdjustment)
+ if (!dp.isLeftRightSplit) {
+ // portrait, phone or tablet - spans width of screen, nothing else to do
+ out.inset(placeholderInset, 0)
+
+ // Adjust the top to account for content off screen. This will help to animate the view
+ // in with rounded corners.
+ val totalHeight =
+ (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset) / screenWidth)
+ .toInt()
+ out.top -= (totalHeight - placeholderHeight)
+ return
+ }
+
+ // Now we rotate the portrait rect depending on what side we want pinned
+ val postRotateScale = screenHeight.toFloat() / screenWidth
+ tmpMatrix.reset()
+ tmpMatrix.postRotate(if (pinToRight) 90f else 270f)
+ tmpMatrix.postTranslate(
+ (if (pinToRight) screenWidth else 0).toFloat(),
+ (if (pinToRight) 0 else screenWidth).toFloat(),
+ )
+ // The placeholder height stays constant after rotation, so we don't change width scale
+ tmpMatrix.postScale(1f, postRotateScale)
+
+ tmpRectF.set(out)
+ tmpMatrix.mapRect(tmpRectF)
+ tmpRectF.inset(0f, placeholderInset.toFloat())
+ tmpRectF.roundOut(out)
+
+ // Adjust the top to account for content off screen. This will help to animate the view in
+ // with rounded corners.
+ val totalWidth =
+ (1.0f * screenWidth / 2 * (screenHeight - 2 * placeholderInset) / screenHeight).toInt()
+ val width = out.width()
+ if (pinToRight) {
+ out.right += totalWidth - width
+ } else {
+ out.left -= totalWidth - width
+ }
+ }
+
+ override fun updateSplitIconParams(
+ out: View,
+ onScreenRectCenterX: Float,
+ onScreenRectCenterY: Float,
+ fullscreenScaleX: Float,
+ fullscreenScaleY: Float,
+ drawableWidth: Int,
+ drawableHeight: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ ) {
+ val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ val insetAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight) / 2f
+ if (!dp.isLeftRightSplit) {
+ out.x = (onScreenRectCenterX / fullscreenScaleX - 1.0f * drawableWidth / 2)
+ out.y =
+ ((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY -
+ 1.0f * drawableHeight / 2)
+ } else {
+ if (pinToRight) {
+ out.x =
+ ((onScreenRectCenterX - insetAdjustment) / fullscreenScaleX -
+ 1.0f * drawableWidth / 2)
+ } else {
+ out.x =
+ ((onScreenRectCenterX + insetAdjustment) / fullscreenScaleX -
+ 1.0f * drawableWidth / 2)
+ }
+ out.y = (onScreenRectCenterY / fullscreenScaleY - 1.0f * drawableHeight / 2)
+ }
+ }
+
+ /**
+ * The split placeholder comes with a default inset to buffer the icon from the top of the
+ * screen. But if the device already has a large inset (from cutouts etc), use that instead.
+ */
+ private fun getPlaceholderSizeAdjustment(dp: DeviceProfile, pinToRight: Boolean): Int {
+ val insetThickness =
+ if (!dp.isLandscape) {
+ dp.insets.top
+ } else {
+ if (pinToRight) dp.insets.right else dp.insets.left
+ }
+ return max((insetThickness - dp.splitPlaceholderInset).toDouble(), 0.0).toInt()
+ }
+
+ override fun setSplitInstructionsParams(
+ out: View,
+ dp: DeviceProfile,
+ splitInstructionsHeight: Int,
+ splitInstructionsWidth: Int,
+ ) {
+ out.pivotX = 0f
+ out.pivotY = splitInstructionsHeight.toFloat()
+ out.rotation = degreesRotated
+ val distanceToEdge =
+ if (dp.isPhone) {
+ if (dp.isLandscape) {
+ out.resources.getDimensionPixelSize(
+ R.dimen.split_instructions_bottom_margin_phone_landscape
+ )
+ } else {
+ out.resources.getDimensionPixelSize(
+ R.dimen.split_instructions_bottom_margin_phone_portrait
+ )
+ }
+ } else {
+ dp.overviewActionsClaimedSpaceBelow
+ }
+
+ // Center the view in case of unbalanced insets on left or right of screen
+ val insetCorrectionX = (dp.insets.right - dp.insets.left) / 2
+ // Adjust for any insets on the bottom edge
+ val insetCorrectionY = dp.insets.bottom
+ out.translationX = insetCorrectionX.toFloat()
+ out.translationY = (-distanceToEdge + insetCorrectionY).toFloat()
+ val lp = out.layoutParams as FrameLayout.LayoutParams
+ lp.gravity = Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+ out.layoutParams = lp
+ }
+
+ override fun getFinalSplitPlaceholderBounds(
+ splitDividerSize: Int,
+ dp: DeviceProfile,
+ @StagePosition stagePosition: Int,
+ out1: Rect,
+ out2: Rect,
+ ) {
+ val screenHeight = dp.heightPx
+ val screenWidth = dp.widthPx
+ out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize)
+ out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight)
+ if (!dp.isLeftRightSplit) {
+ // Portrait - the window bounds are always top and bottom half
+ return
+ }
+
+ // Now we rotate the portrait rect depending on what side we want pinned
+ val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ val postRotateScale = screenHeight.toFloat() / screenWidth
+
+ tmpMatrix.reset()
+ tmpMatrix.postRotate(if (pinToRight) 90f else 270f)
+ tmpMatrix.postTranslate(
+ (if (pinToRight) screenHeight else 0).toFloat(),
+ (if (pinToRight) 0 else screenWidth).toFloat(),
+ )
+ tmpMatrix.postScale(1 / postRotateScale, postRotateScale)
+
+ tmpRectF.set(out1)
+ tmpMatrix.mapRect(tmpRectF)
+ tmpRectF.roundOut(out1)
+
+ tmpRectF.set(out2)
+ tmpMatrix.mapRect(tmpRectF)
+ tmpRectF.roundOut(out2)
+ }
+
+ override fun setSplitTaskSwipeRect(
+ dp: DeviceProfile,
+ outRect: Rect,
+ splitInfo: SplitConfigurationOptions.SplitBounds,
+ desiredStagePosition: Int,
+ ) {
+ val topLeftTaskPercent = splitInfo.leftTopTaskPercent
+ val dividerBarPercent = splitInfo.dividerPercent
+
+ val taskbarHeight = if (dp.isTransientTaskbar) 0 else dp.taskbarHeight
+ val scale = outRect.height().toFloat() / (dp.availableHeightPx - taskbarHeight)
+ val topTaskHeight = dp.availableHeightPx * topLeftTaskPercent
+ val scaledTopTaskHeight = topTaskHeight * scale
+ val dividerHeight = dp.availableHeightPx * dividerBarPercent
+ val scaledDividerHeight = dividerHeight * scale
+
+ if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
+ if (dp.isLeftRightSplit) {
+ outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent)
+ } else {
+ outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight)
+ }
+ } else {
+ if (dp.isLeftRightSplit) {
+ outRect.left +=
+ Math.round(outRect.width() * (topLeftTaskPercent + dividerBarPercent))
+ } else {
+ outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight)
+ }
+ }
+ }
+
+ /**
+ * @param inSplitSelection Whether user currently has a task from this task group staged for
+ * split screen. If true, we have custom translations/scaling in place for the remaining
+ * snapshot, so we'll skip setting translation/scale here.
+ */
+ override fun measureGroupedTaskViewThumbnailBounds(
+ primarySnapshot: View,
+ secondarySnapshot: View,
+ parentWidth: Int,
+ parentHeight: Int,
+ splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+ dp: DeviceProfile,
+ isRtl: Boolean,
+ inSplitSelection: Boolean,
+ ) {
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+
+ val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
+ val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
+
+ // Reset margins that aren't used in this method, but are used in other
+ // `RecentsPagedOrientationHandler` variants.
+ secondaryParams.topMargin = 0
+ primaryParams.topMargin = spaceAboveSnapshot
+
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerScale = splitBoundsConfig.dividerPercent
+ val taskViewSizes =
+ getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight)
+ if (!inSplitSelection) {
+ // Reset translations that aren't used in this method, but are used in other
+ // `RecentsPagedOrientationHandler` variants.
+ primarySnapshot.translationY = 0f
+
+ if (dp.isLeftRightSplit) {
+ val scaledDividerBar = Math.round(parentWidth * dividerScale)
+ if (isRtl) {
+ val translationX = taskViewSizes.second.x + scaledDividerBar
+ primarySnapshot.translationX = -translationX.toFloat()
+ secondarySnapshot.translationX = 0f
+ } else {
+ val translationX = taskViewSizes.first.x + scaledDividerBar
+ secondarySnapshot.translationX = translationX.toFloat()
+ primarySnapshot.translationX = 0f
+ }
+ secondarySnapshot.translationY = spaceAboveSnapshot.toFloat()
+ } else {
+ val finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale).toFloat()
+ val translationY = taskViewSizes.first.y + spaceAboveSnapshot + finalDividerHeight
+ secondarySnapshot.translationY = translationY
+
+ // Reset unused translations.
+ secondarySnapshot.translationX = 0f
+ primarySnapshot.translationX = 0f
+ }
+ }
+
+ primarySnapshot.measure(
+ View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY),
+ )
+ secondarySnapshot.measure(
+ View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y, View.MeasureSpec.EXACTLY),
+ )
+ }
+
+ override fun getGroupedTaskViewSizes(
+ dp: DeviceProfile,
+ splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+ parentWidth: Int,
+ parentHeight: Int,
+ ): Pair<Point, Point> {
+ val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+ val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+ val dividerScale = splitBoundsConfig.dividerPercent
+ val taskPercent = splitBoundsConfig.leftTopTaskPercent
+
+ val firstTaskViewSize = Point()
+ val secondTaskViewSize = Point()
+
+ if (dp.isLeftRightSplit) {
+ val scaledDividerBar = Math.round(parentWidth * dividerScale)
+ firstTaskViewSize.x = Math.round(parentWidth * taskPercent)
+ firstTaskViewSize.y = totalThumbnailHeight
+
+ secondTaskViewSize.x = parentWidth - firstTaskViewSize.x - scaledDividerBar
+ secondTaskViewSize.y = totalThumbnailHeight
+ } else {
+ val taskbarHeight = if (dp.isTransientTaskbar) 0 else dp.taskbarHeight
+ val scale = totalThumbnailHeight.toFloat() / (dp.availableHeightPx - taskbarHeight)
+ val topTaskHeight = dp.availableHeightPx * taskPercent
+ val finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale).toFloat()
+ val scaledTopTaskHeight = topTaskHeight * scale
+ firstTaskViewSize.x = parentWidth
+ firstTaskViewSize.y = Math.round(scaledTopTaskHeight)
+
+ secondTaskViewSize.x = parentWidth
+ secondTaskViewSize.y =
+ Math.round((totalThumbnailHeight - firstTaskViewSize.y - finalDividerHeight))
+ }
+
+ return Pair(firstTaskViewSize, secondTaskViewSize)
+ }
+
+ override fun setTaskIconParams(
+ iconParams: FrameLayout.LayoutParams,
+ taskIconMargin: Int,
+ taskIconHeight: Int,
+ thumbnailTopMargin: Int,
+ isRtl: Boolean,
+ ) {
+ iconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ // Reset margins, since they may have been set on rotation
+ iconParams.rightMargin = 0
+ iconParams.leftMargin = iconParams.rightMargin
+ iconParams.bottomMargin = 0
+ iconParams.topMargin = iconParams.bottomMargin
+ }
+
+ override fun setIconAppChipChildrenParams(
+ iconParams: FrameLayout.LayoutParams,
+ chipChildMarginStart: Int,
+ ) {
+ iconParams.marginStart = chipChildMarginStart
+ iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+ iconParams.topMargin = 0
+ }
+
+ override fun setIconAppChipMenuParams(
+ iconAppChipView: IconAppChipView,
+ iconMenuParams: FrameLayout.LayoutParams,
+ iconMenuMargin: Int,
+ thumbnailTopMargin: Int,
+ ) {
+ iconMenuParams.gravity = Gravity.TOP or Gravity.START
+ iconMenuParams.marginStart = iconMenuMargin
+ iconMenuParams.topMargin = thumbnailTopMargin
+ iconMenuParams.bottomMargin = 0
+ iconMenuParams.marginEnd = 0
+
+ iconAppChipView.pivotX = 0f
+ iconAppChipView.pivotY = 0f
+ iconAppChipView.setSplitTranslationY(0f)
+ iconAppChipView.rotation = degreesRotated
+ }
+
+ /**
+ * @param inSplitSelection Whether user currently has a task from this task group staged for
+ * split screen. If true, we have custom translations in place for the remaining icon, so
+ * we'll skip setting translations here.
+ */
+ override fun setSplitIconParams(
+ primaryIconView: View,
+ secondaryIconView: View,
+ taskIconHeight: Int,
+ primarySnapshotWidth: Int,
+ primarySnapshotHeight: Int,
+ groupedTaskViewHeight: Int,
+ groupedTaskViewWidth: Int,
+ isRtl: Boolean,
+ deviceProfile: DeviceProfile,
+ splitConfig: SplitConfigurationOptions.SplitBounds,
+ inSplitSelection: Boolean,
+ oneIconHiddenDueToSmallWidth: Boolean,
+ ) {
+ val primaryIconParams = primaryIconView.layoutParams as FrameLayout.LayoutParams
+ val secondaryIconParams =
+ if (enableOverviewIconMenu()) secondaryIconView.layoutParams as FrameLayout.LayoutParams
+ else FrameLayout.LayoutParams(primaryIconParams)
+
+ if (enableOverviewIconMenu()) {
+ val primaryAppChipView = primaryIconView as IconAppChipView
+ val secondaryAppChipView = secondaryIconView as IconAppChipView
+ primaryIconParams.gravity = Gravity.TOP or Gravity.START
+ secondaryIconParams.gravity = Gravity.TOP or Gravity.START
+ secondaryIconParams.topMargin = primaryIconParams.topMargin
+ secondaryIconParams.marginStart = primaryIconParams.marginStart
+ if (!inSplitSelection) {
+ if (deviceProfile.isLeftRightSplit) {
+ if (isRtl) {
+ val secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth
+ primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth.toFloat())
+ } else {
+ secondaryAppChipView.setSplitTranslationX(primarySnapshotWidth.toFloat())
+ }
+ } else {
+ primaryAppChipView.setSplitTranslationX(0f)
+ secondaryAppChipView.setSplitTranslationX(0f)
+ val dividerThickness =
+ min(
+ splitConfig.visualDividerBounds.width().toDouble(),
+ splitConfig.visualDividerBounds.height().toDouble(),
+ )
+ .toInt()
+ secondaryAppChipView.setSplitTranslationY(
+ (primarySnapshotHeight +
+ (if (deviceProfile.isTablet) 0 else dividerThickness))
+ .toFloat()
+ )
+ }
+ }
+ } else if (deviceProfile.isLeftRightSplit) {
+ // We calculate the "midpoint" of the thumbnail area, and place the icons there.
+ // This is the place where the thumbnail area splits by default, in a near-50/50 split.
+ // It is usually not exactly 50/50, due to insets/screen cutouts.
+ val fullscreenInsetThickness =
+ if (deviceProfile.isSeascape) deviceProfile.insets.right
+ else deviceProfile.insets.left
+ val fullscreenMidpointFromBottom =
+ ((deviceProfile.widthPx - fullscreenInsetThickness) / 2)
+ val midpointFromEndPct = fullscreenMidpointFromBottom.toFloat() / deviceProfile.widthPx
+ val insetPct = fullscreenInsetThickness.toFloat() / deviceProfile.widthPx
+ val spaceAboveSnapshots = 0
+ val overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots
+ val bottomToMidpointOffset =
+ (overviewThumbnailAreaThickness * midpointFromEndPct).toInt()
+ val insetOffset = (overviewThumbnailAreaThickness * insetPct).toInt()
+
+ if (deviceProfile.isSeascape) {
+ primaryIconParams.gravity =
+ Gravity.TOP or (if (isRtl) Gravity.END else Gravity.START)
+ secondaryIconParams.gravity =
+ Gravity.TOP or (if (isRtl) Gravity.END else Gravity.START)
+ if (!inSplitSelection) {
+ if (splitConfig.initiatedFromSeascape) {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerX = bottomToMidpointOffset - (taskIconHeight / 2f)
+ primaryIconView.translationX = centerX
+ secondaryIconView.translationX = centerX
+ } else {
+ // the task on the right (secondary) is slightly larger
+ primaryIconView.translationX =
+ (bottomToMidpointOffset - taskIconHeight).toFloat()
+ secondaryIconView.translationX = bottomToMidpointOffset.toFloat()
+ }
+ } else {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerX =
+ bottomToMidpointOffset + insetOffset - (taskIconHeight / 2f)
+ primaryIconView.translationX = centerX
+ secondaryIconView.translationX = centerX
+ } else {
+ // the task on the left (primary) is slightly larger
+ primaryIconView.translationX =
+ (bottomToMidpointOffset + insetOffset - taskIconHeight).toFloat()
+ secondaryIconView.translationX =
+ (bottomToMidpointOffset + insetOffset).toFloat()
+ }
+ }
+ }
+ } else {
+ primaryIconParams.gravity =
+ Gravity.TOP or (if (isRtl) Gravity.START else Gravity.END)
+ secondaryIconParams.gravity =
+ Gravity.TOP or (if (isRtl) Gravity.START else Gravity.END)
+ if (!inSplitSelection) {
+ if (!splitConfig.initiatedFromSeascape) {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerX = -bottomToMidpointOffset + (taskIconHeight / 2f)
+ primaryIconView.translationX = centerX
+ secondaryIconView.translationX = centerX
+ } else {
+ // the task on the left (primary) is slightly larger
+ primaryIconView.translationX = -bottomToMidpointOffset.toFloat()
+ secondaryIconView.translationX =
+ (-bottomToMidpointOffset + taskIconHeight).toFloat()
+ }
+ } else {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ val centerX =
+ -bottomToMidpointOffset - insetOffset + (taskIconHeight / 2f)
+ primaryIconView.translationX = centerX
+ secondaryIconView.translationX = centerX
+ } else {
+ // the task on the right (secondary) is slightly larger
+ primaryIconView.translationX =
+ (-bottomToMidpointOffset - insetOffset).toFloat()
+ secondaryIconView.translationX =
+ (-bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat()
+ }
+ }
+ }
+ }
+ } else {
+ primaryIconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ secondaryIconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+ if (!inSplitSelection) {
+ if (oneIconHiddenDueToSmallWidth) {
+ // Center both icons
+ primaryIconView.translationX = 0f
+ secondaryIconView.translationX = 0f
+ } else {
+ // shifts icon half a width left (height is used here since icons are square)
+ primaryIconView.translationX = -(taskIconHeight / 2f)
+ secondaryIconView.translationX = taskIconHeight / 2f
+ }
+ }
+ }
+ if (!enableOverviewIconMenu() && !inSplitSelection) {
+ primaryIconView.translationY = 0f
+ secondaryIconView.translationY = 0f
+ }
+
+ primaryIconView.layoutParams = primaryIconParams
+ secondaryIconView.layoutParams = secondaryIconParams
+ }
+
+ override fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int {
+ check(deviceProfile.isTablet) { "Default position available only for large screens" }
+ return if (deviceProfile.isLeftRightSplit) {
+ SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+ } else {
+ SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+ }
+ }
+
+ override fun <T> getSplitSelectTaskOffset(
+ primary: FloatProperty<T>,
+ secondary: FloatProperty<T>,
+ deviceProfile: DeviceProfile,
+ ): Pair<FloatProperty<T>, FloatProperty<T>> =
+ if (deviceProfile.isLeftRightSplit) { // or seascape
+ Pair(primary, secondary)
+ } else {
+ Pair(secondary, primary)
+ }
+
+ override fun getFloatingTaskOffscreenTranslationTarget(
+ floatingTask: View,
+ onScreenRect: RectF,
+ @StagePosition stagePosition: Int,
+ dp: DeviceProfile,
+ ): Float {
+ if (dp.isLeftRightSplit) {
+ val currentTranslationX = floatingTask.translationX
+ return if (stagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT)
+ currentTranslationX - onScreenRect.width()
+ else currentTranslationX + onScreenRect.width()
+ } else {
+ val currentTranslationY = floatingTask.translationY
+ return currentTranslationY - onScreenRect.height()
+ }
+ }
+
+ override fun setFloatingTaskPrimaryTranslation(
+ floatingTask: View,
+ translation: Float,
+ dp: DeviceProfile,
+ ) {
+ if (dp.isLeftRightSplit) {
+ floatingTask.translationX = translation
+ } else {
+ floatingTask.translationY = translation
+ }
+ }
+
+ override fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float =
+ if (dp.isLeftRightSplit) floatingTask.translationX else floatingTask.translationY
+
+ override fun getHandlerTypeForLogging(): LauncherAtom.TaskSwitcherContainer.OrientationHandler =
+ LauncherAtom.TaskSwitcherContainer.OrientationHandler.PORTRAIT
+}
diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
index 9b3c467..a7bc93b 100644
--- a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
@@ -82,13 +82,13 @@
fun getSplitTranslationDirectionFactor(
@StagePosition stagePosition: Int,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Int
fun <T> getSplitSelectTaskOffset(
primary: FloatProperty<T>,
secondary: FloatProperty<T>,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Pair<FloatProperty<T>, FloatProperty<T>>
fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int
@@ -101,7 +101,7 @@
placeholderInset: Int,
dp: DeviceProfile,
@StagePosition stagePosition: Int,
- out: Rect
+ out: Rect,
)
/**
@@ -128,7 +128,7 @@
drawableWidth: Int,
drawableHeight: Int,
dp: DeviceProfile,
- @StagePosition stagePosition: Int
+ @StagePosition stagePosition: Int,
)
/**
@@ -143,7 +143,7 @@
out: View,
dp: DeviceProfile,
splitInstructionsHeight: Int,
- splitInstructionsWidth: Int
+ splitInstructionsWidth: Int,
)
/**
@@ -159,7 +159,7 @@
dp: DeviceProfile,
@StagePosition stagePosition: Int,
out1: Rect,
- out2: Rect
+ out2: Rect,
)
fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int
@@ -174,7 +174,7 @@
dp: DeviceProfile,
outRect: Rect,
splitInfo: SplitConfigurationOptions.SplitBounds,
- @StagePosition desiredStagePosition: Int
+ @StagePosition desiredStagePosition: Int,
)
fun measureGroupedTaskViewThumbnailBounds(
@@ -185,7 +185,7 @@
splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
dp: DeviceProfile,
isRtl: Boolean,
- inSplitSelection: Boolean
+ inSplitSelection: Boolean,
)
/**
@@ -198,7 +198,7 @@
dp: DeviceProfile,
splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
parentWidth: Int,
- parentHeight: Int
+ parentHeight: Int,
): Pair<Point, Point>
// Overview TaskMenuView methods
@@ -208,7 +208,7 @@
taskIconMargin: Int,
taskIconHeight: Int,
thumbnailTopMargin: Int,
- isRtl: Boolean
+ isRtl: Boolean,
)
/**
@@ -216,14 +216,14 @@
*/
fun setIconAppChipChildrenParams(
iconParams: FrameLayout.LayoutParams,
- chipChildMarginStart: Int
+ chipChildMarginStart: Int,
)
fun setIconAppChipMenuParams(
iconAppChipView: IconAppChipView,
iconMenuParams: FrameLayout.LayoutParams,
iconMenuMargin: Int,
- thumbnailTopMargin: Int
+ thumbnailTopMargin: Int,
)
fun setSplitIconParams(
@@ -252,7 +252,7 @@
thumbnailView: View,
deviceProfile: DeviceProfile,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float
fun getTaskMenuY(
@@ -261,20 +261,20 @@
stagePosition: Int,
taskMenuView: View,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float
fun getTaskMenuWidth(
thumbnailView: View,
deviceProfile: DeviceProfile,
- @StagePosition stagePosition: Int
+ @StagePosition stagePosition: Int,
): Int
fun getTaskMenuHeight(
taskInsetMargin: Float,
deviceProfile: DeviceProfile,
taskMenuX: Float,
- taskMenuY: Float
+ taskMenuY: Float,
): Int
/**
@@ -285,7 +285,7 @@
deviceProfile: DeviceProfile,
taskMenuLayout: LinearLayout,
dividerSpacing: Int,
- dividerDrawable: ShapeDrawable
+ dividerDrawable: ShapeDrawable,
)
/**
@@ -295,7 +295,7 @@
fun setLayoutParamsForTaskMenuOptionItem(
lp: LinearLayout.LayoutParams,
viewGroup: LinearLayout,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
)
/** Layout a Digital Wellbeing Banner on its parent. TaskView. */
@@ -306,7 +306,7 @@
deviceProfile: DeviceProfile,
snapshotViewWidth: Int,
snapshotViewHeight: Int,
- banner: View
+ banner: View,
)
/**
@@ -322,7 +322,7 @@
deviceProfile: DeviceProfile,
thumbnailViews: Array<View>,
desiredTaskId: Int,
- banner: View
+ banner: View,
): Pair<Float, Float>
// The following are only used by TaskViewTouchHandler.
@@ -342,6 +342,15 @@
/** @return Either 1 or -1, a factor to multiply by so the animation goes the correct way. */
fun getTaskDragDisplacementFactor(isRtl: Boolean): Int
+ /** @return Either 1 or -1, the direction sign towards task dismiss. */
+ fun getTaskDismissVerticalDirection(): Int
+
+ /** @return the length to drag a task off screen for dismiss. */
+ fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int
+
+ /** @return the length to drag a task to full screen for launch. */
+ fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int
+
/**
* Maps the velocity from the coordinate plane of the foreground app to that of Launcher's
* (which now will always be portrait)
@@ -371,7 +380,7 @@
floatingTask: View,
onScreenRect: RectF,
@StagePosition stagePosition: Int,
- dp: DeviceProfile
+ dp: DeviceProfile,
): Float
/**
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index 0cb983d..1f9f752 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -53,7 +53,7 @@
override fun getSplitTranslationDirectionFactor(
stagePosition: Int,
- deviceProfile: DeviceProfile
+ deviceProfile: DeviceProfile,
): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
override fun getRecentsRtlSetting(resources: Resources): Boolean = Utilities.isRtl(resources)
@@ -70,7 +70,7 @@
thumbnailView: View,
deviceProfile: DeviceProfile,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float = x + taskInsetMargin
override fun getTaskMenuY(
@@ -79,7 +79,7 @@
stagePosition: Int,
taskMenuView: View,
taskInsetMargin: Float,
- taskViewIcon: View
+ taskViewIcon: View,
): Float {
if (Flags.enableOverviewIconMenu()) {
return y
@@ -97,14 +97,14 @@
taskInsetMargin: Float,
deviceProfile: DeviceProfile,
taskMenuX: Float,
- taskMenuY: Float
+ taskMenuY: Float,
): Int = (deviceProfile.availableWidthPx - taskInsetMargin - taskMenuX).toInt()
override fun setSplitTaskSwipeRect(
dp: DeviceProfile,
outRect: Rect,
splitInfo: SplitBounds,
- desiredStagePosition: Int
+ desiredStagePosition: Int,
) {
val topLeftTaskPercent = splitInfo.leftTopTaskPercent
val dividerBarPercent = splitInfo.dividerPercent
@@ -126,7 +126,7 @@
deviceProfile: DeviceProfile,
snapshotViewWidth: Int,
snapshotViewHeight: Int,
- banner: View
+ banner: View,
) {
banner.pivotX = 0f
banner.pivotY = 0f
@@ -149,7 +149,7 @@
deviceProfile: DeviceProfile,
thumbnailViews: Array<View>,
desiredTaskId: Int,
- banner: View
+ banner: View,
): Pair<Float, Float> {
val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
val translationX: Float = (taskViewWidth - banner.height).toFloat()
@@ -181,7 +181,7 @@
R.drawable.ic_split_horizontal,
R.string.recent_task_option_split_screen,
STAGE_POSITION_BOTTOM_OR_RIGHT,
- STAGE_TYPE_MAIN
+ STAGE_TYPE_MAIN,
)
)
@@ -189,7 +189,7 @@
out: View,
dp: DeviceProfile,
splitInstructionsHeight: Int,
- splitInstructionsWidth: Int
+ splitInstructionsWidth: Int,
) {
out.pivotX = 0f
out.pivotY = splitInstructionsHeight.toFloat()
@@ -217,7 +217,7 @@
taskIconMargin: Int,
taskIconHeight: Int,
thumbnailTopMargin: Int,
- isRtl: Boolean
+ isRtl: Boolean,
) {
iconParams.gravity =
if (isRtl) {
@@ -230,7 +230,7 @@
override fun setIconAppChipChildrenParams(
iconParams: FrameLayout.LayoutParams,
- chipChildMarginStart: Int
+ chipChildMarginStart: Int,
) {
iconParams.setMargins(0, 0, 0, 0)
iconParams.marginStart = chipChildMarginStart
@@ -241,7 +241,7 @@
iconAppChipView: IconAppChipView,
iconMenuParams: FrameLayout.LayoutParams,
iconMenuMargin: Int,
- thumbnailTopMargin: Int
+ thumbnailTopMargin: Int,
) {
val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
val iconCenter = iconAppChipView.getHeight() / 2f
@@ -268,7 +268,7 @@
/**
* @param inSplitSelection Whether user currently has a task from this task group staged for
- * split screen. Currently this state is not reachable in fake seascape.
+ * split screen. Currently this state is not reachable in fake seascape.
*/
override fun measureGroupedTaskViewThumbnailBounds(
primarySnapshot: View,
@@ -278,7 +278,7 @@
splitBoundsConfig: SplitBounds,
dp: DeviceProfile,
isRtl: Boolean,
- inSplitSelection: Boolean
+ inSplitSelection: Boolean,
) {
val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
@@ -300,11 +300,11 @@
(taskViewSecond.y + spaceAboveSnapshot + dividerBar).toFloat()
primarySnapshot.measure(
MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY)
+ MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY),
)
secondarySnapshot.measure(
MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY)
+ MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY),
)
}
@@ -312,7 +312,7 @@
dp: DeviceProfile,
splitBoundsConfig: SplitBounds,
parentWidth: Int,
- parentHeight: Int
+ parentHeight: Int,
): Pair<Point, Point> {
// Measure and layout the thumbnails bottom up, since the primary is on the visual left
// (portrait bottom) and secondary is on the right (portrait top)
@@ -344,6 +344,14 @@
override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) -1 else 1
+ override fun getTaskDismissVerticalDirection(): Int = -1
+
+ override fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ taskThumbnailBounds.right
+
+ override fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+ secondaryDimension - taskThumbnailBounds.right
+
/* -------------------- */
override fun getSplitIconsPosition(
@@ -376,10 +384,7 @@
if (oneIconHiddenDueToSmallWidth) {
// Center both icons
val centerY = primarySnapshotHeight + ((dividerSize - taskIconHeight) / 2)
- SplitIconPositions(
- topLeftY = centerY,
- bottomRightY = centerY,
- )
+ SplitIconPositions(topLeftY = centerY, bottomRightY = centerY)
} else {
SplitIconPositions(
topLeftY = primarySnapshotHeight - taskIconHeight,
diff --git a/quickstep/src/com/android/quickstep/util/ActivityPreloadUtil.kt b/quickstep/src/com/android/quickstep/util/ActivityPreloadUtil.kt
index 47b39db..df26b6b 100644
--- a/quickstep/src/com/android/quickstep/util/ActivityPreloadUtil.kt
+++ b/quickstep/src/com/android/quickstep/util/ActivityPreloadUtil.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.content.Intent
import android.os.Trace
+import android.view.Display.DEFAULT_DISPLAY
import com.android.launcher3.provider.RestoreDbTask
import com.android.launcher3.util.Executors
import com.android.launcher3.util.LockedUserState
@@ -59,7 +60,12 @@
// The activity has been created before the initialization of overview service. It is
// usually happens when booting or launcher is the top activity, so we should already
// have the latest state.
- if (fromInit && overviewCompObserver.containerInterface.createdContainer != null) return
+ if (
+ fromInit &&
+ overviewCompObserver.getContainerInterface(DEFAULT_DISPLAY).createdContainer !=
+ null
+ )
+ return
ActiveGestureProtoLogProxy.logPreloadRecentsAnimation()
val overviewIntent = Intent(overviewCompObserver.overviewIntentIgnoreSysUiState)
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
index d00a39c..3bc9adc 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
@@ -21,6 +21,7 @@
import android.app.contextualsearch.ContextualSearchManager.FEATURE_CONTEXTUAL_SEARCH
import android.content.Context
import android.util.Log
+import android.view.Display.DEFAULT_DISPLAY
import androidx.annotation.VisibleForTesting
import com.android.internal.app.AssistUtils
import com.android.launcher3.logging.StatsLogManager
@@ -222,7 +223,8 @@
@VisibleForTesting
fun getRecentsContainerInterface(): BaseContainerInterface<*, *>? {
- return OverviewComponentObserver.INSTANCE.get(context).containerInterface
+ return OverviewComponentObserver.INSTANCE.get(context)
+ .getContainerInterface(DEFAULT_DISPLAY)
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index a9dbbf2..96a5733 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -143,7 +143,7 @@
} else {
// Initiating split from overview on fullscreen task TaskView
val taskView = taskViewSupplier.get()
- taskView.taskContainers.first().let {
+ taskView.firstTaskContainer!!.let {
val drawable = getDrawable(it.iconView, splitSelectSource)
return SplitAnimInitProps(
it.snapshotView,
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index b844079..661fe89 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -378,6 +378,29 @@
}
/**
+ * Calculates the crop rect for desktop tasks given the current matrix.
+ */
+ private void calculateDesktopTaskCropRect() {
+ // The approach here is to map a rect that represents the untransformed thumbnail position
+ // using the current matrix. This will give us a rect that can be intersected with
+ // [mFullTaskSize]. Using the intersection, we then compute how much of the task window that
+ // needs to be cropped (which will be nothing if the window is entirely within the desktop).
+ mTempRectF.set(0, 0, mThumbnailPosition.width(), mThumbnailPosition.height());
+ mMatrix.mapRect(mTempRectF);
+
+ float offsetX = mTempRectF.left;
+ float offsetY = mTempRectF.top;
+ float scale = mThumbnailPosition.width() / mTempRectF.width();
+
+ if (mTempRectF.intersect(mFullTaskSize.left, mFullTaskSize.top, mFullTaskSize.right,
+ mFullTaskSize.bottom)) {
+ mTempRectF.offset(-offsetX, -offsetY);
+ mTempRectF.scale(scale);
+ mTempRectF.round(mTmpCropRect);
+ }
+ }
+
+ /**
* Applies the rotation on the matrix to so that it maps from launcher coordinate space to
* window coordinate space.
*/
@@ -442,7 +465,16 @@
mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
if (mTaskRectTransform != null) {
mMatrix.postConcat(mTaskRectTransform);
+
+ // Calculate cropping for desktop tasks. The order is important since it uses the
+ // current matrix. Therefore we calculate it here, after applying the task rect
+ // transform, but before applying scaling/translation that affects the whole
+ // recentsview.
+ if (mIsDesktopTask) {
+ calculateDesktopTaskCropRect();
+ }
}
+
mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
taskPrimaryTranslation.value);
mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
index 244c3b2..37359a1 100644
--- a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
+++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
@@ -20,10 +20,12 @@
import android.graphics.Canvas
import android.graphics.Rect
import android.util.AttributeSet
-import android.view.View
+import android.util.FloatProperty
import android.widget.ImageButton
import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
import com.android.launcher3.R
+import com.android.launcher3.util.KFloatProperty
+import com.android.launcher3.util.MultiPropertyDelegate
import com.android.launcher3.util.MultiPropertyFactory
import com.android.launcher3.util.MultiValueAlpha
import com.android.quickstep.util.BorderAnimator
@@ -36,44 +38,17 @@
class AddDesktopButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
ImageButton(context, attrs) {
- private enum class Alpha {
- CONTENT,
- VISIBILITY,
- }
-
private val addDeskButtonAlpha = MultiValueAlpha(this, Alpha.entries.size)
-
- var contentAlpha
- set(value) {
- addDeskButtonAlpha.get(Alpha.CONTENT.ordinal).value = value
- }
- get() = addDeskButtonAlpha.get(Alpha.CONTENT.ordinal).value
-
- val visibilityAlphaProperty: MultiPropertyFactory<View>.MultiProperty
- get() = addDeskButtonAlpha.get(Alpha.VISIBILITY.ordinal)
-
- private enum class TranslationX {
- GRID,
- OFFSET,
- }
+ var contentAlpha by MultiPropertyDelegate(addDeskButtonAlpha, Alpha.CONTENT)
+ var visibilityAlpha by MultiPropertyDelegate(addDeskButtonAlpha, Alpha.VISIBILITY)
private val multiTranslationX =
MultiPropertyFactory(this, VIEW_TRANSLATE_X, TranslationX.entries.size) { a: Float, b: Float
->
a + b
}
-
- var gridTranslationX
- get() = multiTranslationX[TranslationX.GRID.ordinal].value
- set(value) {
- multiTranslationX[TranslationX.GRID.ordinal].value = value
- }
-
- var offsetTranslationX
- get() = multiTranslationX[TranslationX.OFFSET.ordinal].value
- set(value) {
- multiTranslationX[TranslationX.OFFSET.ordinal].value = value
- }
+ var gridTranslationX by MultiPropertyDelegate(multiTranslationX, TranslationX.GRID)
+ var offsetTranslationX by MultiPropertyDelegate(multiTranslationX, TranslationX.OFFSET)
private val focusBorderAnimator: BorderAnimator =
createSimpleBorderAnimator(
@@ -109,9 +84,6 @@
}
}
- protected fun getScrollAdjustment(showAsGrid: Boolean): Int =
- if (showAsGrid) gridTranslationX.toInt() else 0
-
private fun getBorderBounds(bounds: Rect) {
bounds.set(0, 0, width, height)
val outlinePadding =
@@ -123,4 +95,20 @@
focusBorderAnimator.drawBorder(canvas)
super.draw(canvas)
}
+
+ companion object {
+ private enum class Alpha {
+ CONTENT,
+ VISIBILITY,
+ }
+
+ private enum class TranslationX {
+ GRID,
+ OFFSET,
+ }
+
+ @JvmField
+ val VISIBILITY_ALPHA: FloatProperty<AddDesktopButton> =
+ KFloatProperty(AddDesktopButton::visibilityAlpha)
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
deleted file mode 100644
index 2426697..0000000
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.views;
-
-import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.FloatProperty;
-import android.widget.Button;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Flags;
-import com.android.launcher3.R;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.util.BorderAnimator;
-
-import kotlin.Unit;
-
-public class ClearAllButton extends Button {
-
- public static final FloatProperty<ClearAllButton> VISIBILITY_ALPHA =
- new FloatProperty<ClearAllButton>("visibilityAlpha") {
- @Override
- public Float get(ClearAllButton view) {
- return view.mVisibilityAlpha;
- }
-
- @Override
- public void setValue(ClearAllButton view, float v) {
- view.setVisibilityAlpha(v);
- }
- };
-
- public static final FloatProperty<ClearAllButton> DISMISS_ALPHA =
- new FloatProperty<ClearAllButton>("dismissAlpha") {
- @Override
- public Float get(ClearAllButton view) {
- return view.mDismissAlpha;
- }
-
- @Override
- public void setValue(ClearAllButton view, float v) {
- view.setDismissAlpha(v);
- }
- };
-
- private float mScrollAlpha = 1;
- private float mContentAlpha = 1;
- private float mVisibilityAlpha = 1;
- private float mDismissAlpha = 1;
- private float mFullscreenProgress = 1;
- private float mGridProgress = 1;
-
- private boolean mIsRtl;
- private float mNormalTranslationPrimary;
- private float mFullscreenTranslationPrimary;
- private float mGridTranslationPrimary;
- private float mTaskAlignmentTranslationY;
- private float mGridScrollOffset;
- private float mScrollOffsetPrimary;
-
- private int mSidePadding;
- private int mOutlinePadding;
- private boolean mBorderEnabled;
- @Nullable
- private final BorderAnimator mFocusBorderAnimator;
-
- public ClearAllButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-
- if (Flags.enableFocusOutline()) {
- TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
- R.styleable.ClearAllButton);
- Resources resources = getResources();
- mOutlinePadding = resources.getDimensionPixelSize(
- R.dimen.recents_clear_all_outline_padding);
- mFocusBorderAnimator =
- BorderAnimator.createSimpleBorderAnimator(
- /* borderRadiusPx= */ resources.getDimensionPixelSize(
- R.dimen.recents_clear_all_outline_radius),
- /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
- R.dimen.keyboard_quick_switch_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
- /* targetView= */ this,
- /* borderColor= */ styledAttrs.getColor(
- R.styleable.ClearAllButton_focusBorderColor,
- DEFAULT_BORDER_COLOR));
- styledAttrs.recycle();
- } else {
- mFocusBorderAnimator = null;
- }
- }
-
- private Unit updateBorderBounds(@NonNull Rect bounds) {
- bounds.set(0, 0, getWidth(), getHeight());
- // Make the value negative to form a padding between button and outline
- bounds.inset(-mOutlinePadding, -mOutlinePadding);
- return Unit.INSTANCE;
- }
-
- @Override
- public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- if (mFocusBorderAnimator != null && mBorderEnabled) {
- mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
- }
- }
-
- /**
- * Enable or disable showing border on focus change
- */
- public void setBorderEnabled(boolean enabled) {
- if (mBorderEnabled == enabled) {
- return;
- }
-
- mBorderEnabled = enabled;
- if (mFocusBorderAnimator != null) {
- mFocusBorderAnimator.setBorderVisibility(/* visible= */
- enabled && isFocused(), /* animated= */true);
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (mFocusBorderAnimator != null) {
- mFocusBorderAnimator.drawBorder(canvas);
- }
- super.draw(canvas);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- RecentsPagedOrientationHandler orientationHandler =
- getRecentsView().getPagedOrientationHandler();
- mSidePadding = orientationHandler.getClearAllSidePadding(getRecentsView(), mIsRtl);
- }
-
- private RecentsView getRecentsView() {
- return (RecentsView) getParent();
- }
-
- @Override
- public void onRtlPropertiesChanged(int layoutDirection) {
- super.onRtlPropertiesChanged(layoutDirection);
- mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-
- public float getScrollAlpha() {
- return mScrollAlpha;
- }
-
- public void setContentAlpha(float alpha) {
- if (mContentAlpha != alpha) {
- mContentAlpha = alpha;
- updateAlpha();
- }
- }
-
- public void setVisibilityAlpha(float alpha) {
- if (mVisibilityAlpha != alpha) {
- mVisibilityAlpha = alpha;
- updateAlpha();
- }
- }
-
- public void setDismissAlpha(float alpha) {
- if (mDismissAlpha != alpha) {
- mDismissAlpha = alpha;
- updateAlpha();
- }
- }
-
- public void onRecentsViewScroll(int scroll, boolean gridEnabled) {
- RecentsView recentsView = getRecentsView();
- if (recentsView == null) {
- return;
- }
-
- RecentsPagedOrientationHandler orientationHandler =
- recentsView.getPagedOrientationHandler();
- float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
- if (orientationSize == 0) {
- return;
- }
-
- int clearAllScroll = recentsView.getClearAllScroll();
- int adjustedScrollFromEdge = Math.abs(scroll - clearAllScroll);
- float shift = Math.min(adjustedScrollFromEdge, orientationSize);
- mNormalTranslationPrimary = mIsRtl ? -shift : shift;
- if (!gridEnabled) {
- mNormalTranslationPrimary += mSidePadding;
- }
- applyPrimaryTranslation();
- applySecondaryTranslation();
- float clearAllSpacing =
- recentsView.getPageSpacing() + recentsView.getClearAllExtraPageSpacing();
- clearAllSpacing = mIsRtl ? -clearAllSpacing : clearAllSpacing;
- mScrollAlpha = Math.max((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing, 0);
- updateAlpha();
- }
-
- private void updateAlpha() {
- final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha * mDismissAlpha;
- setAlpha(alpha);
- setClickable(Math.min(alpha, 1) == 1);
- }
-
- public void setFullscreenTranslationPrimary(float fullscreenTranslationPrimary) {
- mFullscreenTranslationPrimary = fullscreenTranslationPrimary;
- applyPrimaryTranslation();
- }
-
- /**
- * Sets `mTaskAlignmentTranslationY` to the given `value`. In order to put the button at the
- * middle in the secondary coordinate.
- */
- public void setTaskAlignmentTranslationY(float value) {
- mTaskAlignmentTranslationY = value;
- applySecondaryTranslation();
- }
-
- public void setGridTranslationPrimary(float gridTranslationPrimary) {
- mGridTranslationPrimary = gridTranslationPrimary;
- applyPrimaryTranslation();
- }
-
- public void setGridScrollOffset(float gridScrollOffset) {
- mGridScrollOffset = gridScrollOffset;
- }
-
- public void setScrollOffsetPrimary(float scrollOffsetPrimary) {
- mScrollOffsetPrimary = scrollOffsetPrimary;
- }
-
- public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
- float scrollAdjustment = 0;
- if (fullscreenEnabled) {
- scrollAdjustment += mFullscreenTranslationPrimary;
- }
- if (gridEnabled) {
- scrollAdjustment += mGridTranslationPrimary + mGridScrollOffset;
- }
- scrollAdjustment += mScrollOffsetPrimary;
- return scrollAdjustment;
- }
-
- public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
- return getScrollAdjustment(fullscreenEnabled, gridEnabled);
- }
-
- /**
- * Adjust translation when this TaskView is about to be shown fullscreen.
- *
- * @param progress: 0 = no translation; 1 = translate according to TaskVIew translations.
- */
- public void setFullscreenProgress(float progress) {
- mFullscreenProgress = progress;
- applyPrimaryTranslation();
- }
-
- /**
- * Moves ClearAllButton between carousel and 2 row grid.
- *
- * @param gridProgress 0 = carousel; 1 = 2 row grid.
- */
- public void setGridProgress(float gridProgress) {
- mGridProgress = gridProgress;
- applyPrimaryTranslation();
- }
-
- private void applyPrimaryTranslation() {
- RecentsView recentsView = getRecentsView();
- if (recentsView == null) {
- return;
- }
-
- RecentsPagedOrientationHandler orientationHandler =
- recentsView.getPagedOrientationHandler();
- orientationHandler.getPrimaryViewTranslate().set(this,
- orientationHandler.getPrimaryValue(0f, mTaskAlignmentTranslationY)
- + mNormalTranslationPrimary + getFullscreenTrans(
- mFullscreenTranslationPrimary) + getGridTrans(mGridTranslationPrimary));
- }
-
- private void applySecondaryTranslation() {
- RecentsView recentsView = getRecentsView();
- if (recentsView == null) {
- return;
- }
-
- RecentsPagedOrientationHandler orientationHandler =
- recentsView.getPagedOrientationHandler();
- orientationHandler.getSecondaryViewTranslate().set(this,
- orientationHandler.getSecondaryValue(0f, mTaskAlignmentTranslationY));
- }
-
- private float getFullscreenTrans(float endTranslation) {
- return mFullscreenProgress > 0 ? endTranslation : 0;
- }
-
- private float getGridTrans(float endTranslation) {
- return mGridProgress > 0 ? endTranslation : 0;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.kt b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt
new file mode 100644
index 0000000..69c85ee
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.util.FloatProperty
+import android.widget.Button
+import com.android.launcher3.Flags.enableFocusOutline
+import com.android.launcher3.R
+import com.android.launcher3.util.KFloatProperty
+import com.android.launcher3.util.MultiPropertyDelegate
+import com.android.launcher3.util.MultiValueAlpha
+import com.android.quickstep.util.BorderAnimator
+import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
+import kotlin.math.abs
+import kotlin.math.min
+
+class ClearAllButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+ Button(context, attrs) {
+
+ private val clearAllButtonAlpha =
+ object : MultiValueAlpha(this, Alpha.entries.size) {
+ override fun apply(value: Float) {
+ super.apply(value)
+ isClickable = value >= 1f
+ }
+ }
+ var scrollAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.SCROLL)
+ var contentAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.CONTENT)
+ var visibilityAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.VISIBILITY)
+ var dismissAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.DISMISS)
+
+ var fullscreenProgress = 1f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applyPrimaryTranslation()
+ }
+
+ /**
+ * Moves ClearAllButton between carousel and 2 row grid.
+ *
+ * 0 = carousel; 1 = 2 row grid.
+ */
+ var gridProgress = 1f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applyPrimaryTranslation()
+ }
+
+ private var normalTranslationPrimary = 0f
+ var fullscreenTranslationPrimary = 0f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applyPrimaryTranslation()
+ }
+
+ var gridTranslationPrimary = 0f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applyPrimaryTranslation()
+ }
+
+ /** Used to put the button at the middle in the secondary coordinate. */
+ var taskAlignmentTranslationY = 0f
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ applySecondaryTranslation()
+ }
+
+ var gridScrollOffset = 0f
+ var scrollOffsetPrimary = 0f
+
+ private var sidePadding = 0
+ var borderEnabled = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
+ }
+
+ private val focusBorderAnimator: BorderAnimator? =
+ if (enableFocusOutline())
+ createSimpleBorderAnimator(
+ context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_radius),
+ context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width),
+ this::getBorderBounds,
+ this,
+ context
+ .obtainStyledAttributes(attrs, R.styleable.ClearAllButton)
+ .getColor(
+ R.styleable.ClearAllButton_focusBorderColor,
+ BorderAnimator.DEFAULT_BORDER_COLOR,
+ ),
+ )
+ else null
+
+ private fun getBorderBounds(bounds: Rect) {
+ bounds.set(0, 0, width, height)
+ val outlinePadding =
+ context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_padding)
+ // Make the value negative to form a padding between button and outline
+ bounds.inset(-outlinePadding, -outlinePadding)
+ }
+
+ public override fun onFocusChanged(
+ gainFocus: Boolean,
+ direction: Int,
+ previouslyFocusedRect: Rect?,
+ ) {
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
+ if (borderEnabled) {
+ focusBorderAnimator?.setBorderVisibility(gainFocus, /* animated= */ true)
+ }
+ }
+
+ override fun draw(canvas: Canvas) {
+ focusBorderAnimator?.drawBorder(canvas)
+ super.draw(canvas)
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ sidePadding =
+ recentsView?.let { it.pagedOrientationHandler?.getClearAllSidePadding(it, isLayoutRtl) }
+ ?: 0
+ }
+
+ private val recentsView: RecentsView<*, *>?
+ get() = parent as? RecentsView<*, *>?
+
+ override fun hasOverlappingRendering() = false
+
+ fun onRecentsViewScroll(scroll: Int, gridEnabled: Boolean) {
+ val recentsView = recentsView ?: return
+
+ val orientationSize =
+ recentsView.pagedOrientationHandler.getPrimaryValue(width, height).toFloat()
+ if (orientationSize == 0f) {
+ return
+ }
+
+ val clearAllScroll = recentsView.clearAllScroll
+ val adjustedScrollFromEdge = abs((scroll - clearAllScroll)).toFloat()
+ val shift = min(adjustedScrollFromEdge, orientationSize)
+ normalTranslationPrimary = if (isLayoutRtl) -shift else shift
+ if (!gridEnabled) {
+ normalTranslationPrimary += sidePadding.toFloat()
+ }
+ applyPrimaryTranslation()
+ applySecondaryTranslation()
+ var clearAllSpacing = recentsView.pageSpacing + recentsView.clearAllExtraPageSpacing
+ clearAllSpacing = if (isLayoutRtl) -clearAllSpacing else clearAllSpacing
+ scrollAlpha =
+ ((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing.toFloat()).coerceAtLeast(
+ 0f
+ )
+ }
+
+ fun getScrollAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean): Float {
+ var scrollAdjustment = 0f
+ if (fullscreenEnabled) {
+ scrollAdjustment += fullscreenTranslationPrimary
+ }
+ if (gridEnabled) {
+ scrollAdjustment += gridTranslationPrimary + gridScrollOffset
+ }
+ scrollAdjustment += scrollOffsetPrimary
+ return scrollAdjustment
+ }
+
+ fun getOffsetAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean) =
+ getScrollAdjustment(fullscreenEnabled, gridEnabled)
+
+ private fun applyPrimaryTranslation() {
+ val recentsView = recentsView ?: return
+ val orientationHandler = recentsView.pagedOrientationHandler
+ orientationHandler.primaryViewTranslate.set(
+ this,
+ (orientationHandler.getPrimaryValue(0f, taskAlignmentTranslationY) +
+ normalTranslationPrimary +
+ getFullscreenTrans(fullscreenTranslationPrimary) +
+ getGridTrans(gridTranslationPrimary)),
+ )
+ }
+
+ private fun applySecondaryTranslation() {
+ val recentsView = recentsView ?: return
+ val orientationHandler = recentsView.pagedOrientationHandler
+ orientationHandler.secondaryViewTranslate.set(
+ this,
+ orientationHandler.getSecondaryValue(0f, taskAlignmentTranslationY),
+ )
+ }
+
+ private fun getFullscreenTrans(endTranslation: Float) =
+ if (fullscreenProgress > 0) endTranslation else 0f
+
+ private fun getGridTrans(endTranslation: Float) = if (gridProgress > 0) endTranslation else 0f
+
+ companion object {
+ private enum class Alpha {
+ SCROLL,
+ CONTENT,
+ VISIBILITY,
+ DISMISS,
+ }
+
+ @JvmField
+ val VISIBILITY_ALPHA: FloatProperty<ClearAllButton> =
+ KFloatProperty(ClearAllButton::visibilityAlpha)
+
+ @JvmField
+ val DISMISS_ALPHA: FloatProperty<ClearAllButton> =
+ KFloatProperty(ClearAllButton::dismissAlpha)
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
index 85d14cc..c20aa11 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
@@ -296,7 +296,7 @@
fun getMenuTranslationY(): MultiPropertyFactory<View>.MultiProperty =
viewTranslationY[INDEX_MENU_TRANSLATION]
- internal fun revealAnim(isRevealing: Boolean) {
+ internal fun revealAnim(isRevealing: Boolean, animated: Boolean = true) {
cancelInProgressAnimations()
val collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds()
val expandedBackgroundBounds = getExpandedBackgroundLtrBounds()
@@ -392,6 +392,7 @@
animator!!.setDuration(MENU_BACKGROUND_HIDE_DURATION.toLong())
}
+ if (!animated) animator!!.duration = 0
animator!!.interpolator = Interpolators.EMPHASIZED
animator!!.start()
}
@@ -427,7 +428,6 @@
private const val INDEX_CONTENT_ALPHA = 0
private const val INDEX_COLOR_FILTER_ALPHA = 1
private const val INDEX_MODAL_ALPHA = 2
-
/** Used to hide the app chip for 90:10 flex split. */
private const val INDEX_MINIMUM_RATIO_ALPHA = 3
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 6b91df1..e434252 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -74,6 +74,7 @@
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE;
import static com.android.quickstep.views.RecentsViewUtils.DESK_EXPLODE_PROGRESS;
+import static com.android.quickstep.views.TaskView.SPLIT_ALPHA;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -143,7 +144,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.Insettable;
-import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -573,8 +573,6 @@
// Keeps track of the previously known visible tasks for purposes of loading/unloading task data
private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
- private final InvariantDeviceProfile mIdp;
-
/**
* Getting views should be done via {@link #getTaskViewFromPool(int)}
*/
@@ -911,7 +909,6 @@
mFastFlingVelocity = getResources()
.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
mModel = RecentsModel.INSTANCE.get(context);
- mIdp = InvariantDeviceProfile.INSTANCE.get(context);
mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
.inflate(R.layout.overview_clear_all_button, this, false);
@@ -1462,7 +1459,8 @@
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- finishRecentsAnimation(false /* toRecents */, null);
+ finishRecentsAnimation(false /* toRecents */, true /*shouldPip*/,
+ allAppsAreTranslucent(apps), null);
}
});
} else {
@@ -1473,6 +1471,18 @@
anim.start();
}
+ private boolean allAppsAreTranslucent(RemoteAnimationTarget[] apps) {
+ if (apps == null) {
+ return false;
+ }
+ for (int i = apps.length - 1; i >= 0; --i) {
+ if (!apps[i].isTranslucent) {
+ return false;
+ }
+ }
+ return true;
+ }
+
public boolean isTaskViewVisible(TaskView tv) {
if (showAsGrid()) {
int screenStart = getPagedOrientationHandler().getPrimaryScroll(this);
@@ -2493,6 +2503,11 @@
int minDistanceFromScreenStart = Integer.MAX_VALUE;
int minDistanceFromScreenStartIndex = INVALID_PAGE;
for (int i = 0; i < getChildCount(); ++i) {
+ // Do not set the destination page to the AddDesktopButton, which has the same page
+ // scrolls as the first [TaskView] and shouldn't be scrolled to.
+ if (getChildAt(i) instanceof AddDesktopButton) {
+ continue;
+ }
int distanceFromScreenStart = Math.abs(mPageScrolls[i] - scaledScroll);
if (distanceFromScreenStart < minDistanceFromScreenStart) {
minDistanceFromScreenStart = distanceFromScreenStart;
@@ -2999,9 +3014,22 @@
startIconFadeInOnGestureComplete();
animateActionsViewIn();
- for (TaskView taskView : getTaskViews()) {
- if (taskView instanceof DesktopTaskView desktopTaskView) {
- desktopTaskView.setRemoteTargetHandles(mRemoteTargetHandles);
+ if (mEnableDrawingLiveTile) {
+ for (TaskView taskView : getTaskViews()) {
+ if (taskView instanceof DesktopTaskView desktopTaskView) {
+ desktopTaskView.setRemoteTargetHandles(mRemoteTargetHandles);
+ }
+ }
+ TaskView runningTaskView = getRunningTaskView();
+ if (showAsGrid() && enableGridOnlyOverview() && runningTaskView != null) {
+ runActionOnRemoteHandles(remoteTargetHandle -> {
+ TaskViewSimulator taskViewSimulator = remoteTargetHandle.getTaskViewSimulator();
+ // After settling in Overview, recentsScroll will be used to adjust horizontally
+ // location and taskGridTranslationX doesn't needs to be applied.
+ taskViewSimulator.taskGridTranslationX.value = 0;
+ taskViewSimulator.taskGridTranslationY.value =
+ runningTaskView.getGridTranslationY();
+ });
}
}
@@ -3516,19 +3544,6 @@
mAddDesktopButton.setGridTranslationX(translationX);
}
- final TaskView runningTask = getRunningTaskView();
- if (showAsGrid() && enableGridOnlyOverview() && runningTask != null) {
- runActionOnRemoteHandles(
- remoteTargetHandle -> {
- remoteTargetHandle.getTaskViewSimulator().taskGridTranslationX.value =
- runningTask.getGridTranslationX()
- - runningTask.getNonGridTranslationX();
- remoteTargetHandle.getTaskViewSimulator().taskGridTranslationY.value =
- runningTask.getGridTranslationY();
- }
- );
- }
-
mClearAllButton.setGridTranslationPrimary(
clearAllTotalTranslationX - snappedTaskGridTranslationX);
mClearAllButton.setGridScrollOffset(
@@ -5341,8 +5356,7 @@
clampToProgress(timings.getDesktopTaskScaleInterpolator(), 0f,
timings.getDesktopFadeSplitAnimationEndOffset()));
}
- builder.addFloat(taskView.getSplitAlphaProperty(),
- MULTI_PROPERTY_VALUE, 1f, 0f,
+ builder.addFloat(taskView, SPLIT_ALPHA, 1f, 0f,
clampToProgress(deskTopFadeInterPolator, 0f,
timings.getDesktopFadeSplitAnimationEndOffset()));
}
@@ -6202,9 +6216,7 @@
private int getFirstViewIndex() {
final View firstView;
- if (mAddDesktopButton != null) {
- firstView = mAddDesktopButton;
- } else if (mShowAsGridLastOnLayout) {
+ if (mShowAsGridLastOnLayout) {
// For grid Overview, it always start if a large tile (focused task or desktop task) if
// they exist, otherwise it start with the first task.
TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView();
@@ -6274,13 +6286,6 @@
outPageScrolls[clearAllIndex] = clearAllScroll;
}
- int addDesktopButtonIndex = indexOfChild(mAddDesktopButton);
- if (addDesktopButtonIndex != -1 && addDesktopButtonIndex < outPageScrolls.length) {
- outPageScrolls[addDesktopButtonIndex] =
- newPageScrolls[addDesktopButtonIndex] + mAddDesktopButton.getScrollAdjustment(
- showAsGrid);
- }
-
int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth);
getTaskViews().forEachWithIndexInParent((index, taskView) -> {
float scrollDiff = taskView.getScrollAdjustment(showAsGrid);
@@ -6295,6 +6300,14 @@
"getPageScrolls - outPageScrolls[" + index + "]: " + outPageScrolls[index]);
}
});
+
+ int addDesktopButtonIndex = indexOfChild(mAddDesktopButton);
+ if (addDesktopButtonIndex >= 0 && addDesktopButtonIndex < outPageScrolls.length) {
+ int firstViewIndex = getFirstViewIndex();
+ if (firstViewIndex >= 0 && firstViewIndex < outPageScrolls.length) {
+ outPageScrolls[addDesktopButtonIndex] = outPageScrolls[firstViewIndex];
+ }
+ }
if (DEBUG) {
Log.d(TAG, "getPageScrolls - clearAllScroll: " + clearAllScroll);
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index 67318ac..31ae890 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -28,6 +28,7 @@
import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA
import com.android.systemui.shared.recents.model.ThumbnailData
import java.util.function.BiConsumer
+import kotlin.reflect.KMutableProperty1
/**
* Helper class for [RecentsView]. This util class contains refactored and extracted functions from
@@ -305,16 +306,19 @@
}
companion object {
- @JvmField
- val DESK_EXPLODE_PROGRESS =
- object : FloatProperty<RecentsView<*, *>>("deskExplodeProgress") {
- override fun setValue(recentsView: RecentsView<*, *>, value: Float) {
- recentsView.mUtils.deskExplodeProgress = value
- }
+ class RecentsViewFloatProperty(
+ private val utilsProperty: KMutableProperty1<RecentsViewUtils, Float>
+ ) : FloatProperty<RecentsView<*, *>>(utilsProperty.name) {
+ override fun get(recentsView: RecentsView<*, *>): Float =
+ utilsProperty.get(recentsView.mUtils)
- override fun get(recentsView: RecentsView<*, *>) =
- recentsView.mUtils.deskExplodeProgress
+ override fun setValue(recentsView: RecentsView<*, *>, value: Float) {
+ utilsProperty.set(recentsView.mUtils, value)
}
+ }
+
+ @JvmField
+ val DESK_EXPLODE_PROGRESS = RecentsViewFloatProperty(RecentsViewUtils::deskExplodeProgress)
val TEMP_RECT = Rect()
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
index 95336cf..4777f4f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
@@ -75,7 +75,7 @@
if (ev.action == MotionEvent.ACTION_DOWN) {
if (!recentsViewContainer.dragLayer.isEventOverView(this, ev)) {
// TODO: log this once we have a new container type for it?
- close(true)
+ animateOpenOrClosed(true)
return true
}
}
@@ -83,7 +83,7 @@
}
override fun handleClose(animate: Boolean) {
- animateClose()
+ animateOpenOrClosed(true, animated = false)
}
override fun isOfType(type: Int): Boolean = (type and TYPE_TASK_MENU) != 0
@@ -260,11 +260,7 @@
private val iconView: View
get() = taskContainer.iconView.asView()
- private fun animateClose() {
- animateOpenOrClosed(true)
- }
-
- private fun animateOpenOrClosed(closing: Boolean) {
+ private fun animateOpenOrClosed(closing: Boolean, animated: Boolean = true) {
openCloseAnimator?.let { if (it.isRunning) it.cancel() }
openCloseAnimator = AnimatorSet()
// If we're opening, we just start from the beginning as a new `TaskMenuView` is created
@@ -312,7 +308,12 @@
}
}
)
- val animationDuration = if (closing) REVEAL_CLOSE_DURATION else REVEAL_OPEN_DURATION
+ val animationDuration =
+ when {
+ animated && closing -> REVEAL_CLOSE_DURATION
+ animated && !closing -> REVEAL_OPEN_DURATION
+ else -> 0L
+ }
openCloseAnimator!!.setDuration(animationDuration)
openCloseAnimator!!.start()
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 27db6d6..3819772 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -61,8 +61,9 @@
import com.android.launcher3.testing.shared.TestProtocol
import com.android.launcher3.util.CancellableTask
import com.android.launcher3.util.Executors
+import com.android.launcher3.util.KFloatProperty
+import com.android.launcher3.util.MultiPropertyDelegate
import com.android.launcher3.util.MultiPropertyFactory
-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.STAGE_POSITION_UNDEFINED
@@ -99,7 +100,6 @@
import com.android.systemui.shared.system.ActivityManagerWrapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@@ -301,15 +301,17 @@
var sysUiStatusNavFlags: Int = 0
get() =
if (enableRefactorTaskThumbnail()) field
- else taskContainers.first().thumbnailViewDeprecated.sysUiStatusNavFlags
+ else firstTaskContainer?.thumbnailViewDeprecated?.sysUiStatusNavFlags ?: 0
private set
// Various animation progress variables.
// progress: 0 = show icon and no insets; 1 = don't show icon and show full insets.
protected var fullscreenProgress = 0f
set(value) {
- field = Utilities.boundToRange(value, 0f, 1f)
- onFullscreenProgressChanged(field)
+ if (value != field) {
+ field = Utilities.boundToRange(value, 0f, 1f)
+ onFullscreenProgressChanged(field)
+ }
}
// gridProgress 0 = carousel; 1 = 2 row grid.
@@ -440,28 +442,10 @@
applyTranslationX()
}
- private val taskViewAlpha = MultiValueAlpha(this, NUM_ALPHA_CHANNELS)
-
- protected var stableAlpha
- set(value) {
- taskViewAlpha.get(ALPHA_INDEX_STABLE).value = value
- }
- get() = taskViewAlpha.get(ALPHA_INDEX_STABLE).value
-
- var attachAlpha
- set(value) {
- taskViewAlpha.get(ALPHA_INDEX_ATTACH).value = value
- }
- get() = taskViewAlpha.get(ALPHA_INDEX_ATTACH).value
-
- var splitAlpha
- set(value) {
- splitAlphaProperty.value = value
- }
- get() = splitAlphaProperty.value
-
- val splitAlphaProperty: MultiPropertyFactory<View>.MultiProperty
- get() = taskViewAlpha.get(ALPHA_INDEX_SPLIT)
+ private val taskViewAlpha = MultiValueAlpha(this, Alpha.entries.size)
+ protected var stableAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.STABLE)
+ var attachAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.ATTACH)
+ var splitAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.SPLIT)
protected var shouldShowScreenshot = false
get() = !isRunningTask || field
@@ -507,28 +491,30 @@
// 1 = The TaskView is settled and no longer transitioning
private var settledProgress = 1f
set(value) {
- field = value
- onSettledProgressUpdated(field)
+ if (value != field) {
+ field = value
+ onSettledProgressUpdated(field)
+ }
}
private val settledProgressPropertyFactory =
MultiPropertyFactory(
this,
SETTLED_PROGRESS,
- SETTLED_PROGRESS_INDEX_COUNT,
+ SettledProgress.entries.size,
{ x: Float, y: Float -> x * y },
1f,
)
- private val settledProgressFullscreen =
- settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_FULLSCREEN)
- private val settledProgressGesture =
- settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_GESTURE)
- private val settledProgressDismiss =
- settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_DISMISS)
+ private var settledProgressFullscreen by
+ MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Fullscreen)
+ private var settledProgressGesture by
+ MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Gesture)
+ private var settledProgressDismiss by
+ MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Dismiss)
private var viewModel: TaskViewModel? = null
private val dispatcherProvider: DispatcherProvider by RecentsDependencies.inject()
- private val coroutineScope by lazy { CoroutineScope(SupervisorJob() + dispatcherProvider.main) }
+ private val coroutineScope: CoroutineScope by RecentsDependencies.inject()
private val coroutineJobs = mutableListOf<Job>()
/**
@@ -536,7 +522,7 @@
* interpolator.
*/
fun getDismissIconFadeInAnimator(): ObjectAnimator =
- ObjectAnimator.ofFloat(settledProgressDismiss, MULTI_PROPERTY_VALUE, 1f).apply {
+ ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_DISMISS, 1f).apply {
duration = FADE_IN_ICON_DURATION
interpolator = FADE_IN_ICON_INTERPOLATOR
}
@@ -548,8 +534,7 @@
*/
fun getDismissIconFadeOutAnimator(): ObjectAnimator =
AnimatedFloat { v ->
- settledProgressDismiss.value =
- SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v)
+ settledProgressDismiss = SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v)
}
.animateToValue(1f, 0f)
@@ -758,8 +743,9 @@
// The TaskView lifecycle is starts the ViewModel during onBind, and cleans it in
// onRecycle. So it should be initialized at this point. TaskView Lifecycle:
// `bind` -> `onBind` -> onAttachedToWindow() -> onDetachFromWindow -> onRecycle
- coroutineJobs +=
- coroutineScope.launch { viewModel!!.state.collectLatest(::updateTaskViewState) }
+ coroutineJobs += coroutineScope.launch(dispatcherProvider.main) {
+ viewModel!!.state.collectLatest(::updateTaskViewState)
+ }
}
}
@@ -1476,7 +1462,8 @@
return if (enableOverviewIconMenu() && menuContainer.iconView is IconAppChipView) {
menuContainer.iconView.revealAnim(/* isRevealing= */ true)
TaskMenuView.showForTask(menuContainer) {
- menuContainer.iconView.revealAnim(/* isRevealing= */ false)
+ val isAnimated = !recentsView.isSplitSelectionActive
+ menuContainer.iconView.revealAnim(/* isRevealing= */ false, isAnimated)
if (enableHoverOfChildElementsInTaskview()) {
recentsView.setTaskBorderEnabled(true)
}
@@ -1602,7 +1589,7 @@
fun startIconFadeInOnGestureComplete() {
iconFadeInOnGestureCompleteAnimator?.cancel()
iconFadeInOnGestureCompleteAnimator =
- ObjectAnimator.ofFloat(settledProgressGesture, MULTI_PROPERTY_VALUE, 1f).apply {
+ ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_GESTURE, 1f).apply {
duration = FADE_IN_ICON_DURATION
interpolator = Interpolators.LINEAR
addListener(
@@ -1618,7 +1605,7 @@
fun setIconVisibleForGesture(isVisible: Boolean) {
iconFadeInOnGestureCompleteAnimator?.cancel()
- settledProgressGesture.value = if (isVisible) 1f else 0f
+ settledProgressGesture = if (isVisible) 1f else 0f
}
/** Set a color tint on the snapshot and supporting views. */
@@ -1704,10 +1691,12 @@
protected open fun onFullscreenProgressChanged(fullscreenProgress: Float) {
taskContainers.forEach {
- it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
+ if (!enableOverviewIconMenu()) {
+ it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
+ }
it.overlay.setFullscreenProgress(fullscreenProgress)
}
- settledProgressFullscreen.value =
+ settledProgressFullscreen =
SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
updateFullscreenParams()
}
@@ -1765,7 +1754,7 @@
dismissScale = 1f
translationZ = 0f
setIconVisibleForGesture(true)
- settledProgressDismiss.value = 1f
+ settledProgressDismiss = 1f
setColorTint(0f, 0)
}
@@ -1787,23 +1776,25 @@
companion object {
private const val TAG = "TaskView"
+
+ private enum class Alpha {
+ STABLE,
+ ATTACH,
+ SPLIT,
+ }
+
+ private enum class SettledProgress {
+ Fullscreen,
+ Gesture,
+ Dismiss,
+ }
+
const val FLAG_UPDATE_ICON = 1
const val FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON shl 1
const val FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL shl 1
const val FLAG_UPDATE_ALL =
(FLAG_UPDATE_ICON or FLAG_UPDATE_THUMBNAIL or FLAG_UPDATE_CORNER_RADIUS)
- const val SETTLED_PROGRESS_INDEX_FULLSCREEN = 0
- const val SETTLED_PROGRESS_INDEX_GESTURE = 1
- const val SETTLED_PROGRESS_INDEX_DISMISS = 2
- const val SETTLED_PROGRESS_INDEX_COUNT = 3
-
- private const val ALPHA_INDEX_STABLE = 0
- private const val ALPHA_INDEX_ATTACH = 1
- private const val ALPHA_INDEX_SPLIT = 2
-
- private const val NUM_ALPHA_CHANNELS = 3
-
/** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
const val MAX_PAGE_SCRIM_ALPHA = 0.4f
const val FADE_IN_ICON_DURATION: Long = 120
@@ -1820,104 +1811,45 @@
private val SYSTEM_GESTURE_EXCLUSION_RECT = listOf(Rect())
private val SETTLED_PROGRESS: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("settleTransition") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.settledProgress = v
- }
+ KFloatProperty(TaskView::settledProgress)
- override fun get(taskView: TaskView) = taskView.settledProgress
- }
+ private val SETTLED_PROGRESS_GESTURE: FloatProperty<TaskView> =
+ KFloatProperty(TaskView::settledProgressGesture)
+
+ private val SETTLED_PROGRESS_DISMISS: FloatProperty<TaskView> =
+ KFloatProperty(TaskView::settledProgressDismiss)
private val SPLIT_SELECT_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("splitSelectTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.splitSelectTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.splitSelectTranslationX
- }
+ KFloatProperty(TaskView::splitSelectTranslationX)
private val SPLIT_SELECT_TRANSLATION_Y: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("splitSelectTranslationY") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.splitSelectTranslationY = v
- }
-
- override fun get(taskView: TaskView) = taskView.splitSelectTranslationY
- }
+ KFloatProperty(TaskView::splitSelectTranslationY)
private val DISMISS_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("dismissTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.dismissTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.dismissTranslationX
- }
+ KFloatProperty(TaskView::dismissTranslationX)
private val DISMISS_TRANSLATION_Y: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("dismissTranslationY") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.dismissTranslationY = v
- }
-
- override fun get(taskView: TaskView) = taskView.dismissTranslationY
- }
+ KFloatProperty(TaskView::dismissTranslationY)
private val TASK_OFFSET_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("taskOffsetTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.taskOffsetTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.taskOffsetTranslationX
- }
+ KFloatProperty(TaskView::taskOffsetTranslationX)
private val TASK_OFFSET_TRANSLATION_Y: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("taskOffsetTranslationY") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.taskOffsetTranslationY = v
- }
-
- override fun get(taskView: TaskView) = taskView.taskOffsetTranslationY
- }
+ KFloatProperty(TaskView::taskOffsetTranslationY)
private val TASK_RESISTANCE_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("taskResistanceTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.taskResistanceTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.taskResistanceTranslationX
- }
+ KFloatProperty(TaskView::taskResistanceTranslationX)
private val TASK_RESISTANCE_TRANSLATION_Y: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("taskResistanceTranslationY") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.taskResistanceTranslationY = v
- }
-
- override fun get(taskView: TaskView) = taskView.taskResistanceTranslationY
- }
+ KFloatProperty(TaskView::taskResistanceTranslationY)
@JvmField
val GRID_END_TRANSLATION_X: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("gridEndTranslationX") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.gridEndTranslationX = v
- }
-
- override fun get(taskView: TaskView) = taskView.gridEndTranslationX
- }
+ KFloatProperty(TaskView::gridEndTranslationX)
@JvmField
- val DISMISS_SCALE: FloatProperty<TaskView> =
- object : FloatProperty<TaskView>("dismissScale") {
- override fun setValue(taskView: TaskView, v: Float) {
- taskView.dismissScale = v
- }
+ val DISMISS_SCALE: FloatProperty<TaskView> = KFloatProperty(TaskView::dismissScale)
- override fun get(taskView: TaskView) = taskView.dismissScale
- }
+ @JvmField val SPLIT_ALPHA: FloatProperty<TaskView> = KFloatProperty(TaskView::splitAlpha)
}
}
diff --git a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
index 37c64cf..18a5338 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
@@ -159,31 +159,37 @@
ProtoLog.d(ACTIVE_GESTURE_LOG, "cleanUpRecentsAnimation");
}
- public static void logOnInputEventUserLocked() {
- ActiveGestureLog.INSTANCE.addLog(
- "TIS.onInputEvent: Cannot process input event: user is locked");
+ public static void logOnInputEventUserLocked(int displayId) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TIS.onInputEvent(displayId=%d): Cannot process input event: user is locked",
+ displayId));
if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
ProtoLog.d(ACTIVE_GESTURE_LOG,
- "TIS.onInputEvent: Cannot process input event: user is locked");
+ "TIS.onInputEvent(displayId=%d): Cannot process input event: user is locked",
+ displayId);
}
- public static void logOnInputIgnoringFollowingEvents() {
- ActiveGestureLog.INSTANCE.addLog("TIS.onMotionEvent: A new gesture has been started, "
+ public static void logOnInputIgnoringFollowingEvents(int displayId) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TIS.onMotionEvent(displayId=%d): A new gesture has been started, "
+ "but a previously-requested recents animation hasn't started. "
- + "Ignoring all following motion events.",
+ + "Ignoring all following motion events.", displayId),
RECENTS_ANIMATION_START_PENDING);
if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
- ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onMotionEvent: A new gesture has been started, "
- + "but a previously-requested recents animation hasn't started. "
- + "Ignoring all following motion events.");
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "TIS.onMotionEvent(displayId=%d): A new gesture has been started, "
+ + "but a previously-requested recents animation hasn't started. "
+ + "Ignoring all following motion events.", displayId);
}
- public static void logOnInputEventThreeButtonNav() {
- ActiveGestureLog.INSTANCE.addLog("TIS.onInputEvent: Cannot process input event: "
- + "using 3-button nav and event is not a trackpad event");
+ public static void logOnInputEventThreeButtonNav(int displayId) {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "TIS.onInputEvent(displayId=%d): Cannot process input event: "
+ + "using 3-button nav and event is not a trackpad event", displayId));
if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
- ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onInputEvent: Cannot process input event: "
- + "using 3-button nav and event is not a trackpad event");
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "TIS.onInputEvent(displayId=%d): Cannot process input event: "
+ + "using 3-button nav and event is not a trackpad event", displayId);
}
public static void logPreloadRecentsAnimation() {
@@ -322,61 +328,84 @@
}
public static void logOnInputEventActionUp(
- int x, int y, int action, @NonNull String classification) {
+ int x, int y, int action, @NonNull String classification, int displayId) {
String actionString = MotionEvent.actionToString(action);
ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "onMotionEvent(%d, %d): %s, %s", x, y, actionString, classification),
+ "onMotionEvent(%d, %d): %s, %s, displayId=%d",
+ x,
+ y,
+ actionString,
+ classification,
+ displayId),
/* gestureEvent= */ action == ACTION_DOWN
? MOTION_DOWN
: MOTION_UP);
if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
ProtoLog.d(ACTIVE_GESTURE_LOG,
- "onMotionEvent(%d, %d): %s, %s", x, y, actionString, classification);
+ "onMotionEvent(%d, %d): %s, %s, displayId=%d",
+ x,
+ y,
+ actionString,
+ classification,
+ displayId);
}
public static void logOnInputEventActionMove(
- @NonNull String action, @NonNull String classification, int pointerCount) {
+ @NonNull String action,
+ @NonNull String classification,
+ int pointerCount,
+ int displayId) {
ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "onMotionEvent: %s, %s, pointerCount: %d",
+ "onMotionEvent: %s, %s, pointerCount: %d, displayId=%d",
action,
classification,
- pointerCount),
+ pointerCount,
+ displayId),
MOTION_MOVE);
if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
ProtoLog.d(ACTIVE_GESTURE_LOG,
- "onMotionEvent: %s, %s, pointerCount: %d", action, classification, pointerCount);
+ "onMotionEvent: %s, %s, pointerCount: %d, displayId=%d",
+ action,
+ classification,
+ pointerCount,
+ displayId);
}
public static void logOnInputEventGenericAction(
- @NonNull String action, @NonNull String classification) {
+ @NonNull String action, @NonNull String classification, int displayId) {
ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "onMotionEvent: %s, %s", action, classification));
+ "onMotionEvent: %s, %s, displayId=%d", action, classification, displayId));
if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
- ProtoLog.d(ACTIVE_GESTURE_LOG, "onMotionEvent: %s, %s", action, classification);
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "onMotionEvent: %s, %s, displayId=%d", action, classification, displayId);
}
public static void logOnInputEventNavModeSwitched(
- @NonNull String startNavMode, @NonNull String currentNavMode) {
+ int displayId, @NonNull String startNavMode, @NonNull String currentNavMode) {
ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "TIS.onInputEvent: Navigation mode switched mid-gesture (%s -> %s); "
+ "TIS.onInputEvent(displayId=%d): Navigation mode switched mid-gesture (%s -> %s); "
+ "cancelling gesture.",
+ displayId,
startNavMode,
currentNavMode),
NAVIGATION_MODE_SWITCHED);
if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
ProtoLog.d(ACTIVE_GESTURE_LOG,
- "TIS.onInputEvent: Navigation mode switched mid-gesture (%s -> %s); "
+ "TIS.onInputEvent(displayId=%d): Navigation mode switched mid-gesture (%s -> %s); "
+ "cancelling gesture.",
+ displayId,
startNavMode,
currentNavMode);
}
- public static void logUnknownInputEvent(@NonNull String event) {
+ public static void logUnknownInputEvent(int displayId, @NonNull String event) {
ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "TIS.onInputEvent: Cannot process input event: received unknown event %s", event));
+ "TIS.onInputEvent(displayId=%d): Cannot process input event: "
+ + "received unknown event %s", displayId, event));
if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
ProtoLog.d(ACTIVE_GESTURE_LOG,
- "TIS.onInputEvent: Cannot process input event: received unknown event %s", event);
+ "TIS.onInputEvent(displayId=%d): Cannot process input event: "
+ + "received unknown event %s", displayId, event);
}
public static void logFinishRunningRecentsAnimation(boolean toHome) {
@@ -433,11 +462,13 @@
ProtoLog.d(ACTIVE_GESTURE_LOG, "Launching side task id=%d", taskId);
}
- public static void logOnInputEventActionDown(@NonNull ActiveGestureLog.CompoundString reason) {
+ public static void logOnInputEventActionDown(
+ int displayId, @NonNull ActiveGestureLog.CompoundString reason) {
ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
- "TIS.onMotionEvent: ").append(reason));
+ "TIS.onMotionEvent(displayId=%d): ", displayId).append(reason));
if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
- ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onMotionEvent: %s", reason.toString());
+ ProtoLog.d(ACTIVE_GESTURE_LOG,
+ "TIS.onMotionEvent(displayId=%d): %s", displayId, reason.toString());
}
public static void logStartNewTask(@NonNull ActiveGestureLog.CompoundString tasks) {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
index 06a939a..91f9e53 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
@@ -41,7 +41,7 @@
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.AllModulesForTest;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.SandboxContext;
import com.android.launcher3.util.UserIconInfo;
import com.android.systemui.shared.system.SysUiStatsLog;
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
index adfbca5..8d20ba8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
@@ -28,7 +28,7 @@
import com.android.launcher3.model.data.TaskViewItemInfo.Companion.createTaskViewAtom
import com.android.launcher3.pm.UserCache
import com.android.launcher3.util.AllModulesForTest
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.SandboxContext
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.launcher3.util.UserIconInfo
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index 3a27bb1..3761044 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -19,6 +19,7 @@
import android.animation.AnimatorTestRule
import android.content.ComponentName
import android.content.Intent
+import android.os.Process
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
@@ -27,6 +28,7 @@
import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
import com.android.launcher3.R
import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
@@ -111,6 +113,7 @@
@InjectController lateinit var recentAppsController: TaskbarRecentAppsController
@InjectController lateinit var bubbleBarViewController: BubbleBarViewController
@InjectController lateinit var bubbleStashController: BubbleStashController
+ @InjectController lateinit var keyboardQuickSwitchController: KeyboardQuickSwitchController
private var desktopTaskListener: IDesktopTaskListener? = null
@@ -209,8 +212,10 @@
runOnMainSync {
val taskbarView: TaskbarView =
taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+ val hotseatItems = createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount)
+
taskbarView.updateItems(
- createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount),
+ recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
recentAppsController.shownTasks,
)
}
@@ -327,13 +332,122 @@
assertThat(taskbarIconsCentered).isTrue()
}
+ @Test
+ @TaskbarMode(PINNED)
+ fun testPressingOverflowButtonOpensKeyboardQuickSwitch() {
+ val maxNumIconViews = maxNumberOfTaskbarIcons
+ // Assume there are at least all apps and divider icon, as they would appear once running
+ // apps are added, even if not present initially.
+ val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+
+ val targetOverflowSize = 5
+ val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
+ createDesktopTask(createdTasks)
+
+ assertThat(taskbarOverflowIconIndex).isEqualTo(initialIconCount)
+ tapOverflowIcon()
+ // Keyboard quick switch view is shown only after list of recent task is asynchronously
+ // retrieved from the recents model.
+ runOnMainSync { recentsModel.resolvePendingTaskRequests() }
+
+ assertThat(getOnUiThread { keyboardQuickSwitchController.isShownFromTaskbar }).isTrue()
+ assertThat(getOnUiThread { keyboardQuickSwitchController.shownTaskIds() })
+ .containsExactlyElementsIn(0..<createdTasks)
+
+ tapOverflowIcon()
+ assertThat(keyboardQuickSwitchController.isShown).isFalse()
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testHotseatItemTasksNotShownInRecents() {
+ val maxNumIconViews = maxNumberOfTaskbarIcons
+ // Assume there are at least all apps and divider icon, as they would appear once running
+ // apps are added, even if not present initially.
+ val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+ val hotseatItems = createHotseatItems(1)
+
+ val targetOverflowSize = 5
+ val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
+ createDesktopTaskWithTasksFromPackages(
+ listOf("fake") +
+ listOf(hotseatItems[0]?.targetPackage ?: "") +
+ List(createdTasks - 2) { "fake" }
+ )
+
+ runOnMainSync {
+ val taskbarView: TaskbarView =
+ taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+ taskbarView.updateItems(
+ recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
+ recentAppsController.shownTasks,
+ )
+ }
+
+ assertThat(maxNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+ assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+ assertThat(taskbarOverflowIconIndex).isEqualTo(initialIconCount + hotseatItems.size)
+ assertThat(overflowItems)
+ .containsExactlyElementsIn(listOf(0) + (2..targetOverflowSize + 1).toList())
+ }
+
+ @Test
+ @TaskbarMode(PINNED)
+ fun testHotseatItemTasksNotShownInKQS() {
+ val maxNumIconViews = maxNumberOfTaskbarIcons
+ // Assume there are at least all apps and divider icon, as they would appear once running
+ // apps are added, even if not present initially.
+ val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+ val hotseatItems = createHotseatItems(1)
+
+ val targetOverflowSize = 5
+ val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
+ createDesktopTaskWithTasksFromPackages(
+ listOf("fake") +
+ listOf(hotseatItems[0]?.targetPackage ?: "") +
+ List(createdTasks - 2) { "fake" }
+ )
+
+ runOnMainSync {
+ val taskbarView: TaskbarView =
+ taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+ taskbarView.updateItems(
+ recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
+ recentAppsController.shownTasks,
+ )
+ }
+
+ tapOverflowIcon()
+ // Keyboard quick switch view is shown only after list of recent task is asynchronously
+ // retrieved from the recents model.
+ runOnMainSync { recentsModel.resolvePendingTaskRequests() }
+
+ assertThat(getOnUiThread { keyboardQuickSwitchController.isShownFromTaskbar }).isTrue()
+ assertThat(getOnUiThread { keyboardQuickSwitchController.shownTaskIds() })
+ .containsExactlyElementsIn(listOf(0) + (2..<createdTasks).toList())
+ }
+
private fun createDesktopTask(tasksToAdd: Int) {
+ createDesktopTaskWithTasksFromPackages((0..<tasksToAdd).map { "fake" })
+ }
+
+ private fun createDesktopTaskWithTasksFromPackages(packages: List<String>) {
val tasks =
- (0..<tasksToAdd).map {
- Task(Task.TaskKey(it, 0, Intent(), ComponentName("", ""), 0, 2000))
- }
+ packages.mapIndexed({ index, p ->
+ Task(
+ Task.TaskKey(
+ index,
+ 0,
+ Intent().apply { `package` = p },
+ ComponentName(p, ""),
+ Process.myUserHandle().identifier,
+ 2000,
+ )
+ )
+ })
+
recentsModel.updateRecentTasks(listOf(DesktopTask(deskId = 0, tasks)))
- for (task in 1..tasksToAdd) {
+ for (task in 1..tasks.size) {
desktopTaskListener?.onTasksVisibilityChanged(
context.virtualDisplay.display.displayId,
task,
@@ -394,6 +508,14 @@
}
}
+ private fun tapOverflowIcon() {
+ runOnMainSync {
+ val overflowIcon =
+ taskbarViewController.iconViews.firstOrNull { it is TaskbarOverflowView }
+ assertThat(overflowIcon?.callOnClick()).isTrue()
+ }
+ }
+
/**
* Adds enough running apps for taskbar to enter overflow of `targetOverflowSize`, and verifies
* * max number of icons in the taskbar remains unchanged
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
index 4b6d5e5..ac1572e 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
@@ -21,6 +21,7 @@
import android.view.View
import com.android.launcher3.Flags.FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION
import com.android.launcher3.R
+import com.android.launcher3.statehandlers.DesktopVisibilityController
import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
import com.android.launcher3.taskbar.TaskbarIconType.ALL_APPS
import com.android.launcher3.taskbar.TaskbarIconType.DIVIDER
@@ -39,6 +40,7 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.whenever
@RunWith(LauncherMultivalentJUnit::class)
@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
@@ -54,6 +56,9 @@
private val iconViews: Array<View>
get() = taskbarView.iconViews
+ private val desktopVisibilityController: DesktopVisibilityController
+ get() = DesktopVisibilityController.INSTANCE[context]
+
@Before
fun obtainView() {
taskbarView = taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
@@ -225,4 +230,34 @@
assertThat(expectedViewToRemove in iconViews).isFalse()
}
}
+
+ @Test
+ fun testUpdateItems_desktopMode_hotseatItem_noDivider() {
+ whenever(desktopVisibilityController.isInDesktopMode(context.displayId)).thenReturn(true)
+ runOnMainSync { taskbarView.updateItems(createHotseatItems(1), emptyList()) }
+ assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT)
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtlAndDesktopMode_hotseatItem_noDivider() {
+ whenever(desktopVisibilityController.isInDesktopMode(context.displayId)).thenReturn(true)
+ runOnMainSync { taskbarView.updateItems(createHotseatItems(1), emptyList()) }
+ assertThat(taskbarView).hasIconTypes(HOTSEAT, ALL_APPS)
+ }
+
+ @Test
+ fun testUpdateItems_desktopMode_recentItem_hasDivider() {
+ whenever(desktopVisibilityController.isInDesktopMode(context.displayId)).thenReturn(true)
+ runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(1)) }
+ assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, RECENT)
+ }
+
+ @Test
+ @ForceRtl
+ fun testUpdateItems_rtlAndDesktopMode_recentItem_hasDivider() {
+ whenever(desktopVisibilityController.isInDesktopMode(context.displayId)).thenReturn(true)
+ runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(1)) }
+ assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, ALL_APPS)
+ }
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
index a7bfa9a..5f7b360 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
@@ -19,6 +19,7 @@
import com.android.quickstep.RecentsModel
import com.android.quickstep.RecentsModel.RecentTasksChangedListener
import com.android.quickstep.TaskIconCache
+import com.android.quickstep.TaskThumbnailCache
import com.android.quickstep.util.GroupTask
import java.util.function.Consumer
import org.mockito.kotlin.any
@@ -27,9 +28,11 @@
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
-/** Helper class to mock the {@link RecentsModel} object in test */
+/** Helper class to mock the [RecentsModel] object in test */
class MockedRecentsModelHelper {
private val mockIconCache: TaskIconCache = mock()
+ private val mockThumbnailCache: TaskThumbnailCache = mock()
+
var taskListId = 0
var recentTasksChangedListener: RecentTasksChangedListener? = null
var taskRequests: MutableList<(List<GroupTask>) -> Unit> = mutableListOf()
@@ -37,6 +40,8 @@
val mockRecentsModel: RecentsModel = mock {
on { iconCache } doReturn mockIconCache
+ on { thumbnailCache } doReturn mockThumbnailCache
+
on { unregisterRecentTasksChangedListener() } doAnswer { recentTasksChangedListener = null }
on { registerRecentTasksChangedListener(any<RecentTasksChangedListener>()) } doAnswer
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
index 3cf912c..f225807 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
@@ -20,7 +20,6 @@
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode
import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
import com.android.launcher3.util.DisplayController
-import com.android.launcher3.util.MainThreadInitializedObject
import com.android.launcher3.util.NavigationMode
import org.junit.rules.TestRule
import org.junit.runner.Description
@@ -31,8 +30,8 @@
/**
* Allows tests to specify which Taskbar [Mode] to run under.
*
- * [context] should match the test's target context, so that [MainThreadInitializedObject] instances
- * are properly sandboxed.
+ * [context] should match the test's target context, so that Dagger singleton instances are properly
+ * sandboxed.
*
* Annotate tests with [TaskbarMode] to set a mode. If the annotation is omitted for any tests, this
* rule is a no-op.
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
index e6806b7..d96e06e 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
@@ -26,11 +26,11 @@
import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.dagger.LauncherAppComponent
import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.statehandlers.DesktopVisibilityController
import com.android.launcher3.util.AllModulesMinusWMProxy
import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.FakePrefsModule
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
import com.android.launcher3.util.SandboxApplication
import com.android.launcher3.util.SettingsCache
import com.android.launcher3.util.SettingsCacheSandbox
@@ -40,12 +40,14 @@
import dagger.BindsInstance
import dagger.Component
import dagger.Module
+import dagger.Provides
import javax.inject.Inject
import org.junit.rules.ExternalResource
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
+import org.mockito.kotlin.spy
/**
* [SandboxApplication] for running Taskbar tests.
@@ -58,7 +60,7 @@
private val base: SandboxApplication,
val virtualDisplay: VirtualDisplay,
private val params: SandboxParams,
-) : ContextWrapper(base), ObjectSandbox by base, TestRule {
+) : ContextWrapper(base), TestRule {
val settingsCacheSandbox = SettingsCacheSandbox()
@@ -136,6 +138,20 @@
@Binds abstract fun bindDisplayController(controller: DisplayControllerSpy): DisplayController
}
+@Module
+object DesktopVisibilityControllerModule {
+ @JvmStatic
+ @Provides
+ @LauncherAppSingleton
+ fun provideDesktopVisibilityController(
+ @ApplicationContext context: Context,
+ systemUiProxy: SystemUiProxy,
+ lifecycleTracker: DaggerSingletonTracker,
+ ): DesktopVisibilityController {
+ return spy(DesktopVisibilityController(context, systemUiProxy, lifecycleTracker))
+ }
+}
+
@LauncherAppSingleton
@Component(
modules =
@@ -144,6 +160,7 @@
FakePrefsModule::class,
DisplayControllerModule::class,
TaskbarSandboxModule::class,
+ DesktopVisibilityControllerModule::class,
]
)
interface TaskbarSandboxComponent : LauncherAppComponent {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/DisplayModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/DisplayModelTest.kt
index a939e84..fa7907f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/DisplayModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/DisplayModelTest.kt
@@ -21,6 +21,7 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import java.io.PrintWriter
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Test
@@ -37,25 +38,29 @@
override fun cleanup() {
isCleanupCalled = true
}
+
+ override fun dump(prefix: String, writer: PrintWriter) {
+ // No-Op
+ }
}
private val testableDisplayModel =
object : DisplayModel<TestableResource>(context) {
- override fun createDisplayResource(displayId: Int) {
- displayResourceArray.put(displayId, TestableResource())
+ override fun createDisplayResource(display: Display): TestableResource {
+ return TestableResource()
}
}
@Test
fun testCreate() {
- testableDisplayModel.createDisplayResource(Display.DEFAULT_DISPLAY)
+ testableDisplayModel.storeDisplayResource(Display.DEFAULT_DISPLAY)
val resource = testableDisplayModel.getDisplayResource(Display.DEFAULT_DISPLAY)
assertNotNull(resource)
}
@Test
fun testCleanAndDelete() {
- testableDisplayModel.createDisplayResource(Display.DEFAULT_DISPLAY)
+ testableDisplayModel.storeDisplayResource(Display.DEFAULT_DISPLAY)
val resource = testableDisplayModel.getDisplayResource(Display.DEFAULT_DISPLAY)!!
assertNotNull(resource)
testableDisplayModel.deleteDisplayResource(Display.DEFAULT_DISPLAY)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
index af741f6..35f1218 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
@@ -91,7 +91,14 @@
.bindRotationHelper(mock(RotationTouchHelper::class.java))
.bindRecentsState(mock(RecentsAnimationDeviceState::class.java))
)
- gestureState = spy(GestureState(OverviewComponentObserver.INSTANCE.get(sandboxContext), 0))
+ gestureState =
+ spy(
+ GestureState(
+ OverviewComponentObserver.INSTANCE.get(sandboxContext),
+ DEFAULT_DISPLAY,
+ 0,
+ )
+ )
underTest =
LauncherSwipeHandlerV2(
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
index b652ee8..a7370b0 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
@@ -1,5 +1,6 @@
package com.android.quickstep
+import android.view.Display
import androidx.test.annotation.UiThreadTest
import androidx.test.filters.SmallTest
import com.android.launcher3.dagger.LauncherComponentProvider
@@ -13,6 +14,7 @@
import com.android.launcher3.util.NavigationMode
import com.android.launcher3.util.SandboxApplication
import com.android.quickstep.util.GestureExclusionManager
+import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION
@@ -150,7 +152,7 @@
allSysUiStates().forEach { state ->
val canStartGesture = !disablingStates.contains(state)
- underTest.setSystemUiFlags(state)
+ underTest.setSysUIStateFlagsForDisplay(state, Display.DEFAULT_DISPLAY)
assertThat(underTest.canStartTrackpadGesture()).isEqualTo(canStartGesture)
}
}
@@ -166,7 +168,7 @@
)
stateToExpectedResult.forEach { (state, allowed) ->
- underTest.setSystemUiFlags(state)
+ underTest.setSysUIStateFlagsForDisplay(state, Display.DEFAULT_DISPLAY)
assertThat(underTest.canStartTrackpadGesture()).isEqualTo(allowed)
}
}
@@ -177,7 +179,7 @@
allSysUiStates().forEach { state ->
val canStartGesture = !disablingStates.contains(state)
- underTest.setSystemUiFlags(state)
+ underTest.setSysUIStateFlagsForDisplay(state, Display.DEFAULT_DISPLAY)
assertThat(underTest.canStartSystemGesture()).isEqualTo(canStartGesture)
}
}
@@ -197,11 +199,42 @@
)
stateToExpectedResult.forEach { (state, gestureAllowed) ->
- underTest.setSystemUiFlags(state)
+ underTest.setSysUIStateFlagsForDisplay(state, Display.DEFAULT_DISPLAY)
assertThat(underTest.canStartSystemGesture()).isEqualTo(gestureAllowed)
}
}
+ @Test
+ fun getSystemUiStateFlags_defaultAwake() {
+ val NOT_EXISTENT_DISPLAY = 2
+ assertThat(underTest.getSystemUiStateFlags(NOT_EXISTENT_DISPLAY))
+ .isEqualTo(QuickStepContract.SYSUI_STATE_AWAKE)
+ }
+
+ @Test
+ fun clearSysUIStateFlagsForDisplay_displayNotReturnedAnymore() {
+ underTest.setSysUIStateFlagsForDisplay(1, /* displayId= */ 1)
+
+ assertThat(underTest.displaysWithSysUIState).contains(1)
+ assertThat(underTest.getSystemUiStateFlags(1)).isEqualTo(1)
+
+ underTest.clearSysUIStateFlagsForDisplay(1)
+
+ assertThat(underTest.displaysWithSysUIState).doesNotContain(1)
+ assertThat(underTest.getSystemUiStateFlags(1))
+ .isEqualTo(QuickStepContract.SYSUI_STATE_AWAKE)
+ }
+
+ @Test
+ fun setSysUIStateFlagsForDisplay_setsCorrectly() {
+ underTest.setSysUIStateFlagsForDisplay(1, /* displayId= */ 1)
+ underTest.setSysUIStateFlagsForDisplay(2, /* displayId= */ 2)
+
+ assertThat(underTest.getSystemUiStateFlags(1)).isEqualTo(1)
+ assertThat(underTest.getSystemUiStateFlags(2)).isEqualTo(2)
+ assertThat(underTest.displaysWithSysUIState).containsAtLeast(1, 2)
+ }
+
private fun allSysUiStates(): List<Long> {
// SYSUI_STATES_* are binary flags
return (0..SYSUI_STATES_COUNT).map { 1L shl it }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
index 99a34ea..cfeade8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
@@ -55,7 +55,7 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.util.AllModulesForTest;
import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.SandboxContext;
import com.android.quickstep.DeviceConfigWrapper;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
@@ -311,6 +311,18 @@
}
@Test
+ public void testTouchCancelWithoutTouchDown() {
+ mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_CANCEL));
+
+ assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
+ assertFalse(mLongPressTriggered.get());
+ verify(mNavHandleLongPressHandler, never()).onTouchStarted(any());
+ verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
+ }
+
+ @Test
public void testLongPressAbortedByTouchSlopPassedVertically() {
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
sleep(MIN_TIME_TO_LOG_ABANDON_MS);
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt
index 44ea73e..0119679 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt
@@ -88,16 +88,16 @@
@Test
fun testCreateSeparateInstances() {
- val display = Display.DEFAULT_DISPLAY + 1
- runOnMainSync { recentsDisplayModel.createDisplayResource(display) }
+ val displayId = Display.DEFAULT_DISPLAY + 1
+ runOnMainSync { recentsDisplayModel.storeDisplayResource(displayId) }
val defaultManager = recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY)
- val secondaryManager = recentsDisplayModel.getRecentsWindowManager(display)
+ val secondaryManager = recentsDisplayModel.getRecentsWindowManager(displayId)
Assert.assertNotSame(defaultManager, secondaryManager)
val defaultInterface =
recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY)
- val secondInterface = recentsDisplayModel.getFallbackWindowInterface(display)
+ val secondInterface = recentsDisplayModel.getFallbackWindowInterface(displayId)
Assert.assertNotSame(defaultInterface, secondInterface)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 9d000a4..c9d7e1d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -94,6 +94,7 @@
whenever(mockTaskContainer.task).thenReturn(mockTask)
whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
+ whenever(mockTaskView.firstTaskContainer).thenReturn(mockTaskContainer)
whenever(splitSelectSource.drawable).thenReturn(mockSplitSourceDrawable)
whenever(splitSelectSource.view).thenReturn(mockSplitSourceView)
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index d52d054..59ce637 100644
--- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -34,7 +34,6 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetId;
@@ -46,7 +45,6 @@
import android.os.Process;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.text.TextUtils;
@@ -71,8 +69,6 @@
import java.util.Arrays;
import java.util.List;
-import java.util.Set;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
@SmallTest
@@ -234,50 +230,6 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER)
- public void widgetsRecommendationRan_keepsWidgetsNotOnWorkspace_addsWidgetsFromEligibleApps() {
- runOnExecutorSync(MODEL_EXECUTOR, () -> {
- WidgetsFilterDataProvider spiedFilterProvider = spy(
- mModelHelper.getModel().getWidgetsFilterDataProvider());
- doAnswer(i -> new Predicate<WidgetItem>() {
- @Override
- public boolean test(WidgetItem widgetItem) {
- // app5's widget is already on workspace, but, app2 is not.
- // And app4's second widget is also not on workspace.
- return Set.of("app5", "app2", "app4").contains(
- widgetItem.componentName.getPackageName());
- }
- }).when(spiedFilterProvider).getPredictedWidgetsFilter();
- mModelHelper.getBgDataModel().widgetsModel.updateWidgetFilters(spiedFilterProvider);
- // App5's widget that's already on workspace.
- AppTarget widget1 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
- mUserHandle);
- // App4's widget eligible and not on workspace.
- AppTarget widget2 = new AppTarget(new AppTargetId("app4"), "app4", "provider2",
- mUserHandle);
-
- mCallback.mRecommendedWidgets = null;
- mModelHelper.getModel().enqueueModelUpdateTask(
- newWidgetsPredicationTask(List.of(widget1, widget2)));
- runOnExecutorSync(MAIN_EXECUTOR, () -> {
- });
-
- List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
- .stream()
- .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
- .collect(Collectors.toList());
- assertThat(recommendedWidgets).hasSize(2);
- List<ComponentName> componentNames = recommendedWidgets.stream().map(
- w -> w.componentName).toList();
- assertThat(componentNames).containsExactly(
- // Locally added, not on workspace, eligible app per filter
- mApp2Provider1.provider,
- // From prediction service, not on workspace, eligible app per filter
- mApp4Provider2.provider);
- });
- }
-
- @Test
public void widgetsRecommendations_excludesWidgetsHiddenForPicker() {
runOnExecutorSync(MODEL_EXECUTOR, () -> {
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
index fc757b4..0ccc76b 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
@@ -55,7 +55,9 @@
"com.android.launcher3.testcomponent.BaseTestingActivity");
mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, layoutBuilder);
AbstractLauncherUiTest.initialize(this);
- startAppFast(CALCULATOR_APP_PACKAGE);
+ if (startCalendarAppDuringSetup()) {
+ startAppFast(CALCULATOR_APP_PACKAGE);
+ }
mLauncher.enableBlockTimeout(true);
mLauncher.showTaskbarIfHidden();
}
@@ -72,8 +74,20 @@
return DisplayController.isTransientTaskbar(context);
}
+ protected boolean startCalendarAppDuringSetup() {
+ return true;
+ }
+
+ protected boolean expectTaskbarIconsMatchHotseat() {
+ return true;
+ }
+
protected Taskbar getTaskbar() {
Taskbar taskbar = mLauncher.getLaunchedAppState().getTaskbar();
+ if (!expectTaskbarIconsMatchHotseat()) {
+ return taskbar;
+ }
+
List<String> taskbarIconNames = taskbar.getIconNames();
List<String> hotseatIconNames = mLauncher.getHotseatIconNames();
diff --git a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
index e2ca91a..ef6f55e 100644
--- a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
@@ -30,6 +30,7 @@
import android.annotation.Nullable;
import android.os.Looper;
import android.view.Choreographer;
+import android.view.Display;
import android.view.MotionEvent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -99,7 +100,8 @@
@Rule public final SandboxApplication mContext = new SandboxApplication();
- @NonNull private final InputMonitorCompat mInputMonitorCompat = new InputMonitorCompat("", 0);
+ @NonNull private final InputMonitorCompat mInputMonitorCompat =
+ new InputMonitorCompat("", Display.DEFAULT_DISPLAY);
private TaskAnimationManager mTaskAnimationManager;
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
@@ -196,7 +198,6 @@
@Before
public void setupDeviceState() {
- when(mDeviceState.getDisplayId()).thenReturn(0);
when(mDeviceState.canStartTrackpadGesture()).thenReturn(true);
when(mDeviceState.canStartSystemGesture()).thenReturn(true);
when(mDeviceState.isFullyGesturalNavMode()).thenReturn(true);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java
index 8fedf5c..b2617dd 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java
@@ -15,14 +15,18 @@
*/
package com.android.quickstep;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.test.InstrumentationRegistry.getTargetContext;
+import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP;
-import android.app.WindowConfiguration;
+import static com.google.common.truth.Truth.assertThat;
+
import android.os.RemoteException;
import android.util.Log;
import android.view.WindowManagerGlobal;
@@ -30,6 +34,7 @@
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.launcher3.tapl.HomeAllApps;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.rule.SetPropRule;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
@@ -40,6 +45,7 @@
import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExternalResource;
import org.junit.runner.RunWith;
@LargeTest
@@ -51,25 +57,40 @@
public SetPropRule mSetPropRule =
new SetPropRule(ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP, "true");
+ // Default-to-desktop feature requires the display to be freeform mode.
+ @Rule
+ public ExternalResource mFreeformDisplayRule = new ExternalResource() {
+ private int mOriginalWindowingMode = WINDOWING_MODE_UNDEFINED;
+
+ @Override
+ protected void before() {
+ mOriginalWindowingMode = setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+ }
+
+ @Override
+ protected void after() {
+ if (mOriginalWindowingMode != WINDOWING_MODE_UNDEFINED) {
+ setDisplayWindowingMode(mOriginalWindowingMode);
+ }
+ }
+ };
+
@Override
public void setUp() throws Exception {
Assume.assumeTrue(mLauncher.isTablet());
Assume.assumeTrue(Flags.enterDesktopByDefaultOnFreeformDisplays());
Assume.assumeTrue(DesktopModeStatus.canEnterDesktopMode(getTargetContext()));
super.setUp();
-
- // Default-to-desktop feature requires the display to be freeform mode.
- setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
}
@Override
- public void tearDown() throws Exception {
- // Reset the display windowing mode to the device default.
- setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_UNDEFINED);
+ protected boolean startCalendarAppDuringSetup() {
+ return false;
+ }
- mLauncher.recreateTaskbar();
-
- super.tearDown();
+ @Override
+ protected boolean expectTaskbarIconsMatchHotseat() {
+ return false;
}
@Test
@@ -87,10 +108,31 @@
mLauncher.getLaunchedAppState().assertTaskbarVisible();
}
- private void setDisplayWindowingMode(int windowingMode) {
+ @Test
+ @PortraitLandscape
+ @NavigationModeSwitch
+ @TaskbarModeSwitch(mode = PERSISTENT)
+ public void testDragFromAllAppsToWorspace() {
+ mDevice.pressHome();
+ waitForResumed("Launcher internal state is still Background");
+
+ final HomeAllApps allApps = getTaskbar().openAllAppsOnHome();
+ allApps.freeze();
try {
+ allApps.getAppIcon(TEST_APP_NAME).dragToWorkspace(false, false);
+ assertThat(mLauncher.getWorkspace().getWorkspaceAppIcon(TEST_APP_NAME)).isNotNull();
+ } finally {
+ allApps.unfreeze();
+ }
+ }
+
+ private int setDisplayWindowingMode(int windowingMode) {
+ try {
+ int originalWindowingMode =
+ WindowManagerGlobal.getWindowManagerService().getWindowingMode(DEFAULT_DISPLAY);
WindowManagerGlobal.getWindowManagerService().setWindowingMode(
DEFAULT_DISPLAY, windowingMode);
+ return originalWindowingMode;
} catch (RemoteException e) {
Log.e(TAG, "error setting windowing mode", e);
throw new RuntimeException(e);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 1c87bce..1af48a9 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -433,18 +433,17 @@
(Math.abs(recentsView.getTopRowTaskCountForTablet()
- recentsView.getBottomRowTaskCountForTablet()) <= 1)));
- // TODO(b/308841019): Re-enable after fixing Overview jank when dismiss
-// // Test dismissing more tasks.
-// assertIsInState(
-// "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
-// overview.getCurrentTask().dismiss();
-// assertIsInState(
-// "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
-// overview.getCurrentTask().dismiss();
-// runOnRecentsView(recentsView -> assertTrue(
-// "Grid did not rebalance after multiple dismissals",
-// (Math.abs(recentsView.getTopRowTaskCountForTablet()
-// - recentsView.getBottomRowTaskCountForTablet()) <= 1)));
+ // Test dismissing more tasks.
+ assertIsInState(
+ "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
+ overview.getCurrentTask().dismiss();
+ assertIsInState(
+ "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
+ overview.getCurrentTask().dismiss();
+ runOnRecentsView(recentsView -> assertTrue(
+ "Grid did not rebalance after multiple dismissals",
+ (Math.abs(recentsView.getTopRowTaskCountForTablet()
+ - recentsView.getBottomRowTaskCountForTablet()) <= 1)));
// Test dismissing all tasks.
mLauncher.goHome().switchToOverview().dismissAllTasks();
diff --git a/res/drawable/ic_unpin.xml b/res/drawable/ic_unpin.xml
new file mode 100644
index 0000000..557b4f9
--- /dev/null
+++ b/res/drawable/ic_unpin.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <group>
+ <clip-path
+ android:pathData="M0,0h24v24h-24z"/>
+ <path
+ android:pathData="M17,3V5H16V13.175L14,11.175V5H10V7.175L7.825,5L7,4.175V3H17ZM12,23L11,22V16H6V14L8,12V10.85L1.4,4.2L2.8,2.8L21.2,21.2L19.75,22.6L13.15,16H13V22L12,23ZM8.85,14H11.15L10.05,12.9L10,12.85L8.85,14Z"
+ android:fillColor="#FF000000"/>
+ </group>
+</vector>
diff --git a/res/values/config.xml b/res/values/config.xml
index 1a2ac9e..74e7bb0 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -80,9 +80,6 @@
<string name="local_colors_extraction_class" translatable="false"></string>
<string name="search_session_manager_class" translatable="false"></string>
- <!-- Filters for widgets displayed in the widget picker -->
- <string name="widgets_filter_data_provider_class" translatable="false"></string>
-
<!-- Scalable Grid configuration -->
<!-- This is a float because it is converted to dp later in DeviceProfile -->
<dimen name="hotseat_bar_bottom_space_default">48</dimen>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c3cb31d..64f67cd 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -23,6 +23,11 @@
<dimen name="dynamic_grid_left_right_margin">8dp</dimen>
<!-- Minimum amount of next page visible in spring loaded mode -->
<dimen name="dynamic_grid_spring_loaded_min_next_space_visible">48dp</dimen>
+ <item name="aspect_ratio_portrait" format="float" type="dimen">1.0</item>
+ <!-- 1.05 was the constant we found to validate square-ish devices
+ to load portrait orientation specs for responsive grids -->
+ <item name="aspect_ratio_portrait_and_square" format="float" type="dimen">1.05</item>
+ <item name="aspect_ratio_landscape" format="float" type="dimen">10</item>
<dimen name="dynamic_grid_cell_border_spacing">16dp</dimen>
<dimen name="cell_layout_padding">10.77dp</dimen>
@@ -307,6 +312,7 @@
<!-- Folders -->
<dimen name="page_indicator_dot_size">6dp</dimen>
+ <dimen name="page_indicator_gap_width">4dp</dimen>
<dimen name="page_indicator_size">10dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a02516a..db87686 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -235,8 +235,6 @@
<string name="pin_prediction">Pin Prediction</string>
<!-- Label for bubbling a launcher item. [CHAR_LIMIT=20] -->
<string name="bubble">Bubble</string>
- <!-- Label for pinning an item to the taskbar. [CHAR_LIMIT=20] -->
- <string name="pin_to_taskbar">Pin to taskbar</string>
<!-- Permissions: -->
<skip />
@@ -293,6 +291,9 @@
<!-- Description for a new page on homescreen[CHAR_LIMIT=none] -->
<string name="workspace_new_page">New home screen page</string>
+ <string name="app_running_state_description">Active</string>
+ <string name="app_minimized_state_description">Minimized</string>
+
<!-- Folder accessibility -->
<!-- The format string for when a folder is opened, speaks the dimensions -->
<string name="folder_opened">Folder opened, <xliff:g id="width" example="5">%1$d</xliff:g> by <xliff:g id="height" example="3">%2$d</xliff:g></string>
diff --git a/res/xml/default_workspace_7x3.xml b/res/xml/default_workspace_7x3.xml
new file mode 100644
index 0000000..d17491e
--- /dev/null
+++ b/res/xml/default_workspace_7x3.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<!-- Google-specific version of Launcher3/res/xml/default_workspace.xml -->
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+ <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+ <!-- Dialer Messaging Play Chrome Camera -->
+ <favorite
+ launcher:container="-101"
+ launcher:screen="0"
+ launcher:x="0"
+ launcher:y="0"
+ launcher:className="com.google.android.dialer.extensions.GoogleDialtactsActivity"
+ launcher:packageName="com.google.android.dialer" />
+
+ <favorite
+ launcher:container="-101"
+ launcher:screen="1"
+ launcher:x="1"
+ launcher:y="0"
+ launcher:className="com.google.android.apps.messaging.ui.ConversationListActivity"
+ launcher:packageName="com.google.android.apps.messaging" />
+
+ <favorite
+ launcher:container="-101"
+ launcher:screen="2"
+ launcher:x="2"
+ launcher:y="0"
+ launcher:className="com.android.vending.AssetBrowserActivity"
+ launcher:packageName="com.android.vending" />
+
+ <favorite
+ launcher:container="-101"
+ launcher:screen="3"
+ launcher:x="3"
+ launcher:y="0"
+ launcher:className="com.google.android.apps.chrome.Main"
+ launcher:packageName="com.android.chrome" />
+
+ <!-- Resolve camera intent if GoogleCamera is not available e.g. on emulator -->
+ <resolve
+ launcher:container="-101"
+ launcher:screen="4"
+ launcher:x="4"
+ launcher:y="0" >
+ <favorite
+ launcher:className="com.android.camera.CameraLauncher"
+ launcher:packageName="com.google.android.GoogleCamera" />
+ <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
+ <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
+ </resolve>
+
+ <!-- Bottom row -->
+ <!-- [space] [space] [space] [space] [space] -->
+
+</favorites>
diff --git a/res/xml/default_workspace_8x3.xml b/res/xml/default_workspace_8x3.xml
new file mode 100644
index 0000000..d17491e
--- /dev/null
+++ b/res/xml/default_workspace_8x3.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<!-- Google-specific version of Launcher3/res/xml/default_workspace.xml -->
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+ <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+ <!-- Dialer Messaging Play Chrome Camera -->
+ <favorite
+ launcher:container="-101"
+ launcher:screen="0"
+ launcher:x="0"
+ launcher:y="0"
+ launcher:className="com.google.android.dialer.extensions.GoogleDialtactsActivity"
+ launcher:packageName="com.google.android.dialer" />
+
+ <favorite
+ launcher:container="-101"
+ launcher:screen="1"
+ launcher:x="1"
+ launcher:y="0"
+ launcher:className="com.google.android.apps.messaging.ui.ConversationListActivity"
+ launcher:packageName="com.google.android.apps.messaging" />
+
+ <favorite
+ launcher:container="-101"
+ launcher:screen="2"
+ launcher:x="2"
+ launcher:y="0"
+ launcher:className="com.android.vending.AssetBrowserActivity"
+ launcher:packageName="com.android.vending" />
+
+ <favorite
+ launcher:container="-101"
+ launcher:screen="3"
+ launcher:x="3"
+ launcher:y="0"
+ launcher:className="com.google.android.apps.chrome.Main"
+ launcher:packageName="com.android.chrome" />
+
+ <!-- Resolve camera intent if GoogleCamera is not available e.g. on emulator -->
+ <resolve
+ launcher:container="-101"
+ launcher:screen="4"
+ launcher:x="4"
+ launcher:y="0" >
+ <favorite
+ launcher:className="com.android.camera.CameraLauncher"
+ launcher:packageName="com.google.android.GoogleCamera" />
+ <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
+ <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
+ </resolve>
+
+ <!-- Bottom row -->
+ <!-- [space] [space] [space] [space] [space] -->
+
+</favorites>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index 1d0dbff..5fac66f 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -207,4 +207,45 @@
</grid-option>
+ <grid-option
+ launcher:name="fixed_landscape_mode"
+ launcher:numFolderRows="2"
+ launcher:numFolderColumns="3"
+ launcher:numFolderRowsLandscape="2"
+ launcher:folderStyle="@style/FolderStyleDefault"
+ launcher:numHotseatIcons="4"
+ launcher:numExtendedHotseatIcons="8"
+ launcher:numAllAppsColumns="8"
+ launcher:numExtendedAllAppsColumns="8"
+ launcher:workspaceSpecsId="@xml/spec_handheld_workspace_3_row"
+ launcher:allAppsSpecsId="@xml/spec_handheld_all_apps_3_row"
+ launcher:folderSpecsId="@xml/spec_handheld_folder_3_row"
+ launcher:hotseatSpecsId="@xml/spec_handheld_hotseat_3_row"
+ launcher:workspaceCellSpecsId="@xml/spec_handheld_workspace_cell_3_row"
+ launcher:allAppsCellSpecsId="@xml/spec_all_apps_cell_match_workspace"
+ launcher:isScalable="true"
+ launcher:inlineQsb="landscape"
+ launcher:devicePaddingId="@xml/paddings_3_row"
+ launcher:gridSizeSpecsId="@xml/spec_col_count_3_row"
+ launcher:isFixedLandscape="true"
+ launcher:defaultLayoutId="@xml/default_workspace_8x3"
+ launcher:deviceCategory="phone">
+
+ <display-option
+ launcher:name="Fixed Landscape"
+ launcher:minWidthDps="387"
+ launcher:minHeightDps="750"
+ launcher:minCellHeight="108"
+ launcher:minCellWidth="61"
+ launcher:borderSpace="16"
+ launcher:horizontalMargin="22"
+ launcher:iconImageSize="57"
+ launcher:iconSizeLandscape="59"
+ launcher:iconTextSize="12"
+ launcher:allAppsBorderSpace="16"
+ launcher:allAppsCellHeight="104"
+ launcher:canBeDefault="true" />
+
+ </grid-option>
+
</profiles>
\ No newline at end of file
diff --git a/res/xml/paddings_3_row.xml b/res/xml/paddings_3_row.xml
new file mode 100644
index 0000000..147c9d1
--- /dev/null
+++ b/res/xml/paddings_3_row.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<device-paddings xmlns:launcher="http://schemas.android.com/apk/res-auto" >
+
+ <device-padding
+ launcher:maxEmptySpace="100dp">
+ <workspaceTopPadding
+ launcher:a="0"
+ launcher:b="0"/>
+ <workspaceBottomPadding
+ launcher:a="0"
+ launcher:b="0"/>
+ <hotseatBottomPadding
+ launcher:a="0"
+ launcher:b="0"/>
+ </device-padding>
+
+ <device-padding
+ launcher:maxEmptySpace="160dp">
+ <workspaceTopPadding
+ launcher:a="0"
+ launcher:b="0"/>
+ <workspaceBottomPadding
+ launcher:a="0"
+ launcher:b="0"/>
+ <hotseatBottomPadding
+ launcher:a="0"
+ launcher:b="0"/>
+ </device-padding>
+
+ <device-padding
+ launcher:maxEmptySpace="9999dp">
+ <workspaceTopPadding
+ launcher:a="0"
+ launcher:b="0"/>
+ <workspaceBottomPadding
+ launcher:a="0"
+ launcher:b="0"/>
+ <hotseatBottomPadding
+ launcher:a="0"
+ launcher:b="0"/>
+ </device-padding>
+</device-paddings>
\ No newline at end of file
diff --git a/res/xml/spec_all_apps_cell_match_workspace.xml b/res/xml/spec_all_apps_cell_match_workspace.xml
new file mode 100644
index 0000000..5843490
--- /dev/null
+++ b/res/xml/spec_all_apps_cell_match_workspace.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<cellSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <specs launcher:maxAspectRatio="@dimen/aspect_ratio_landscape">
+ <cellSpec
+ launcher:dimensionType="height"
+ launcher:maxAvailableSize="9999dp">
+ <iconDrawablePadding launcher:matchWorkspace="true" />
+ <iconSize launcher:matchWorkspace="true" />
+ <iconTextSize launcher:matchWorkspace="true" />
+ </cellSpec>
+ </specs>
+</cellSpecs>
\ No newline at end of file
diff --git a/res/xml/spec_col_count_3_row.xml b/res/xml/spec_col_count_3_row.xml
new file mode 100644
index 0000000..bbde542
--- /dev/null
+++ b/res/xml/spec_col_count_3_row.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+<GridSizeSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <GridSize
+ launcher:numGridColumns="7"
+ launcher:numGridRows="3"
+ launcher:dbFile="launcher_7_by_3.db"
+ launcher:defaultLayoutId="@xml/default_workspace_7x3"
+ launcher:minDeviceWidthPx="0"
+ launcher:minDeviceHeightPx="0"
+ />
+ <GridSize
+ launcher:numGridColumns="8"
+ launcher:numGridRows="3"
+ launcher:dbFile="launcher_8_by_3.db"
+ launcher:defaultLayoutId="@xml/default_workspace_8x3"
+ launcher:minDeviceWidthPx="0"
+ launcher:minDeviceHeightPx="2093"
+ />
+</GridSizeSpecs>
\ No newline at end of file
diff --git a/res/xml/spec_handheld_all_apps_3_row.xml b/res/xml/spec_handheld_all_apps_3_row.xml
new file mode 100644
index 0000000..00b3310
--- /dev/null
+++ b/res/xml/spec_handheld_all_apps_3_row.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<allAppsSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <!-- landscape -->
+ <specs launcher:maxAspectRatio="@dimen/aspect_ratio_landscape">
+ <allAppsSpec
+ launcher:dimensionType="height"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="0dp" />
+ <endPadding launcher:fixedSize="0dp" />
+ <gutter launcher:matchWorkspace="true" />
+ <cellSize launcher:matchWorkspace="true" />
+ </allAppsSpec>
+
+ <allAppsSpec
+ launcher:dimensionType="width"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:ofRemainderSpace="0.5" />
+ <endPadding launcher:ofRemainderSpace="0.5" />
+ <gutter launcher:matchWorkspace="true" />
+ <cellSize launcher:matchWorkspace="true" />
+ </allAppsSpec>
+ </specs>
+</allAppsSpecs>
diff --git a/res/xml/spec_handheld_folder_3_row.xml b/res/xml/spec_handheld_folder_3_row.xml
new file mode 100644
index 0000000..2614d96
--- /dev/null
+++ b/res/xml/spec_handheld_folder_3_row.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Handheld folders 7x3 -->
+<folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <!-- landscape -->
+ <specs launcher:maxAspectRatio="@dimen/aspect_ratio_landscape">
+ <folderSpec launcher:dimensionType="width" launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="16dp" />
+ <endPadding launcher:fixedSize="16dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:fixedSize="66dp" />
+ </folderSpec>
+ <folderSpec launcher:dimensionType="height" launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:fixedSize="16dp" />
+ <!-- mapped to footer height size -->
+ <endPadding launcher:fixedSize="48dp" />
+ <gutter launcher:fixedSize="0dp" />
+ <cellSize launcher:fixedSize="80dp" />
+ </folderSpec>
+ </specs>
+</folderSpecs>
diff --git a/res/xml/spec_handheld_hotseat_3_row.xml b/res/xml/spec_handheld_hotseat_3_row.xml
new file mode 100644
index 0000000..bd47c90
--- /dev/null
+++ b/res/xml/spec_handheld_hotseat_3_row.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<hotseatSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <!-- landscape -->
+ <specs launcher:maxAspectRatio="@dimen/aspect_ratio_landscape">
+ <hotseatSpec
+ launcher:maxAvailableSize="9999dp"
+ launcher:dimensionType="width">
+ <hotseatQsbSpace launcher:fixedSize="0dp" />
+ <edgePadding launcher:fixedSize="0dp" />
+ </hotseatSpec>
+ </specs>
+</hotseatSpecs>
\ No newline at end of file
diff --git a/res/xml/spec_handheld_workspace_3_row.xml b/res/xml/spec_handheld_workspace_3_row.xml
new file mode 100644
index 0000000..8e024d3
--- /dev/null
+++ b/res/xml/spec_handheld_workspace_3_row.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <!-- landscape -->
+ <specs launcher:maxAspectRatio="@dimen/aspect_ratio_landscape">
+ <workspaceSpec
+ launcher:dimensionType="height"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:ofAvailableSpace="0.01" />
+ <endPadding launcher:ofAvailableSpace="0.055" />
+ <gutter launcher:ofAvailableSpace="0.02" />
+ <cellSize launcher:ofRemainderSpace="1" />
+ </workspaceSpec>
+ <workspaceSpec
+ launcher:dimensionType="width"
+ launcher:maxAvailableSize="9999dp">
+ <startPadding launcher:ofAvailableSpace="0.0660867583" />
+ <endPadding launcher:ofAvailableSpace="0.0660867583" />
+ <gutter launcher:ofAvailableSpace="0.0125" />
+ <cellSize launcher:ofRemainderSpace="1" />
+ </workspaceSpec>
+ </specs>
+</workspaceSpecs>
diff --git a/res/xml/spec_handheld_workspace_cell_3_row.xml b/res/xml/spec_handheld_workspace_cell_3_row.xml
new file mode 100644
index 0000000..607dbb9
--- /dev/null
+++ b/res/xml/spec_handheld_workspace_cell_3_row.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<cellSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <!-- portrait TODO: remove portrait specs-->
+ <specs launcher:maxAspectRatio="@dimen/aspect_ratio_portrait">
+ <cellSpec
+ launcher:dimensionType="height"
+ launcher:maxAvailableSize="9999dp">
+ <iconDrawablePadding launcher:fixedSize="0dp" />
+ <iconSize launcher:fixedSize="@dimen/iconSize52dp" />
+ <iconTextSize launcher:fixedSize="12sp" />
+ </cellSpec>
+ </specs>
+ <!-- landscape -->
+ <specs launcher:maxAspectRatio="@dimen/aspect_ratio_landscape">
+ <cellSpec
+ launcher:dimensionType="height"
+ launcher:maxAvailableSize="9999dp">
+ <iconDrawablePadding launcher:fixedSize="4dp" />
+ <iconSize launcher:fixedSize="@dimen/iconSize52dp" />
+ <iconTextSize launcher:fixedSize="12sp" />
+ </cellSpec>
+ </specs>
+</cellSpecs>
\ No newline at end of file
diff --git a/shared/src/com/android/launcher3/testing/shared/TestProtocol.java b/shared/src/com/android/launcher3/testing/shared/TestProtocol.java
index 0583d6d..cdeab95 100644
--- a/shared/src/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/shared/src/com/android/launcher3/testing/shared/TestProtocol.java
@@ -125,6 +125,8 @@
"is-predictive-back-swipe-enabled";
public static final String REQUEST_ENABLE_TASKBAR_NAVBAR_UNIFICATION =
"enable-taskbar-navbar-unification";
+ public static final String REQUEST_TASKBAR_SHOWN_ON_HOME =
+ "taskbar-shown-on-home";
public static final String REQUEST_NUM_ALL_APPS_COLUMNS = "num-all-apps-columns";
public static final String REQUEST_IS_TWO_PANELS = "is-two-panel";
public static final String REQUEST_CELL_LAYOUT_BOARDER_HEIGHT = "cell-layout-boarder-height";
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 753b2e2..b90200b 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -126,7 +126,8 @@
/** Type of popups that should get exclusive accessibility focus. */
public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
& ~TYPE_ALL_APPS_EDU & ~TYPE_TASKBAR_ALL_APPS & ~TYPE_PIN_IME_POPUP
- & ~TYPE_WIDGET_RESIZE_FRAME & ~TYPE_ONE_GRID_MIGRATION_EDU & ~TYPE_ON_BOARD_POPUP;
+ & ~TYPE_WIDGET_RESIZE_FRAME & ~TYPE_ONE_GRID_MIGRATION_EDU & ~TYPE_ON_BOARD_POPUP
+ & ~TYPE_TASKBAR_OVERLAY_PROXY;
// These view all have particular operation associated with swipe down interaction.
public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 6277e41..84c8040 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -481,10 +481,8 @@
public static <T extends BaseActivity> T fromContext(Context context) {
if (context instanceof BaseActivity) {
return (T) context;
- } else if (context instanceof ActivityContextDelegate) {
- return (T) ((ActivityContextDelegate) context).mDelegate;
- } else if (context instanceof ContextWrapper) {
- return fromContext(((ContextWrapper) context).getBaseContext());
+ } else if (context instanceof ContextWrapper cw) {
+ return fromContext(cw.getBaseContext());
} else {
throw new IllegalArgumentException("Cannot find BaseActivity in parent tree");
}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index bd42b2b..250bbe5 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -20,6 +20,9 @@
import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
import static android.text.Layout.Alignment.ALIGN_NORMAL;
+import static com.android.launcher3.BubbleTextView.RunningAppState.RUNNING;
+import static com.android.launcher3.BubbleTextView.RunningAppState.NOT_RUNNING;
+import static com.android.launcher3.BubbleTextView.RunningAppState.MINIMIZED;
import static com.android.launcher3.Flags.enableContrastTiles;
import static com.android.launcher3.Flags.enableCursorHoverStates;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
@@ -208,6 +211,9 @@
private final int mRunningAppIndicatorColor;
private final int mMinimizedAppIndicatorColor;
+ private final String mMinimizedStateDescription;
+ private final String mRunningStateDescription;
+
/**
* Various options for the running state of an app.
*/
@@ -240,6 +246,9 @@
super(context, attrs, defStyle);
mActivity = ActivityContext.lookupContext(context);
FastBitmapDrawable.setFlagHoverEnabled(enableCursorHoverStates());
+ mMinimizedStateDescription = getContext().getString(
+ R.string.app_minimized_state_description);
+ mRunningStateDescription = getContext().getString(R.string.app_running_state_description);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.BubbleTextView, defStyle, 0);
@@ -432,6 +441,19 @@
invalidate();
}
+ /**
+ * Returns state description of this icon.
+ */
+ public String getIconStateDescription() {
+ if (mRunningAppState == MINIMIZED) {
+ return mMinimizedStateDescription;
+ } else if (mRunningAppState == RUNNING) {
+ return mRunningStateDescription;
+ } else {
+ return "";
+ }
+ }
+
protected void setItemInfo(ItemInfoWithIcon itemInfo) {
setTag(itemInfo);
}
@@ -768,13 +790,13 @@
/** Draws a line under the app icon if this is representing a running app in Desktop Mode. */
protected void drawRunningAppIndicatorIfNecessary(Canvas canvas) {
- if (mRunningAppState == RunningAppState.NOT_RUNNING || mDisplay != DISPLAY_TASKBAR) {
+ if (mRunningAppState == NOT_RUNNING || mDisplay != DISPLAY_TASKBAR) {
return;
}
getIconBounds(mRunningAppIconBounds);
Utilities.scaleRectAboutCenter(mRunningAppIconBounds, ICON_VISIBLE_AREA_FACTOR);
- final boolean isMinimized = mRunningAppState == RunningAppState.MINIMIZED;
+ final boolean isMinimized = mRunningAppState == MINIMIZED;
final int indicatorTop = mRunningAppIconBounds.bottom + mRunningAppIndicatorTopMargin;
final int indicatorWidth =
isMinimized ? mMinimizedAppIndicatorWidth : mRunningAppIndicatorWidth;
@@ -961,11 +983,14 @@
@Override
public void setTextColor(ColorStateList colors) {
- mTextColor = (shouldDrawAppContrastTile() && !TextUtils.isEmpty(getText()))
- ? PillColorProvider.getInstance(
- getContext()).getAppTitleTextPaint().getColor()
- : colors.getDefaultColor();
- mTextColorStateList = colors;
+ if (shouldDrawAppContrastTile()) {
+ mTextColor = PillColorProvider.getInstance(
+ getContext()).getAppTitleTextPaint().getColor();
+ } else {
+ mTextColor = colors.getDefaultColor();
+ mTextColorStateList = colors;
+ }
+
if (Float.compare(mTextAlpha, 1) == 0) {
super.setTextColor(colors);
} else {
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 2d99510..7f0c7b5 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.Rect;
+import android.view.View;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
import com.android.launcher3.dragndrop.DragOptions;
@@ -145,4 +146,9 @@
// These methods are implemented in Views
void getHitRectRelativeToDragLayer(Rect outRect);
+
+ /** Returns the drop target view. By default, the implementor class is cast to the view. */
+ default View getDropView() {
+ return (View) this;
+ }
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 3edba99..5c1a528 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -185,7 +185,6 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.debug.TestEventEmitter;
import com.android.launcher3.debug.TestEventEmitter.TestEvent;
-import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
@@ -237,6 +236,7 @@
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInflater;
import com.android.launcher3.util.KeyboardShortcutsDelegate;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.PackageUserKey;
@@ -544,8 +544,8 @@
mItemInflater = new ItemInflater<>(this, mAppWidgetHolder, getItemOnClickListener(),
mFocusHandler, new CellLayout(mWorkspace.getContext(), mWorkspace));
- mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
- mWidgetPickerDataProvider = new WidgetPickerDataProvider();
+ mPopupDataProvider = new PopupDataProvider(this);
+ mWidgetPickerDataProvider = new WidgetPickerDataProvider(this);
PillColorProvider.getInstance(mWorkspace.getContext()).registerObserver();
boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
@@ -1598,11 +1598,6 @@
private final ScreenOnListener mScreenOnListener = this::onScreenOnChanged;
- private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- mWorkspace.updateNotificationDots(updatedDots);
- mAppsView.getAppsStore().updateNotificationDots(updatedDots);
- }
-
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -1674,7 +1669,7 @@
} else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
showAllAppsFromIntent(alreadyOnHome);
} else if (INTENT_ACTION_ALL_APPS_TOGGLE.equals(intent.getAction())) {
- toggleAllAppsSearch(alreadyOnHome);
+ toggleAllApps(alreadyOnHome, true);
} else if (Intent.ACTION_SHOW_WORK_APPS.equals(intent.getAction())) {
showAllAppsWithSelectedTabFromIntent(alreadyOnHome,
ActivityAllAppsContainerView.AdapterHolder.WORK);
@@ -1688,12 +1683,15 @@
// Overridden
}
- /** Toggles Launcher All Apps with keyboard ready for search. */
- public void toggleAllAppsSearch() {
- toggleAllAppsSearch(/* alreadyOnHome= */ true);
+ /**
+ * Toggles Launcher All Apps.
+ * @param focusSearch Indicates whether to make All Apps keyboard ready for search.
+ */
+ public void toggleAllApps(boolean focusSearch) {
+ toggleAllApps(/* alreadyOnHome= */ true, focusSearch);
}
- protected void toggleAllAppsSearch(boolean alreadyOnHome) {
+ private void toggleAllApps(boolean alreadyOnHome, boolean focusSearch) {
if (getStateManager().isInStableState(ALL_APPS)) {
getStateManager().goToState(NORMAL, alreadyOnHome);
} else {
@@ -1705,7 +1703,8 @@
new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
- if (mAppsView.getSearchUiManager().getEditText() != null) {
+ if (focusSearch
+ && mAppsView.getSearchUiManager().getEditText() != null) {
mAppsView.getSearchUiManager().getEditText().requestFocus();
}
}
@@ -1804,6 +1803,7 @@
mAppWidgetHolder.stopListening();
mAppWidgetHolder.destroy();
+ mWidgetPickerDataProvider.destroy();
TextKeyListener.getInstance().release();
mModelCallbacks.clearPendingBinds();
@@ -2630,9 +2630,8 @@
* See {@code LauncherBindingDelegate}
*/
@Override
- public void bindAllWidgets(@NonNull final List<WidgetsListBaseEntry> allWidgets,
- @NonNull final List<WidgetsListBaseEntry> defaultWidgets) {
- mModelCallbacks.bindAllWidgets(allWidgets, defaultWidgets);
+ public void bindAllWidgets(@NonNull final List<WidgetsListBaseEntry> allWidgets) {
+ mModelCallbacks.bindAllWidgets(allWidgets);
}
@Override
@@ -2948,11 +2947,6 @@
return mModelCallbacks.getWorkspaceLoading();
}
- @Override
- public boolean isBindingItems() {
- return isWorkspaceLoading();
- }
-
/**
* Returns true if a touch interaction is in progress
*/
@@ -3027,11 +3021,6 @@
return mWidgetPickerDataProvider;
}
- @Override
- public DotInfo getDotInfoForItem(ItemInfo info) {
- return mPopupDataProvider.getDotInfoForItem(info);
- }
-
@NonNull
public LauncherOverlayManager getOverlayManager() {
return mOverlayManager;
@@ -3046,6 +3035,12 @@
return mDragLayer;
}
+ @NonNull
+ @Override
+ public LauncherBindableItemsContainer getContent() {
+ return mWorkspace;
+ }
+
@Override
public ActivityAllAppsContainerView<Launcher> getAppsView() {
return mAppsView;
diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt
index 892a218..add0ad8 100644
--- a/src/com/android/launcher3/LauncherModel.kt
+++ b/src/com/android/launcher3/LauncherModel.kt
@@ -43,7 +43,6 @@
import com.android.launcher3.model.ReloadStringCacheTask
import com.android.launcher3.model.ShortcutsChangedTask
import com.android.launcher3.model.UserLockStateChangedTask
-import com.android.launcher3.model.WidgetsFilterDataProvider
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.pm.UserCache
@@ -82,8 +81,6 @@
val modelDelegate: ModelDelegate,
) {
- private val widgetsFilterDataProvider = WidgetsFilterDataProvider.newInstance(context)
-
private val mCallbacksList = ArrayList<BgDataModel.Callbacks>(1)
// < only access in worker thread >
@@ -151,11 +148,6 @@
owner: BgDataModel.Callbacks?,
) = ModelWriter(context, this, mBgDataModel, verifyChanges, cellPosMapper, owner)
- /** Returns the [WidgetsFilterDataProvider] that manages widget filters. */
- fun getWidgetsFilterDataProvider(): WidgetsFilterDataProvider {
- return widgetsFilterDataProvider
- }
-
/** Called when the icon for an app changes, outside of package event */
@WorkerThread
fun onAppIconChanged(packageName: String, user: UserHandle) {
@@ -176,10 +168,7 @@
/** Called when the model is destroyed */
fun destroy() {
mModelDestroyed = true
- MODEL_EXECUTOR.execute {
- modelDelegate.destroy()
- widgetsFilterDataProvider.destroy()
- }
+ MODEL_EXECUTOR.execute { modelDelegate.destroy() }
}
fun reloadStringCache() {
@@ -335,7 +324,6 @@
mBgDataModel,
this.modelDelegate,
launcherBinder,
- widgetsFilterDataProvider,
)
mLoaderTask = task
@@ -442,14 +430,6 @@
}
}
- /** Called when the widget filters are refreshed and available to bind to the model. */
- fun onWidgetFiltersLoaded() {
- enqueueModelUpdateTask { taskController, dataModel, _ ->
- dataModel.widgetsModel.updateWidgetFilters(widgetsFilterDataProvider)
- taskController.bindUpdatedWidgets(dataModel)
- }
- }
-
fun enqueueModelUpdateTask(task: ModelUpdateTask) {
if (mModelDestroyed) {
return
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 2a5cd63..7a04b0f 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -75,18 +75,18 @@
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
private fun <T> getInner(item: Item, default: T): T {
val sp = getSharedPrefs(item)
-
- return when (item.type) {
- String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
- Boolean::class.java,
- java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
- Int::class.java,
- java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int)
- Float::class.java,
- java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
- Long::class.java,
- java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long)
- Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
+ return when {
+ item.type == String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
+ item.type == Boolean::class.java || item.type == java.lang.Boolean::class.java ->
+ sp.getBoolean(item.sharedPrefKey, default as Boolean)
+ item.type == Int::class.java || item.type == java.lang.Integer::class.java ->
+ sp.getInt(item.sharedPrefKey, default as Int)
+ item.type == Float::class.java || item.type == java.lang.Float::class.java ->
+ sp.getFloat(item.sharedPrefKey, default as Float)
+ item.type == Long::class.java || item.type == java.lang.Long::class.java ->
+ sp.getLong(item.sharedPrefKey, default as Long)
+ Set::class.java.isAssignableFrom(item.type) ->
+ sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
else ->
throw IllegalArgumentException(
"item type: ${item.type}" + " is not compatible with sharedPref methods"
@@ -147,17 +147,18 @@
item: Item,
value: Any?,
): SharedPreferences.Editor =
- when (item.type) {
- String::class.java -> putString(item.sharedPrefKey, value as? String)
- Boolean::class.java,
- java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
- Int::class.java,
- java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int)
- Float::class.java,
- java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
- Long::class.java,
- java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
- Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>)
+ when {
+ item.type == String::class.java -> putString(item.sharedPrefKey, value as? String)
+ item.type == Boolean::class.java || item.type == java.lang.Boolean::class.java ->
+ putBoolean(item.sharedPrefKey, value as Boolean)
+ item.type == Int::class.java || item.type == java.lang.Integer::class.java ->
+ putInt(item.sharedPrefKey, value as Int)
+ item.type == Float::class.java || item.type == java.lang.Float::class.java ->
+ putFloat(item.sharedPrefKey, value as Float)
+ item.type == Long::class.java || item.type == java.lang.Long::class.java ->
+ putLong(item.sharedPrefKey, value as Long)
+ Set::class.java.isAssignableFrom(item.type) ->
+ putStringSet(item.sharedPrefKey, value as? Set<String>)
else ->
throw IllegalArgumentException(
"item type: ${item.type} is not compatible with sharedPref methods"
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index d01f35d..32b47d0 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -234,11 +234,8 @@
PopupContainerWithArrow.dismissInvalidPopup(launcher)
}
- override fun bindAllWidgets(
- allWidgets: List<WidgetsListBaseEntry>,
- defaultWidgets: List<WidgetsListBaseEntry>,
- ) {
- launcher.widgetPickerDataProvider.setWidgets(allWidgets, defaultWidgets)
+ override fun bindAllWidgets(allWidgets: List<WidgetsListBaseEntry>) {
+ launcher.widgetPickerDataProvider.setWidgets(allWidgets)
}
/** Returns the ids of the workspaces to bind. */
diff --git a/src/com/android/launcher3/PillColorPorovider.kt b/src/com/android/launcher3/PillColorPorovider.kt
index 347c5d6..e7f37bd 100644
--- a/src/com/android/launcher3/PillColorPorovider.kt
+++ b/src/com/android/launcher3/PillColorPorovider.kt
@@ -58,13 +58,8 @@
}
fun setup() {
- appTitlePillPaint.color =
- context.resources.getColor(
- R.color.material_color_surface_container_lowest,
- context.theme,
- )
- appTitleTextPaint.color =
- context.resources.getColor(R.color.material_color_on_surface, context.theme)
+ appTitlePillPaint.color = context.getColor(R.color.materialColorSurfaceContainer)
+ appTitleTextPaint.color = context.getColor(R.color.materialColorOnSurface)
isMatchaEnabledInternal = Settings.Secure.getInt(context.contentResolver, MATCHA_SETTING, 0)
isMatchaEnabled = isMatchaEnabledInternal != 0
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 94ff441..1b5e2e6 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -83,7 +83,6 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.debug.TestEventEmitter;
import com.android.launcher3.debug.TestEventEmitter.TestEvent;
-import com.android.launcher3.dot.FolderDotInfo;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
@@ -119,7 +118,6 @@
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.OverlayEdgeEffect;
-import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.WallpaperOffsetInterpolator;
@@ -893,6 +891,9 @@
mScreenOrder.removeValue(extraEmptyPageId);
});
+ // Since we removed some screens, before moving to next page, update the state
+ // description with correct page numbers.
+ updateAccessibilityViewPageDescription();
setCurrentPage(getNextPage());
// Update the page indicator to reflect the removed page.
@@ -1118,6 +1119,9 @@
if (pageShift >= 0) {
setCurrentPage(currentPage - pageShift);
}
+
+ // Now that we have removed some pages, ensure state description is up to date.
+ updateAccessibilityViewPageDescription();
}
/**
@@ -3419,38 +3423,6 @@
return null;
}
- public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- final PackageUserKey packageUserKey = new PackageUserKey(null, null);
- Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
- || updatedDots.test(packageUserKey);
-
- ItemOperator op = (info, v) -> {
- if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
- if (matcher.test(info)) {
- ((BubbleTextView) v).applyDotState(info, true /* animate */);
- }
- } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
- FolderInfo fi = (FolderInfo) info;
- if (fi.anyMatch(matcher)) {
- FolderDotInfo folderDotInfo = new FolderDotInfo();
- for (ItemInfo si : fi.getContents()) {
- folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
- }
- ((FolderIcon) v).setDotInfo(folderDotInfo);
- }
- }
-
- // process all the shortcuts
- return false;
- };
-
- mapOverItems(op);
- Folder folder = Folder.getOpen(mLauncher);
- if (folder != null) {
- folder.iterateOverItems(op);
- }
- }
-
/**
* Remove workspace icons & widget information related to items in matcher.
*
@@ -3513,6 +3485,18 @@
protected void announcePageForAccessibility() {
// Talkback focuses on AccessibilityActionView by default, so we need to modify the state
// description there in order for the change in page scroll to be announced.
+ updateAccessibilityViewPageDescription();
+ }
+
+ /**
+ * Updates the state description that is set on the accessibility actions view for the
+ * workspace.
+ * <p>The updated value is called out when talkback focuses on the view and is not disruptive.
+ * </p>
+ */
+ protected void updateAccessibilityViewPageDescription() {
+ // Set the state description on accessibility action view so that when it is focused,
+ // talkback describes the correct state of home screen pages.
ViewCompat.setStateDescription(mLauncher.getAccessibilityActionView(),
getCurrentPageDescription());
}
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 150761f..06643d3 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -28,6 +28,7 @@
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.LauncherIcons.IconPool;
import com.android.launcher3.model.ItemInstallQueue;
+import com.android.launcher3.model.WidgetsFilterDataProvider;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.ApiWrapper;
@@ -84,6 +85,7 @@
RemoveAnimationSettingsTracker getRemoveAnimationSettingsTracker();
LauncherAppState getLauncherAppState();
GridCustomizationsProxy getGridCustomizationsProxy();
+ WidgetsFilterDataProvider getWidgetsFilterDataProvider();
/** Builder for LauncherBaseAppComponent. */
interface Builder {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index c50c008..613b430 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -558,7 +558,7 @@
target.getHitRectRelativeToDragLayer(r);
if (r.contains(x, y)) {
- mActivity.getDragLayer().mapCoordInSelfToDescendant((View) target,
+ mActivity.getDragLayer().mapCoordInSelfToDescendant(target.getDropView(),
mCoordinatesTemp);
mDragObject.x = mCoordinatesTemp[0];
mDragObject.y = mCoordinatesTemp[1];
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index cf5150a..36dad89 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -53,8 +53,18 @@
} else if (index >= MAX_NUM_ITEMS_IN_PREVIEW) {
// Items beyond those displayed in the preview are animated to the center
mTmpPoint[0] = mTmpPoint[1] = mAvailableSpace / 2 - (mIconSize * totalScale) / 2;
- } else {
- getPosition(index, curNumItems, mTmpPoint);
+ } else if (index == 0) {
+ // top left
+ getGridPosition(0, 0, mTmpPoint);
+ } else if (index == 1) {
+ // top right
+ getGridPosition(0, 1, mTmpPoint);
+ } else if (index == 2) {
+ // bottom left
+ getGridPosition(1, 0, mTmpPoint);
+ } else if (index == 3) {
+ // bottom right
+ getGridPosition(1, 1, mTmpPoint);
}
transX = mTmpPoint[0];
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index fb48a4d..0ce7249 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -65,6 +65,7 @@
import android.widget.TextView;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.content.res.ResourcesCompat;
@@ -261,7 +262,7 @@
@Nullable
private KeyboardInsetAnimationCallback mKeyboardInsetAnimationCallback;
- private GradientDrawable mBackground;
+ private final @NonNull GradientDrawable mBackground;
/**
* Used to inflate the Workspace from XML.
@@ -283,6 +284,10 @@
// click).
setFocusableInTouchMode(true);
+ mBackground = (GradientDrawable) Objects.requireNonNull(
+ ResourcesCompat.getDrawable(getResources(),
+ R.drawable.round_rect_folder, getContext().getTheme()));
+ mBackground.setCallback(this);
}
@Override
@@ -296,9 +301,6 @@
final DeviceProfile dp = mActivityContext.getDeviceProfile();
final int paddingLeftRight = dp.folderContentPaddingLeftRight;
- mBackground = (GradientDrawable) ResourcesCompat.getDrawable(getResources(),
- R.drawable.round_rect_folder, getContext().getTheme());
-
mContent = findViewById(R.id.folder_content);
mContent.setPadding(paddingLeftRight, dp.folderContentPaddingTop, paddingLeftRight, 0);
mContent.setFolder(this);
@@ -345,6 +347,11 @@
return true;
}
+ @Override
+ protected boolean verifyDrawable(@NonNull Drawable who) {
+ return super.verifyDrawable(who) || (who == mBackground);
+ }
+
void callBeginDragShared(View v, DragOptions options) {
mLauncherDelegate.beginDragShared(v, this, options);
}
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 740b87b..3836f7d 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -94,7 +94,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.SandboxContext;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.views.ActivityContext;
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 8af18f5..d425f03 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -88,8 +88,9 @@
private static final String KEY_COLOR_RESOURCE_IDS = "color_resource_ids";
private static final String KEY_COLOR_VALUES = "color_values";
private static final String KEY_DARK_MODE = "use_dark_mode";
+ public static final String KEY_SKIP_ANIMATIONS = "skip_animations";
- private Context mContext;
+ private final Context mContext;
private SparseIntArray mPreviewColorOverride;
private String mGridName;
private String mShapeKey;
@@ -102,6 +103,7 @@
private final IBinder mHostToken;
private final int mWidth;
private final int mHeight;
+ private final boolean mSkipAnimations;
private final int mDisplayId;
private final Display mDisplay;
private final WallpaperColors mWallpaperColors;
@@ -128,6 +130,7 @@
mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
mWidth = bundle.getInt(KEY_VIEW_WIDTH);
mHeight = bundle.getInt(KEY_VIEW_HEIGHT);
+ mSkipAnimations = bundle.getBoolean(KEY_SKIP_ANIMATIONS, false);
mDisplayId = bundle.getInt(KEY_DISPLAY_ID);
mDisplay = context.getSystemService(DisplayManager.class)
.getDisplay(mDisplayId);
@@ -343,9 +346,7 @@
bgModel,
LauncherAppState.getInstance(previewContext).getModel().getModelDelegate(),
new BaseLauncherBinder(LauncherAppState.getInstance(previewContext), bgModel,
- /* bgAllAppsList= */ null, new Callbacks[0]),
- LauncherAppState.getInstance(
- previewContext).getModel().getWidgetsFilterDataProvider()) {
+ /* bgAllAppsList= */ null, new Callbacks[0])) {
@Override
public void run() {
@@ -421,7 +422,7 @@
if (!Flags.newCustomizationPickerUi()) {
- view.setAlpha(0);
+ view.setAlpha(mSkipAnimations ? 1 : 0);
view.animate().alpha(1)
.setInterpolator(new AccelerateDecelerateInterpolator())
.setDuration(FADE_IN_ANIMATION_DURATION)
@@ -442,7 +443,7 @@
);
mViewRoot.setLayoutParams(layoutParams);
mViewRoot.addView(view);
- mViewRoot.setAlpha(0);
+ mViewRoot.setAlpha(mSkipAnimations ? 1 : 0);
mViewRoot.animate().alpha(1)
.setInterpolator(new AccelerateDecelerateInterpolator())
.setDuration(FADE_IN_ANIMATION_DURATION)
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index a2ca6b6..262bf67 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -26,8 +26,6 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static java.util.Collections.emptyList;
-
import android.os.Trace;
import android.util.Log;
import android.util.Pair;
@@ -45,7 +43,6 @@
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -165,17 +162,10 @@
if (!WIDGETS_ENABLED) {
return;
}
- Map<PackageItemInfo, List<WidgetItem>>
- widgetsByPackageItem = mBgDataModel.widgetsModel.getWidgetsByPackageItemForPicker();
List<WidgetsListBaseEntry> widgets = new WidgetsListBaseEntriesBuilder(mApp.getContext())
- .build(widgetsByPackageItem);
- Predicate<WidgetItem> filter = mBgDataModel.widgetsModel.getDefaultWidgetsFilter();
- List<WidgetsListBaseEntry> defaultWidgets =
- filter != null ? new WidgetsListBaseEntriesBuilder(
- mApp.getContext()).build(widgetsByPackageItem,
- mBgDataModel.widgetsModel.getDefaultWidgetsFilter()) : emptyList();
+ .build(mBgDataModel.widgetsModel.getWidgetsByPackageItemForPicker());
- executeCallbacksTask(c -> c.bindAllWidgets(widgets, defaultWidgets), mUiExecutor);
+ executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
}
/**
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index eab28b7..d9eccaf 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -431,9 +431,8 @@
/**
* Binds the app widgets to the providers that share widgets with the UI.
*/
- default void bindAllWidgets(@NonNull List<WidgetsListBaseEntry> widgets,
- @NonNull List<WidgetsListBaseEntry> defaultWidgets) {
- }
+ default void bindAllWidgets(@NonNull List<WidgetsListBaseEntry> widgets) { }
+
default void bindSmartspaceWidget() { }
/** Called when workspace has been bound. */
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index d44b289..fb1ebaf 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -20,7 +20,6 @@
import static com.android.launcher3.Flags.enableLauncherBrMetricsFixed;
import static com.android.launcher3.Flags.enableSmartspaceAsAWidget;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
-import static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker;
import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
@@ -148,7 +147,6 @@
private final UserManager mUserManager;
private final UserCache mUserCache;
private final PackageManagerHelper mPmHelper;
- private final WidgetsFilterDataProvider mWidgetsFilterDataProvider;
private final InstallSessionHelper mSessionHelper;
private final IconCache mIconCache;
@@ -167,16 +165,13 @@
private String mDbName;
public LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel,
- ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder,
- @NonNull WidgetsFilterDataProvider widgetsFilterDataProvider) {
- this(app, bgAllAppsList, bgModel, modelDelegate, launcherBinder, widgetsFilterDataProvider,
- new UserManagerState());
+ ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder) {
+ this(app, bgAllAppsList, bgModel, modelDelegate, launcherBinder, new UserManagerState());
}
@VisibleForTesting
LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel,
ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder,
- WidgetsFilterDataProvider widgetsFilterDataProvider,
UserManagerState userManagerState) {
mApp = app;
mBgAllAppsList = bgAllAppsList;
@@ -191,7 +186,6 @@
mIconCache = mApp.getIconCache();
mUserManagerState = userManagerState;
mInstallingPkgsCached = null;
- mWidgetsFilterDataProvider = widgetsFilterDataProvider;
}
protected synchronized void waitForIdle() {
@@ -348,13 +342,6 @@
// fourth step
WidgetsModel widgetsModel = mBgDataModel.widgetsModel;
- if (enableTieredWidgetsByDefaultInPicker()) {
- // Begin periodic refresh of filters
- mWidgetsFilterDataProvider.initPeriodicDataRefresh(
- mApp.getModel()::onWidgetFiltersLoaded);
- // And, update model with currently cached data.
- widgetsModel.updateWidgetFilters(mWidgetsFilterDataProvider);
- }
List<CachedObject> allWidgetsList = widgetsModel.update(mApp, /*packageUser=*/null);
logASplit("load widgets finished");
diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt
index 6e3e35e..5566482 100644
--- a/src/com/android/launcher3/model/ModelTaskController.kt
+++ b/src/com/android/launcher3/model/ModelTaskController.kt
@@ -77,19 +77,10 @@
}
fun bindUpdatedWidgets(dataModel: BgDataModel) {
- val widgetsByPackageItem = dataModel.widgetsModel.widgetsByPackageItemForPicker
- val allWidgets = WidgetsListBaseEntriesBuilder(app.context).build(widgetsByPackageItem)
-
- val defaultWidgetsFilter = dataModel.widgetsModel.defaultWidgetsFilter
- val defaultWidgets =
- if (defaultWidgetsFilter != null) {
- WidgetsListBaseEntriesBuilder(app.context)
- .build(widgetsByPackageItem, defaultWidgetsFilter)
- } else {
- emptyList()
- }
-
- scheduleCallbackTask { it.bindAllWidgets(allWidgets, defaultWidgets) }
+ val allWidgets =
+ WidgetsListBaseEntriesBuilder(app.context)
+ .build(dataModel.widgetsModel.widgetsByPackageItemForPicker)
+ scheduleCallbackTask { it.bindAllWidgets(allWidgets) }
}
fun deleteAndBindComponentsRemoved(matcher: Predicate<ItemInfo?>, reason: String?) {
diff --git a/src/com/android/launcher3/model/WidgetsFilterDataProvider.kt b/src/com/android/launcher3/model/WidgetsFilterDataProvider.kt
index 0571de3..90d6fb2 100644
--- a/src/com/android/launcher3/model/WidgetsFilterDataProvider.kt
+++ b/src/com/android/launcher3/model/WidgetsFilterDataProvider.kt
@@ -16,55 +16,38 @@
package com.android.launcher3.model
-import android.content.Context
-import androidx.annotation.WorkerThread
-import com.android.launcher3.R
-import com.android.launcher3.util.ResourceBasedOverride
+import com.android.launcher3.dagger.LauncherAppSingleton
import java.util.function.Predicate
+import javax.inject.Inject
/** Helper for the widgets model to load the filters that can be applied to available widgets. */
-open class WidgetsFilterDataProvider(val context: Context) : ResourceBasedOverride {
+@LauncherAppSingleton
+open class WidgetsFilterDataProvider @Inject constructor() {
+
+ /** Filter that should be applied to the widget predictions */
+ open val predictedWidgetsFilter: Predicate<WidgetItem>? = null
+
/**
- * Start regular periodic refresh of widget filtering data starting now (if not started
- * already).
+ * Filter that should be applied to the widgets list to see which widgets can be shown by
+ * default.
*/
- @WorkerThread
- open fun initPeriodicDataRefresh(callback: WidgetsFilterLoadedCallback? = null) {
- // no-op
+ open val defaultWidgetsFilter: Predicate<WidgetItem>? = null
+
+ protected val listeners = mutableListOf<WidgetsFilterLoadedCallback>()
+
+ /** Adds a callback for listening to filter changes */
+ fun addFilterChangeCallback(callback: WidgetsFilterLoadedCallback) {
+ listeners.add(callback)
}
- /**
- * Returns a filter that should be applied to the widget predictions.
- *
- * @return null if no filter needs to be applied
- */
- @WorkerThread open fun getPredictedWidgetsFilter(): Predicate<WidgetItem>? = null
-
- /**
- * Returns a filter that should be applied to the widgets list to see which widgets can be shown
- * by default.
- *
- * @return null if no separate "default" list is supported
- */
- @WorkerThread open fun getDefaultWidgetsFilter(): Predicate<WidgetItem>? = null
-
- /** Called when filter data provider is no longer needed. */
- open fun destroy() {}
-
- companion object {
- /** Returns a new instance of the [WidgetsFilterDataProvider] based on resource override. */
- fun newInstance(context: Context?): WidgetsFilterDataProvider {
- return ResourceBasedOverride.Overrides.getObject(
- WidgetsFilterDataProvider::class.java,
- context,
- R.string.widgets_filter_data_provider_class,
- )
- }
+ /** Removes a previously added callback */
+ fun removeFilterChangeCallback(callback: WidgetsFilterLoadedCallback) {
+ listeners.remove(callback)
}
-}
-/** Interface for the model callback to be invoked when filters are loaded. */
-interface WidgetsFilterLoadedCallback {
- /** Method called back when widget filters are loaded */
- fun onWidgetsFilterLoaded()
+ /** Interface for the model callback to be invoked when filters are loaded. */
+ interface WidgetsFilterLoadedCallback {
+ /** Method called back when widget filters are loaded */
+ fun onWidgetsFilterLoaded()
+ }
}
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index ab960d8..52b142d 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -19,8 +19,6 @@
import android.util.Log;
import android.util.Pair;
-import androidx.annotation.AnyThread;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.ArrayMap;
@@ -68,8 +66,6 @@
/* Map of widgets and shortcuts that are tracked per package. */
private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsByPackageItem = new HashMap<>();
- @Nullable private Predicate<WidgetItem> mDefaultWidgetsFilter = null;
- @Nullable private Predicate<WidgetItem> mPredictedWidgetsFilter = null;
@Nullable private WidgetValidityCheckForPicker mWidgetValidityCheckForPicker = null;
/**
@@ -129,37 +125,6 @@
}
/**
- * Returns widget filter that can be applied to {@link WidgetItem}s to check if they can be
- * shown in the default widgets list.
- * <p>Returns null if filtering isn't available</p>
- */
- @AnyThread
- public @Nullable Predicate<WidgetItem> getDefaultWidgetsFilter() {
- return mDefaultWidgetsFilter;
- }
-
- /**
- * Returns widget filter that can be applied to {@link WidgetItem}s to check if they can be
- * part of widget predictions.
- * <p>Returns null if filter isn't available</p>
- */
- @AnyThread
- public @Nullable Predicate<WidgetItem> getPredictedWidgetsFilter() {
- return mPredictedWidgetsFilter;
- }
-
- /**
- * Updates model with latest filter data in cache.
- */
- public void updateWidgetFilters(@NonNull WidgetsFilterDataProvider widgetsFilterDataProvider) {
- if (!WIDGETS_ENABLED) {
- return;
- }
- mDefaultWidgetsFilter = widgetsFilterDataProvider.getDefaultWidgetsFilter();
- mPredictedWidgetsFilter = widgetsFilterDataProvider.getPredictedWidgetsFilter();
- }
-
- /**
* @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
* only widgets and shortcuts associated with the package/user are.
*/
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index a691e45..37f5189 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -16,6 +16,7 @@
package com.android.launcher3.pageindicators;
+import static com.android.launcher3.Flags.enableLauncherVisualRefresh;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
import android.animation.Animator;
@@ -57,8 +58,8 @@
public class PageIndicatorDots extends View implements Insettable, PageIndicator {
private static final float SHIFT_PER_ANIMATION = 0.5f;
- private static final float SHIFT_THRESHOLD = 0.1f;
- private static final long ANIMATION_DURATION = 150;
+ private static final float SHIFT_THRESHOLD = (enableLauncherVisualRefresh() ? 0.5f : 0.2f);
+ private static final long ANIMATION_DURATION = (enableLauncherVisualRefresh() ? 200 : 150);
private static final int PAGINATION_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay();
private static final int PAGINATION_FADE_IN_DURATION = 83;
private static final int PAGINATION_FADE_OUT_DURATION = 167;
@@ -78,6 +79,7 @@
// This value approximately overshoots to 1.5 times the original size.
private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f;
+ // This is used to optimize the onDraw method by not constructing a new RectF each draw.
private static final RectF sTempRect = new RectF();
private static final FloatProperty<PageIndicatorDots> CURRENT_POSITION =
@@ -93,7 +95,7 @@
obj.invalidate();
obj.invalidateOutline();
}
- };
+ };
private static final IntProperty<PageIndicatorDots> PAGINATION_ALPHA =
new IntProperty<PageIndicatorDots>("pagination_alpha") {
@@ -111,6 +113,7 @@
private final Handler mDelayedPaginationFadeHandler = new Handler(Looper.getMainLooper());
private final float mDotRadius;
+ private final float mGapWidth;
private final float mCircleGap;
private final boolean mIsRtl;
@@ -130,6 +133,7 @@
* 1.0 => Active dot is at position 1
*/
private float mCurrentPosition;
+ private int mLastPosition;
private float mFinalPosition;
private boolean mIsScrollPaused;
@VisibleForTesting
@@ -157,7 +161,10 @@
mPaginationPaint.setStyle(Style.FILL);
mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.pageIndicatorDotColor));
mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
- mCircleGap = DOT_GAP_FACTOR * mDotRadius;
+ mGapWidth = getResources().getDimension(R.dimen.page_indicator_gap_width);
+ mCircleGap = (enableLauncherVisualRefresh())
+ ? mDotRadius * 2 + mGapWidth
+ : DOT_GAP_FACTOR * mDotRadius;
setOutlineProvider(new MyOutlineProver());
mIsRtl = Utilities.isRtl(getResources());
}
@@ -188,29 +195,40 @@
mTotalScroll = totalScroll;
- int scrollPerPage = totalScroll / (mNumPages - 1);
- int pageToLeft = scrollPerPage == 0 ? 0 : currentScroll / scrollPerPage;
- int pageToLeftScroll = pageToLeft * scrollPerPage;
- int pageToRightScroll = pageToLeftScroll + scrollPerPage;
+ if (enableLauncherVisualRefresh()) {
+ float scrollPerPage = (float) totalScroll / (mNumPages - 1);
+ float position = currentScroll / scrollPerPage;
+ animateToPosition(Math.round(position));
- float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
- if (currentScroll < pageToLeftScroll + scrollThreshold) {
- // scroll is within the left page's threshold
- animateToPosition(pageToLeft);
- if (mShouldAutoHide) {
- hideAfterDelay();
- }
- } else if (currentScroll > pageToRightScroll - scrollThreshold) {
- // scroll is far enough from left page to go to the right page
- animateToPosition(pageToLeft + 1);
- if (mShouldAutoHide) {
+ float delta = Math.abs((int) position - position);
+ if (mShouldAutoHide && (delta < 0.1 || delta > 0.9)) {
hideAfterDelay();
}
} else {
- // scroll is between left and right page
- animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
- if (mShouldAutoHide) {
- mDelayedPaginationFadeHandler.removeCallbacksAndMessages(null);
+ int scrollPerPage = totalScroll / (mNumPages - 1);
+ int pageToLeft = scrollPerPage == 0 ? 0 : currentScroll / scrollPerPage;
+ int pageToLeftScroll = pageToLeft * scrollPerPage;
+ int pageToRightScroll = pageToLeftScroll + scrollPerPage;
+
+ float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
+ if (currentScroll < pageToLeftScroll + scrollThreshold) {
+ // scroll is within the left page's threshold
+ animateToPosition(pageToLeft);
+ if (mShouldAutoHide) {
+ hideAfterDelay();
+ }
+ } else if (currentScroll > pageToRightScroll - scrollThreshold) {
+ // scroll is far enough from left page to go to the right page
+ animateToPosition(pageToLeft + 1);
+ if (mShouldAutoHide) {
+ hideAfterDelay();
+ }
+ } else {
+ // scroll is between left and right page
+ animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
+ if (mShouldAutoHide) {
+ mDelayedPaginationFadeHandler.removeCallbacksAndMessages(null);
+ }
}
}
}
@@ -283,15 +301,23 @@
private void animateToPosition(float position) {
mFinalPosition = position;
- if (Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) {
+ if (!enableLauncherVisualRefresh()
+ && Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) {
mCurrentPosition = mFinalPosition;
}
- if (mAnimator == null && Float.compare(mCurrentPosition, mFinalPosition) != 0) {
- float positionForThisAnim = mCurrentPosition > mFinalPosition ?
- mCurrentPosition - SHIFT_PER_ANIMATION : mCurrentPosition + SHIFT_PER_ANIMATION;
+ if (mAnimator == null && Float.compare(mCurrentPosition, position) != 0) {
+ float positionForThisAnim = enableLauncherVisualRefresh()
+ ? position
+ : (mCurrentPosition > mFinalPosition
+ ? mCurrentPosition - SHIFT_PER_ANIMATION
+ : mCurrentPosition + SHIFT_PER_ANIMATION);
mAnimator = ObjectAnimator.ofFloat(this, CURRENT_POSITION, positionForThisAnim);
mAnimator.addListener(new AnimationCycleListener());
mAnimator.setDuration(ANIMATION_DURATION);
+ if (enableLauncherVisualRefresh()) {
+ mLastPosition = (int) mCurrentPosition;
+ mAnimator.setInterpolator(new OvershootInterpolator());
+ }
mAnimator.start();
}
}
@@ -314,6 +340,7 @@
invalidate();
}
+ // TODO(b/394355070): Verify Folder Entry Animation works correctly with visual updates
public void playEntryAnimation() {
int count = mEntryAnimationRadiusFactors.length;
if (count == 0) {
@@ -391,6 +418,7 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // TODO(b/394355070): Verify Folder Entry Animation works correctly with visual updates
// Add extra spacing of mDotRadius on all sides so than entry animation could be run.
int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ?
MeasureSpec.getSize(widthMeasureSpec) : (int) ((mNumPages * 3 + 2) * mDotRadius);
@@ -410,17 +438,14 @@
return;
}
- // Draw all page indicators;
float circleGap = mCircleGap;
- float startX = ((float) getWidth() / 2)
- - (mCircleGap * (((float) mNumPages - 1) / 2))
- - mDotRadius;
-
- float x = startX + mDotRadius;
+ float x = ((float) getWidth() / 2) - (mCircleGap * ((float) mNumPages - 1) / 2);
float y = getHeight() / 2;
if (mEntryAnimationRadiusFactors != null) {
// During entry animation, only draw the circles
+ // TODO(b/394355070): Verify Folder Entry Animation works correctly - visual updates
+
if (mIsRtl) {
x = getWidth() - x;
circleGap = -circleGap;
@@ -432,18 +457,84 @@
x += circleGap;
}
} else {
+ // Save the current alpha value, so we can reset to it again after drawing the dots
int alpha = mPaginationPaint.getAlpha();
- // Here we draw the dots
- mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION));
- for (int i = 0; i < mNumPages; i++) {
- canvas.drawCircle(x, y, mDotRadius, mPaginationPaint);
- x += circleGap;
+ if (enableLauncherVisualRefresh()) {
+ int nonActiveAlpha = (int) (alpha * DOT_ALPHA_FRACTION);
+
+ float diameter = 2 * mDotRadius;
+ sTempRect.top = y - mDotRadius;
+ sTempRect.bottom = y + mDotRadius;
+ sTempRect.left = x - diameter;
+
+ float posDif = Math.abs(mLastPosition - mCurrentPosition);
+ float boundedPosition = (posDif > 1)
+ ? Math.round(mCurrentPosition)
+ : mCurrentPosition;
+ float bounceProgress = (posDif > 1) ? posDif - 1 : 0;
+ float bounceAdjustment = Math.abs(mCurrentPosition - boundedPosition) * diameter;
+
+ // Here we draw the dots, one at a time from the left-most dot to the right-most dot
+ // 1.0 => 000000 000000111111 000000
+ // 1.3 => 000000 0000001111 11000000
+ // 1.6 => 000000 00000011 1111000000
+ // 2.0 => 000000 000000 111111000000
+ for (int i = 0; i < mNumPages; i++) {
+ mPaginationPaint.setAlpha(nonActiveAlpha);
+ float delta = Math.abs(boundedPosition - i);
+ if (delta <= SHIFT_THRESHOLD) {
+ mPaginationPaint.setAlpha(alpha);
+ }
+
+ // If boundedPosition is 3.3, both 3 and 4 should enter this condition.
+ // If boundedPosition is 3, only 3 should enter this condition.
+ if (delta < 1) {
+ sTempRect.right = sTempRect.left + diameter + ((1 - delta) * diameter);
+
+ // While the animation is shifting the active pagination dots size from
+ // the previously active one, to the newly active dot, there is no bounce
+ // adjustment. The bounce happens in the "Overshoot" phase of the animation.
+ // mLastPosition is used to determine when the currentPosition is just
+ // leaving the page, or if it is in the overshoot phase.
+ if (boundedPosition == i && bounceProgress != 0) {
+ if (mLastPosition < mCurrentPosition) {
+ sTempRect.left -= bounceAdjustment;
+ } else {
+ sTempRect.right += bounceAdjustment;
+ }
+ }
+ } else {
+ sTempRect.right = sTempRect.left + diameter;
+
+ if (mLastPosition == i && bounceProgress != 0) {
+ if (mLastPosition > mCurrentPosition) {
+ sTempRect.left += bounceAdjustment;
+ } else {
+ sTempRect.right -= bounceAdjustment;
+ }
+ }
+ }
+ canvas.drawRoundRect(sTempRect, mDotRadius, mDotRadius, mPaginationPaint);
+
+ // TODO(b/394355070) Verify RTL experience works correctly with visual updates
+ sTempRect.left = sTempRect.right + mGapWidth;
+ }
+ } else {
+ // Here we draw the dots
+ mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION));
+ for (int i = 0; i < mNumPages; i++) {
+ canvas.drawCircle(x, y, mDotRadius, mPaginationPaint);
+ x += circleGap;
+ }
+
+ // Here we draw the current page indicator
+ mPaginationPaint.setAlpha(alpha);
+ canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
}
- // Here we draw the current page indicator
+ // Reset the alpha so it doesn't become progressively more transparent each onDraw call
mPaginationPaint.setAlpha(alpha);
- canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
}
}
@@ -499,6 +590,7 @@
@Override
public void getOutline(View view, Outline outline) {
if (mEntryAnimationRadiusFactors == null) {
+ // TODO(b/394355070): Verify Outline works correctly with visual updates
RectF activeRect = getActiveRect();
outline.setRoundRect(
(int) activeRect.left,
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 8a5e388..318b3ce 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -23,20 +23,27 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.dot.FolderDotInfo;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.notification.NotificationKeyData;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.ShortcutUtil;
+import com.android.launcher3.views.ActivityContext;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -47,19 +54,49 @@
private static final boolean LOGD = false;
private static final String TAG = "PopupDataProvider";
- private final Consumer<Predicate<PackageUserKey>> mNotificationDotsChangeListener;
+ private final ActivityContext mContext;
+
+ /** Maps packages to their DotInfo's . */
+ private final Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
/** Maps launcher activity components to a count of how many shortcuts they have. */
private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
- /** Maps packages to their DotInfo's . */
- private Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
- public PopupDataProvider(Consumer<Predicate<PackageUserKey>> notificationDotsChangeListener) {
- mNotificationDotsChangeListener = notificationDotsChangeListener;
+ public PopupDataProvider(ActivityContext context) {
+ mContext = context;
}
private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- mNotificationDotsChangeListener.accept(updatedDots);
+ final PackageUserKey packageUserKey = new PackageUserKey(null, null);
+ Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
+ || updatedDots.test(packageUserKey);
+
+ ItemOperator op = (info, v) -> {
+ if (v instanceof BubbleTextView && info != null && matcher.test(info)) {
+ ((BubbleTextView) v).applyDotState(info, true /* animate */);
+ } else if (v instanceof FolderIcon icon
+ && info instanceof FolderInfo fi && fi.anyMatch(matcher)) {
+ FolderDotInfo folderDotInfo = new FolderDotInfo();
+ for (ItemInfo si : fi.getContents()) {
+ folderDotInfo.addDotInfo(getDotInfoForItem(si));
+ }
+ icon.setDotInfo(folderDotInfo);
+ }
+
+ // process all the shortcuts
+ return false;
+ };
+
+ mContext.getContent().mapOverItems(op);
+ Folder folder = Folder.getOpen(mContext);
+ if (folder != null) {
+ folder.iterateOverItems(op);
+ }
+
+ ActivityAllAppsContainerView<?> appsView = mContext.getAppsView();
+ if (appsView != null) {
+ appsView.getAppsStore().updateNotificationDots(updatedDots);
+ }
}
@Override
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 7e08c6e..b7efdec 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -210,30 +210,6 @@
}
}
- public static final Factory<ActivityContext> PIN_UNPIN_ITEM =
- (context, itemInfo, originalView) -> {
- // Predicted items use {@code HotseatPredictionController.PinPrediction} shortcut
- // to pin.
- if (itemInfo.isPredictedItem()) {
- return null;
- }
- return new PinUnpinItem<>(context, itemInfo, originalView);
- };
-
- private static class PinUnpinItem<T extends ActivityContext> extends SystemShortcut<T> {
- PinUnpinItem(T target, ItemInfo itemInfo, @NonNull View originalView) {
- // TODO(b/375648361): Check the pin state of the item to determine if the pin or the
- // unpin option should be used.
- super(R.drawable.ic_pin, R.string.pin_to_taskbar, target,
- itemInfo, originalView);
- }
-
- @Override
- public void onClick(View view) {
- // TODO(b/375648361): Pin/Unpin the item here.
- }
- }
-
public static final Factory<ActivityContext> PRIVATE_PROFILE_INSTALL =
(context, itemInfo, originalView) -> {
if (originalView == null) {
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index e4c50f0..2d1a5f5 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.util.Log
+import android.view.ContextThemeWrapper
import android.view.InflateException
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.PROTECTED
@@ -33,8 +34,6 @@
import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
import com.android.launcher3.util.Themes
import com.android.launcher3.views.ActivityContext
-import com.android.launcher3.views.ActivityContext.ActivityContextDelegate
-import java.lang.IllegalStateException
const val PREINFLATE_ICONS_ROW_COUNT = 4
const val EXTRA_ICONS_COUNT = 2
@@ -80,11 +79,9 @@
// create a separate AssetManager obj internally to avoid lock contention with
// AssetManager obj that is associated with the launcher context on the main thread.
val allAppsPreInflationContext =
- ActivityContextDelegate(
- context.createConfigurationContext(context.resources.configuration),
- Themes.getActivityThemeRes(context),
- context,
- )
+ ContextThemeWrapper(context, Themes.getActivityThemeRes(context)).apply {
+ applyOverrideConfiguration(context.resources.configuration)
+ }
// Because we perform onCreateViewHolder() on worker thread, we need a separate
// adapter/inflator object as they are not thread-safe. Note that the adapter
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index b1653d0..fd8b0e7 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -29,6 +29,7 @@
import android.view.ViewAnimationUtils;
import android.view.inputmethod.InputMethodManager;
+import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import com.android.launcher3.AbstractFloatingView;
@@ -57,7 +58,6 @@
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Themes;
@@ -83,7 +83,6 @@
private boolean mAppDrawerShown = false;
private StringCache mStringCache;
- private boolean mBindingItems = false;
private SecondaryDisplayPredictions mSecondaryDisplayPredictions;
private final int[] mTempXY = new int[2];
@@ -131,8 +130,7 @@
}
mDragController.addDragListener(this);
- mPopupDataProvider = new PopupDataProvider(
- mAppsView.getAppsStore()::updateNotificationDots);
+ mPopupDataProvider = new PopupDataProvider(this);
mModel.addCallbacksAndLoad(this);
}
@@ -260,21 +258,10 @@
@Override
public void startBinding() {
- mBindingItems = true;
mDragController.cancelDrag();
}
@Override
- public boolean isBindingItems() {
- return mBindingItems;
- }
-
- @Override
- public void finishBindingItems(IntSet pagesBoundFirst) {
- mBindingItems = false;
- }
-
- @Override
public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) {
mPopupDataProvider.setDeepShortcutMap(deepShortcutMap);
}
@@ -306,6 +293,8 @@
mStringCache = cache;
}
+ @Override
+ @NonNull
public PopupDataProvider getPopupDataProvider() {
return mPopupDataProvider;
}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 943a913..e5105cd 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -214,6 +214,10 @@
ENABLE_TASKBAR_NAVBAR_UNIFICATION);
return response;
+ case TestProtocol.REQUEST_TASKBAR_SHOWN_ON_HOME:
+ response.putBoolean(TEST_INFO_RESPONSE_FIELD,
+ DisplayController.showLockedTaskbarOnHome(mContext));
+ return response;
case TestProtocol.REQUEST_NUM_ALL_APPS_COLUMNS:
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
mDeviceProfile.numShownAllAppsColumns);
diff --git a/src/com/android/launcher3/util/DaggerSingletonObject.java b/src/com/android/launcher3/util/DaggerSingletonObject.java
index a245761..a4bd30a 100644
--- a/src/com/android/launcher3/util/DaggerSingletonObject.java
+++ b/src/com/android/launcher3/util/DaggerSingletonObject.java
@@ -24,8 +24,7 @@
import java.util.function.Function;
/**
- * A class to provide DaggerSingleton objects in a traditional way for
- * {@link MainThreadInitializedObject}.
+ * A class to provide DaggerSingleton objects in a traditional way.
* We should delete this class at the end and use @Inject to get dagger provided singletons.
*/
diff --git a/src/com/android/launcher3/util/DaggerSingletonTracker.java b/src/com/android/launcher3/util/DaggerSingletonTracker.java
index b7a88db..34b3760 100644
--- a/src/com/android/launcher3/util/DaggerSingletonTracker.java
+++ b/src/com/android/launcher3/util/DaggerSingletonTracker.java
@@ -45,7 +45,7 @@
* Adds the SafeCloseable Singletons to the mLauncherAppSingletons list.
* This helps to track the singletons and close them appropriately.
* See {@link DaggerSingletonTracker#close()} and
- * {@link MainThreadInitializedObject.SandboxContext#onDestroy()}
+ * {@link SandboxContext#onDestroy()}
*/
public void addCloseable(SafeCloseable closeable) {
MAIN_EXECUTOR.execute(() -> {
diff --git a/src/com/android/launcher3/util/KFloatProperty.kt b/src/com/android/launcher3/util/KFloatProperty.kt
new file mode 100644
index 0000000..5579241
--- /dev/null
+++ b/src/com/android/launcher3/util/KFloatProperty.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.util
+
+import android.util.FloatProperty
+import kotlin.reflect.KMutableProperty1
+
+/** Maps any Kotlin mutable property (var) to [FloatProperty]. */
+class KFloatProperty<T>(private val kProperty: KMutableProperty1<T, Float>) :
+ FloatProperty<T>(kProperty.name) {
+ override fun get(target: T) = kProperty.get(target)
+
+ override fun setValue(target: T, value: Float) {
+ kProperty.set(target, value)
+ }
+}
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
deleted file mode 100644
index 20e3eaf..0000000
--- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2021 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.util;
-
-import android.view.View;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.apppairs.AppPairIcon;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.model.data.AppPairInfo;
-import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
-
-import java.util.Set;
-
-/**
- * Interface representing a container which can bind Launcher items with some utility methods
- */
-public interface LauncherBindableItemsContainer {
-
- /**
- * Called to update workspace items as a result of
- * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindItemsUpdated(Set)}
- */
- default void updateContainerItems(Set<ItemInfo> updates, ActivityContext context) {
- ItemOperator op = (info, v) -> {
- if (v instanceof BubbleTextView shortcut
- && info instanceof WorkspaceItemInfo wii
- && updates.contains(info)) {
- shortcut.applyFromWorkspaceItem(wii);
- } else if (info instanceof FolderInfo && v instanceof FolderIcon folderIcon) {
- folderIcon.updatePreviewItems(updates::contains);
- } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
- appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
- } else if (v instanceof PendingAppWidgetHostView pendingView
- && updates.contains(info)) {
- pendingView.applyState();
- pendingView.postProviderAvailabilityCheck();
- }
-
- // Iterate all items
- return false;
- };
-
- mapOverItems(op);
- Folder openFolder = Folder.getOpen(context);
- if (openFolder != null) {
- openFolder.iterateOverItems(op);
- }
- }
-
- /**
- * Map the operator over the shortcuts and widgets.
- *
- * @param op the operator to map over the shortcuts
- */
- void mapOverItems(ItemOperator op);
-
- interface ItemOperator {
- /**
- * Process the next itemInfo, possibly with side-effect on the next item.
- *
- * @param info info for the shortcut
- * @param view view for the shortcut
- * @return true if done, false to continue the map
- */
- boolean evaluate(ItemInfo info, View view);
- }
-}
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt b/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt
new file mode 100644
index 0000000..1661796
--- /dev/null
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.util
+
+import android.view.View
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.apppairs.AppPairIcon
+import com.android.launcher3.folder.Folder
+import com.android.launcher3.folder.FolderIcon
+import com.android.launcher3.model.data.AppPairInfo
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.views.ActivityContext
+import com.android.launcher3.widget.PendingAppWidgetHostView
+
+/** Interface representing a container which can bind Launcher items with some utility methods */
+interface LauncherBindableItemsContainer {
+
+ /**
+ * Called to update workspace items as a result of {@link
+ * com.android.launcher3.model.BgDataModel.Callbacks#bindItemsUpdated(Set)}
+ */
+ fun updateContainerItems(updates: Set<ItemInfo>, context: ActivityContext) {
+ val op = ItemOperator { info, v ->
+ when {
+ v is BubbleTextView && info is WorkspaceItemInfo && updates.contains(info) ->
+ v.applyFromWorkspaceItem(info)
+ v is FolderIcon && info is FolderInfo -> v.updatePreviewItems(updates::contains)
+ v is AppPairIcon && info is AppPairInfo ->
+ v.maybeRedrawForWorkspaceUpdate(updates::contains)
+ v is PendingAppWidgetHostView && updates.contains(info) -> {
+ v.applyState()
+ v.postProviderAvailabilityCheck()
+ }
+ }
+
+ // Iterate all items
+ false
+ }
+
+ mapOverItems(op)
+ Folder.getOpen(context)?.iterateOverItems(op)
+ }
+
+ /** Map the [op] over the shortcuts and widgets. */
+ fun mapOverItems(op: ItemOperator)
+
+ fun interface ItemOperator {
+
+ /**
+ * Process the next itemInfo, possibly with side-effect on the next item.
+ *
+ * @param info info for the shortcut
+ * @param view view for the shortcut
+ * @return true if done, false to continue the map
+ */
+ fun evaluate(info: ItemInfo?, view: View): Boolean
+ }
+}
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
deleted file mode 100644
index 356a551..0000000
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (C) 2018 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.util;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.content.Context;
-import android.os.Looper;
-import android.util.Log;
-
-import androidx.annotation.UiThread;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.launcher3.LauncherApplication;
-import com.android.launcher3.util.ResourceBasedOverride.Overrides;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
-
-/**
- * Utility class for defining singletons which are initiated on main thread.
- *
- * TODO(b/361850561): Do not delete MainThreadInitializedObject until we find a way to
- * unregister and understand how singleton objects are destroyed in dagger graph.
- */
-public class MainThreadInitializedObject<T extends SafeCloseable> {
-
- private final ObjectProvider<T> mProvider;
- private T mValue;
-
- public MainThreadInitializedObject(ObjectProvider<T> provider) {
- mProvider = provider;
- }
-
- public T get(Context context) {
- Context app = context.getApplicationContext();
- if (app instanceof ObjectSandbox sc) {
- return sc.getObject(this);
- }
-
- if (mValue == null) {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- mValue = TraceHelper.allowIpcs("main.thread.object", () -> mProvider.get(app));
- } else {
- try {
- return MAIN_EXECUTOR.submit(() -> get(context)).get();
- } catch (InterruptedException|ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
- }
- return mValue;
- }
-
- /**
- * Executes the callback is the value is already created
- * @return true if the callback was executed, false otherwise
- */
- public boolean executeIfCreated(Consumer<T> callback) {
- T v = mValue;
- if (v != null) {
- callback.accept(v);
- return true;
- } else {
- return false;
- }
- }
-
- @VisibleForTesting
- public void initializeForTesting(T value) {
- mValue = value;
- }
-
- /**
- * Initializes a provider based on resource overrides
- */
- public static <T extends ResourceBasedOverride & SafeCloseable> MainThreadInitializedObject<T>
- forOverride(Class<T> clazz, int resourceId) {
- return new MainThreadInitializedObject<>(c -> Overrides.getObject(clazz, c, resourceId));
- }
-
- public interface ObjectProvider<T> {
-
- T get(Context context);
- }
-
- /** Sandbox for isolating {@link MainThreadInitializedObject} instances from Launcher. */
- public interface ObjectSandbox {
-
- /**
- * Find a cached object from mObjectMap if we have already created one. If not, generate
- * an object using the provider.
- */
- <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object);
-
-
- /**
- * Put a value into cache, can be used to put mocked MainThreadInitializedObject
- * instances.
- */
- <T extends SafeCloseable> void putObject(MainThreadInitializedObject<T> object, T value);
-
- /**
- * Returns whether this sandbox should cleanup all objects when its destroyed or leave it
- * to the GC.
- * These objects can have listeners attached to the system server and mey not be able to get
- * GCed themselves when running on a device.
- * Some environments like Robolectric tear down the whole system at the end of the test,
- * so manual cleanup may not be required.
- */
- default boolean shouldCleanUpOnDestroy() {
- return true;
- }
-
- @UiThread
- default <T extends SafeCloseable> T createObject(MainThreadInitializedObject<T> object) {
- return object.mProvider.get((Context) this);
- }
- }
-
- /**
- * Abstract Context which allows custom implementations for
- * {@link MainThreadInitializedObject} providers
- */
- public static class SandboxContext extends LauncherApplication implements ObjectSandbox {
-
- private static final String TAG = "SandboxContext";
-
- private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
- private final ArrayList<SafeCloseable> mOrderedObjects = new ArrayList<>();
-
- private final Object mDestroyLock = new Object();
- private boolean mDestroyed = false;
-
- public SandboxContext(Context base) {
- attachBaseContext(base);
- }
-
- @Override
- public Context getApplicationContext() {
- return this;
- }
-
- @Override
- public boolean shouldCleanUpOnDestroy() {
- return (getBaseContext().getApplicationContext() instanceof ObjectSandbox os)
- ? os.shouldCleanUpOnDestroy() : true;
- }
-
- public void onDestroy() {
- if (shouldCleanUpOnDestroy()) {
- cleanUpObjects();
- }
- }
-
- protected void cleanUpObjects() {
- getAppComponent().getDaggerSingletonTracker().close();
- synchronized (mDestroyLock) {
- // Destroy in reverse order
- for (int i = mOrderedObjects.size() - 1; i >= 0; i--) {
- mOrderedObjects.get(i).close();
- }
- mDestroyed = true;
- }
- }
-
- @Override
- public <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object) {
- synchronized (mDestroyLock) {
- if (mDestroyed) {
- Log.e(TAG, "Static object access with a destroyed context");
- }
- T t = (T) mObjectMap.get(object);
- if (t != null) {
- return t;
- }
- if (Looper.myLooper() == Looper.getMainLooper()) {
- t = createObject(object);
- mObjectMap.put(object, t);
- mOrderedObjects.add(t);
- return t;
- }
- }
-
- try {
- return MAIN_EXECUTOR.submit(() -> getObject(object)).get();
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public <T extends SafeCloseable> void putObject(
- MainThreadInitializedObject<T> object, T value) {
- mObjectMap.put(object, value);
- }
- }
-}
diff --git a/src/com/android/launcher3/util/MultiPropertyDelegate.kt b/src/com/android/launcher3/util/MultiPropertyDelegate.kt
new file mode 100644
index 0000000..837a586
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiPropertyDelegate.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.util
+
+import kotlin.reflect.KProperty
+
+/** Delegate Kotlin mutable property (var) to a property in [MultiPropertyFactory] */
+class MultiPropertyDelegate(private val property: MultiPropertyFactory<*>.MultiProperty) {
+ constructor(factory: MultiPropertyFactory<*>, enum: Enum<*>) : this(factory[enum.ordinal])
+
+ operator fun getValue(thisRef: Any?, kProperty: KProperty<*>): Float = property.value
+
+ operator fun setValue(thisRef: Any?, kProperty: KProperty<*>, value: Float) {
+ property.value = value
+ }
+}
diff --git a/src/com/android/launcher3/util/SandboxContext.kt b/src/com/android/launcher3/util/SandboxContext.kt
new file mode 100644
index 0000000..c6224e2
--- /dev/null
+++ b/src/com/android/launcher3/util/SandboxContext.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.util
+
+import android.content.Context
+import com.android.launcher3.LauncherApplication
+
+/** Abstract Context which allows custom implementations for dagger components. */
+open class SandboxContext(base: Context?) : LauncherApplication() {
+ init {
+ base?.let { attachBaseContext(it) }
+ }
+
+ override fun getApplicationContext(): Context {
+ return this
+ }
+
+ /**
+ * Returns whether this sandbox should cleanup all objects when its destroyed or leave it to the
+ * GC. These objects can have listeners attached to the system server and mey not be able to get
+ * GCed themselves when running on a device. Some environments like Robolectric tear down the
+ * whole system at the end of the test, so manual cleanup may not be required.
+ */
+ open fun shouldCleanUpOnDestroy(): Boolean {
+ return (getBaseContext().getApplicationContext() as? SandboxContext)
+ ?.shouldCleanUpOnDestroy() ?: true
+ }
+
+ fun onDestroy() {
+ if (shouldCleanUpOnDestroy()) {
+ cleanUpObjects()
+ }
+ }
+
+ open protected fun cleanUpObjects() {
+ appComponent.daggerSingletonTracker.close()
+ }
+
+ companion object {
+ private const val TAG = "SandboxContext"
+ }
+}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 81968fc..30af586 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -43,7 +43,6 @@
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
@@ -81,6 +80,7 @@
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.util.ActivityOptionsWrapper;
import com.android.launcher3.util.ApplicationInfoWrapper;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SplitConfigurationOptions;
@@ -102,10 +102,6 @@
return false;
}
- default DotInfo getDotInfoForItem(ItemInfo info) {
- return null;
- }
-
default AccessibilityDelegate getAccessibilityDelegate() {
return null;
}
@@ -195,6 +191,14 @@
}
/**
+ * Returns the primary content of this context
+ */
+ @NonNull
+ default LauncherBindableItemsContainer getContent() {
+ return op -> { };
+ }
+
+ /**
* The all apps container, if it exists in this context.
*/
default ActivityAllAppsContainerView<?> getAppsView() {
@@ -271,11 +275,6 @@
*/
default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) { }
- /** Returns {@code true} if items are currently being bound within this context. */
- default boolean isBindingItems() {
- return false;
- }
-
default View.OnClickListener getItemOnClickListener() {
return v -> {
// No op.
@@ -287,9 +286,13 @@
return v -> false;
}
- @Nullable
+ @NonNull
default PopupDataProvider getPopupDataProvider() {
- return null;
+ return new PopupDataProvider(this);
+ }
+
+ default DotInfo getDotInfoForItem(ItemInfo info) {
+ return getPopupDataProvider().getDotInfoForItem(info);
}
/**
@@ -552,21 +555,10 @@
static <T extends Context & ActivityContext> T lookupContextNoThrow(Context context) {
if (context instanceof ActivityContext) {
return (T) context;
- } else if (context instanceof ActivityContextDelegate acd) {
- return (T) acd.mDelegate;
- } else if (context instanceof ContextWrapper) {
- return lookupContextNoThrow(((ContextWrapper) context).getBaseContext());
+ } else if (context instanceof ContextWrapper cw) {
+ return lookupContextNoThrow(cw.getBaseContext());
} else {
return null;
}
}
-
- class ActivityContextDelegate extends ContextThemeWrapper {
- public final ActivityContext mDelegate;
-
- public ActivityContextDelegate(Context base, int themeResId, ActivityContext delegate) {
- super(base, themeResId);
- mDelegate = delegate;
- }
- }
}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index b07d807..7a44c6a 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -79,7 +79,8 @@
private Runnable mAutoAdvanceRunnable;
private long mDeferUpdatesUntilMillis = 0;
- RemoteViews mLastRemoteViews;
+ private RemoteViews mLastRemoteViews;
+ private boolean mReapplyOnResumeUpdates = false;
private boolean mTrackingWidgetUpdate = false;
@@ -138,11 +139,11 @@
TRACE_METHOD_NAME + getAppWidgetInfo().provider, getAppWidgetId());
mTrackingWidgetUpdate = false;
}
- if (isDeferringUpdates()) {
- mLastRemoteViews = remoteViews;
+ mLastRemoteViews = remoteViews;
+ mReapplyOnResumeUpdates = isDeferringUpdates();
+ if (mReapplyOnResumeUpdates) {
return;
}
- mLastRemoteViews = null;
super.updateAppWidget(remoteViews);
@@ -150,6 +151,18 @@
checkIfAutoAdvance();
}
+ @Override
+ public void onViewAdded(View child) {
+ super.onViewAdded(child);
+ mReapplyOnResumeUpdates |= isDeferringUpdates();
+ }
+
+ @Override
+ public void onViewRemoved(View child) {
+ super.onViewRemoved(child);
+ mReapplyOnResumeUpdates |= isDeferringUpdates();
+ }
+
private boolean checkScrollableRecursively(ViewGroup viewGroup) {
if (viewGroup instanceof AdapterView) {
return true;
@@ -204,18 +217,16 @@
* {@link #updateAppWidget} and apply any deferred updates.
*/
public void endDeferringUpdates() {
- RemoteViews remoteViews;
mDeferUpdatesUntilMillis = 0;
- remoteViews = mLastRemoteViews;
-
- if (remoteViews != null) {
- updateAppWidget(remoteViews);
+ if (mReapplyOnResumeUpdates) {
+ updateAppWidget(mLastRemoteViews);
}
}
+ @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- BaseDragLayer dragLayer = mActivityContext.getDragLayer();
+ BaseDragLayer<?> dragLayer = mActivityContext.getDragLayer();
if (mIsScrollable) {
dragLayer.requestDisallowInterceptTouchEvent(true);
}
@@ -225,6 +236,7 @@
return mLongPressHelper.hasPerformedLongPress();
}
+ @Override
public boolean onTouchEvent(MotionEvent ev) {
mLongPressHelper.onTouchEvent(ev);
// We want to keep receiving though events to be able to cancel long press on ACTION_UP
diff --git a/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
index 5b1da5b..a761ecd 100644
--- a/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
@@ -33,4 +33,9 @@
Collections.EMPTY_LIST);
mPkgItem.title = "";
}
+
+ @Override
+ public WidgetsListBaseEntry copy() {
+ return new WidgetListSpaceEntry();
+ }
}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
index 0003b76..9246e45 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
@@ -43,4 +43,7 @@
this.mWidgets =
items.stream().sorted(new WidgetItemComparator()).collect(Collectors.toList());
}
+
+
+ public abstract WidgetsListBaseEntry copy();
}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
index d709196..cc1739f 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
@@ -58,6 +58,11 @@
}
@Override
+ public WidgetsListBaseEntry copy() {
+ return new WidgetsListContentEntry(mPkgItem, mTitleSectionName, mWidgets, mMaxSpanSize);
+ }
+
+ @Override
public String toString() {
return "Content:" + mPkgItem.packageName + ":" + mWidgets.size() + " maxSpanSize: "
+ mMaxSpanSize;
diff --git a/src/com/android/launcher3/widget/model/WidgetsListExpandActionEntry.java b/src/com/android/launcher3/widget/model/WidgetsListExpandActionEntry.java
index 8c84030..7519bb7 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListExpandActionEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListExpandActionEntry.java
@@ -35,4 +35,9 @@
/*items=*/ Collections.EMPTY_LIST);
mPkgItem.title = "";
}
+
+ @Override
+ public WidgetsListBaseEntry copy() {
+ return new WidgetsListExpandActionEntry();
+ }
}
diff --git a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
index 0d775c3..e2ea068 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
@@ -83,6 +83,12 @@
mIsWidgetListShown = isWidgetListShown;
}
+ @Override
+ public WidgetsListBaseEntry copy() {
+ return new WidgetsListHeaderEntry(mPkgItem, mTitleSectionName, mWidgets,
+ mVisibleWidgetsCount, mIsSearchEntry, mIsWidgetListShown);
+ }
+
/** Returns {@code true} if the widgets list associated with this header is shown. */
public boolean isWidgetListShown() {
return mIsWidgetListShown;
diff --git a/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProvider.kt b/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProvider.kt
index 46d3e7a..5b97a49 100644
--- a/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProvider.kt
+++ b/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProvider.kt
@@ -16,43 +16,79 @@
package com.android.launcher3.widget.picker.model
+import android.content.Context
+import com.android.launcher3.dagger.LauncherComponentProvider.appComponent
+import com.android.launcher3.model.WidgetItem
+import com.android.launcher3.model.WidgetsFilterDataProvider
+import com.android.launcher3.model.WidgetsFilterDataProvider.WidgetsFilterLoadedCallback
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.widget.model.WidgetsListBaseEntry
import com.android.launcher3.widget.picker.model.data.WidgetPickerData
import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.withRecommendedWidgets
import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.withWidgets
import java.io.PrintWriter
+import java.util.function.Predicate
/**
* Provides [WidgetPickerData] to various views such as widget picker, app-specific widget picker,
* widgets shortcut.
*/
-class WidgetPickerDataProvider {
+class WidgetPickerDataProvider(private val filterProvider: WidgetsFilterDataProvider) :
+ WidgetsFilterLoadedCallback {
+
+ constructor(context: Context) : this(context.appComponent.widgetsFilterDataProvider)
+
/** All the widgets data provided for the views */
private var mWidgetPickerData: WidgetPickerData = WidgetPickerData()
private var changeListener: WidgetPickerDataChangeListener? = null
+ var hostSpecifiedDefaultWidgetsFilter: Predicate<WidgetItem>? = null
+
+ private var allWidgets: List<WidgetsListBaseEntry> = emptyList()
+
/** Sets a listener to be called back when widget data is updated. */
fun setChangeListener(changeListener: WidgetPickerDataChangeListener?) {
this.changeListener = changeListener
}
+ init {
+ filterProvider.addFilterChangeCallback(this)
+ }
+
/** Returns the current snapshot of [WidgetPickerData]. */
fun get(): WidgetPickerData {
return mWidgetPickerData
}
+ override fun onWidgetsFilterLoaded() {
+ setWidgets(allWidgets)
+ }
+
/**
* Updates the widgets available to the widget picker.
*
* Generally called when the widgets model has new data.
*/
- @JvmOverloads
- fun setWidgets(
- allWidgets: List<WidgetsListBaseEntry>,
- defaultWidgets: List<WidgetsListBaseEntry> = listOf()
- ) {
+ fun setWidgets(allWidgets: List<WidgetsListBaseEntry>) {
+ this.allWidgets = allWidgets
+
+ val currentFilter = filterProvider.defaultWidgetsFilter
+ val finalFilter =
+ when {
+ currentFilter != null && hostSpecifiedDefaultWidgetsFilter != null ->
+ currentFilter.and(hostSpecifiedDefaultWidgetsFilter)
+ hostSpecifiedDefaultWidgetsFilter != null -> hostSpecifiedDefaultWidgetsFilter
+ else -> currentFilter
+ }
+
+ val defaultWidgets =
+ if (finalFilter != null)
+ allWidgets
+ .map { it.copy().apply { mWidgets.removeIf(finalFilter) } }
+ .filter { it.mWidgets.isNotEmpty() }
+ else emptyList()
+
mWidgetPickerData =
mWidgetPickerData.withWidgets(allWidgets = allWidgets, defaultWidgets = defaultWidgets)
changeListener?.onWidgetsBound()
@@ -74,6 +110,10 @@
writer.println("$prefix\twidgetPickerData:$mWidgetPickerData")
}
+ fun destroy() {
+ filterProvider.removeFilterChangeCallback(this)
+ }
+
interface WidgetPickerDataChangeListener {
/** A callback to get notified when widgets are bound. */
fun onWidgetsBound()
diff --git a/tests/Launcher3Tests.xml b/tests/Launcher3Tests.xml
index 56dd6a4..da86357 100644
--- a/tests/Launcher3Tests.xml
+++ b/tests/Launcher3Tests.xml
@@ -62,6 +62,7 @@
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/data/user/0/com.android.launcher3/files" />
+ <option name="directory-keys" value="/data/user/10/com.android.launcher3/files" />
<option name="collect-on-run-ended-only" value="true" />
</metrics_collector>
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS b/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS
new file mode 100644
index 0000000..02e8ebc
--- /dev/null
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/OWNERS
@@ -0,0 +1,4 @@
+vadimt@google.com
+sunnygoyal@google.com
+winsonc@google.com
+hyunyoungs@google.com
diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 3658989..ad6afcf 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -35,8 +35,8 @@
import com.android.launcher3.util.AllModulesMinusWMProxy
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.FakePrefsModule
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
import com.android.launcher3.util.NavigationMode
+import com.android.launcher3.util.SandboxContext
import com.android.launcher3.util.WindowBounds
import com.android.launcher3.util.rule.TestStabilityRule
import com.android.launcher3.util.rule.setFlags
diff --git a/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt b/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
index 0e06051..6483bd5 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
@@ -6,7 +6,7 @@
import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS
import android.appwidget.AppWidgetManager.EXTRA_HOST_ID
import android.content.Intent
-import android.platform.uiautomator_helpers.DeviceHelpers
+import android.platform.uiautomatorhelpers.DeviceHelpers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherPrefs.Companion.APP_WIDGET_IDS
diff --git a/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
index 4aeef2e..da9cc86 100644
--- a/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
@@ -24,12 +24,18 @@
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
+import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
private val TEST_BOOLEAN_ITEM = LauncherPrefs.nonRestorableItem("1", false)
private val TEST_STRING_ITEM = LauncherPrefs.nonRestorableItem("2", "( ͡❛ ͜ʖ ͡❛)")
private val TEST_INT_ITEM = LauncherPrefs.nonRestorableItem("3", -1)
+private val TEST_FLOAT_ITEM = LauncherPrefs.nonRestorableItem("4", -1f)
+private val TEST_LONG_ITEM = LauncherPrefs.nonRestorableItem("5", -1L)
+private val TEST_SET_ITEM = LauncherPrefs.nonRestorableItem("6", setOf<String>())
+private val TEST_HASHSET_ITEM = LauncherPrefs.nonRestorableItem("7", hashSetOf<String>())
+
private val TEST_CONTEXTUAL_ITEM =
ContextualItem("4", true, { true }, EncryptionType.ENCRYPTED, Boolean::class.java)
@@ -144,15 +150,49 @@
}
@Test
+ fun whenItemType_isInvalid_thenThrowException() {
+ val badItem = LauncherPrefs.nonRestorableItem("8", mapOf<String, String>())
+ with(launcherPrefs) {
+ assertThrows(IllegalArgumentException::class.java) {
+ putSync(badItem.to(badItem.defaultValue))
+ }
+ assertThrows(IllegalArgumentException::class.java) { get(badItem) }
+ }
+ }
+
+ @Test
fun put_storesListOfItemsInLauncherPrefs_successfully() {
with(launcherPrefs) {
putSync(
TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue),
TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue),
TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue),
+ TEST_FLOAT_ITEM.to(TEST_FLOAT_ITEM.defaultValue),
+ TEST_LONG_ITEM.to(TEST_LONG_ITEM.defaultValue),
+ TEST_SET_ITEM.to(TEST_SET_ITEM.defaultValue),
+ TEST_HASHSET_ITEM.to(TEST_HASHSET_ITEM.defaultValue),
)
- assertThat(has(TEST_BOOLEAN_ITEM, TEST_INT_ITEM, TEST_STRING_ITEM)).isTrue()
- remove(TEST_STRING_ITEM, TEST_INT_ITEM, TEST_BOOLEAN_ITEM)
+ assertThat(
+ has(
+ TEST_STRING_ITEM,
+ TEST_INT_ITEM,
+ TEST_BOOLEAN_ITEM,
+ TEST_FLOAT_ITEM,
+ TEST_LONG_ITEM,
+ TEST_SET_ITEM,
+ TEST_HASHSET_ITEM,
+ )
+ )
+ .isTrue()
+ remove(
+ TEST_STRING_ITEM,
+ TEST_INT_ITEM,
+ TEST_BOOLEAN_ITEM,
+ TEST_FLOAT_ITEM,
+ TEST_LONG_ITEM,
+ TEST_SET_ITEM,
+ TEST_HASHSET_ITEM,
+ )
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
index 12c14fb..2c9cb2f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
@@ -38,6 +38,7 @@
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -77,6 +78,8 @@
@EnableFlags(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS)
fun `createThemedBitmap when mono generation is enabled`() {
ensureBitmapSerializationSupported()
+ // Make sure forced theme icon is enabled in BaseIconFactory
+ assumeTrue(iconFactory.shouldForceThemeIcon())
val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
assertNotNull(
MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory)
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
index d704195..777d81b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
@@ -43,7 +43,6 @@
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
-import java.util.function.Predicate
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
@@ -65,7 +64,6 @@
@Mock private lateinit var appWidgetManager: AppWidgetManager
@Mock private lateinit var app: LauncherAppState
@Mock private lateinit var iconCacheMock: IconCache
- @Mock private lateinit var widgetsFilterDataProvider: WidgetsFilterDataProvider
private lateinit var context: Context
private lateinit var idp: InvariantDeviceProfile
@@ -252,27 +250,6 @@
// No exception
}
- @Test
- fun updateWidgetFilters_setsFiltersCorrectly() {
- val testDefaultWidgetFilter = Predicate<WidgetItem> { w -> w.widgetInfo != null }
- whenever(widgetsFilterDataProvider.getDefaultWidgetsFilter())
- .thenReturn(testDefaultWidgetFilter)
- val testPredicatedWidgetFilter = Predicate<WidgetItem> { w -> w.widgetInfo != null }
- whenever(widgetsFilterDataProvider.getPredictedWidgetsFilter())
- .thenReturn(testPredicatedWidgetFilter)
-
- underTest.updateWidgetFilters(widgetsFilterDataProvider)
-
- assertThat(underTest.defaultWidgetsFilter).isEqualTo(testDefaultWidgetFilter)
- assertThat(underTest.predictedWidgetsFilter).isEqualTo(testPredicatedWidgetFilter)
- }
-
- @Test
- fun widgetFilters_nullInitially() {
- assertThat(underTest.defaultWidgetsFilter).isNull()
- assertThat(underTest.predictedWidgetsFilter).isNull()
- }
-
private fun loadWidgets() {
val latch = CountDownLatch(1)
Executors.MODEL_EXECUTOR.execute {
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
index cee5559..4458e8f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -56,7 +56,6 @@
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ModelDbController;
import com.android.launcher3.testing.TestInformationProvider;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
index 547d05e..ceefb0d 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
@@ -1,6 +1,7 @@
package com.android.launcher3.util
import android.content.ContentValues
+import android.os.Process
import com.android.launcher3.Flags
import com.android.launcher3.LauncherModel
import com.android.launcher3.LauncherSettings.Favorites
@@ -66,7 +67,7 @@
spanX: Int = 1,
spanY: Int = 1,
id: Int = 0,
- profileId: Int = 0,
+ profileId: Int = Process.myUserHandle().identifier,
tableName: String = Favorites.TABLE_NAME,
appWidgetId: Int = -1,
appWidgetSource: Int = -1,
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
index 0da8891..2fa4cad 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
@@ -27,7 +27,6 @@
import android.view.Display
import androidx.test.core.app.ApplicationProvider
import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
import org.junit.Rule
import org.junit.rules.ExternalResource
import org.junit.rules.TestRule
@@ -69,7 +68,7 @@
// Defer to the true application to decide whether to clean up. For instance, we do not want
// to cleanup under Robolectric.
val app = ApplicationProvider.getApplicationContext<Context>()
- return if (app is ObjectSandbox) app.shouldCleanUpOnDestroy() else true
+ return (app as? SandboxContext)?.shouldCleanUpOnDestroy() ?: true
}
override fun apply(statement: Statement, description: Description): Statement {
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
index d87a406..8a21cff 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
@@ -16,7 +16,6 @@
package com.android.launcher3.util
-import android.content.Context
import android.hardware.display.DisplayManager
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
@@ -63,16 +62,4 @@
onDestroy()
}
}
-
- @Test
- fun testGetObject_objectCreatesDisplayContext_isSandboxed() {
- class TestSingleton(context: Context) : SafeCloseable {
- override fun close() = Unit
-
- val displayContext = context.createDisplayContext(display)
- }
-
- val displayContext = MainThreadInitializedObject { TestSingleton(it) }[app].displayContext
- assertThat(displayContext.applicationContext).isEqualTo(app)
- }
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
index 393282f..acd17d1 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
@@ -18,10 +18,9 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
-
import android.content.ContextWrapper;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -29,6 +28,7 @@
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.WidgetsFilterDataProvider;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.popup.PopupDataProvider;
@@ -44,7 +44,7 @@
* There are 2 constructors in this class. The base context can be {@link SandboxContext} or
* Instrumentation target context.
* Using {@link SandboxContext} as base context allows custom implementations for
- * MainThreadInitializedObject providers.
+ * providing objects in Dagger components.
*/
public class TestSandboxModelContextWrapper extends ActivityContextWrapper implements
@@ -57,9 +57,9 @@
protected ActivityAllAppsContainerView<ActivityContextWrapper> mAppsView;
- private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
+ private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(this);
private final WidgetPickerDataProvider mWidgetPickerDataProvider =
- new WidgetPickerDataProvider();
+ new WidgetPickerDataProvider(new WidgetsFilterDataProvider());
protected final UserCache mUserCache;
public TestSandboxModelContextWrapper(SandboxContext base) {
@@ -80,7 +80,7 @@
mAllAppsStore = mAppsView.getAppsStore();
}
- @Nullable
+ @NonNull
@Override
public PopupDataProvider getPopupDataProvider() {
return mPopupDataProvider;
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt
index 1da74cb..c1827bc 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt
@@ -40,6 +40,7 @@
import com.android.launcher3.widget.model.WidgetsListHeaderEntry
import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider.WidgetPickerDataChangeListener
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -72,14 +73,14 @@
private lateinit var appWidgetItem: WidgetItem
- private var underTest = WidgetPickerDataProvider()
+ private lateinit var underTest: WidgetPickerDataProvider
@Before
fun setUp() {
userHandle = UserHandle.CURRENT
context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
testInvariantProfile = LauncherAppState.getIDP(context)
-
+ underTest = WidgetPickerDataProvider(context)
doAnswer { invocation: InvocationOnMock ->
val componentWithLabel = invocation.getArgument<Any>(0) as CachedObject
componentWithLabel.getComponent().shortClassName
@@ -90,6 +91,11 @@
appWidgetItem = createWidgetItem()
}
+ @After
+ fun tearDown() {
+ underTest.destroy()
+ }
+
@Test
fun setWidgets_invokesTheListener_andUpdatedWidgetsAvailable() {
assertThat(underTest.get().allWidgets).isEmpty()
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index f490bd6..95d5076 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -36,6 +36,7 @@
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.rule.ScreenRecordRule;
import org.junit.Test;
@@ -126,6 +127,7 @@
* Adds three icons to the workspace and removes one of them by dragging to uninstall.
*/
@Test
+ @ScreenRecordRule.ScreenRecord // b/399756302
@PlatinumTest(focusArea = "launcher")
public void uninstallWorkspaceIcon() throws IOException {
Point[] gridPositions = TestUtil.getCornersAndCenterPositions(mLauncher);
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index 582cf3c..246219f 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -48,7 +48,6 @@
import dagger.BindsInstance
import dagger.Component
import java.util.concurrent.CountDownLatch
-import java.util.function.Predicate
import junit.framework.Assert.assertEquals
import org.junit.After
import org.junit.Before
@@ -96,7 +95,6 @@
@Mock private lateinit var modelDelegate: ModelDelegate
@Mock private lateinit var launcherBinder: BaseLauncherBinder
private lateinit var launcherModel: LauncherModel
- @Mock private lateinit var widgetsFilterDataProvider: WidgetsFilterDataProvider
@Mock private lateinit var transaction: LoaderTransaction
@Mock private lateinit var iconCache: IconCache
@Mock private lateinit var idleLock: LooperIdleLock
@@ -130,7 +128,6 @@
`when`(launcherBinder.newIdleLock(any())).thenReturn(idleLock)
`when`(idleLock.awaitLocked(1000)).thenReturn(false)
`when`(iconCache.getUpdateHandler()).thenReturn(iconCacheUpdateHandler)
- `when`(widgetsFilterDataProvider.getDefaultWidgetsFilter()).thenReturn(Predicate { true })
context.initDaggerComponent(
DaggerLoaderTaskTest_TestComponent.builder()
.bindUserCache(userCache)
@@ -160,14 +157,7 @@
val mockUserHandles = arrayListOf<UserHandle>(MAIN_HANDLE)
`when`(userCache.userProfiles).thenReturn(mockUserHandles)
`when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 1))
- LoaderTask(
- app,
- bgAllAppsList,
- this,
- modelDelegate,
- launcherBinder,
- widgetsFilterDataProvider,
- )
+ LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder)
.runSyncOnBackgroundThread()
Truth.assertThat(
itemsIdMap
@@ -188,19 +178,11 @@
)
.isAtLeast(8)
Truth.assertThat(itemsIdMap.size()).isAtLeast(40)
- Truth.assertThat(widgetsModel.defaultWidgetsFilter).isNotNull()
}
@Test
fun bindsLoadedDataCorrectly() {
- LoaderTask(
- app,
- bgAllAppsList,
- BgDataModel(),
- modelDelegate,
- launcherBinder,
- widgetsFilterDataProvider,
- )
+ LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
.runSyncOnBackgroundThread()
verify(launcherBinder).bindWorkspace(true, false)
@@ -209,7 +191,6 @@
verify(launcherBinder).bindAllApps()
verify(iconCacheUpdateHandler, times(4)).updateIcons(any(), any<CachingLogic<Any>>(), any())
verify(launcherBinder).bindDeepShortcuts()
- verify(widgetsFilterDataProvider).initPeriodicDataRefresh(any())
verify(launcherBinder).bindWidgets()
verify(modelDelegate).loadAndBindOtherItems(anyOrNull())
verify(iconCacheUpdateHandler).finish()
@@ -227,15 +208,7 @@
`when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true)
`when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 1))
- LoaderTask(
- app,
- bgAllAppsList,
- this,
- modelDelegate,
- launcherBinder,
- widgetsFilterDataProvider,
- userManagerState,
- )
+ LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder, userManagerState)
.runSyncOnBackgroundThread()
verify(bgAllAppsList)
@@ -256,15 +229,7 @@
`when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true)
`when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 3))
- LoaderTask(
- app,
- bgAllAppsList,
- this,
- modelDelegate,
- launcherBinder,
- widgetsFilterDataProvider,
- userManagerState,
- )
+ LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder, userManagerState)
.runSyncOnBackgroundThread()
verify(bgAllAppsList)
@@ -303,14 +268,7 @@
RestoreDbTask.setPending(spyContext)
// When
- LoaderTask(
- app,
- bgAllAppsList,
- BgDataModel(),
- modelDelegate,
- launcherBinder,
- widgetsFilterDataProvider,
- )
+ LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
.runSyncOnBackgroundThread()
// Then
@@ -378,14 +336,7 @@
Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 0)
// When
- LoaderTask(
- app,
- bgAllAppsList,
- BgDataModel(),
- modelDelegate,
- launcherBinder,
- widgetsFilterDataProvider,
- )
+ LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
.runSyncOnBackgroundThread()
// Then
@@ -424,14 +375,7 @@
RestoreDbTask.setPending(spyContext)
// When
- LoaderTask(
- app,
- bgAllAppsList,
- BgDataModel(),
- modelDelegate,
- launcherBinder,
- widgetsFilterDataProvider,
- )
+ LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
.runSyncOnBackgroundThread()
// Then
@@ -470,14 +414,7 @@
RestoreDbTask.setPending(spyContext)
// When
- LoaderTask(
- app,
- bgAllAppsList,
- BgDataModel(),
- modelDelegate,
- launcherBinder,
- widgetsFilterDataProvider,
- )
+ LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
.runSyncOnBackgroundThread()
// Then
@@ -507,15 +444,7 @@
)
val expectedAppInfo = AppInfo().apply { componentName = expectedComponent }
// When
- val loader =
- LoaderTask(
- app,
- bgAllAppsList,
- BgDataModel(),
- modelDelegate,
- launcherBinder,
- widgetsFilterDataProvider,
- )
+ val loader = LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
val actualIconRequest =
loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
// Then
@@ -545,15 +474,7 @@
)
val expectedAppInfo = AppInfo().apply { componentName = expectedComponent }
// When
- val loader =
- LoaderTask(
- app,
- bgAllAppsList,
- BgDataModel(),
- modelDelegate,
- launcherBinder,
- widgetsFilterDataProvider,
- )
+ val loader = LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
val actualIconRequest =
loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
// Then
@@ -584,15 +505,7 @@
val expectedAppInfo =
AppInfo().apply { componentName = ComponentName("differentPkg", "differentClass") }
// When
- val loader =
- LoaderTask(
- app,
- bgAllAppsList,
- BgDataModel(),
- modelDelegate,
- launcherBinder,
- widgetsFilterDataProvider,
- )
+ val loader = LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
val actualIconRequest =
loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
// Then
@@ -619,15 +532,7 @@
)
val expectedAppInfo = AppInfo()
// When
- val loader =
- LoaderTask(
- app,
- bgAllAppsList,
- BgDataModel(),
- modelDelegate,
- launcherBinder,
- widgetsFilterDataProvider,
- )
+ val loader = LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
val actualIconRequest =
loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
// Then
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index 124c18f..41685d7 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -97,19 +97,22 @@
}
private void evaluateInLandscape() throws Throwable {
- if (Flags.oneGridSpecs()
- && WindowManagerProxy.INSTANCE.get(mTest.mTargetContext)
- .isTaskbarDrawnInProcess()) {
- mTest.executeOnLauncher(launcher -> LauncherPrefs.get(launcher)
- .put(FIXED_LANDSCAPE_MODE, true)
- );
- }
+ mTest.executeOnLauncher(launcher -> LauncherPrefs.get(launcher)
+ .put(FIXED_LANDSCAPE_MODE, shouldHaveFixedLandscape(launcher)));
mTest.mDevice.setOrientationLeft();
mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90);
AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher, true);
base.evaluate();
mTest.getDevice().pressHome();
}
+
+ private boolean shouldHaveFixedLandscape(Launcher launcher) {
+ return Flags.oneGridSpecs()
+ && !launcher.getDeviceProfile().isTablet
+ && !launcher.getDeviceProfile().isMultiDisplay
+ && WindowManagerProxy.INSTANCE.get(mTest.mTargetContext)
+ .isTaskbarDrawnInProcess();
+ }
};
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 16faf14..e0d2f39 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -929,7 +929,11 @@
waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
- waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
+ if (isTaskbarShownOnHome()) {
+ waitForSystemLauncherObject(TASKBAR_RES_ID);
+ } else {
+ waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
+ }
return waitForLauncherObject(WORKSPACE_RES_ID);
}
@@ -947,7 +951,9 @@
waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
waitUntilLauncherObjectGone(WIDGETS_RES_ID);
waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
- waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
+ if (isTransientTaskbar()) {
+ waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
+ }
waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
@@ -959,7 +965,8 @@
waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
- if (is3PLauncher() && isTablet() && !isTransientTaskbar()) {
+ if ((is3PLauncher() && isTablet() && !isTransientTaskbar())
+ || isTaskbarShownOnHome()) {
waitForSystemLauncherObject(TASKBAR_RES_ID);
} else {
waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
@@ -2421,6 +2428,12 @@
.getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ /** Whether taskbar will be shown on home for current default display. */
+ public boolean isTaskbarShownOnHome() {
+ return getTestInfo(TestProtocol.REQUEST_TASKBAR_SHOWN_ON_HOME).getBoolean(
+ TEST_INFO_RESPONSE_FIELD);
+ }
+
public boolean isImeDocked() {
return getTestInfo(TestProtocol.REQUEST_TASKBAR_IME_DOCKED).getBoolean(
TestProtocol.TEST_INFO_RESPONSE_FIELD);
diff --git a/tests/tapl/com/android/launcher3/tapl/Taskbar.java b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
index b4aaab7..d4e6d31 100644
--- a/tests/tapl/com/android/launcher3/tapl/Taskbar.java
+++ b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
@@ -115,6 +115,23 @@
}
}
+ /**
+ * Opens the Home all apps page by clicking the taskbar all apps icon. To be used to open all
+ * apps when taskbar is visible on home.
+ */
+ public HomeAllApps openAllAppsOnHome() {
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "want to open home all apps from taskbar");
+ LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+
+ mLauncher.clickLauncherObject(mLauncher.waitForObjectInContainer(
+ mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID),
+ getAllAppsButtonSelector()));
+
+ return mLauncher.getAllApps();
+ }
+ }
+
/** Opens the Taskbar all apps page with the meta keyboard shortcut. */
public TaskbarAllApps openAllAppsFromKeyboardShortcut() {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {