Snap for 12304452 from a4ce092bea to 24Q4-release

Change-Id: I5217a8dc04051904a2ae9546a6c0c5cc215c4ed6
This commit is contained in:
Android Build Coastguard Worker
2024-08-31 01:21:00 +00:00
89 changed files with 1801 additions and 941 deletions
+3 -3
View File
@@ -221,7 +221,7 @@ launcher_compose_tests_java_defaults {
android_library {
name: "launcher-aosp-tapl",
libs: [
"framework-statsd",
"framework-statsd.stubs.module_lib",
],
static_libs: [
"androidx.annotation_annotation",
@@ -388,7 +388,7 @@ android_library {
"quickstep/res",
],
libs: [
"framework-statsd",
"framework-statsd.stubs.module_lib",
],
static_libs: [
"Launcher3ResLib",
@@ -453,7 +453,7 @@ android_library {
],
resource_dirs: [],
libs: [
"framework-statsd",
"framework-statsd.stubs.module_lib",
],
// Note the ordering here is important when it comes to resource
// overriding. We want the most specific resource overrides defined
@@ -15,8 +15,7 @@
-->
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
<solid android:color="?attr/materialColorSurfaceBright" />
<corners android:radius="@dimen/keyboard_quick_switch_task_view_radius" />
<solid android:color="@android:color/white" />
<corners android:radius="@dimen/keyboard_quick_switch_text_button_radius" />
</shape>
@@ -18,8 +18,8 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="yes"
android:background="@drawable/keyboard_quick_switch_task_view_background"
android:clipToOutline="true"
@@ -27,8 +27,8 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
@@ -52,7 +52,7 @@
android:layout_width="0dp"
android:layout_height="match_parent"
android:visibility="gone"
android:layout_marginStart="@dimen/keyboard_quick_switch_split_view_spacing"
android:layout_marginStart="@dimen/keyboard_quick_switch_view_small_spacing"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="yes"
android:background="@drawable/keyboard_quick_switch_task_view_background"
android:clipToOutline="true"
launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
android:layout_width="0dp"
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<include
layout="@layout/keyboard_quick_switch_taskview_thumbnail"
android:id="@+id/thumbnail_1"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/thumbnail_2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<include
layout="@layout/keyboard_quick_switch_taskview_thumbnail"
android:id="@+id/thumbnail_2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"
android:layout_marginTop="@dimen/keyboard_quick_switch_view_small_spacing"
app:layout_constraintTop_toBottomOf="@id/thumbnail_1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<ImageView
android:id="@+id/icon_1"
android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
app:layout_constraintTop_toTopOf="@id/thumbnail_1"
app:layout_constraintBottom_toBottomOf="@id/thumbnail_1"
app:layout_constraintStart_toStartOf="@id/thumbnail_1"
app:layout_constraintEnd_toEndOf="@id/thumbnail_1"/>
<ImageView
android:id="@+id/icon_2"
android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
android:importantForAccessibility="no"
android:visibility="gone"
android:scaleType="centerCrop"
app:layout_constraintTop_toTopOf="@id/thumbnail_2"
app:layout_constraintBottom_toBottomOf="@id/thumbnail_2"
app:layout_constraintStart_toStartOf="@id/thumbnail_2"
app:layout_constraintEnd_toEndOf="@id/thumbnail_2"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
@@ -18,17 +18,20 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipToOutline="true"
android:importantForAccessibility="yes"
launcher:focusBorderColor="?attr/materialColorOutline">
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:focusBorderRadius="@dimen/keyboard_quick_switch_text_button_radius">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/keyboard_quick_switch_overview_button_background"
android:layout_width="@dimen/keyboard_quick_switch_text_button_width"
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
android:background="@drawable/keyboard_quick_switch_text_button_background"
android:backgroundTint="?androidprv:attr/materialColorSurfaceContainer"
android:paddingHorizontal="@dimen/keyboard_quick_switch_text_button_horizontal_padding"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
@@ -37,10 +40,11 @@
<ImageView
android:id="@+id/icon"
android:layout_width="@dimen/keyboard_quick_switch_recents_icon_size"
android:layout_height="@dimen/keyboard_quick_switch_recents_icon_size"
android:layout_marginBottom="8dp"
android:tint="?attr/materialColorOnSurface"
android:layout_width="@dimen/keyboard_quick_switch_desktop_icon_size"
android:layout_height="@dimen/keyboard_quick_switch_desktop_icon_size"
android:layout_marginBottom="4dp"
android:tint="?androidprv:attr/materialColorOnSurface"
android:src="@drawable/ic_desktop"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
@@ -49,9 +53,9 @@
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
style="@style/KeyboardQuickSwitchText"
style="@style/KeyboardQuickSwitchText.OnTaskView"
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipToOutline="true"
android:importantForAccessibility="yes"
launcher:focusBorderColor="?androidprv:attr/materialColorOutline"
launcher:focusBorderRadius="@dimen/keyboard_quick_switch_text_button_radius">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
android:layout_width="@dimen/keyboard_quick_switch_text_button_width"
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
android:background="@drawable/keyboard_quick_switch_text_button_background"
android:backgroundTint="?androidprv:attr/materialColorSurfaceBright"
android:paddingHorizontal="@dimen/keyboard_quick_switch_text_button_horizontal_padding"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
style="@style/KeyboardQuickSwitchText.LargeText"
android:id="@+id/large_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="center"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/small_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
style="@style/KeyboardQuickSwitchText.OnTaskView"
android:id="@+id/small_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
app:layout_constraintTop_toBottomOf="@id/large_text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
@@ -18,8 +18,8 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="yes"
android:background="@drawable/keyboard_quick_switch_task_view_background"
android:clipToOutline="true"
@@ -27,8 +27,8 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="@dimen/keyboard_quick_switch_taskview_width"
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
@@ -52,7 +52,7 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"
android:layout_marginTop="@dimen/keyboard_quick_switch_split_view_spacing"
android:layout_marginTop="@dimen/keyboard_quick_switch_view_small_spacing"
app:layout_constraintTop_toBottomOf="@id/thumbnail_1"
app:layout_constraintBottom_toBottomOf="parent"
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 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.
-->
<com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="yes"
android:background="@drawable/keyboard_quick_switch_task_view_background"
android:clipToOutline="true"
launcher:focusBorderColor="?androidprv:attr/materialColorOutline">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
android:layout_width="0dp"
android:layout_height="@dimen/keyboard_quick_switch_taskview_height"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<include
layout="@layout/keyboard_quick_switch_taskview_thumbnail"
android:id="@+id/thumbnail_1"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/thumbnail_2"/>
<include
layout="@layout/keyboard_quick_switch_taskview_thumbnail"
android:id="@+id/thumbnail_2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:visibility="gone"
android:layout_marginStart="@dimen/keyboard_quick_switch_view_small_spacing"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/thumbnail_1"
app:layout_constraintEnd_toEndOf="parent"/>
<ImageView
android:id="@+id/icon_1"
android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
app:layout_constraintTop_toTopOf="@id/thumbnail_1"
app:layout_constraintBottom_toBottomOf="@id/thumbnail_1"
app:layout_constraintStart_toStartOf="@id/thumbnail_1"
app:layout_constraintEnd_toEndOf="@id/thumbnail_1"/>
<ImageView
android:id="@+id/icon_2"
android:layout_width="@dimen/keyboard_quick_switch_taskview_icon_size"
android:layout_height="@dimen/keyboard_quick_switch_taskview_icon_size"
android:importantForAccessibility="no"
android:visibility="gone"
android:scaleType="centerCrop"
app:layout_constraintTop_toTopOf="@id/thumbnail_2"
app:layout_constraintBottom_toBottomOf="@id/thumbnail_2"
app:layout_constraintStart_toStartOf="@id/thumbnail_2"
app:layout_constraintEnd_toEndOf="@id/thumbnail_2"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
+2 -2
View File
@@ -83,7 +83,7 @@
<dimen name="taskbar_suw_frame">96dp</dimen>
<dimen name="taskbar_suw_insets">24dp</dimen>
<dimen name="keyboard_quick_switch_taskview_width">205dp</dimen>
<dimen name="keyboard_quick_switch_taskview_height">119dp</dimen>
<!-- Keyboard Quick Switch -->
<dimen name="keyboard_quick_switch_taskview_width">217.6dp</dimen>
</resources>
+1 -10
View File
@@ -14,16 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<style name="AllSetTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<resources>
<style name="TextAppearance.GestureTutorial.MainTitle.Home"
parent="TextAppearance.GestureTutorial.MainTitle">
+1
View File
@@ -28,6 +28,7 @@
<!-- Border color for a keyboard quick switch task views -->
<attr name="focusBorderColor" format="color" />
<attr name="hoverBorderColor" format="color" />
<attr name="focusBorderRadius" format="dimension" />
</declare-styleable>
<declare-styleable name="ClearAllButton">
+8 -2
View File
@@ -437,6 +437,7 @@
<dimen name="bubblebar_stashed_handle_width">55dp</dimen>
<dimen name="bubblebar_stashed_size">@dimen/transient_taskbar_stashed_height</dimen>
<dimen name="bubblebar_stashed_handle_height">@dimen/taskbar_stashed_handle_height</dimen>
<dimen name="bubblebar_stashed_handle_spring_velocity_dp_per_s">@dimen/transient_taskbar_stash_spring_velocity_dp_per_s</dimen>
<!-- this is a pointer height minus 1dp to ensure the pointer overlaps with the bubblebar
background appropriately when close to the rounded corner -->
<dimen name="bubblebar_pointer_visible_size">9dp</dimen>
@@ -487,17 +488,22 @@
<!-- Keyboard Quick Switch -->
<dimen name="keyboard_quick_switch_border_width">4dp</dimen>
<dimen name="keyboard_quick_switch_taskview_width">104dp</dimen>
<dimen name="keyboard_quick_switch_taskview_height">134dp</dimen>
<dimen name="keyboard_quick_switch_taskview_height">136dp</dimen>
<dimen name="keyboard_quick_switch_taskview_icon_size">52dp</dimen>
<dimen name="keyboard_quick_switch_recents_icon_size">20dp</dimen>
<dimen name="keyboard_quick_switch_desktop_icon_size">32dp</dimen>
<dimen name="keyboard_quick_switch_margin_top">56dp</dimen>
<dimen name="keyboard_quick_switch_margin_ends">16dp</dimen>
<dimen name="keyboard_quick_switch_view_spacing">16dp</dimen>
<dimen name="keyboard_quick_switch_split_view_spacing">2dp</dimen>
<dimen name="keyboard_quick_switch_view_small_spacing">4dp</dimen>
<dimen name="keyboard_quick_switch_view_radius">28dp</dimen>
<dimen name="keyboard_quick_switch_task_view_radius">16dp</dimen>
<dimen name="keyboard_quick_switch_no_recent_items_icon_size">24dp</dimen>
<dimen name="keyboard_quick_switch_no_recent_items_icon_margin">8dp</dimen>
<dimen name="keyboard_quick_switch_text_button_width">104dp</dimen>
<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>
<!-- Digital Wellbeing -->
<dimen name="digital_wellbeing_toast_height">48dp</dimen>
+5 -8
View File
@@ -327,17 +327,14 @@
<!-- 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&#47;right</string>
<!-- Label for quick switch tile showing how many more apps are available [CHAR LIMIT=NONE] -->
<!-- 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,
=1{Show # more app.}
other{Show # more apps.}
=1{more app}
other{more apps}
}</string>
<!-- Label for quick switch tile showing how many apps are available in desktop mode [CHAR LIMIT=NONE] -->
<string name="quick_switch_desktop">{count, plural,
=1{Show # desktop app.}
other{Show # desktop apps.}
}</string>
<!-- Label for quick switch tile to launch desktop mode [CHAR LIMIT=NONE] -->
<string name="quick_switch_desktop">Desktop</string>
<!-- 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>
+13 -1
View File
@@ -205,7 +205,7 @@
<item name="android:textColor">?attr/tutorialSubtitle</item>
</style>
<style name="AllSetTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
<style name="AllSetTheme" parent="@style/DynamicColorsBaseLauncherTheme.NoActionBar">
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:enforceNavigationBarContrast">false</item>
@@ -272,10 +272,22 @@
<item name="lineHeight">20sp</item>
</style>
<style name="KeyboardQuickSwitchText.LargeText" parent="KeyboardQuickSwitchText">
<item name="android:textSize">32sp</item>
</style>
<style name="KeyboardQuickSwitchText.OnBackground" parent="KeyboardQuickSwitchText">
<item name="android:textColor">?attr/materialColorOnSurface</item>
</style>
<style name="KeyboardQuickSwitchText.OnTaskView" parent="KeyboardQuickSwitchText">
<item name="android:requiresFadingEdge">horizontal</item>
<item name="android:fadeScrollbars">false</item>
<item name="android:fadingEdgeLength">20dp</item>
<item name="android:ellipsize">none</item>
<item name="android:singleLine">true</item>
</style>
<style name="GestureTutorialActivity" parent="@style/AppTheme">
<item name="background">@android:color/transparent</item>
<item name="tutorialSubtitle">@android:color/black</item>
@@ -65,7 +65,6 @@ import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VAL
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
import static com.android.quickstep.util.AnimUtils.clampToDuration;
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
@@ -1228,9 +1227,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
* Registers remote animations used when closing apps to home screen.
*/
public void registerRemoteTransitions() {
if (ENABLE_SHELL_TRANSITIONS) {
SystemUiProxy.INSTANCE.get(mLauncher).shareTransactionQueue();
}
SystemUiProxy.INSTANCE.get(mLauncher).shareTransactionQueue();
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
}
@@ -1294,9 +1291,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
protected void unregisterRemoteTransitions() {
if (ENABLE_SHELL_TRANSITIONS) {
SystemUiProxy.INSTANCE.get(mLauncher).unshareTransactionQueue();
}
SystemUiProxy.INSTANCE.get(mLauncher).unshareTransactionQueue();
if (SEPARATE_RECENTS_ACTIVITY.get()) {
return;
}
@@ -20,7 +20,6 @@ import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
import static com.android.launcher3.Flags.enablePredictiveBackGesture;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -170,11 +169,6 @@ public class WidgetPickerActivity extends BaseActivity {
@Override
protected void registerBackDispatcher() {
if (!enablePredictiveBackGesture()) {
super.registerBackDispatcher();
return;
}
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
new BackAnimationCallback());
@@ -29,6 +29,7 @@ import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LayoutUtils;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -66,6 +67,9 @@ public final class KeyboardQuickSwitchController implements
@Nullable private KeyboardQuickSwitchViewController mQuickSwitchViewController;
private boolean mHasDesktopTask = false;
private boolean mWasDesktopTaskFilteredOut = false;
/** Initialize the controller. */
public void init(@NonNull TaskbarControllers controllers) {
mControllers = controllers;
@@ -126,11 +130,15 @@ public final class KeyboardQuickSwitchController implements
/* updateTasks= */ false,
currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
? 0 : currentFocusedIndex,
onDesktop);
onDesktop,
mHasDesktopTask,
mWasDesktopTaskFilteredOut);
return;
}
mTaskListChangeId = mModel.getTasks((tasks) -> {
mHasDesktopTask = false;
mWasDesktopTaskFilteredOut = false;
if (onDesktop) {
processLoadedTasksOnDesktop(tasks);
} else {
@@ -144,7 +152,9 @@ public final class KeyboardQuickSwitchController implements
/* updateTasks= */ true,
currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
? 0 : currentFocusedIndex,
onDesktop);
onDesktop,
mHasDesktopTask,
mWasDesktopTaskFilteredOut);
});
}
@@ -152,9 +162,22 @@ public final class KeyboardQuickSwitchController implements
// Only store MAX_TASK tasks, from most to least recent
Collections.reverse(tasks);
mTasks = tasks.stream()
.filter(task -> !(task instanceof DesktopTask))
.limit(MAX_TASKS)
.collect(Collectors.toList());
mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS);
for (int i = 0; i < tasks.size(); i++) {
if (tasks.get(i) instanceof DesktopTask) {
mHasDesktopTask = true;
if (i < mTasks.size()) {
mWasDesktopTaskFilteredOut = true;
}
break;
}
}
mNumHiddenTasks = Math.max(0,
tasks.size() - (mWasDesktopTaskFilteredOut ? 1 : 0) - MAX_TASKS);
}
private void processLoadedTasksOnDesktop(List<GroupTask> tasks) {
@@ -214,6 +237,8 @@ public final class KeyboardQuickSwitchController implements
pw.println(prefix + "\tisOpen=" + (mQuickSwitchViewController != null));
pw.println(prefix + "\tmNumHiddenTasks=" + mNumHiddenTasks);
pw.println(prefix + "\tmTaskListChangeId=" + mTaskListChangeId);
pw.println(prefix + "\tmHasDesktopTask=" + mHasDesktopTask);
pw.println(prefix + "\tmWasDesktopTaskFilteredOut=" + mWasDesktopTaskFilteredOut);
pw.println(prefix + "\tmTasks=[");
for (GroupTask task : mTasks) {
Task task1 = task.task1;
@@ -235,10 +260,6 @@ public final class KeyboardQuickSwitchController implements
class ControllerCallbacks {
int getTaskCount() {
return mTasks.size() + (mNumHiddenTasks == 0 ? 0 : 1);
}
@Nullable
GroupTask getTaskAt(int index) {
return index < 0 || index >= mTasks.size() ? null : mTasks.get(index);
@@ -279,5 +300,10 @@ public final class KeyboardQuickSwitchController implements
boolean isFirstTaskRunning() {
return isTaskRunning(getTaskAt(0));
}
boolean isAspectRatioSquare() {
return mControllers != null && LayoutUtils.isAspectRatioSquare(
mControllers.taskbarActivityContext.getDeviceProfile().aspectRatio);
}
}
}
@@ -50,8 +50,10 @@ import kotlin.Unit;
public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
private static final float THUMBNAIL_BLUR_RADIUS = 1f;
private static final int INVALID_BORDER_RADIUS = -1;
@ColorInt private final int mBorderColor;
@ColorInt private final int mBorderRadius;
@Nullable private BorderAnimator mBorderAnimator;
@@ -87,6 +89,8 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
mBorderColor = ta.getColor(
R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR);
mBorderRadius = ta.getDimensionPixelSize(
R.styleable.TaskView_focusBorderRadius, INVALID_BORDER_RADIUS);
ta.recycle();
}
@@ -103,8 +107,10 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
Preconditions.assertNotNull(mContent);
mBorderAnimator = BorderAnimator.createScalingBorderAnimator(
/* borderRadiusPx= */ resources.getDimensionPixelSize(
R.dimen.keyboard_quick_switch_task_view_radius),
/* borderRadiusPx= */ mBorderRadius != INVALID_BORDER_RADIUS
? mBorderRadius
: resources.getDimensionPixelSize(
R.dimen.keyboard_quick_switch_task_view_radius),
/* borderWidthPx= */ resources.getDimensionPixelSize(
R.dimen.keyboard_quick_switch_border_width),
/* boundsBuilder= */ bounds -> {
@@ -36,14 +36,12 @@ import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.res.ResourcesCompat;
import com.android.app.animation.Interpolators;
import com.android.internal.jank.Cuj;
@@ -52,9 +50,7 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import java.util.HashMap;
@@ -102,9 +98,13 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
private int mTaskViewWidth;
private int mTaskViewHeight;
private int mSpacing;
private int mSmallSpacing;
private int mOutlineRadius;
private boolean mIsRtl;
private int mOverviewTaskIndex = -1;
private int mDesktopTaskIndex = -1;
@Nullable private AnimatorSet mOpenAnimation;
@Nullable private KeyboardQuickSwitchViewController.ViewCallbacks mViewCallbacks;
@@ -141,6 +141,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
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);
mIsRtl = Utilities.isRtl(resources);
}
@@ -148,6 +150,7 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
private KeyboardQuickSwitchTaskView createAndAddTaskView(
int index,
boolean isFinalView,
boolean useSmallStartSpacing,
@LayoutRes int resId,
@NonNull LayoutInflater layoutInflater,
@Nullable View previousView) {
@@ -156,7 +159,7 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
taskView.setId(View.generateViewId());
taskView.setOnClickListener(v -> mViewCallbacks.launchTaskAt(index));
LayoutParams lp = new LayoutParams(mTaskViewWidth, mTaskViewHeight);
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
// Create a left-to-right ordering of views (or right-to-left in RTL locales)
if (previousView != null) {
lp.startToEnd = previousView.getId();
@@ -166,7 +169,7 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
lp.topToTop = PARENT_ID;
lp.bottomToBottom = PARENT_ID;
// Add spacing between views
lp.setMarginStart(mSpacing);
lp.setMarginStart(useSmallStartSpacing ? mSmallSpacing : mSpacing);
if (isFinalView) {
// Add spacing to the end of the final view so that scrolling ends with some padding.
lp.endToEnd = PARENT_ID;
@@ -185,7 +188,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
int numHiddenTasks,
boolean updateTasks,
int currentFocusIndexOverride,
@NonNull KeyboardQuickSwitchViewController.ViewCallbacks viewCallbacks) {
@NonNull KeyboardQuickSwitchViewController.ViewCallbacks viewCallbacks,
boolean useDesktopTaskView) {
mViewCallbacks = viewCallbacks;
Resources resources = context.getResources();
Resources.Theme theme = context.getTheme();
@@ -197,57 +201,62 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
GroupTask groupTask = groupTasks.get(i);
KeyboardQuickSwitchTaskView currentTaskView = createAndAddTaskView(
i,
/* isFinalView= */ i == tasksToDisplay - 1 && numHiddenTasks == 0,
groupTask instanceof DesktopTask
? R.layout.keyboard_quick_switch_textonly_taskview
/* isFinalView= */ i == tasksToDisplay - 1
&& numHiddenTasks == 0 && !useDesktopTaskView,
/* useSmallStartSpacing= */ false,
mViewCallbacks.isAspectRatioSquare()
? R.layout.keyboard_quick_switch_taskview_square
: R.layout.keyboard_quick_switch_taskview,
layoutInflater,
previousTaskView);
if (groupTask instanceof DesktopTask desktopTask) {
HashMap<String, Integer> args = new HashMap<>();
args.put("count", desktopTask.tasks.size());
final boolean firstTaskIsLeftTopTask =
groupTask.mSplitBounds == null
|| groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id
|| groupTask.task2 == null;
currentTaskView.setThumbnails(
firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2,
firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1,
updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
updateTasks ? mViewCallbacks::updateIconInBackground : null);
currentTaskView.<ImageView>findViewById(R.id.icon).setImageDrawable(
ResourcesCompat.getDrawable(resources, R.drawable.ic_desktop, theme));
currentTaskView.<TextView>findViewById(R.id.text).setText(new MessageFormat(
resources.getString(R.string.quick_switch_desktop),
Locale.getDefault()).format(args));
} else {
final boolean firstTaskIsLeftTopTask =
groupTask.mSplitBounds == null
|| groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
final Task leftTopTask = firstTaskIsLeftTopTask
? groupTask.task1 : groupTask.task2;
final Task rightBottomTask = firstTaskIsLeftTopTask
? groupTask.task2 : groupTask.task1;
currentTaskView.setThumbnails(
leftTopTask,
rightBottomTask,
updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
updateTasks ? mViewCallbacks::updateIconInBackground : null);
}
previousTaskView = currentTaskView;
}
if (numHiddenTasks > 0) {
HashMap<String, Integer> args = new HashMap<>();
args.put("count", numHiddenTasks);
mOverviewTaskIndex = getTaskCount();
View overviewButton = createAndAddTaskView(
MAX_TASKS,
/* isFinalView= */ true,
R.layout.keyboard_quick_switch_textonly_taskview,
mOverviewTaskIndex,
/* isFinalView= */ !useDesktopTaskView,
/* useSmallStartSpacing= */ false,
R.layout.keyboard_quick_switch_overview_taskview,
layoutInflater,
previousTaskView);
overviewButton.<ImageView>findViewById(R.id.icon).setImageDrawable(
ResourcesCompat.getDrawable(resources, R.drawable.view_carousel, theme));
overviewButton.<TextView>findViewById(R.id.text).setText(new MessageFormat(
overviewButton.<TextView>findViewById(R.id.large_text).setText(
String.format(Locale.getDefault(), "%d", numHiddenTasks));
overviewButton.<TextView>findViewById(R.id.small_text).setText(new MessageFormat(
resources.getString(R.string.quick_switch_overflow),
Locale.getDefault()).format(args));
previousTaskView = overviewButton;
}
mDisplayingRecentTasks = !groupTasks.isEmpty();
if (useDesktopTaskView) {
mDesktopTaskIndex = getTaskCount();
View desktopButton = createAndAddTaskView(
mDesktopTaskIndex,
/* isFinalView= */ true,
/* useSmallStartSpacing= */ numHiddenTasks > 0,
R.layout.keyboard_quick_switch_desktop_taskview,
layoutInflater,
previousTaskView);
desktopButton.<TextView>findViewById(R.id.text).setText(
resources.getString(R.string.quick_switch_desktop));
}
mDisplayingRecentTasks = !groupTasks.isEmpty() || useDesktopTaskView;
getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@@ -260,6 +269,14 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
});
}
int getOverviewTaskIndex() {
return mOverviewTaskIndex;
}
int getDesktopTaskIndex() {
return mDesktopTaskIndex;
}
protected Animator getCloseAnimation() {
AnimatorSet closeAnimation = new AnimatorSet();
@@ -370,7 +387,7 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
}
});
animateFocusMove(-1, Math.min(
mContent.getChildCount() - 1,
getTaskCount() - 1,
currentFocusIndexOverride == -1 ? 1 : currentFocusIndexOverride));
displayedContent.setVisibility(VISIBLE);
setVisibility(VISIBLE);
@@ -577,7 +594,11 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
@Nullable
protected KeyboardQuickSwitchTaskView getTaskAt(int index) {
return !mDisplayingRecentTasks || index < 0 || index >= mContent.getChildCount()
return !mDisplayingRecentTasks || index < 0 || index >= getTaskCount()
? null : (KeyboardQuickSwitchTaskView) mContent.getChildAt(index);
}
public int getTaskCount() {
return mContent.getChildCount();
}
}
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.taskbar;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.view.KeyEvent;
@@ -28,6 +30,7 @@ import com.android.internal.jank.Cuj;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.SlideInRemoteTransition;
import com.android.systemui.shared.recents.model.Task;
@@ -56,6 +59,7 @@ public class KeyboardQuickSwitchViewController {
private int mCurrentFocusIndex = -1;
private boolean mOnDesktop;
private boolean mWasDesktopTaskFilteredOut;
protected KeyboardQuickSwitchViewController(
@NonNull TaskbarControllers controllers,
@@ -77,9 +81,12 @@ public class KeyboardQuickSwitchViewController {
int numHiddenTasks,
boolean updateTasks,
int currentFocusIndexOverride,
boolean onDesktop) {
boolean onDesktop,
boolean hasDesktopTask,
boolean wasDesktopTaskFilteredOut) {
mOverlayContext.getDragLayer().addView(mKeyboardQuickSwitchView);
mOnDesktop = onDesktop;
mWasDesktopTaskFilteredOut = wasDesktopTaskFilteredOut;
mKeyboardQuickSwitchView.applyLoadPlan(
mOverlayContext,
@@ -87,7 +94,8 @@ public class KeyboardQuickSwitchViewController {
numHiddenTasks,
updateTasks,
currentFocusIndexOverride,
mViewCallbacks);
mViewCallbacks,
/* useDesktopTaskView= */ !onDesktop && hasDesktopTask);
}
boolean isCloseAnimationRunning() {
@@ -136,7 +144,7 @@ public class KeyboardQuickSwitchViewController {
}
// If the user quick switches too quickly, updateCurrentFocusIndex might not have run.
return launchTaskAt(mControllerCallbacks.isFirstTaskRunning()
&& mControllerCallbacks.getTaskCount() > 1 ? 1 : 0);
&& mKeyboardQuickSwitchView.getTaskCount() > 1 ? 1 : 0);
}
private int launchTaskAt(int index) {
@@ -144,17 +152,11 @@ public class KeyboardQuickSwitchViewController {
// Ignore taps on task views and alt key unpresses while the close animation is running.
return -1;
}
// Even with a valid index, this can be null if the user tries to quick switch before the
// views have been added in the KeyboardQuickSwitchView.
GroupTask task = mControllerCallbacks.getTaskAt(index);
if (task == null) {
return mOnDesktop ? 1 : Math.max(0, index);
if (index == mKeyboardQuickSwitchView.getOverviewTaskIndex()) {
// If there is a desktop task view, then we should account for it when focusing the
// first hidden non-desktop task view in recents view
return mOnDesktop ? 1 : (mWasDesktopTaskFilteredOut ? index + 1 : index);
}
if (mControllerCallbacks.isTaskRunning(task)) {
// Ignore attempts to run the selected task if it is already running.
return -1;
}
Runnable onStartCallback = () -> InteractionJankMonitorWrapper.begin(
mKeyboardQuickSwitchView, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH);
Runnable onFinishCallback = () -> InteractionJankMonitorWrapper.end(
@@ -169,6 +171,24 @@ public class KeyboardQuickSwitchViewController {
onStartCallback,
onFinishCallback),
"SlideInTransition");
if (index == mKeyboardQuickSwitchView.getDesktopTaskIndex()) {
UI_HELPER_EXECUTOR.execute(() ->
SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
.showDesktopApps(
mKeyboardQuickSwitchView.getDisplay().getDisplayId(),
remoteTransition));
return -1;
}
// Even with a valid index, this can be null if the user tries to quick switch before the
// views have been added in the KeyboardQuickSwitchView.
GroupTask task = mControllerCallbacks.getTaskAt(index);
if (task == null) {
return mOnDesktop ? 1 : Math.max(0, index);
}
if (mControllerCallbacks.isTaskRunning(task)) {
// Ignore attempts to run the selected task if it is already running.
return -1;
}
mControllers.taskbarActivityContext.handleGroupTaskLaunch(
task,
remoteTransition,
@@ -195,6 +215,8 @@ public class KeyboardQuickSwitchViewController {
pw.println(prefix + "\thasFocus=" + mKeyboardQuickSwitchView.hasFocus());
pw.println(prefix + "\tisCloseAnimationRunning=" + isCloseAnimationRunning());
pw.println(prefix + "\tmCurrentFocusIndex=" + mCurrentFocusIndex);
pw.println(prefix + "\tmOnDesktop=" + mOnDesktop);
pw.println(prefix + "\tmWasDesktopTaskFilteredOut=" + mWasDesktopTaskFilteredOut);
}
class ViewCallbacks {
@@ -222,7 +244,7 @@ public class KeyboardQuickSwitchViewController {
boolean traverseBackwards = (keyCode == KeyEvent.KEYCODE_TAB && event.isShiftPressed())
|| (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && isRTL)
|| (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && !isRTL);
int taskCount = mControllerCallbacks.getTaskCount();
int taskCount = mKeyboardQuickSwitchView.getTaskCount();
int toIndex = mCurrentFocusIndex == -1
// Focus the second-most recent app if possible
? (taskCount > 1 ? 1 : 0)
@@ -257,5 +279,9 @@ public class KeyboardQuickSwitchViewController {
void updateIconInBackground(Task task, Consumer<Task> callback) {
mControllerCallbacks.updateIconInBackground(task, callback);
}
boolean isAspectRatioSquare() {
return mControllerCallbacks.isAspectRatioSquare();
}
}
}
@@ -19,7 +19,6 @@ import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR
import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.WALLPAPER_ACTIVITY;
import android.animation.Animator;
@@ -232,9 +231,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
LauncherState state = mLauncher.getStateManager().getState();
boolean nonInteractiveState = state.hasFlag(FLAG_NON_INTERACTIVE)
&& !state.isTaskbarAlignedWithHotseat(mLauncher);
if ((ENABLE_SHELL_TRANSITIONS
&& isVisible
&& (nonInteractiveState || mSkipLauncherVisibilityChange))) {
if (isVisible && (nonInteractiveState || mSkipLauncherVisibilityChange)) {
return null;
}
@@ -41,8 +41,8 @@ import com.android.launcher3.util.Executors;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.NavHandle;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.wm.shell.shared.handles.RegionSamplingHelper;
import java.io.PrintWriter;
@@ -29,7 +29,6 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP;
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY;
import static com.android.launcher3.Flags.enableCursorHoverStates;
import static com.android.launcher3.Flags.enableTaskbarCustomization;
import static com.android.launcher3.Utilities.calculateTextHeight;
import static com.android.launcher3.Utilities.isRunningInTestHarness;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
@@ -230,15 +229,12 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
mNavigationBarPanelContext = navigationBarPanelContext;
applyDeviceProfile(launcherDp);
final Resources resources = getResources();
if (enableTaskbarCustomization()) {
mTaskbarFeatureEvaluator = TaskbarFeatureEvaluator.getInstance(this);
mTaskbarSpecsEvaluator = new TaskbarSpecsEvaluator(
this,
mTaskbarFeatureEvaluator,
mDeviceProfile.inv.numRows,
mDeviceProfile.inv.numColumns);
}
mTaskbarFeatureEvaluator = TaskbarFeatureEvaluator.getInstance(this);
mTaskbarSpecsEvaluator = new TaskbarSpecsEvaluator(
this,
mTaskbarFeatureEvaluator,
mDeviceProfile.inv.numRows,
mDeviceProfile.inv.numColumns);
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
@@ -287,7 +283,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
TaskbarHotseatDimensionsProvider dimensionsProvider =
new DeviceProfileDimensionsProviderAdapter(this);
BubbleStashController bubbleStashController = isTransientTaskbar
? new TransientBubbleStashController(dimensionsProvider, getResources())
? new TransientBubbleStashController(dimensionsProvider, this)
: new PersistentBubbleStashController(dimensionsProvider);
bubbleControllersOptional = Optional.of(new BubbleControllers(
new BubbleBarController(this, bubbleBarView),
@@ -1761,12 +1757,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
return mControllers.taskbarStashController.isInStashedLauncherState();
}
@Nullable
public TaskbarFeatureEvaluator getTaskbarFeatureEvaluator() {
return mTaskbarFeatureEvaluator;
}
@Nullable
public TaskbarSpecsEvaluator getTaskbarSpecsEvaluator() {
return mTaskbarSpecsEvaluator;
}
@@ -169,8 +169,11 @@ constructor(
override fun addArrow() {
super.addArrow()
val location = IntArray(2)
popupContainer.getLocationInDragLayer(dividerView, location)
val dividerViewX = location[0].toFloat()
// Change arrow location to the middle of popup.
mArrow.x = (dividerView.x + dividerView.width / 2) - (mArrowWidth / 2)
mArrow.x = (dividerViewX + dividerView.width / 2) - (mArrowWidth / 2)
}
override fun updateArrowColor() {
@@ -171,6 +171,10 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
}
private void updateBackgroundAlpha() {
if (mActivity.isPhoneMode()) {
return;
}
final float bgNavbar = mBgNavbar.value;
final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value
* mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value
@@ -20,7 +20,6 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
import static com.android.quickstep.OverviewCommandHelper.TYPE_HIDE;
import android.content.Intent;
import android.graphics.drawable.BitmapDrawable;
@@ -40,6 +39,7 @@ import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.OverviewCommandHelper.CommandType;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.RecentsView;
@@ -394,7 +394,7 @@ public class TaskbarUIController {
if (overviewCommandHelper == null) {
return;
}
overviewCommandHelper.addCommand(TYPE_HIDE);
overviewCommandHelper.addCommand(CommandType.HIDE);
}
/**
@@ -22,10 +22,8 @@ import static com.android.launcher3.Flags.enableCursorHoverStates;
import static com.android.launcher3.Flags.enableRecentsInTaskbar;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.content.Context;
import android.content.res.Resources;
@@ -39,12 +37,9 @@ import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import androidx.annotation.DimenRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -62,13 +57,12 @@ import com.android.launcher3.model.data.CollectionInfo;
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.taskbar.customization.TaskbarAllAppsButtonContainer;
import com.android.launcher3.taskbar.customization.TaskbarDividerContainer;
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.launcher3.views.IconButtonView;
import com.android.quickstep.DeviceConfigWrapper;
import com.android.quickstep.util.AssistStateManager;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
@@ -100,16 +94,13 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
private View.OnLongClickListener mIconLongClickListener;
// Only non-null when the corresponding Folder is open.
private @Nullable FolderIcon mLeaveBehindFolderIcon;
@Nullable private FolderIcon mLeaveBehindFolderIcon;
// Only non-null when device supports having an All Apps button.
private @Nullable IconButtonView mAllAppsButton;
private Runnable mAllAppsTouchRunnable;
private long mAllAppsButtonTouchDelayMs;
private boolean mAllAppsTouchTriggered;
@Nullable private final TaskbarAllAppsButtonContainer mAllAppsButtonContainer;
// Only non-null when device supports having an All Apps button, or Recent Apps.
private @Nullable IconButtonView mTaskbarDivider;
// Only non-null when device supports having an All Apps button.
@Nullable private TaskbarDividerContainer mTaskbarDividerContainer;
/**
* Whether the divider is between Hotseat icons and Recents,
@@ -173,55 +164,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
// Needed to draw folder leave-behind when opening one.
setWillNotDraw(false);
mAllAppsButton = (IconButtonView) LayoutInflater.from(context)
.inflate(R.layout.taskbar_all_apps_button, this, false);
mAllAppsButton.setIconDrawable(resources.getDrawable(
getAllAppsButton(isTransientTaskbar)));
mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
mAllAppsButton.setForegroundTint(
mActivityContext.getColor(R.color.all_apps_button_color));
mAllAppsButtonContainer = new TaskbarAllAppsButtonContainer(context);
if (enableTaskbarPinning() || enableRecentsInTaskbar()) {
mTaskbarDivider = (IconButtonView) LayoutInflater.from(context).inflate(
R.layout.taskbar_divider,
this, false);
mTaskbarDivider.setIconDrawable(
resources.getDrawable(R.drawable.taskbar_divider_button));
mTaskbarDivider.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
mTaskbarDividerContainer = new TaskbarDividerContainer(context);
}
// TODO: Disable touch events on QSB otherwise it can crash.
mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
// Default long press (touch) delay = 400ms
mAllAppsButtonTouchDelayMs = ViewConfiguration.getLongPressTimeout();
}
@DrawableRes
private int getAllAppsButton(boolean isTransientTaskbar) {
boolean shouldSelectTransientIcon =
(isTransientTaskbar || enableTaskbarPinning())
&& !mActivityContext.isThreeButtonNav();
if (ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
return shouldSelectTransientIcon
? R.drawable.ic_transient_taskbar_all_apps_search_button
: R.drawable.ic_taskbar_all_apps_search_button;
} else {
return shouldSelectTransientIcon
? R.drawable.ic_transient_taskbar_all_apps_button
: R.drawable.ic_taskbar_all_apps_button;
}
}
@DimenRes
public int getAllAppsButtonTranslationXOffset(boolean isTransientTaskbar) {
if (isTransientTaskbar) {
return R.dimen.transient_taskbar_all_apps_button_translation_x_offset;
} else {
return ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()
? R.dimen.taskbar_all_apps_search_button_translation_x_offset
: R.dimen.taskbar_all_apps_button_translation_x_offset;
}
}
@Override
@@ -306,27 +256,11 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
mIconClickListener = mControllerCallbacks.getIconOnClickListener();
mIconLongClickListener = mControllerCallbacks.getIconOnLongClickListener();
if (mAllAppsButton != null) {
mAllAppsButton.setOnClickListener(this::onAllAppsButtonClick);
mAllAppsButton.setOnLongClickListener(this::onAllAppsButtonLongClick);
mAllAppsButton.setOnTouchListener(this::onAllAppsButtonTouch);
mAllAppsButton.setHapticFeedbackEnabled(
mControllerCallbacks.isAllAppsButtonHapticFeedbackEnabled());
mAllAppsTouchRunnable = () -> {
mControllerCallbacks.triggerAllAppsButtonLongClick();
mAllAppsTouchTriggered = true;
};
AssistStateManager assistStateManager = AssistStateManager.INSTANCE.get(mContext);
if (DeviceConfigWrapper.get().getCustomLpaaThresholds()
&& assistStateManager.getLPNHDurationMillis().isPresent()) {
mAllAppsButtonTouchDelayMs = assistStateManager.getLPNHDurationMillis().get();
}
if (mAllAppsButtonContainer != null) {
mAllAppsButtonContainer.setUpCallbacks(callbacks);
}
if (mTaskbarDivider != null && !mActivityContext.isThreeButtonNav()) {
mTaskbarDivider.setOnLongClickListener(
mControllerCallbacks.getTaskbarDividerLongClickListener());
mTaskbarDivider.setOnTouchListener(
mControllerCallbacks.getTaskbarDividerRightClickListener());
if (mTaskbarDividerContainer != null && callbacks.supportsDividerLongPress()) {
mTaskbarDividerContainer.setUpCallbacks(callbacks);
}
}
@@ -348,11 +282,11 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
int numViewsAnimated = 0;
mAddedDividerForRecents = false;
if (mAllAppsButton != null) {
removeView(mAllAppsButton);
if (mAllAppsButtonContainer != null) {
removeView(mAllAppsButtonContainer);
if (mTaskbarDivider != null) {
removeView(mTaskbarDivider);
if (mTaskbarDividerContainer != null) {
removeView(mTaskbarDividerContainer);
}
}
removeView(mQsb);
@@ -439,8 +373,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
nextViewIndex++;
}
if (mTaskbarDivider != null && !recentTasks.isEmpty()) {
addView(mTaskbarDivider, nextViewIndex++);
if (mTaskbarDividerContainer != null && !recentTasks.isEmpty()) {
addView(mTaskbarDividerContainer, nextViewIndex++);
mAddedDividerForRecents = true;
}
@@ -504,12 +438,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
removeAndRecycle(getChildAt(nextViewIndex));
}
if (mAllAppsButton != null) {
addView(mAllAppsButton, mIsRtl ? hotseatItemInfos.length : 0);
if (mAllAppsButtonContainer != null) {
addView(mAllAppsButtonContainer, mIsRtl ? hotseatItemInfos.length : 0);
// If there are no recent tasks, add divider after All Apps (unless it's the only view).
if (!mAddedDividerForRecents && mTaskbarDivider != null && getChildCount() > 1) {
addView(mTaskbarDivider, mIsRtl ? (getChildCount() - 1) : 1);
if (!mAddedDividerForRecents
&& mTaskbarDividerContainer != null
&& getChildCount() > 1) {
addView(mTaskbarDividerContainer, mIsRtl ? (getChildCount() - 1) : 1);
}
}
if (mActivityContext.getDeviceProfile().isQsbInline) {
@@ -637,7 +573,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
int qsbTop = (bottom - top - deviceProfile.hotseatQsbHeight) / 2;
int qsbBottom = qsbTop + deviceProfile.hotseatQsbHeight;
child.layout(qsbStart, qsbTop, qsbEnd, qsbBottom);
} else if (child == mTaskbarDivider) {
} else if (child == mTaskbarDividerContainer) {
iconEnd += mItemMarginLeftRight;
int iconStart = iconEnd - mIconTouchSize;
child.layout(iconStart, mIconLayoutBounds.top, iconEnd, mIconLayoutBounds.bottom);
@@ -727,16 +663,16 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
* Returns the all apps button in the taskbar.
*/
@Nullable
public View getAllAppsButtonView() {
return mAllAppsButton;
public TaskbarAllAppsButtonContainer getAllAppsButtonContainer() {
return mAllAppsButtonContainer;
}
/**
* Returns the taskbar divider in the taskbar.
*/
@Nullable
public View getTaskbarDividerView() {
return mTaskbarDivider;
public TaskbarDividerContainer getTaskbarDividerViewContainer() {
return mTaskbarDividerContainer;
}
/**
@@ -832,48 +768,6 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
}
}
}
return mAllAppsButton;
}
private boolean onAllAppsButtonTouch(View view, MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mAllAppsTouchTriggered = false;
MAIN_EXECUTOR.getHandler().postDelayed(
mAllAppsTouchRunnable, mAllAppsButtonTouchDelayMs);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
cancelAllAppsButtonTouch();
}
return false;
}
private void cancelAllAppsButtonTouch() {
MAIN_EXECUTOR.getHandler().removeCallbacks(mAllAppsTouchRunnable);
// ACTION_UP is first triggered, then click listener / long-click listener is triggered on
// the next frame, so we need to post twice and delay the reset.
if (mAllAppsButton != null) {
mAllAppsButton.post(() -> {
mAllAppsButton.post(() -> {
mAllAppsTouchTriggered = false;
});
});
}
}
private void onAllAppsButtonClick(View view) {
if (!mAllAppsTouchTriggered) {
mControllerCallbacks.triggerAllAppsButtonClick(view);
}
}
// Handle long click from Switch Access and Voice Access
private boolean onAllAppsButtonLongClick(View view) {
if (!MAIN_EXECUTOR.getHandler().hasCallbacks(mAllAppsTouchRunnable)
&& !mAllAppsTouchTriggered) {
mControllerCallbacks.triggerAllAppsButtonLongClick();
}
return true;
return mAllAppsButtonContainer;
}
}
@@ -51,7 +51,7 @@ public class TaskbarViewCallbacks {
}
/** Trigger All Apps button click action. */
protected void triggerAllAppsButtonClick(View v) {
public void triggerAllAppsButtonClick(View v) {
InteractionJankMonitorWrapper.begin(v, Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS,
/* tag= */ "TASKBAR_BUTTON");
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP);
@@ -59,7 +59,7 @@ public class TaskbarViewCallbacks {
}
/** Trigger All Apps button long click action. */
protected void triggerAllAppsButtonLongClick() {
public void triggerAllAppsButtonLongClick() {
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS);
}
@@ -74,6 +74,11 @@ public class TaskbarViewCallbacks {
};
}
/** Check to see if we support long press on taskbar divider */
public boolean supportsDividerLongPress() {
return !mActivity.isThreeButtonNav();
}
public View.OnTouchListener getTaskbarDividerRightClickListener() {
return (v, event) -> {
if (event.isFromSource(InputDevice.SOURCE_MOUSE)
@@ -72,7 +72,6 @@ import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.views.IconButtonView;
import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
@@ -299,7 +298,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
@Nullable
public View getAllAppsButtonView() {
return mTaskbarView.getAllAppsButtonView();
return mTaskbarView.getAllAppsButtonContainer();
}
public AnimatedFloat getTaskbarIconScaleForStash() {
@@ -366,9 +365,9 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
View[] iconViews = mTaskbarView.getIconViews();
float scale = mTaskbarIconTranslationXForPinning.value;
float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension(
mTaskbarView.getAllAppsButtonTranslationXOffset(true));
mTaskbarView.getAllAppsButtonContainer().getAllAppsButtonTranslationXOffset(true));
float persistentTaskbarAllAppsOffset = mActivity.getResources().getDimension(
mTaskbarView.getAllAppsButtonTranslationXOffset(false));
mTaskbarView.getAllAppsButtonContainer().getAllAppsButtonTranslationXOffset(false));
float allAppIconTranslateRange = mapRange(scale, transientTaskbarAllAppsOffset,
persistentTaskbarAllAppsOffset);
@@ -383,7 +382,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
}
if (mActivity.isThreeButtonNav()) {
((IconButtonView) mTaskbarView.getAllAppsButtonView())
mTaskbarView.getAllAppsButtonContainer()
.setTranslationXForTaskbarAllAppsIcon(allAppIconTranslateRange);
return;
}
@@ -408,8 +407,8 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
-finalMarginScale * (iconIndex - halfIconCount));
}
if (iconView.equals(mTaskbarView.getAllAppsButtonView())) {
((IconButtonView) iconView).setTranslationXForTaskbarAllAppsIcon(
if (iconView.equals(mTaskbarView.getAllAppsButtonContainer())) {
mTaskbarView.getAllAppsButtonContainer().setTranslationXForTaskbarAllAppsIcon(
allAppIconTranslateRange);
}
}
@@ -537,7 +536,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
}
public View getTaskbarDividerView() {
return mTaskbarView.getTaskbarDividerView();
return mTaskbarView.getTaskbarDividerViewContainer();
}
/**
@@ -753,8 +752,8 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
int firstRecentTaskIndex = -1;
for (int i = 0; i < mTaskbarView.getChildCount(); i++) {
View child = mTaskbarView.getChildAt(i);
boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonView();
boolean isTaskbarDividerView = child == mTaskbarView.getTaskbarDividerView();
boolean isAllAppsButton = child == mTaskbarView.getAllAppsButtonContainer();
boolean isTaskbarDividerView = child == mTaskbarView.getTaskbarDividerViewContainer();
boolean isRecentTask = child.getTag() instanceof GroupTask;
// TODO(b/343522351): show recents on the home screen.
final boolean isRecentsInHotseat = false;
@@ -16,7 +16,6 @@
package com.android.launcher3.taskbar.allapps;
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.Flags.enablePredictiveBackGesture;
import static com.android.launcher3.touch.AllAppsSwipeController.ALL_APPS_FADE_MANUAL;
import static com.android.launcher3.touch.AllAppsSwipeController.SCRIM_FADE_MANUAL;
@@ -193,14 +192,12 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mActivityContext.addOnDeviceProfileChangeListener(this);
if (enablePredictiveBackGesture()) {
mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(mViewOutlineProvider);
mAppsView.getAppsRecyclerViewContainer().setClipToOutline(true);
OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
if (dispatcher != null) {
dispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, this);
}
mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(mViewOutlineProvider);
mAppsView.getAppsRecyclerViewContainer().setClipToOutline(true);
OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
if (dispatcher != null) {
dispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, this);
}
}
@@ -208,13 +205,11 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mActivityContext.removeOnDeviceProfileChangeListener(this);
if (enablePredictiveBackGesture()) {
mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(null);
mAppsView.getAppsRecyclerViewContainer().setClipToOutline(false);
OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
if (dispatcher != null) {
dispatcher.unregisterOnBackInvokedCallback(this);
}
mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(null);
mAppsView.getAppsRecyclerViewContainer().setClipToOutline(false);
OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
if (dispatcher != null) {
dispatcher.unregisterOnBackInvokedCallback(this);
}
}
@@ -1473,8 +1473,9 @@ public class BubbleBarView extends FrameLayout {
pw.println("BubbleBarView state:");
pw.println(" visibility: " + getVisibility());
pw.println(" alpha: " + getAlpha());
pw.println(" translation Y: " + getTranslationY());
pw.println(" bubbles in bar (childCount = " + getChildCount() + ")");
pw.println(" translationY: " + getTranslationY());
pw.println(" childCount: " + getChildCount());
pw.println(" hasOverflow: " + hasOverflow());
for (BubbleView bubbleView: getBubbles()) {
BubbleBarItem bubble = bubbleView.getBubble();
String key = bubble == null ? "null" : bubble.getKey();
@@ -80,12 +80,14 @@ public class BubbleBarViewController {
// These are exposed to {@link BubbleStashController} to animate for stashing/un-stashing
private final MultiValueAlpha mBubbleBarAlpha;
private final AnimatedFloat mBubbleBarScale = new AnimatedFloat(this::updateScale);
private final AnimatedFloat mBubbleBarScaleY = new AnimatedFloat(this::updateScaleY);
private final AnimatedFloat mBubbleBarTranslationY = new AnimatedFloat(
this::updateTranslationY);
// Modified when swipe up is happening on the bubble bar or task bar.
private float mBubbleBarSwipeUpTranslationY;
// Modified when bubble bar is springing back into the stash handle.
private float mBubbleBarStashTranslationY;
// Whether the bar is hidden for a sysui state.
private boolean mHiddenForSysui;
@@ -125,7 +127,7 @@ public class BubbleBarViewController {
onBubbleBarConfigurationChanged(/* animate= */ false);
mActivity.addOnDeviceProfileChangeListener(
dp -> onBubbleBarConfigurationChanged(/* animate= */ true));
mBubbleBarScale.updateValue(1f);
mBubbleBarScaleY.updateValue(1f);
mBubbleClickListener = v -> onBubbleClicked((BubbleView) v);
mBubbleBarClickListener = v -> expandBubbleBar();
mBubbleDragController.setupBubbleBarView(mBarView);
@@ -255,8 +257,8 @@ public class BubbleBarViewController {
return mBubbleBarAlpha;
}
public AnimatedFloat getBubbleBarScale() {
return mBubbleBarScale;
public AnimatedFloat getBubbleBarScaleY() {
return mBubbleBarScaleY;
}
public AnimatedFloat getBubbleBarTranslationY() {
@@ -267,6 +269,27 @@ public class BubbleBarViewController {
return mBarView.getBubbleBarCollapsedHeight();
}
/**
* @see BubbleBarView#getRelativePivotX()
*/
public float getBubbleBarRelativePivotX() {
return mBarView.getRelativePivotX();
}
/**
* @see BubbleBarView#getRelativePivotY()
*/
public float getBubbleBarRelativePivotY() {
return mBarView.getRelativePivotY();
}
/**
* @see BubbleBarView#setRelativePivot(float, float)
*/
public void setBubbleBarRelativePivot(float x, float y) {
mBarView.setRelativePivot(x, y);
}
/**
* Whether the bubble bar is visible or not.
*/
@@ -474,17 +497,20 @@ public class BubbleBarViewController {
updateTranslationY();
}
private void updateTranslationY() {
mBarView.setTranslationY(mBubbleBarTranslationY.value
+ mBubbleBarSwipeUpTranslationY);
/**
* Sets the translation of the bubble bar during the stash animation.
*/
public void setTranslationYForStash(float transY) {
mBubbleBarStashTranslationY = transY;
updateTranslationY();
}
/**
* Applies scale properties for the entire bubble bar.
*/
private void updateScale() {
float scale = mBubbleBarScale.value;
mBarView.setScaleX(scale);
private void updateTranslationY() {
mBarView.setTranslationY(mBubbleBarTranslationY.value + mBubbleBarSwipeUpTranslationY
+ mBubbleBarStashTranslationY);
}
private void updateScaleY(float scale) {
mBarView.setScaleY(scale);
}
@@ -793,6 +819,7 @@ public class BubbleBarViewController {
pw.println(" mShouldShowEducation: " + mShouldShowEducation);
pw.println(" mBubbleBarTranslationY.value: " + mBubbleBarTranslationY.value);
pw.println(" mBubbleBarSwipeUpTranslationY: " + mBubbleBarSwipeUpTranslationY);
pw.println(" mOverflowAdded: " + mOverflowAdded);
if (mBarView != null) {
mBarView.dump(pw);
} else {
@@ -21,6 +21,7 @@ import static android.view.View.VISIBLE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.content.res.Resources;
import android.graphics.Outline;
import android.graphics.Rect;
@@ -28,6 +29,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewOutlineProvider;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.RevealOutlineAnimation;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
@@ -37,9 +39,9 @@ import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
import com.android.wm.shell.shared.handles.RegionSamplingHelper;
/**
* Handles properties/data collection, then passes the results to our stashed handle View to render.
@@ -49,23 +51,29 @@ public class BubbleStashedHandleViewController {
private final TaskbarActivityContext mActivity;
private final StashedHandleView mStashedHandleView;
private final MultiValueAlpha mStashedHandleAlpha;
private float mTranslationForSwipeY;
private float mTranslationForStashY;
// Initialized in init.
private BubbleBarViewController mBarViewController;
private BubbleStashController mBubbleStashController;
private RegionSamplingHelper mRegionSamplingHelper;
private int mBarSize;
private int mStashedTaskbarHeight;
// Height of the area for the stash handle. Handle will be drawn in the center of this.
// This is also the area where touch is handled on the handle.
private int mStashedBubbleBarHeight;
private int mStashedHandleWidth;
private int mStashedHandleHeight;
// The bounds we want to clip to in the settled state when showing the stashed handle.
// The bounds of the stashed handle in settled state.
private final Rect mStashedHandleBounds = new Rect();
private float mStashedHandleRadius;
// When the reveal animation is cancelled, we can assume it's about to create a new animation,
// which should start off at the same point the cancelled one left off.
private float mStartProgressForNextRevealAnim;
private boolean mWasLastRevealAnimReversed;
// Use a nullable boolean to handle initial case where the last animation direction is not known
@Nullable
private Boolean mWasLastRevealAnimReversed = null;
// XXX: if there are more of these maybe do state flags instead
private boolean mHiddenForSysui;
@@ -77,6 +85,7 @@ public class BubbleStashedHandleViewController {
mActivity = activity;
mStashedHandleView = stashedHandleView;
mStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1);
mStashedHandleAlpha.setUpdateVisibility(true);
}
/** Initialize controller. */
@@ -84,26 +93,31 @@ public class BubbleStashedHandleViewController {
mBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleStashController = bubbleControllers.bubbleStashController;
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
Resources resources = mActivity.getResources();
mStashedHandleHeight = resources.getDimensionPixelSize(
R.dimen.bubblebar_stashed_handle_height);
mStashedHandleWidth = resources.getDimensionPixelSize(
R.dimen.bubblebar_stashed_handle_width);
mBarSize = resources.getDimensionPixelSize(R.dimen.bubblebar_size);
final int bottomMargin = resources.getDimensionPixelSize(
R.dimen.transient_taskbar_bottom_margin);
mStashedHandleView.getLayoutParams().height = mBarSize + bottomMargin;
int barSize = resources.getDimensionPixelSize(R.dimen.bubblebar_size);
// Use the max translation for bubble bar whether it is on the home screen or in app.
// Use values directly from device profile to avoid referencing other bubble controllers
// during init flow.
int maxTy = Math.max(deviceProfile.hotseatBarBottomSpacePx,
deviceProfile.taskbarBottomMargin);
// Adjust handle view size to accommodate the handle morphing into the bubble bar
mStashedHandleView.getLayoutParams().height = barSize + maxTy;
mStashedHandleAlpha.get(0).setValue(0);
mStashedTaskbarHeight = resources.getDimensionPixelSize(
mStashedBubbleBarHeight = resources.getDimensionPixelSize(
R.dimen.bubblebar_stashed_size);
mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
float stashedHandleRadius = view.getHeight() / 2f;
outline.setRoundRect(mStashedHandleBounds, stashedHandleRadius);
mStashedHandleRadius = view.getHeight() / 2f;
outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius);
}
});
@@ -132,28 +146,25 @@ public class BubbleStashedHandleViewController {
private void updateBounds(BubbleBarLocation bubbleBarLocation) {
// As more bubbles get added, the icon bounds become larger. To ensure a consistent
// handle bar position, we pin it to the edge of the screen.
final int stashedCenterY = mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2;
final int stashedCenterY = mStashedHandleView.getHeight() - mStashedBubbleBarHeight / 2;
final int stashedCenterX;
if (bubbleBarLocation.isOnLeft(mStashedHandleView.isLayoutRtl())) {
final int left = mBarViewController.getHorizontalMargin();
mStashedHandleBounds.set(
left,
stashedCenterY - mStashedHandleHeight / 2,
left + mStashedHandleWidth,
stashedCenterY + mStashedHandleHeight / 2);
mStashedHandleView.setPivotX(0);
stashedCenterX = left + mStashedHandleWidth / 2;
} else {
final int right =
mActivity.getDeviceProfile().widthPx - mBarViewController.getHorizontalMargin();
mStashedHandleBounds.set(
right - mStashedHandleWidth,
stashedCenterY - mStashedHandleHeight / 2,
right,
stashedCenterY + mStashedHandleHeight / 2);
mStashedHandleView.setPivotX(mStashedHandleView.getWidth());
mStashedHandleView.getRight() - mBarViewController.getHorizontalMargin();
stashedCenterX = right - mStashedHandleWidth / 2;
}
mStashedHandleBounds.set(
stashedCenterX - mStashedHandleWidth / 2,
stashedCenterY - mStashedHandleHeight / 2,
stashedCenterX + mStashedHandleWidth / 2,
stashedCenterY + mStashedHandleHeight / 2
);
mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
mStashedHandleView.setPivotY(mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2f);
mStashedHandleView.setPivotX(stashedCenterX);
mStashedHandleView.setPivotY(stashedCenterY);
}
public void onDestroy() {
@@ -168,13 +179,6 @@ public class BubbleStashedHandleViewController {
return mStashedHandleHeight;
}
/**
* Returns the height when the bubble bar is unstashed (so the height of the bubble bar).
*/
public int getUnstashedHeight() {
return mBarSize;
}
/**
* Called when system ui state changes. Bubbles don't show when the device is locked.
*/
@@ -242,7 +246,20 @@ public class BubbleStashedHandleViewController {
* Sets the translation of the stashed handle during the swipe up gesture.
*/
public void setTranslationYForSwipe(float transY) {
mStashedHandleView.setTranslationY(transY);
mTranslationForSwipeY = transY;
updateTranslationY();
}
/**
* Sets the translation of the stashed handle during the spring on stash animation.
*/
public void setTranslationYForStash(float transY) {
mTranslationForStashY = transY;
updateTranslationY();
}
private void updateTranslationY() {
mStashedHandleView.setTranslationY(mTranslationForSwipeY + mTranslationForStashY);
}
/** Returns the translation of the stashed handle. */
@@ -263,18 +280,17 @@ public class BubbleStashedHandleViewController {
* the size of where the bubble bar icons will be.
*/
public Animator createRevealAnimToIsStashed(boolean isStashed) {
Rect bubbleBarBounds = new Rect(mBarViewController.getBubbleBarBounds());
Rect bubbleBarBounds = getLocalBubbleBarBounds();
// Account for the full visual height of the bubble bar
int heightDiff = (mBarSize - bubbleBarBounds.height()) / 2;
bubbleBarBounds.top -= heightDiff;
bubbleBarBounds.bottom += heightDiff;
float stashedHandleRadius = mStashedHandleView.getHeight() / 2f;
float bubbleBarRadius = bubbleBarBounds.height() / 2f;
final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
stashedHandleRadius, stashedHandleRadius, bubbleBarBounds, mStashedHandleBounds);
bubbleBarRadius, mStashedHandleRadius, bubbleBarBounds, mStashedHandleBounds);
boolean isReversed = !isStashed;
boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
// We are only changing direction when mWasLastRevealAnimReversed is set at least once
boolean changingDirection =
mWasLastRevealAnimReversed != null && mWasLastRevealAnimReversed != isReversed;
mWasLastRevealAnimReversed = isReversed;
if (changingDirection) {
mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
@@ -291,6 +307,21 @@ public class BubbleStashedHandleViewController {
return revealAnim;
}
/**
* Get bounds for the bubble bar in the space of the handle view
*/
private Rect getLocalBubbleBarBounds() {
// Position the bubble bar bounds to the space of handle view
Rect bubbleBarBounds = new Rect(mBarViewController.getBubbleBarBounds());
// Start by moving bubble bar bounds to the bottom of handle view
int height = bubbleBarBounds.height();
bubbleBarBounds.bottom = mStashedHandleView.getHeight();
bubbleBarBounds.top = bubbleBarBounds.bottom - height;
// Then apply translation that is applied to the bubble bar
bubbleBarBounds.offset(0, (int) mBubbleStashController.getBubbleBarTranslationY());
return bubbleBarBounds;
}
/** Checks that the stash handle is visible and that the motion event is within bounds. */
public boolean isEventOverHandle(MotionEvent ev) {
if (mStashedHandleView.getVisibility() != VISIBLE) {
@@ -299,7 +330,7 @@ public class BubbleStashedHandleViewController {
// the bounds of the handle only include the visible part, so we check that the Y coordinate
// is anywhere within the stashed height of bubble bar (same as taskbar stashed height).
final int top = mActivity.getDeviceProfile().heightPx - mStashedTaskbarHeight;
final int top = mActivity.getDeviceProfile().heightPx - mStashedBubbleBarHeight;
final float x = ev.getRawX();
return ev.getRawY() >= top && x >= mStashedHandleBounds.left
&& x <= mStashedHandleBounds.right;
@@ -237,7 +237,11 @@ public class BubbleView extends ConstraintLayout {
mBubble = bubble;
mIcon = bubble.getIcon();
updateBubbleIcon();
mAppIcon.setImageBitmap(bubble.getBadge());
if (bubble.getInfo().showAppBadge()) {
mAppIcon.setImageBitmap(bubble.getBadge());
} else {
mAppIcon.setVisibility(GONE);
}
mDotColor = bubble.getDotColor();
mDotRenderer = new DotRenderer(mBubbleSize, bubble.getDotPath(), DEFAULT_PATH_SIZE);
String contentDesc = bubble.getInfo().getTitle();
@@ -302,8 +306,10 @@ public class BubbleView extends ConstraintLayout {
}
void setBadgeScale(float fraction) {
mAppIcon.setScaleX(fraction);
mAppIcon.setScaleY(fraction);
if (mAppIcon.getVisibility() == VISIBLE) {
mAppIcon.setScaleX(fraction);
mAppIcon.setScaleY(fraction);
}
}
boolean hasUnseenContent() {
@@ -179,10 +179,10 @@ interface BubbleStashController {
/** How long to stash/unstash. */
const val BAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE.toLong()
const val BAR_STASH_ALPHA_DURATION = 50L
const val BAR_STASH_ALPHA_DELAY = 33L
/** How long to translate Y coordinate of the BubbleBar. */
const val BAR_TRANSLATION_DURATION = 300L
/** The scale bubble bar animates to when being stashed. */
const val STASHED_BAR_SCALE = 0.5f
}
}
@@ -116,7 +116,7 @@ class PersistentBubbleStashController(
bubbleBarTranslationYAnimator = bubbleBarViewController.bubbleBarTranslationY
// bubble bar has only alpha property, getting it at index 0
bubbleBarAlphaAnimator = bubbleBarViewController.bubbleBarAlpha.get(/* index= */ 0)
bubbleBarScaleAnimator = bubbleBarViewController.bubbleBarScale
bubbleBarScaleAnimator = bubbleBarViewController.bubbleBarScaleY
}
private fun animateAfterUnlock() {
@@ -17,30 +17,36 @@
package com.android.launcher3.taskbar.bubbles.stashing
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.content.res.Resources
import android.content.Context
import android.view.MotionEvent
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import androidx.dynamicanimation.animation.SpringForce
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.app.animation.Interpolators.LINEAR
import com.android.launcher3.R
import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.taskbar.StashedHandleViewController
import com.android.launcher3.anim.SpringAnimationBuilder
import com.android.launcher3.taskbar.TaskbarInsetsController
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_ALPHA_DELAY
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_ALPHA_DURATION
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_STASH_DURATION
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.BAR_TRANSLATION_DURATION
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.STASHED_BAR_SCALE
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.ControllersAfterInitAction
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.TaskbarHotseatDimensionsProvider
import com.android.launcher3.util.MultiPropertyFactory
import com.android.wm.shell.common.bubbles.BubbleBarLocation
import com.android.wm.shell.shared.animation.PhysicsAnimator
import kotlin.math.max
class TransientBubbleStashController(
private val taskbarHotseatDimensionsProvider: TaskbarHotseatDimensionsProvider,
resources: Resources
private val context: Context
) : BubbleStashController {
private lateinit var bubbleBarViewController: BubbleBarViewController
@@ -50,14 +56,20 @@ class TransientBubbleStashController(
// stash view properties
private var bubbleStashedHandleViewController: BubbleStashedHandleViewController? = null
private var stashHandleViewAlpha: MultiPropertyFactory<View>.MultiProperty? = null
private var translationYDuringStash = AnimatedFloat { transY ->
bubbleStashedHandleViewController?.setTranslationYForStash(transY)
bubbleBarViewController.setTranslationYForStash(transY)
}
private val stashHandleStashVelocity =
context.resources.getDimension(R.dimen.bubblebar_stashed_handle_spring_velocity_dp_per_s)
private var stashedHeight: Int = 0
// bubble bar properties
private lateinit var bubbleBarAlpha: MultiPropertyFactory<View>.MultiProperty
private lateinit var bubbleBarTranslationYAnimator: AnimatedFloat
private lateinit var bubbleBarScale: AnimatedFloat
private val mHandleCenterFromScreenBottom =
resources.getDimensionPixelSize(R.dimen.bubblebar_stashed_size) / 2f
private val handleCenterFromScreenBottom =
context.resources.getDimensionPixelSize(R.dimen.bubblebar_stashed_size) / 2f
private var animator: AnimatorSet? = null
@@ -136,12 +148,9 @@ class TransientBubbleStashController(
bubbleBarTranslationYAnimator = bubbleBarViewController.bubbleBarTranslationY
// bubble bar has only alpha property, getting it at index 0
bubbleBarAlpha = bubbleBarViewController.bubbleBarAlpha.get(/* index= */ 0)
bubbleBarScale = bubbleBarViewController.bubbleBarScale
bubbleBarScale = bubbleBarViewController.bubbleBarScaleY
stashedHeight = bubbleStashedHandleViewController?.stashedHeight ?: 0
stashHandleViewAlpha =
bubbleStashedHandleViewController
?.stashedHandleAlpha
?.get(StashedHandleViewController.ALPHA_INDEX_STASHED)
stashHandleViewAlpha = bubbleStashedHandleViewController?.stashedHandleAlpha?.get(0)
}
private fun animateAfterUnlock() {
@@ -179,7 +188,7 @@ class TransientBubbleStashController(
stashHandleViewAlpha?.value = 1f
this.bubbleBarTranslationYAnimator.updateValue(getStashTranslation())
bubbleBarAlpha.setValue(0f)
bubbleBarScale.updateValue(STASHED_BAR_SCALE)
bubbleBarScale.updateValue(getStashScale())
isStashed = true
onIsStashedChanged()
}
@@ -223,11 +232,11 @@ class TransientBubbleStashController(
// the difference between the centers of the handle and the bubble bar is the difference
// between their distance from the bottom of the screen.
val barCenter: Float = bubbleBarViewController.bubbleBarCollapsedHeight / 2f
return mHandleCenterFromScreenBottom - barCenter
return handleCenterFromScreenBottom - barCenter
}
override fun getStashedHandleTranslationForNewBubbleAnimation(): Float {
return -mHandleCenterFromScreenBottom
return -handleCenterFromScreenBottom
}
override fun getStashedHandlePhysicsAnimator(): PhysicsAnimator<View>? {
@@ -245,7 +254,13 @@ class TransientBubbleStashController(
override fun getHandleTranslationY(): Float? = bubbleStashedHandleViewController?.translationY
private fun getStashTranslation(): Float {
return (bubbleBarViewController.bubbleBarCollapsedHeight - stashedHeight) / 2f
return bubbleBarTranslationY / 2f
}
@VisibleForTesting
fun getStashScale(): Float {
val handleHeight = bubbleStashedHandleViewController?.stashedHeight ?: 0
return handleHeight / bubbleBarViewController.bubbleBarCollapsedHeight
}
/**
@@ -258,61 +273,84 @@ class TransientBubbleStashController(
@Suppress("SameParameterValue")
private fun createStashAnimator(isStashed: Boolean, duration: Long): AnimatorSet {
val animatorSet = AnimatorSet()
val fullLengthAnimatorSet = AnimatorSet()
// Not exactly half and may overlap. See [first|second]HalfDurationScale below.
val firstHalfAnimatorSet = AnimatorSet()
val secondHalfAnimatorSet = AnimatorSet()
val firstHalfDurationScale: Float
val secondHalfDurationScale: Float
val stashHandleAlphaValue: Float
if (isStashed) {
firstHalfDurationScale = 0.75f
secondHalfDurationScale = 0.5f
stashHandleAlphaValue = 1f
fullLengthAnimatorSet.play(
bubbleBarTranslationYAnimator.animateToValue(getStashTranslation())
)
firstHalfAnimatorSet.playTogether(
bubbleBarAlpha.animateToValue(0f),
bubbleBarScale.animateToValue(STASHED_BAR_SCALE)
)
} else {
firstHalfDurationScale = 0.5f
secondHalfDurationScale = 0.75f
stashHandleAlphaValue = 0f
fullLengthAnimatorSet.playTogether(
bubbleBarScale.animateToValue(1f),
bubbleBarTranslationYAnimator.animateToValue(bubbleBarTranslationY)
)
secondHalfAnimatorSet.playTogether(bubbleBarAlpha.animateToValue(1f))
}
stashHandleViewAlpha?.let {
secondHalfAnimatorSet.playTogether(it.animateToValue(stashHandleAlphaValue))
}
bubbleStashedHandleViewController?.createRevealAnimToIsStashed(isStashed)?.let {
fullLengthAnimatorSet.play(it)
}
fullLengthAnimatorSet.setDuration(duration)
firstHalfAnimatorSet.setDuration((duration * firstHalfDurationScale).toLong())
secondHalfAnimatorSet.setDuration((duration * secondHalfDurationScale).toLong())
secondHalfAnimatorSet.startDelay = (duration * (1 - secondHalfDurationScale)).toLong()
animatorSet.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet, secondHalfAnimatorSet)
animatorSet.addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
animator = null
controllersAfterInitAction.runAfterInit {
if (isStashed) {
bubbleBarViewController.isExpanded = false
}
taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
}
}
val alphaDuration = if (isStashed) duration else BAR_STASH_ALPHA_DURATION
val alphaDelay = if (isStashed) BAR_STASH_ALPHA_DELAY else 0L
animatorSet.play(
createStashAlphaAnimator(isStashed).apply {
this.duration = max(0L, alphaDuration - alphaDelay)
this.startDelay = alphaDelay
this.interpolator = LINEAR
}
)
animatorSet.play(
createSpringOnStashAnimator(isStashed).apply {
this.duration = duration
this.interpolator = LINEAR
}
)
animatorSet.play(
bubbleStashedHandleViewController?.createRevealAnimToIsStashed(isStashed)?.apply {
this.duration = duration
this.interpolator = EMPHASIZED
}
)
val scaleTarget = if (isStashed) getStashScale() else 1f
animatorSet.play(
bubbleBarScale.animateToValue(scaleTarget).apply {
this.duration = duration
this.interpolator = EMPHASIZED
this.setBubbleBarPivotDuringAnim(0.5f, 1f)
}
)
val translationYTarget = if (isStashed) getStashTranslation() else bubbleBarTranslationY
animatorSet.play(
bubbleBarTranslationYAnimator.animateToValue(translationYTarget).apply {
this.duration = duration
this.interpolator = EMPHASIZED
}
)
animatorSet.doOnEnd {
animator = null
controllersAfterInitAction.runAfterInit {
if (isStashed) {
bubbleBarViewController.isExpanded = false
}
taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
}
}
return animatorSet
}
private fun createStashAlphaAnimator(isStashed: Boolean): AnimatorSet {
val stashHandleAlphaTarget = if (isStashed) 1f else 0f
val barAlphaTarget = if (isStashed) 0f else 1f
return AnimatorSet().apply {
play(bubbleBarAlpha.animateToValue(barAlphaTarget))
play(stashHandleViewAlpha?.animateToValue(stashHandleAlphaTarget))
}
}
private fun createSpringOnStashAnimator(isStashed: Boolean): Animator {
if (!isStashed) {
// Animate the stash translation back to 0
return translationYDuringStash.animateToValue(0f)
}
// Apply a spring to the handle
return SpringAnimationBuilder(context)
.setStartValue(translationYDuringStash.value)
.setEndValue(0f)
.setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_LOW)
.setStartVelocity(stashHandleStashVelocity)
.build(translationYDuringStash, AnimatedFloat.VALUE)
}
private fun onIsStashedChanged() {
controllersAfterInitAction.runAfterInit {
taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
@@ -363,13 +401,23 @@ class TransientBubbleStashController(
}
private fun Animator.updateTouchRegionOnAnimationEnd(): Animator {
this.addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
onIsStashedChanged()
}
doOnEnd { onIsStashedChanged() }
return this
}
private fun Animator.setBubbleBarPivotDuringAnim(pivotX: Float, pivotY: Float): Animator {
var initialPivotX = Float.NaN
var initialPivotY = Float.NaN
doOnStart {
initialPivotX = bubbleBarViewController.bubbleBarRelativePivotX
initialPivotY = bubbleBarViewController.bubbleBarRelativePivotY
bubbleBarViewController.setBubbleBarRelativePivot(pivotX, pivotY)
}
doOnEnd {
if (!initialPivotX.isNaN() && !initialPivotY.isNaN()) {
bubbleBarViewController.setBubbleBarRelativePivot(initialPivotX, initialPivotY)
}
)
}
return this
}
}
@@ -18,18 +18,27 @@ package com.android.launcher3.taskbar.customization
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color.TRANSPARENT
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import androidx.annotation.DimenRes
import androidx.annotation.DrawableRes
import androidx.core.view.setPadding
import com.android.launcher3.R
import com.android.launcher3.Utilities.dpToPx
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR
import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.TaskbarViewCallbacks
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.views.ActivityContext
import com.android.launcher3.views.IconButtonView
import com.android.quickstep.DeviceConfigWrapper
import com.android.quickstep.util.AssistStateManager
/** Taskbar all apps button container for customizable taskbar. */
class TaskbarAllAppsButtonContainer
@@ -38,19 +47,21 @@ constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : LinearLayout(context, attrs), TaskbarContainer {
) : IconButtonView(context, attrs), TaskbarContainer {
private val allAppsButton: IconButtonView =
LayoutInflater.from(context).inflate(R.layout.taskbar_all_apps_button, this, false)
as IconButtonView
private val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
private var allAppsTouchTriggered = false
private var allAppsTouchRunnable: Runnable? = null
private var allAppsButtonTouchDelayMs: Long = ViewConfiguration.getLongPressTimeout().toLong()
private lateinit var taskbarViewCallbacks: TaskbarViewCallbacks
override val spaceNeeded: Int
get() {
return dpToPx(activityContext.taskbarSpecsEvaluator!!.taskbarIconSize.size.toFloat())
return dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconSize.size.toFloat())
}
init {
LayoutInflater.from(context).inflate(R.layout.taskbar_all_apps_button, null, false)
setUpIcon()
}
@@ -58,24 +69,39 @@ constructor(
private fun setUpIcon() {
val drawable =
resources.getDrawable(
getAllAppsButton(activityContext.taskbarFeatureEvaluator!!.isTransient)
getAllAppsButton(activityContext.taskbarFeatureEvaluator.isTransient)
)
val padding = activityContext.taskbarSpecsEvaluator!!.taskbarIconPadding
backgroundTintList = ColorStateList.valueOf(TRANSPARENT)
setIconDrawable(drawable)
setPadding(dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconPadding.toFloat()))
setForegroundTint(activityContext.getColor(R.color.all_apps_button_color))
}
allAppsButton.setIconDrawable(drawable)
allAppsButton.setPadding(padding)
allAppsButton.setForegroundTint(activityContext.getColor(R.color.all_apps_button_color))
// TODO(b/356465292) : add click listeners in future cl
addView(allAppsButton)
@SuppressLint("ClickableViewAccessibility")
fun setUpCallbacks(callbacks: TaskbarViewCallbacks) {
taskbarViewCallbacks = callbacks
setOnClickListener(this::onAllAppsButtonClick)
setOnLongClickListener(this::onAllAppsButtonLongClick)
setOnTouchListener(this::onAllAppsButtonTouch)
isHapticFeedbackEnabled = taskbarViewCallbacks.isAllAppsButtonHapticFeedbackEnabled()
allAppsTouchRunnable = Runnable {
taskbarViewCallbacks.triggerAllAppsButtonLongClick()
allAppsTouchTriggered = true
}
val assistStateManager = AssistStateManager.INSTANCE[mContext]
if (
DeviceConfigWrapper.get().customLpaaThresholds &&
assistStateManager.lpnhDurationMillis.isPresent
) {
allAppsButtonTouchDelayMs = assistStateManager.lpnhDurationMillis.get()
}
}
@DrawableRes
private fun getAllAppsButton(isTransientTaskbar: Boolean): Int {
val shouldSelectTransientIcon =
isTransientTaskbar ||
(FeatureFlags.enableTaskbarPinning() && !activityContext.isThreeButtonNav)
return if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
isTransientTaskbar || (enableTaskbarPinning() && !activityContext.isThreeButtonNav)
return if (ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
if (shouldSelectTransientIcon) R.drawable.ic_transient_taskbar_all_apps_search_button
else R.drawable.ic_taskbar_all_apps_search_button
} else {
@@ -88,10 +114,43 @@ constructor(
fun getAllAppsButtonTranslationXOffset(isTransientTaskbar: Boolean): Int {
return if (isTransientTaskbar) {
R.dimen.transient_taskbar_all_apps_button_translation_x_offset
} else if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
} else if (ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
R.dimen.taskbar_all_apps_search_button_translation_x_offset
} else {
R.dimen.taskbar_all_apps_button_translation_x_offset
}
}
private fun onAllAppsButtonTouch(view: View, ev: MotionEvent): Boolean {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
allAppsTouchTriggered = false
MAIN_EXECUTOR.handler.postDelayed(allAppsTouchRunnable!!, allAppsButtonTouchDelayMs)
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> cancelAllAppsButtonTouch()
}
return false
}
private fun cancelAllAppsButtonTouch() {
MAIN_EXECUTOR.handler.removeCallbacks(allAppsTouchRunnable!!)
// ACTION_UP is first triggered, then click listener / long-click listener is triggered on
// the next frame, so we need to post twice and delay the reset.
this.post { this.post { allAppsTouchTriggered = false } }
}
private fun onAllAppsButtonClick(view: View) {
if (!allAppsTouchTriggered) {
taskbarViewCallbacks.triggerAllAppsButtonClick(view)
}
}
// Handle long click from Switch Access and Voice Access
private fun onAllAppsButtonLongClick(view: View): Boolean {
if (!MAIN_EXECUTOR.handler.hasCallbacks(allAppsTouchRunnable!!) && !allAppsTouchTriggered) {
taskbarViewCallbacks.triggerAllAppsButtonLongClick()
}
return true
}
}
@@ -18,13 +18,15 @@ package com.android.launcher3.taskbar.customization
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color.TRANSPARENT
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import androidx.core.view.setPadding
import com.android.launcher3.R
import com.android.launcher3.Utilities.dpToPx
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.TaskbarViewCallbacks
import com.android.launcher3.views.ActivityContext
import com.android.launcher3.views.IconButtonView
@@ -35,31 +37,30 @@ constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : LinearLayout(context, attrs), TaskbarContainer {
private val taskbarDivider: IconButtonView =
LayoutInflater.from(context).inflate(R.layout.taskbar_divider, this, false)
as IconButtonView
) : IconButtonView(context, attrs), TaskbarContainer {
private val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
override val spaceNeeded: Int
get() {
return dpToPx(activityContext.taskbarSpecsEvaluator!!.taskbarIconSize.size.toFloat())
return dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconSize.size.toFloat())
}
init {
LayoutInflater.from(context).inflate(R.layout.taskbar_divider, null, false)
setUpIcon()
}
@SuppressLint("UseCompatLoadingForDrawables")
fun setUpIcon() {
backgroundTintList = ColorStateList.valueOf(TRANSPARENT)
val drawable = resources.getDrawable(R.drawable.taskbar_divider_button)
val padding = activityContext.taskbarSpecsEvaluator!!.taskbarIconPadding
setIconDrawable(drawable)
setPadding(dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconPadding.toFloat()))
}
taskbarDivider.setIconDrawable(drawable)
taskbarDivider.setPadding(padding)
// TODO(b/356465292):: add click listeners in future cl
addView(taskbarDivider)
@SuppressLint("ClickableViewAccessibility")
fun setUpCallbacks(callbacks: TaskbarViewCallbacks) {
setOnLongClickListener(callbacks.taskbarDividerLongClickListener)
setOnTouchListener(callbacks.taskbarDividerRightClickListener)
}
}
@@ -19,10 +19,11 @@ package com.android.launcher3.taskbar.customization
/** Taskbar Icon Specs */
object TaskbarIconSpecs {
val iconSize40dp = TaskbarIconSize(40)
val iconSize44dp = TaskbarIconSize(44)
val iconSize48dp = TaskbarIconSize(48)
val iconSize52dp = TaskbarIconSize(52)
// Mapping of visual icon size to icon specs value http://b/235886078
val iconSize40dp = TaskbarIconSize(44)
val iconSize44dp = TaskbarIconSize(48)
val iconSize48dp = TaskbarIconSize(52)
val iconSize52dp = TaskbarIconSize(57)
val transientTaskbarIconSizes = arrayOf(iconSize44dp, iconSize48dp, iconSize52dp)
@@ -25,14 +25,14 @@ class TaskbarSpecsEvaluator(
numRows: Int = taskbarActivityContext.deviceProfile.inv.numRows,
numColumns: Int = taskbarActivityContext.deviceProfile.inv.numColumns,
) {
var taskbarIconSize: TaskbarIconSize = getIconSizeByGrid(numRows, numColumns)
var taskbarIconSize: TaskbarIconSize = getIconSizeByGrid(numColumns, numRows)
// TODO(b/341146605) : initialize it to taskbar container in later cl.
private var taskbarContainer: List<TaskbarContainer> = emptyList()
val taskbarIconPadding: Int =
if (TaskbarIconSpecs.minimumTaskbarIconTouchSize.size > taskbarIconSize.size) {
(TaskbarIconSpecs.minimumTaskbarIconTouchSize.size - taskbarIconSize.size) / 2
if (TaskbarIconSpecs.iconSize52dp.size > taskbarIconSize.size) {
(TaskbarIconSpecs.iconSize52dp.size - taskbarIconSize.size) / 2
} else {
0
}
@@ -44,10 +44,10 @@ class TaskbarSpecsEvaluator(
TaskbarIconSpecs.defaultPersistentIconMargin
}
fun getIconSizeByGrid(row: Int, column: Int): TaskbarIconSize {
fun getIconSizeByGrid(columns: Int, rows: Int): TaskbarIconSize {
return if (taskbarFeatureEvaluator.isTransient) {
TaskbarIconSpecs.transientTaskbarIconSizeByGridSize.getOrDefault(
TransientTaskbarIconSizeKey(row, column, taskbarFeatureEvaluator.isLandscape),
TransientTaskbarIconSizeKey(columns, rows, taskbarFeatureEvaluator.isLandscape),
TaskbarIconSpecs.defaultTransientIconSize,
)
} else {
@@ -102,6 +102,6 @@ class TaskbarSpecsEvaluator(
data class TaskbarIconSize(val size: Int)
data class TransientTaskbarIconSizeKey(val row: Int, val column: Int, val isLandscape: Boolean)
data class TransientTaskbarIconSizeKey(val columns: Int, val rows: Int, val isLandscape: Boolean)
data class TaskbarIconMarginSize(val size: Int)
@@ -379,12 +379,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
}
@Override
public void setIconVisible(boolean visible) {
setForceHideRing(!visible);
super.setIconVisible(visible);
}
private void setForceHideRing(boolean forceHideRing) {
public void setForceHideRing(boolean forceHideRing) {
if (mForceHideRing == forceHideRing) {
return;
}
@@ -417,7 +412,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
private void drawEffect(Canvas canvas) {
// Don't draw ring effect if item is about to be dragged or if the icon is not visible.
if (mDrawForDrag || !mIsIconVisible) {
if (mDrawForDrag || !mIsIconVisible || mForceHideRing) {
return;
}
mIconRingPaint.setColor(RING_SHADOW_COLOR);
@@ -22,7 +22,6 @@ import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
import static com.android.launcher3.Flags.enablePredictiveBackGesture;
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
@@ -499,10 +498,8 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
if (started) {
DeviceProfile profile = getDeviceProfile();
boolean willUserBeActive =
(getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
boolean visible = (state == NORMAL || state == OVERVIEW)
&& (willUserBeActive || isUserActive())
&& isUserActive()
&& !profile.isVerticalBarLayout()
&& !mIsOverlayVisible;
SystemUiProxy.INSTANCE.get(this)
@@ -696,9 +693,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
// Back dispatcher is registered in {@link BaseActivity#onCreate}. For predictive back to
// work, we must opt-in BEFORE registering back dispatcher. So we need to call
// setEnableOnBackInvokedCallback() before super.onCreate()
if (Utilities.ATLEAST_U && enablePredictiveBackGesture()) {
getApplicationInfo().setEnableOnBackInvokedCallback(true);
}
getApplicationInfo().setEnableOnBackInvokedCallback(true);
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mPendingSplitSelectInfo = ObjectWrapper.unwrap(
@@ -910,8 +905,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
// event won't go through ViewRootImpl#InputStage#onProcess.
// So when receive back key, try to do the same check thing in
// ViewRootImpl#NativePreImeInputStage#onProcess
if (!Utilities.ATLEAST_U || !enablePredictiveBackGesture()
|| event.getKeyCode() != KeyEvent.KEYCODE_BACK
if (event.getKeyCode() != KeyEvent.KEYCODE_BACK
|| event.getAction() != KeyEvent.ACTION_UP || event.isCanceled()) {
return false;
}
@@ -922,10 +916,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
@Override
protected void registerBackDispatcher() {
if (!enablePredictiveBackGesture()) {
super.registerBackDispatcher();
return;
}
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
new OnBackAnimationCallback() {
@@ -1279,10 +1269,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
return ObjectWrapper.wrap(new Integer(info.id));
}
public void setHintUserWillBeActive() {
addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
}
@Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
super.onDisplayInfoChanged(context, info, flags);
@@ -17,7 +17,6 @@ package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import android.content.Context;
import android.graphics.Color;
@@ -107,8 +106,7 @@ public class BackgroundAppState extends OverviewState {
@Override
public boolean isTaskbarAlignedWithHotseat(Launcher launcher) {
if (ENABLE_SHELL_TRANSITIONS) return false;
return super.isTaskbarAlignedWithHotseat(launcher);
return false;
}
@Override
@@ -97,6 +97,7 @@ import android.window.PictureInPictureSurfaceTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import com.android.internal.jank.Cuj;
import com.android.internal.util.LatencyTracker;
@@ -170,10 +171,12 @@ import java.util.function.Consumer;
/**
* Handles the navigation gestures when Launcher is the default home activity.
*/
public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
Q extends RecentsView, S extends BaseState<S>>
extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
RecentsAnimationCallbacks.RecentsAnimationListener {
public abstract class AbsSwipeUpHandler<
RECENTS_CONTAINER extends Context & RecentsViewContainer,
RECENTS_VIEW extends RecentsView<RECENTS_CONTAINER, STATE>,
STATE extends BaseState<STATE>>
extends SwipeUpAnimationLogic
implements OnApplyWindowInsetsListener, RecentsAnimationCallbacks.RecentsAnimationListener {
private static final String TAG = "AbsSwipeUpHandler";
private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
@@ -181,7 +184,7 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
// Fraction of the scroll and transform animation in which the current task fades out
private static final float KQS_TASK_FADE_ANIMATION_FRACTION = 0.4f;
protected final BaseContainerInterface<S, T> mContainerInterface;
protected final BaseContainerInterface<STATE, RECENTS_CONTAINER> mContainerInterface;
protected final InputConsumerProxy mInputConsumerProxy;
protected final ActivityInitListener mActivityInitListener;
// Callbacks to be made once the recents animation starts
@@ -192,8 +195,8 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
protected @Nullable RecentsAnimationController mRecentsAnimationController;
protected @Nullable RecentsAnimationController mDeferredCleanupRecentsAnimationController;
protected RecentsAnimationTargets mRecentsAnimationTargets;
protected @Nullable T mContainer;
protected @Nullable Q mRecentsView;
protected @Nullable RECENTS_CONTAINER mContainer;
protected @Nullable RECENTS_VIEW mRecentsView;
protected Runnable mGestureEndCallback;
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
@@ -486,11 +489,11 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
return false;
}
T createdContainer = (T) mContainerInterface.getCreatedContainer();
RECENTS_CONTAINER createdContainer = mContainerInterface.getCreatedContainer();
if (createdContainer != null) {
initTransitionEndpoints(createdContainer.getDeviceProfile());
}
final T container = (T) mContainerInterface.getCreatedContainer();
final RECENTS_CONTAINER container = mContainerInterface.getCreatedContainer();
if (mContainer == container) {
return true;
}
@@ -564,7 +567,7 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
}
private void onLauncherStart() {
final T container = (T) mContainerInterface.getCreatedContainer();
final RECENTS_CONTAINER container = mContainerInterface.getCreatedContainer();
if (container == null || mContainer != container) {
return;
}
@@ -949,7 +952,6 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
// We will handle the sysui flags based on the centermost task view.
mRecentsAnimationController.setUseLauncherSystemBarFlags(swipeUpThresholdPassed
|| (quickswitchThresholdPassed && centermostTaskFlags != 0));
mRecentsAnimationController.setSplitScreenMinimized(mContext, swipeUpThresholdPassed);
// Provide a hint to WM the direction that we will be settling in case the animation
// needs to be canceled
mRecentsAnimationController.setWillFinishToHome(swipeUpThresholdPassed);
@@ -1097,7 +1099,7 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
*/
@UiThread
private void notifyGestureStarted() {
final T curActivity = mContainer;
final RECENTS_CONTAINER curActivity = mContainer;
if (curActivity != null) {
// Once the gesture starts, we can no longer transition home through the button, so
// reset the force override of the activity visibility
@@ -1168,7 +1170,8 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
/**
* Called if the end target has been set and the recents animation is started.
*/
private void onCalculateEndTarget() {
@VisibleForTesting
protected void onCalculateEndTarget() {
final GestureEndTarget endTarget = mGestureState.getEndTarget();
switch (endTarget) {
@@ -1181,7 +1184,8 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
}
}
private void onSettledOnEndTarget() {
@VisibleForTesting
protected void onSettledOnEndTarget() {
// Fast-finish the attaching animation if it's still running.
maybeUpdateRecentsAttachedState(false);
final GestureEndTarget endTarget = mGestureState.getEndTarget();
@@ -1417,7 +1421,7 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
}
}
Interpolator interpolator;
S state = mContainerInterface.stateFromGestureEndTarget(endTarget);
STATE state = mContainerInterface.stateFromGestureEndTarget(endTarget);
if (isKeyboardTaskFocusPending()) {
interpolator = EMPHASIZED;
} else if (state.displayOverviewTasksAsGrid(mDp)) {
@@ -1767,8 +1771,7 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
private int calculateWindowRotation(RemoteAnimationTarget runningTaskTarget,
RecentsOrientedState orientationState) {
if (runningTaskTarget.rotationChange != 0
&& TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
if (runningTaskTarget.rotationChange != 0) {
return Math.abs(runningTaskTarget.rotationChange) == ROTATION_90
? ROTATION_270 : ROTATION_90;
} else {
@@ -2499,7 +2502,7 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
}
private void animateSplashScreenExit(
@NonNull T activity,
@NonNull RECENTS_CONTAINER activity,
@NonNull RemoteAnimationTarget[] appearedTaskTargets,
@NonNull RemoteAnimationTarget[] animatingTargets) {
ViewGroup splashView = activity.getDragLayer();
@@ -68,8 +68,6 @@ public abstract class BaseContainerInterface<STATE_TYPE extends BaseState<STATE_
public abstract void onAssistantVisibilityChanged(float assistantVisibility);
public abstract boolean allowMinimizeSplitScreen();
public abstract boolean isResumed();
public abstract boolean isStarted();
@@ -132,12 +132,6 @@ public final class FallbackActivityInterface extends
return target.screenSpaceBounds;
}
@Override
public boolean allowMinimizeSplitScreen() {
// TODO: Remove this once b/77875376 is fixed
return false;
}
@Override
public boolean allowAllAppsFromOverview() {
return false;
@@ -270,11 +270,6 @@ public final class LauncherActivityInterface extends
return homeBounds;
}
@Override
public boolean allowMinimizeSplitScreen() {
return true;
}
@Override
public boolean allowAllAppsFromOverview() {
return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
@@ -62,8 +62,8 @@ import java.util.List;
/**
* Temporary class to allow easier refactoring
*/
public class LauncherSwipeHandlerV2 extends
AbsSwipeUpHandler<QuickstepLauncher, RecentsView, LauncherState> {
public class LauncherSwipeHandlerV2 extends AbsSwipeUpHandler<
QuickstepLauncher, RecentsView<QuickstepLauncher, LauncherState>, LauncherState> {
public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
@@ -107,9 +107,6 @@ public class LauncherSwipeHandlerV2 extends
|| !mContainer.getDesktopVisibilityController().areDesktopTasksVisible());
mContainer.getRootView().setForceHideBackArrow(true);
if (!TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
mContainer.setHintUserWillBeActive();
}
if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) {
return new LauncherHomeAnimationFactory() {
@@ -33,6 +33,8 @@ import com.android.launcher3.logging.StatsLogManager
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.*
import com.android.launcher3.util.Executors
import com.android.launcher3.util.RunnableList
import com.android.quickstep.OverviewCommandHelper.CommandInfo.CommandStatus
import com.android.quickstep.OverviewCommandHelper.CommandType.*
import com.android.quickstep.util.ActiveGestureLog
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsViewContainer
@@ -40,6 +42,7 @@ import com.android.quickstep.views.TaskView
import com.android.systemui.shared.recents.model.ThumbnailData
import com.android.systemui.shared.system.InteractionJankMonitorWrapper
import java.io.PrintWriter
import java.util.concurrent.ConcurrentLinkedDeque
/** Helper class to handle various atomic commands for switching between Overview. */
class OverviewCommandHelper(
@@ -47,7 +50,7 @@ class OverviewCommandHelper(
private val overviewComponentObserver: OverviewComponentObserver,
private val taskAnimationManager: TaskAnimationManager
) {
private val pendingCommands = mutableListOf<CommandInfo>()
private val commandQueue = ConcurrentLinkedDeque<CommandInfo>()
/**
* Index of the TaskView that should be focused when launching Overview. Persisted so that we do
@@ -64,23 +67,39 @@ class OverviewCommandHelper(
*/
private var waitForToggleCommandComplete = false
/** Called when the command finishes execution. */
private fun scheduleNextTask(command: CommandInfo) {
if (pendingCommands.isEmpty()) {
Log.d(TAG, "no pending commands to schedule")
private val activityInterface: BaseActivityInterface<*, *>
get() = overviewComponentObserver.activityInterface
private val visibleRecentsView: RecentsView<*, *>?
get() = activityInterface.getVisibleRecentsView<RecentsView<*, *>>()
/**
* Adds a command to be executed next, after all pending tasks are completed. Max commands that
* can be queued is [.MAX_QUEUE_SIZE]. Requests after reaching that limit will be silently
* dropped.
*/
@BinderThread
fun addCommand(type: CommandType) {
if (commandQueue.size >= MAX_QUEUE_SIZE) {
Log.d(TAG, "commands queue is full ($commandQueue). command not added: $type")
return
}
if (pendingCommands.first() !== command) {
Log.d(
TAG,
"next task not scheduled. First pending command type " +
"is ${pendingCommands.first()} - command type is: $command"
)
return
val command = CommandInfo(type)
commandQueue.add(command)
Log.d(TAG, "command added: $command")
if (commandQueue.size == 1) {
Executors.MAIN_EXECUTOR.execute { executeNext() }
}
Log.d(TAG, "scheduleNextTask called: $command")
pendingCommands.removeFirst()
executeNext()
}
fun canStartHomeSafely(): Boolean = commandQueue.isEmpty() || commandQueue.first().type == HOME
/** Clear pending commands from the queue */
fun clearPendingCommands() {
Log.d(TAG, "clearing pending commands: $commandQueue")
commandQueue.clear()
}
/**
@@ -90,67 +109,76 @@ class OverviewCommandHelper(
*/
@UiThread
private fun executeNext() {
if (pendingCommands.isEmpty()) {
Log.d(TAG, "executeNext - pendingCommands is empty")
return
}
val command = pendingCommands.first()
val result = executeCommand(command)
Log.d(TAG, "executeNext command type: $command, result: $result")
if (result) {
scheduleNextTask(command)
}
}
val command: CommandInfo =
commandQueue.firstOrNull()
?: run {
Log.d(TAG, "no pending commands to be executed.")
return
}
@UiThread
private fun addCommand(command: CommandInfo) {
val wasEmpty = pendingCommands.isEmpty()
pendingCommands.add(command)
if (wasEmpty) {
executeNext()
command.status = CommandStatus.PROCESSING
Log.d(TAG, "executing command: $command")
val result = executeCommand(command)
Log.d(TAG, "command executed: $command with result: $result")
if (result) {
onCommandFinished(command)
} else {
Log.d(TAG, "waiting for command callback: $command")
}
}
/**
* Adds a command to be executed next, after all pending tasks are completed. Max commands that
* can be queued is [.MAX_QUEUE_SIZE]. Requests after reaching that limit will be silently
* dropped.
* Executes the task and returns true if next task can be executed. If false, then the next task
* is deferred until [.scheduleNextTask] is called
*/
@BinderThread
fun addCommand(type: Int) {
if (pendingCommands.size >= MAX_QUEUE_SIZE) {
Log.d(
TAG,
"the pending command queue is full (${pendingCommands.size}). command not added: $type"
)
return
private fun executeCommand(command: CommandInfo): Boolean {
if (waitForToggleCommandComplete && command.type == TOGGLE) {
Log.d(TAG, "executeCommand: $command - waiting for toggle command complete")
return true
}
Log.d(TAG, "adding command type: $type")
val command = CommandInfo(type)
Executors.MAIN_EXECUTOR.execute { addCommand(command) }
}
@UiThread
fun clearPendingCommands() {
Log.d(TAG, "clearing pending commands - size: ${pendingCommands.size}")
pendingCommands.clear()
}
@UiThread
fun canStartHomeSafely(): Boolean =
pendingCommands.isEmpty() || pendingCommands.first().type == TYPE_HOME
private fun getNextTask(view: RecentsView<*, *>): TaskView? {
val runningTaskView = view.runningTaskView
return if (runningTaskView == null) {
view.getTaskViewAt(0)
val recentsView = visibleRecentsView
Log.d(TAG, "executeCommand: $command - visibleRecentsView: $recentsView")
return if (recentsView != null) {
executeWhenRecentsIsVisible(command, recentsView)
} else {
val nextTask = view.nextTaskView
nextTask ?: runningTaskView
executeWhenRecentsIsNotVisible(command)
}
}
private fun executeWhenRecentsIsVisible(
command: CommandInfo,
recentsView: RecentsView<*, *>,
): Boolean =
when (command.type) {
SHOW -> true // already visible
KEYBOARD_INPUT,
HIDE -> {
if (recentsView.isHandlingTouch) {
true
} else {
keyboardTaskFocusIndex = PagedView.INVALID_PAGE
val currentPage = recentsView.nextPage
val taskView = recentsView.getTaskViewAt(currentPage)
launchTask(recentsView, taskView, command)
}
}
TOGGLE -> {
val taskView =
if (recentsView.runningTaskView == null) {
recentsView.getTaskViewAt(0)
} else {
recentsView.nextTaskView ?: recentsView.runningTaskView
}
launchTask(recentsView, taskView, command)
}
HOME -> {
recentsView.startHome()
true
}
}
private fun launchTask(
recents: RecentsView<*, *>,
taskView: TaskView?,
@@ -166,7 +194,7 @@ class OverviewCommandHelper(
if (callbackList != null) {
callbackList.add {
Log.d(TAG, "launching task callback: $command")
scheduleNextTask(command)
onCommandFinished(command)
waitForToggleCommandComplete = false
}
Log.d(TAG, "launching task - waiting for callback: $command")
@@ -178,94 +206,54 @@ class OverviewCommandHelper(
}
}
/**
* Executes the task and returns true if next task can be executed. If false, then the next task
* is deferred until [.scheduleNextTask] is called
*/
private fun executeCommand(command: CommandInfo): Boolean {
if (waitForToggleCommandComplete && command.type == TYPE_TOGGLE) {
Log.d(TAG, "executeCommand: $command - waiting for toggle command complete")
return true
}
val activityInterface: BaseActivityInterface<*, *> =
overviewComponentObserver.activityInterface
private fun executeWhenRecentsIsNotVisible(command: CommandInfo): Boolean {
val recentsViewContainer = activityInterface.getCreatedContainer() as? RecentsViewContainer
val recentsView: RecentsView<*, *>? = recentsViewContainer?.getOverviewPanel()
val deviceProfile = recentsViewContainer?.getDeviceProfile()
val uiController = activityInterface.getTaskbarController()
val allowQuickSwitch =
FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get() &&
uiController != null &&
deviceProfile != null &&
(deviceProfile.isTablet || deviceProfile.isTwoPanels)
val visibleRecentsView: RecentsView<*, *>? =
activityInterface.getVisibleRecentsView<RecentsView<*, *>>()
val createdRecentsView: RecentsView<*, *>?
Log.d(TAG, "executeCommand: $command - visibleRecentsView: $visibleRecentsView")
if (visibleRecentsView == null) {
val activity = activityInterface.getCreatedContainer() as? RecentsViewContainer
createdRecentsView = activity?.getOverviewPanel()
val deviceProfile = activity?.getDeviceProfile()
val uiController = activityInterface.getTaskbarController()
val allowQuickSwitch =
FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get() &&
uiController != null &&
deviceProfile != null &&
(deviceProfile.isTablet || deviceProfile.isTwoPanels)
when (command.type) {
TYPE_HIDE -> {
if (!allowQuickSwitch) return true
keyboardTaskFocusIndex = uiController!!.launchFocusedTask()
if (keyboardTaskFocusIndex == -1) return true
}
TYPE_KEYBOARD_INPUT ->
if (allowQuickSwitch) {
uiController!!.openQuickSwitchView()
return true
} else {
keyboardTaskFocusIndex = 0
}
TYPE_HOME -> {
ActiveGestureLog.INSTANCE.addLog(
"OverviewCommandHelper.executeCommand(TYPE_HOME)"
)
// Although IActivityTaskManager$Stub$Proxy.startActivity is a slow binder call,
// we should still call it on main thread because launcher is waiting for
// ActivityTaskManager to resume it. Also calling startActivity() on bg thread
// could potentially delay resuming launcher. See b/348668521 for more details.
touchInteractionService.startActivity(overviewComponentObserver.homeIntent)
when (command.type) {
HIDE -> {
if (!allowQuickSwitch) return true
keyboardTaskFocusIndex = uiController!!.launchFocusedTask()
if (keyboardTaskFocusIndex == -1) return true
}
KEYBOARD_INPUT ->
if (allowQuickSwitch) {
uiController!!.openQuickSwitchView()
return true
}
TYPE_SHOW ->
// When Recents is not currently visible, the command's type is
// TYPE_SHOW
// when overview is triggered via the keyboard overview button or Action+Tab
// keys (Not Alt+Tab which is KQS). The overview button on-screen in 3-button
// nav is TYPE_TOGGLE.
} else {
keyboardTaskFocusIndex = 0
else -> {}
}
} else {
createdRecentsView = visibleRecentsView
when (command.type) {
TYPE_SHOW -> return true // already visible
TYPE_KEYBOARD_INPUT,
TYPE_HIDE -> {
if (visibleRecentsView.isHandlingTouch) return true
keyboardTaskFocusIndex = PagedView.INVALID_PAGE
val currentPage = visibleRecentsView.nextPage
val taskView = visibleRecentsView.getTaskViewAt(currentPage)
return launchTask(visibleRecentsView, taskView, command)
}
TYPE_TOGGLE ->
return launchTask(visibleRecentsView, getNextTask(visibleRecentsView), command)
TYPE_HOME -> {
visibleRecentsView.startHome()
return true
}
HOME -> {
ActiveGestureLog.INSTANCE.addLog("OverviewCommandHelper.executeCommand(HOME)")
// Although IActivityTaskManager$Stub$Proxy.startActivity is a slow binder call,
// we should still call it on main thread because launcher is waiting for
// ActivityTaskManager to resume it. Also calling startActivity() on bg thread
// could potentially delay resuming launcher. See b/348668521 for more details.
touchInteractionService.startActivity(overviewComponentObserver.homeIntent)
return true
}
SHOW ->
// When Recents is not currently visible, the command's type is SHOW
// when overview is triggered via the keyboard overview button or Action+Tab
// keys (Not Alt+Tab which is KQS). The overview button on-screen in 3-button
// nav is TYPE_TOGGLE.
keyboardTaskFocusIndex = 0
TOGGLE -> {}
}
createdRecentsView?.setKeyboardTaskFocusIndex(keyboardTaskFocusIndex)
recentsView?.setKeyboardTaskFocusIndex(keyboardTaskFocusIndex)
// Handle recents view focus when launching from home
val animatorListener: Animator.AnimatorListener =
object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator) {
Log.d(TAG, "switching to Overview state - onAnimationStart: $command")
super.onAnimationStart(animation)
updateRecentsViewFocus(command)
logShowOverviewFrom(command.type)
@@ -275,7 +263,7 @@ class OverviewCommandHelper(
Log.d(TAG, "switching to Overview state - onAnimationEnd: $command")
super.onAnimationEnd(animation)
onRecentsViewFocusUpdated(command)
scheduleNextTask(command)
onCommandFinished(command)
}
}
if (activityInterface.switchToRecentsIfVisible(animatorListener)) {
@@ -311,9 +299,11 @@ class OverviewCommandHelper(
controller: RecentsAnimationController,
targets: RecentsAnimationTargets
) {
Log.d(TAG, "recents animation started: $command")
updateRecentsViewFocus(command)
logShowOverviewFrom(command.type)
activityInterface.runOnInitBackgroundStateUI {
Log.d(TAG, "recents animation started - onInitBackgroundStateUI: $command")
interactionHandler.onGestureEnded(0f, PointF())
}
command.removeListener(this)
@@ -322,11 +312,12 @@ class OverviewCommandHelper(
override fun onRecentsAnimationCanceled(
thumbnailDatas: HashMap<Int, ThumbnailData>
) {
Log.d(TAG, "recents animation canceled: $command")
interactionHandler.onGestureCancelled()
command.removeListener(this)
activityInterface.getCreatedContainer() ?: return
createdRecentsView?.onRecentsAnimationComplete()
recentsView?.onRecentsAnimationComplete()
}
}
@@ -347,7 +338,7 @@ class OverviewCommandHelper(
taskAnimationManager.notifyRecentsAnimationState(recentAnimListener)
} else {
val intent =
Intent(interactionHandler.launchIntent)
Intent(interactionHandler.getLaunchIntent())
.putExtra(ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID, gestureState.gestureId)
command.setAnimationCallbacks(
taskAnimationManager.startRecentsAnimation(gestureState, intent, interactionHandler)
@@ -365,17 +356,29 @@ class OverviewCommandHelper(
command.removeListener(handler)
Trace.endAsyncSection(TRANSITION_NAME, 0)
onRecentsViewFocusUpdated(command)
scheduleNextTask(command)
onCommandFinished(command)
}
/** Called when the command finishes execution. */
private fun onCommandFinished(command: CommandInfo) {
command.status = CommandStatus.COMPLETED
if (commandQueue.first() !== command) {
Log.d(
TAG,
"next task not scheduled. First pending command type " +
"is ${commandQueue.first()} - command type is: $command"
)
return
}
Log.d(TAG, "command executed successfully! $command")
commandQueue.remove(command)
executeNext()
}
private fun updateRecentsViewFocus(command: CommandInfo) {
val recentsView: RecentsView<*, *> =
overviewComponentObserver.activityInterface.getVisibleRecentsView() ?: return
if (
command.type != TYPE_KEYBOARD_INPUT &&
command.type != TYPE_HIDE &&
command.type != TYPE_SHOW
) {
val recentsView: RecentsView<*, *> = visibleRecentsView ?: return
if (command.type != KEYBOARD_INPUT && command.type != HIDE && command.type != SHOW) {
return
}
@@ -387,16 +390,16 @@ class OverviewCommandHelper(
// here we launch overview with live tile.
recentsView.viewRootImpl.touchModeChanged(false)
// Ensure that recents view has focus so that it receives the followup key inputs
if (requestFocus(recentsView.getTaskViewAt(keyboardTaskFocusIndex))) return
if (requestFocus(recentsView.nextTaskView)) return
if (requestFocus(recentsView.getTaskViewAt(0))) return
requestFocus(recentsView)
// Stops requesting focused after first view gets focused.
recentsView.getTaskViewAt(keyboardTaskFocusIndex).requestFocus() ||
recentsView.nextTaskView.requestFocus() ||
recentsView.getTaskViewAt(0).requestFocus() ||
recentsView.requestFocus()
}
private fun onRecentsViewFocusUpdated(command: CommandInfo) {
val recentsView: RecentsView<*, *> =
overviewComponentObserver.activityInterface.getVisibleRecentsView() ?: return
if (command.type != TYPE_HIDE || keyboardTaskFocusIndex == PagedView.INVALID_PAGE) {
val recentsView: RecentsView<*, *> = visibleRecentsView ?: return
if (command.type != HIDE || keyboardTaskFocusIndex == PagedView.INVALID_PAGE) {
return
}
recentsView.setKeyboardTaskFocusIndex(PagedView.INVALID_PAGE)
@@ -404,24 +407,22 @@ class OverviewCommandHelper(
keyboardTaskFocusIndex = PagedView.INVALID_PAGE
}
private fun requestFocus(taskView: View?): Boolean {
if (taskView == null) return false
taskView.post {
taskView.requestFocus()
taskView.requestAccessibilityFocus()
private fun View?.requestFocus(): Boolean {
if (this == null) return false
post {
requestFocus()
requestAccessibilityFocus()
}
return true
}
private fun logShowOverviewFrom(commandType: Int) {
val activityInterface: BaseActivityInterface<*, *> =
overviewComponentObserver.activityInterface
private fun logShowOverviewFrom(commandType: CommandType) {
val container = activityInterface.getCreatedContainer() as? RecentsViewContainer ?: return
val event =
when (commandType) {
TYPE_SHOW -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT
TYPE_HIDE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH
TYPE_TOGGLE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON
SHOW -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT
HIDE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH
TOGGLE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON
else -> return
}
StatsLogManager.newInstance(container.asContext())
@@ -438,16 +439,17 @@ class OverviewCommandHelper(
fun dump(pw: PrintWriter) {
pw.println("OverviewCommandHelper:")
pw.println(" pendingCommands=${pendingCommands.size}")
if (pendingCommands.isNotEmpty()) {
pw.println(" pendingCommandType=${pendingCommands.first().type}")
pw.println(" pendingCommands=${commandQueue.size}")
if (commandQueue.isNotEmpty()) {
pw.println(" pendingCommandType=${commandQueue.first().type}")
}
pw.println(" mKeyboardTaskFocusIndex=$keyboardTaskFocusIndex")
pw.println(" mWaitForToggleCommandComplete=$waitForToggleCommandComplete")
pw.println(" keyboardTaskFocusIndex=$keyboardTaskFocusIndex")
pw.println(" waitForToggleCommandComplete=$waitForToggleCommandComplete")
}
private data class CommandInfo(
val type: Int,
val type: CommandType,
var status: CommandStatus = CommandStatus.IDLE,
val createTime: Long = SystemClock.elapsedRealtime(),
private var animationCallbacks: RecentsAnimationCallbacks? = null
) {
@@ -462,23 +464,30 @@ class OverviewCommandHelper(
fun removeListener(listener: RecentsAnimationCallbacks.RecentsAnimationListener?) {
animationCallbacks?.removeListener(listener)
}
enum class CommandStatus {
IDLE,
PROCESSING,
COMPLETED
}
}
enum class CommandType {
SHOW,
KEYBOARD_INPUT,
HIDE,
TOGGLE, // Navigate to Overview
HOME, // Navigate to Home
}
companion object {
private const val TAG = "OverviewCommandHelper"
const val TYPE_SHOW: Int = 1
const val TYPE_KEYBOARD_INPUT: Int = 2
const val TYPE_HIDE: Int = 3
const val TYPE_TOGGLE: Int = 4
const val TYPE_HOME: Int = 5
private const val TRANSITION_NAME = "Transition:toOverview"
/**
* Use case for needing a queue is double tapping recents button in 3 button nav. Size of 2
* should be enough. We'll toss in one more because we're kind hearted.
*/
private const val MAX_QUEUE_SIZE = 3
private const val TRANSITION_NAME = "Transition:toOverview"
}
}
@@ -54,17 +54,14 @@ public class RecentsAnimationCallbacks implements
private final Set<RecentsAnimationListener> mListeners = new ArraySet<>();
private final SystemUiProxy mSystemUiProxy;
private final boolean mAllowMinimizeSplitScreen;
// TODO(141886704): Remove these references when they are no longer needed
private RecentsAnimationController mController;
private boolean mCancelled;
public RecentsAnimationCallbacks(SystemUiProxy systemUiProxy,
boolean allowMinimizeSplitScreen) {
public RecentsAnimationCallbacks(SystemUiProxy systemUiProxy) {
mSystemUiProxy = systemUiProxy;
mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
}
@UiThread
@@ -122,7 +119,7 @@ public class RecentsAnimationCallbacks implements
}
mController = new RecentsAnimationController(animationController,
mAllowMinimizeSplitScreen, this::onAnimationFinished);
this::onAnimationFinished);
if (mCancelled) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
mController::finishAnimationToApp);
@@ -219,7 +216,6 @@ public class RecentsAnimationCallbacks implements
public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "RecentsAnimationCallbacks:");
pw.println(prefix + "\tmAllowMinimizeSplitScreen=" + mAllowMinimizeSplitScreen);
pw.println(prefix + "\tmCancelled=" + mCancelled);
}
@@ -17,7 +17,6 @@ package com.android.quickstep;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION;
import android.content.Context;
@@ -52,21 +51,17 @@ public class RecentsAnimationController {
private static final String TAG = "RecentsAnimationController";
private final RecentsAnimationControllerCompat mController;
private final Consumer<RecentsAnimationController> mOnFinishedListener;
private final boolean mAllowMinimizeSplitScreen;
private boolean mUseLauncherSysBarFlags = false;
private boolean mSplitScreenMinimized = false;
private boolean mFinishRequested = false;
// Only valid when mFinishRequested == true.
private boolean mFinishTargetIsLauncher;
private RunnableList mPendingFinishCallbacks = new RunnableList();
public RecentsAnimationController(RecentsAnimationControllerCompat controller,
boolean allowMinimizeSplitScreen,
Consumer<RecentsAnimationController> onFinishedListener) {
mController = controller;
mOnFinishedListener = onFinishedListener;
mAllowMinimizeSplitScreen = allowMinimizeSplitScreen;
}
/**
@@ -85,33 +80,16 @@ public class RecentsAnimationController {
if (mUseLauncherSysBarFlags != useLauncherSysBarFlags) {
mUseLauncherSysBarFlags = useLauncherSysBarFlags;
UI_HELPER_EXECUTOR.execute(() -> {
if (!ENABLE_SHELL_TRANSITIONS) {
mController.setAnimationTargetsBehindSystemBars(!useLauncherSysBarFlags);
} else {
try {
WindowManagerGlobal.getWindowManagerService().setRecentsAppBehindSystemBars(
useLauncherSysBarFlags);
} catch (RemoteException e) {
Log.e(TAG, "Unable to reach window manager", e);
}
try {
WindowManagerGlobal.getWindowManagerService().setRecentsAppBehindSystemBars(
useLauncherSysBarFlags);
} catch (RemoteException e) {
Log.e(TAG, "Unable to reach window manager", e);
}
});
}
}
/**
* Indicates that the gesture has crossed the window boundary threshold and we should minimize
* if we are in splitscreen.
*/
public void setSplitScreenMinimized(Context context, boolean splitScreenMinimized) {
if (!mAllowMinimizeSplitScreen) {
return;
}
if (mSplitScreenMinimized != splitScreenMinimized) {
mSplitScreenMinimized = splitScreenMinimized;
}
}
/**
* Remove task remote animation target from
* {@link RecentsAnimationCallbacks#onTasksAppeared}}.
@@ -272,9 +250,7 @@ public class RecentsAnimationController {
public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "RecentsAnimationController:");
pw.println(prefix + "\tmAllowMinimizeSplitScreen=" + mAllowMinimizeSplitScreen);
pw.println(prefix + "\tmUseLauncherSysBarFlags=" + mUseLauncherSysBarFlags);
pw.println(prefix + "\tmSplitScreenMinimized=" + mSplitScreenMinimized);
pw.println(prefix + "\tmFinishRequested=" + mFinishRequested);
pw.println(prefix + "\tmFinishTargetIsLauncher=" + mFinishTargetIsLauncher);
}
@@ -60,9 +60,8 @@ import java.io.PrintWriter;
import java.util.HashMap;
public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
public static final boolean ENABLE_SHELL_TRANSITIONS = true;
public static final boolean SHELL_TRANSITIONS_ROTATION = ENABLE_SHELL_TRANSITIONS
&& SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
public static final boolean SHELL_TRANSITIONS_ROTATION =
SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
private final Context mCtx;
private RecentsAnimationController mController;
@@ -160,8 +159,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
final BaseContainerInterface containerInterface = gestureState.getContainerInterface();
mLastGestureState = gestureState;
RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(
getSystemUiProxy(), containerInterface.allowMinimizeSplitScreen());
RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(getSystemUiProxy());
mCallbacks = newCallbacks;
mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
@Override
@@ -191,7 +189,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
}
mLastGestureState.updateLastAppearedTaskTargets(mLastAppearedTaskTargets);
if (ENABLE_SHELL_TRANSITIONS && mTargets.hasRecents
if (mTargets.hasRecents
// The filtered (MODE_CLOSING) targets only contain 1 home activity.
&& mTargets.apps.length == 1
&& mTargets.apps[0].windowConfiguration.getActivityType()
@@ -109,6 +109,7 @@ import com.android.launcher3.util.PluginManagerWrapper;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.OverviewCommandHelper.CommandType;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
import com.android.quickstep.inputconsumers.AssistantInputConsumer;
import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
@@ -246,7 +247,7 @@ public class TouchInteractionService extends Service {
return;
}
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
tis.mOverviewCommandHelper.addCommand(CommandType.TOGGLE);
});
}
@@ -256,10 +257,9 @@ public class TouchInteractionService extends Service {
executeForTouchInteractionService(tis -> {
if (triggeredFromAltTab) {
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
tis.mOverviewCommandHelper.addCommand(
OverviewCommandHelper.TYPE_KEYBOARD_INPUT);
tis.mOverviewCommandHelper.addCommand(CommandType.KEYBOARD_INPUT);
} else {
tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_SHOW);
tis.mOverviewCommandHelper.addCommand(CommandType.SHOW);
}
});
}
@@ -270,7 +270,7 @@ public class TouchInteractionService extends Service {
executeForTouchInteractionService(tis -> {
if (triggeredFromAltTab && !triggeredFromHomeKey) {
// onOverviewShownFromAltTab hides the overview and ends at the target app
tis.mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HIDE);
tis.mOverviewCommandHelper.addCommand(CommandType.HIDE);
}
});
}
@@ -595,12 +595,12 @@ public class TouchInteractionService extends Service {
private final TaskbarNavButtonCallbacks mNavCallbacks = new TaskbarNavButtonCallbacks() {
@Override
public void onNavigateHome() {
mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HOME);
mOverviewCommandHelper.addCommand(CommandType.HOME);
}
@Override
public void onToggleOverview() {
mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
mOverviewCommandHelper.addCommand(CommandType.TOGGLE);
}
};
@@ -45,6 +45,7 @@ public class BubbleBarInputConsumer implements InputConsumer {
private boolean mSwipeUpOnBubbleHandle;
private boolean mPassedTouchSlop;
private boolean mStashedOrCollapsedOnDown;
private final int mTouchSlop;
private final PointF mDownPos = new PointF();
@@ -69,13 +70,13 @@ public class BubbleBarInputConsumer implements InputConsumer {
@Override
public void onMotionEvent(MotionEvent ev) {
final boolean isStashed = mBubbleStashController.isStashed();
final int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
mStashedOrCollapsedOnDown = mBubbleStashController.isStashed() || isCollapsed();
break;
case MotionEvent.ACTION_MOVE:
int pointerIndex = ev.findPointerIndex(mActivePointerId);
@@ -89,7 +90,7 @@ public class BubbleBarInputConsumer implements InputConsumer {
if (!mPassedTouchSlop) {
mPassedTouchSlop = Math.abs(dY) > mTouchSlop || Math.abs(dX) > mTouchSlop;
}
if ((isCollapsed() || isStashed) && !mSwipeUpOnBubbleHandle && mPassedTouchSlop) {
if (mStashedOrCollapsedOnDown && !mSwipeUpOnBubbleHandle && mPassedTouchSlop) {
boolean verticalGesture = Math.abs(dY) > Math.abs(dX);
if (verticalGesture && !mBubbleDragController.isDragging()) {
mSwipeUpOnBubbleHandle = true;
@@ -102,11 +103,10 @@ public class BubbleBarInputConsumer implements InputConsumer {
break;
case MotionEvent.ACTION_UP:
boolean isWithinTapTime = ev.getEventTime() - ev.getDownTime() <= mTimeForTap;
if (isWithinTapTime && !mSwipeUpOnBubbleHandle && !mPassedTouchSlop) {
if (isWithinTapTime && !mSwipeUpOnBubbleHandle && !mPassedTouchSlop
&& mStashedOrCollapsedOnDown) {
// Taps on the handle / collapsed state should open the bar
if (isStashed || isCollapsed()) {
mBubbleStashController.showBubbleBar(/* expandBubbles= */ true);
}
mBubbleStashController.showBubbleBar(/* expandBubbles= */ true);
}
break;
}
@@ -24,7 +24,6 @@ import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
import static com.android.quickstep.AbsSwipeUpHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
import android.animation.Animator;
@@ -212,15 +211,13 @@ public class DeviceLockedInputConsumer implements InputConsumer,
// This will come back and cancel the interaction.
startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null, TAG);
mHomeLaunched = true;
} else if (ENABLE_SHELL_TRANSITIONS) {
if (mTaskAnimationManager.getCurrentCallbacks() != null) {
if (mRecentsAnimationController != null) {
finishRecentsAnimationForShell(dismissTask);
} else {
// the transition of recents animation hasn't started, wait for it
mCancelWhenRecentsStart = true;
mDismissTask = dismissTask;
}
} else if (mTaskAnimationManager.getCurrentCallbacks() != null) {
if (mRecentsAnimationController != null) {
finishRecentsAnimationForShell(dismissTask);
} else {
// the transition of recents animation hasn't started, wait for it
mCancelWhenRecentsStart = true;
mDismissTask = dismissTask;
}
}
mStateCallback.setState(STATE_HANDLER_INVALIDATED);
@@ -46,6 +46,7 @@ import com.android.launcher3.util.DisplayController;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.OverviewCommandHelper.CommandType;
import com.android.systemui.shared.system.InputMonitorCompat;
/**
@@ -200,7 +201,7 @@ public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
break;
case MotionEvent.ACTION_BUTTON_RELEASE:
if (isStashedTaskbarHovered) {
mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_HOME);
mOverviewCommandHelper.addCommand(CommandType.HOME);
}
break;
}
@@ -41,6 +41,7 @@ import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager;
import com.android.quickstep.TouchInteractionService.TISBinder;
import com.android.quickstep.interaction.TutorialController.TutorialType;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.TISBindHelper;
import java.util.ArrayList;
@@ -54,8 +55,6 @@ public class GestureSandboxActivity extends FragmentActivity {
static final String KEY_TUTORIAL_TYPE = "tutorial_type";
static final String KEY_GESTURE_COMPLETE = "gesture_complete";
static final String KEY_USE_TUTORIAL_MENU = "use_tutorial_menu";
public static final double SQUARE_ASPECT_RATIO_BOTTOM_BOUND = 0.95;
public static final double SQUARE_ASPECT_RATIO_UPPER_BOUND = 1.05;
@Nullable private TutorialType[] mTutorialSteps;
private GestureSandboxFragment mCurrentFragment;
@@ -170,10 +169,7 @@ public class GestureSandboxActivity extends FragmentActivity {
getApplicationContext()).getDeviceProfile(this);
if (deviceProfile.isTablet) {
// The tutorial will work in either orientation if the height and width are similar
boolean isAspectRatioSquare =
deviceProfile.aspectRatio > SQUARE_ASPECT_RATIO_BOTTOM_BOUND
&& deviceProfile.aspectRatio < SQUARE_ASPECT_RATIO_UPPER_BOUND;
boolean showRotationPrompt = !isAspectRatioSquare
boolean showRotationPrompt = !LayoutUtils.isAspectRatioSquare(deviceProfile.aspectRatio)
&& getResources().getConfiguration().orientation
== ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
@@ -28,6 +28,8 @@ import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
public class LayoutUtils {
private static final float SQUARE_ASPECT_RATIO_TOLERANCE = 0.05f;
/**
* The height for the swipe up motion
*/
@@ -61,4 +63,13 @@ public class LayoutUtils {
}
}
}
/**
* Returns true iff the device's aspect ratio is within
* {@link LayoutUtils#SQUARE_ASPECT_RATIO_TOLERANCE} of 1:1
*/
public static boolean isAspectRatioSquare(float aspectRatio) {
return Float.compare(aspectRatio, 1f - SQUARE_ASPECT_RATIO_TOLERANCE) >= 0
&& Float.compare(aspectRatio, 1f + SQUARE_ASPECT_RATIO_TOLERANCE) <= 0;
}
}
@@ -17,9 +17,11 @@
package com.android.quickstep.util
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
import com.android.quickstep.RecentsAnimationController
import com.android.quickstep.views.DesktopTaskView
import com.android.quickstep.views.TaskView
import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.ThumbnailData
/**
* Helper class for [com.android.quickstep.views.RecentsView]. This util class contains refactored
@@ -68,4 +70,12 @@ class RecentsViewUtils {
if (taskView?.isLargeTile == true) taskView else null
}
}
fun screenshotTasks(
taskView: TaskView,
recentsAnimationController: RecentsAnimationController
): Map<Int, ThumbnailData> =
taskView.taskContainers.associate {
it.task.key.id to recentsAnimationController.screenshotTask(it.task.key.id)
}
}
@@ -886,8 +886,7 @@ public class SplitSelectStateController {
Log.w(TAG, "Package not found: " + packageName, e);
}
RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks(
SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()),
false /* allowMinimizeSplitScreen */);
SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()));
DesktopSplitRecentsAnimationListener listener =
new DesktopSplitRecentsAnimationListener(splitPosition, taskBounds);
@@ -84,8 +84,7 @@ public class SplitWithKeyboardShortcutController {
return;
}
RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks(
SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()),
false /* allowMinimizeSplitScreen */);
SystemUiProxy.INSTANCE.get(mLauncher.getApplicationContext()));
SplitWithKeyboardShortcutRecentsAnimationListener listener =
new SplitWithKeyboardShortcutRecentsAnimationListener(leftOrTop);
@@ -24,7 +24,6 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITIO
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
@@ -535,21 +534,12 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy {
// If mDrawsBelowRecents is unset, no reordering will be enforced.
if (mDrawsBelowRecents != null) {
// In legacy transitions, the animation leashes remain in same hierarchy in the
// TaskDisplayArea, so we don't want to bump the layer too high otherwise it will
// conflict with layers that WM core positions (ie. the input consumers). For shell
// transitions, the animation leashes are reparented to an animation container so we
// can bump layers as needed.
if (ENABLE_SHELL_TRANSITIONS) {
builder.setLayer(mDrawsBelowRecents
? Integer.MIN_VALUE + app.prefixOrderIndex
// 1000 is an arbitrary number to give room for multiple layers.
: Integer.MAX_VALUE - 1000 + app.prefixOrderIndex);
} else {
builder.setLayer(mDrawsBelowRecents
? Integer.MIN_VALUE + app.prefixOrderIndex
: 0);
}
// In shell transitions, the animation leashes are reparented to an animation container
// so we can bump layers as needed.
builder.setLayer(mDrawsBelowRecents
? Integer.MIN_VALUE + app.prefixOrderIndex
// 1000 is an arbitrary number to give room for multiple layers.
: Integer.MAX_VALUE - 1000 + app.prefixOrderIndex);
}
}
@@ -123,8 +123,8 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener,
@Override
public void onGlobalLayout() {
if (isUninitialized()) return;
positionViews();
if (mOnTargetChangeRunnable != null) {
boolean positionsChanged = positionViews();
if (mOnTargetChangeRunnable != null && positionsChanged) {
mOnTargetChangeRunnable.run();
}
}
@@ -212,21 +212,43 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener,
onGlobalLayout();
}
/** Sets the layout parameters of the floating view and its background view child. */
private void positionViews() {
/**
* Sets the layout parameters of the floating view and its background view child.
* @return true if any of the views positions change due to this call.
*/
private boolean positionViews() {
boolean positionsChanged = false;
LayoutParams layoutParams = (LayoutParams) getLayoutParams();
layoutParams.setMargins(0, 0, 0, 0);
setLayoutParams(layoutParams);
if (layoutParams.topMargin != 0 || layoutParams.bottomMargin != 0
|| layoutParams.rightMargin != 0 || layoutParams.leftMargin != 0) {
positionsChanged = true;
layoutParams.setMargins(0, 0, 0, 0);
setLayoutParams(layoutParams);
}
// FloatingWidgetView layout is forced LTR
mBackgroundView.setTranslationX(mBackgroundPosition.left);
mBackgroundView.setTranslationY(mBackgroundPosition.top + mIconOffsetY);
float targetY = mBackgroundPosition.top + mIconOffsetY;
if (mBackgroundView.getTranslationX() != mBackgroundPosition.left
|| mBackgroundView.getTranslationY() != targetY) {
positionsChanged = true;
mBackgroundView.setTranslationX(mBackgroundPosition.left);
mBackgroundView.setTranslationY(targetY);
}
LayoutParams backgroundParams = (LayoutParams) mBackgroundView.getLayoutParams();
backgroundParams.leftMargin = 0;
backgroundParams.topMargin = 0;
backgroundParams.width = (int) mBackgroundPosition.width();
backgroundParams.height = (int) mBackgroundPosition.height();
mBackgroundView.setLayoutParams(backgroundParams);
if (backgroundParams.leftMargin != 0 || backgroundParams.topMargin != 0
|| backgroundParams.width != Math.round(mBackgroundPosition.width())
|| backgroundParams.height != Math.round(mBackgroundPosition.height())) {
positionsChanged = true;
backgroundParams.leftMargin = 0;
backgroundParams.topMargin = 0;
backgroundParams.width = Math.round(mBackgroundPosition.width());
backgroundParams.height = Math.round(mBackgroundPosition.height());
mBackgroundView.setLayoutParams(backgroundParams);
}
if (mForegroundOverlayView != null) {
sTmpMatrix.reset();
@@ -237,8 +259,15 @@ public class FloatingWidgetView extends FrameLayout implements AnimatorListener,
sTmpMatrix.postScale(foregroundScale, foregroundScale);
sTmpMatrix.postTranslate(mBackgroundPosition.left, mBackgroundPosition.top
+ mIconOffsetY);
mForegroundOverlayView.setMatrix(sTmpMatrix);
// We use the animation matrix here, because calling setMatrix on the GhostView
// actually sets the animation matrix, not the regular one.
if (!sTmpMatrix.equals(mForegroundOverlayView.getAnimationMatrix())) {
positionsChanged = true;
mForegroundOverlayView.setMatrix(sTmpMatrix);
}
}
return positionsChanged;
}
private void finish(DragLayer dragLayer) {
@@ -3058,14 +3058,15 @@ public abstract class RecentsView<
}
private void setRunningTaskViewShowScreenshot(boolean showScreenshot) {
setRunningTaskViewShowScreenshot(showScreenshot, /*updatedThumbnails=*/null);
}
private void setRunningTaskViewShowScreenshot(boolean showScreenshot,
@Nullable Map<Integer, ThumbnailData> updatedThumbnails) {
mRunningTaskShowScreenshot = showScreenshot;
TaskView runningTaskView = getRunningTaskView();
if (runningTaskView != null) {
runningTaskView.setShouldShowScreenshot(mRunningTaskShowScreenshot);
if (!enableRefactorTaskThumbnail()) {
runningTaskView.getTaskContainers().forEach(
taskContainer -> taskContainer.getThumbnailViewDeprecated().refresh());
}
runningTaskView.setShouldShowScreenshot(mRunningTaskShowScreenshot, updatedThumbnails);
}
if (enableRefactorTaskThumbnail()) {
mRecentsViewModel.setRunningTaskShowScreenshot(showScreenshot);
@@ -6154,20 +6155,12 @@ public abstract class RecentsView<
return;
}
Map<Integer, ThumbnailData> updatedThumbnails = mRecentsViewUtils.screenshotTasks(taskView,
mRecentsAnimationController);
if (enableRefactorTaskThumbnail()) {
mHelper.switchToScreenshot(taskView, mRecentsAnimationController, onFinishRunnable);
mHelper.switchToScreenshot(taskView, updatedThumbnails, onFinishRunnable);
} else {
setRunningTaskViewShowScreenshot(true);
for (TaskContainer container : taskView.getTaskContainers()) {
ThumbnailData thumbnailData =
mRecentsAnimationController.screenshotTask(container.getTask().key.id);
TaskThumbnailViewDeprecated thumbnailView = container.getThumbnailViewDeprecated();
if (thumbnailData != null) {
thumbnailView.setThumbnail(container.getTask(), thumbnailData);
} else {
thumbnailView.refresh();
}
}
setRunningTaskViewShowScreenshot(true, updatedThumbnails);
ViewUtils.postFrameDrawn(taskView, onFinishRunnable);
}
}
@@ -6186,8 +6179,7 @@ public abstract class RecentsView<
if (enableRefactorTaskThumbnail()) {
mHelper.switchToScreenshot(taskView, thumbnailDatas, onFinishRunnable);
} else {
taskView.setShouldShowScreenshot(true);
taskView.refreshThumbnails(thumbnailDatas);
taskView.setShouldShowScreenshot(true, thumbnailDatas);
ViewUtils.postFrameDrawn(taskView, onFinishRunnable);
}
} else {
@@ -16,7 +16,6 @@
package com.android.quickstep.views
import com.android.quickstep.RecentsAnimationController
import com.android.quickstep.ViewUtils
import com.android.quickstep.recents.viewmodel.RecentsViewModel
import com.android.systemui.shared.recents.model.ThumbnailData
@@ -40,18 +39,6 @@ class RecentsViewModelHelper(private val recentsViewModel: RecentsViewModel) {
viewAttachedScope.cancel("RecentsView detaching from window")
}
fun switchToScreenshot(
taskView: TaskView,
recentsAnimationController: RecentsAnimationController,
onFinishRunnable: Runnable,
) {
val updatedThumbnails =
taskView.taskContainers.associate {
it.task.key.id to recentsAnimationController.screenshotTask(it.task.key.id)
}
switchToScreenshot(taskView, updatedThumbnails, onFinishRunnable)
}
fun switchToScreenshot(
taskView: TaskView,
updatedThumbnails: Map<Int, ThumbnailData>?,
@@ -79,7 +79,6 @@ import com.android.launcher3.util.rects.set
import com.android.launcher3.views.ActivityContext
import com.android.quickstep.RecentsModel
import com.android.quickstep.RemoteAnimationTargets
import com.android.quickstep.TaskAnimationManager
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.TaskViewUtils
import com.android.quickstep.orientation.RecentsPagedOrientationHandler
@@ -408,6 +407,7 @@ constructor(
protected var shouldShowScreenshot = false
get() = !isRunningTask || field
private set
/** Enable or disable showing border on hover and focus change */
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
@@ -974,7 +974,13 @@ constructor(
iconView.setText(text)
}
open fun refreshThumbnails(thumbnailDatas: Map<Int, ThumbnailData?>?) {
@JvmOverloads
open fun setShouldShowScreenshot(
shouldShowScreenshot: Boolean,
thumbnailDatas: Map<Int, ThumbnailData?>? = null
) {
if (this.shouldShowScreenshot == shouldShowScreenshot) return
this.shouldShowScreenshot = shouldShowScreenshot
if (enableRefactorTaskThumbnail()) {
return
}
@@ -1041,14 +1047,12 @@ constructor(
// triggered by QuickstepTransitionManager.AppLaunchAnimationRunner.
return RunnableList().also { recentsView.addSideTaskLaunchCallback(it) }
}
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
// If the recents transition is running (ie. in live tile mode), then the start
// of a new task will merge into the existing transition and it currently will
// not be run independently, so we need to rely on the onTaskAppeared() call
// for the new task to trigger the side launch callback to flush this runnable
// list (which is usually flushed when the app launch animation finishes)
recentsView.addSideTaskLaunchCallback(opts.onEndCallback)
}
// If the recents transition is running (ie. in live tile mode), then the start
// of a new task will merge into the existing transition and it currently will
// not be run independently, so we need to rely on the onTaskAppeared() call
// for the new task to trigger the side launch callback to flush this runnable
// list (which is usually flushed when the app launch animation finishes)
recentsView.addSideTaskLaunchCallback(opts.onEndCallback)
return opts.onEndCallback
} else {
notifyTaskLaunchFailed()
@@ -99,7 +99,7 @@ class BubbleViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
val flags =
if (suppressNotification) Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION else 0
val bubbleInfo =
BubbleInfo("key", flags, null, null, 0, context.packageName, null, null, false)
BubbleInfo("key", flags, null, null, 0, context.packageName, null, null, false, true)
val bubbleView = inflater.inflate(R.layout.bubblebar_item_view, null) as BubbleView
val dotPath =
PathParser.createPathFromPathData(
@@ -65,7 +65,7 @@ class BubbleViewTest {
overflowView.setOverflow(BubbleBarOverflow(overflowView), bitmap)
val bubbleInfo =
BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false)
BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false, true)
bubbleView = inflater.inflate(R.layout.bubblebar_item_view, null, false) as BubbleView
bubble =
BubbleBarBubble(bubbleInfo, bubbleView, bitmap, bitmap, Color.WHITE, Path(), "")
@@ -870,7 +870,7 @@ class BubbleBarViewAnimatorTest {
bubbleBarView.addView(overflowView)
val bubbleInfo =
BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false)
BubbleInfo("key", 0, null, null, 0, context.packageName, null, null, false, true)
bubbleView =
inflater.inflate(R.layout.bubblebar_item_view, bubbleBarView, false) as BubbleView
bubble =
@@ -251,7 +251,7 @@ class PersistentBubbleStashControllerTest {
whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
whenever(bubbleBarViewController.bubbleBarTranslationY).thenReturn(translationY)
whenever(bubbleBarViewController.bubbleBarScale).thenReturn(scale)
whenever(bubbleBarViewController.bubbleBarScaleY).thenReturn(scale)
whenever(bubbleBarViewController.bubbleBarAlpha).thenReturn(alpha)
whenever(bubbleBarViewController.bubbleBarCollapsedHeight).thenReturn(BUBBLE_BAR_HEIGHT)
}
@@ -31,7 +31,6 @@ import com.android.launcher3.taskbar.TaskbarInsetsController
import com.android.launcher3.taskbar.bubbles.BubbleBarView
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.Companion.STASHED_BAR_SCALE
import com.android.launcher3.util.MultiValueAlpha
import com.android.wm.shell.shared.animation.PhysicsAnimator
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
@@ -59,7 +58,7 @@ class TransientBubbleStashControllerTest {
const val HOTSEAT_TRANSLATION_Y = -45f
const val TASK_BAR_TRANSLATION_Y = -TASKBAR_BOTTOM_SPACE
const val HANDLE_VIEW_HEIGHT = 4
const val BUBBLE_BAR_STASHED_TRANSLATION_Y = 48
const val BUBBLE_BAR_STASHED_TRANSLATION_Y = -2.5f
}
@get:Rule val animatorTestRule: AnimatorTestRule = AnimatorTestRule(this)
@@ -90,7 +89,7 @@ class TransientBubbleStashControllerTest {
val taskbarHotseatDimensionsProvider =
DefaultDimensionsProvider(taskBarBottomSpace = TASKBAR_BOTTOM_SPACE)
mTransientBubbleStashController =
TransientBubbleStashController(taskbarHotseatDimensionsProvider, context.resources)
TransientBubbleStashController(taskbarHotseatDimensionsProvider, context)
setUpBubbleBarView()
setUpBubbleBarController()
setUpStashedHandleView()
@@ -174,8 +173,8 @@ class TransientBubbleStashControllerTest {
assertThat(mTransientBubbleStashController.isStashed).isTrue()
assertThat(bubbleBarView.translationY).isEqualTo(BUBBLE_BAR_STASHED_TRANSLATION_Y)
assertThat(bubbleBarView.alpha).isEqualTo(0f)
assertThat(bubbleBarView.scaleX).isEqualTo(STASHED_BAR_SCALE)
assertThat(bubbleBarView.scaleY).isEqualTo(STASHED_BAR_SCALE)
assertThat(bubbleBarView.scaleX).isEqualTo(mTransientBubbleStashController.getStashScale())
assertThat(bubbleBarView.scaleY).isEqualTo(mTransientBubbleStashController.getStashScale())
// Handle view is visible
assertThat(stashedHandleView.translationY).isEqualTo(0)
assertThat(stashedHandleView.alpha).isEqualTo(1)
@@ -243,8 +242,8 @@ class TransientBubbleStashControllerTest {
// Then all property values are updated
assertThat(bubbleBarView.translationY).isEqualTo(BUBBLE_BAR_STASHED_TRANSLATION_Y)
assertThat(bubbleBarView.alpha).isEqualTo(0)
assertThat(bubbleBarView.scaleX).isEqualTo(STASHED_BAR_SCALE)
assertThat(bubbleBarView.scaleY).isEqualTo(STASHED_BAR_SCALE)
assertThat(bubbleBarView.scaleX).isEqualTo(mTransientBubbleStashController.getStashScale())
assertThat(bubbleBarView.scaleY).isEqualTo(mTransientBubbleStashController.getStashScale())
// Handle is visible at correct Y position
assertThat(stashedHandleView.alpha).isEqualTo(1)
assertThat(stashedHandleView.translationY).isEqualTo(0)
@@ -306,7 +305,7 @@ class TransientBubbleStashControllerTest {
whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
whenever(bubbleBarViewController.bubbleBarTranslationY).thenReturn(barTranslationY)
whenever(bubbleBarViewController.bubbleBarScale).thenReturn(barScale)
whenever(bubbleBarViewController.bubbleBarScaleY).thenReturn(barScale)
whenever(bubbleBarViewController.bubbleBarAlpha).thenReturn(barAlpha)
whenever(bubbleBarViewController.bubbleBarCollapsedHeight).thenReturn(BUBBLE_BAR_HEIGHT)
}
@@ -0,0 +1,276 @@
/*
* 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;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.ViewTreeObserver;
import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherRootView;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.util.SystemUiController;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
import com.android.systemui.shared.system.InputConsumerController;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.Collections;
import java.util.HashMap;
public abstract class AbsSwipeUpHandlerTestCase<
RECENTS_CONTAINER extends Context & RecentsViewContainer,
STATE extends BaseState<STATE>,
RECENTS_VIEW extends RecentsView<RECENTS_CONTAINER, STATE>,
ACTIVITY_TYPE extends StatefulActivity<STATE> & RecentsViewContainer,
ACTIVITY_INTERFACE extends BaseActivityInterface<STATE, ACTIVITY_TYPE>,
SWIPE_HANDLER extends AbsSwipeUpHandler<RECENTS_CONTAINER, RECENTS_VIEW, STATE>> {
protected final Context mContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
protected final TaskAnimationManager mTaskAnimationManager = new TaskAnimationManager(mContext);
protected final RecentsAnimationDeviceState mRecentsAnimationDeviceState =
new RecentsAnimationDeviceState(mContext, true);
protected final InputConsumerController mInputConsumerController =
InputConsumerController.getRecentsAnimationInputConsumer();
protected final ActivityManager.RunningTaskInfo mRunningTaskInfo =
new ActivityManager.RunningTaskInfo();
protected final TopTaskTracker.CachedTaskInfo mCachedTaskInfo =
new TopTaskTracker.CachedTaskInfo(Collections.singletonList(mRunningTaskInfo));
protected final RemoteAnimationTarget mRemoteAnimationTarget = new RemoteAnimationTarget(
/* taskId= */ 0,
/* mode= */ RemoteAnimationTarget.MODE_CLOSING,
/* leash= */ new SurfaceControl(),
/* isTranslucent= */ false,
/* clipRect= */ null,
/* contentInsets= */ null,
/* prefixOrderIndex= */ 0,
/* position= */ null,
/* localBounds= */ null,
/* screenSpaceBounds= */ null,
new Configuration().windowConfiguration,
/* isNotInRecents= */ false,
/* startLeash= */ null,
/* startBounds= */ null,
/* taskInfo= */ mRunningTaskInfo,
/* allowEnterPip= */ false);
protected final RecentsAnimationTargets mRecentsAnimationTargets = new RecentsAnimationTargets(
new RemoteAnimationTarget[] {mRemoteAnimationTarget},
new RemoteAnimationTarget[] {mRemoteAnimationTarget},
new RemoteAnimationTarget[] {mRemoteAnimationTarget},
/* homeContentInsets= */ new Rect(),
/* minimizedHomeBounds= */ null,
new Bundle());
@Mock protected ACTIVITY_INTERFACE mActivityInterface;
@Mock protected ActivityInitListener<?> mActivityInitListener;
@Mock protected RecentsAnimationController mRecentsAnimationController;
@Mock protected STATE mState;
@Mock protected ViewTreeObserver mViewTreeObserver;
@Mock protected DragLayer mDragLayer;
@Mock protected LauncherRootView mRootView;
@Mock protected SystemUiController mSystemUiController;
@Mock protected GestureState mGestureState;
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Before
public void setUpRunningTaskInfo() {
mRunningTaskInfo.baseIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
@Before
public void setUpGestureState() {
when(mGestureState.getRunningTask()).thenReturn(mCachedTaskInfo);
when(mGestureState.getLastAppearedTaskIds()).thenReturn(new int[0]);
when(mGestureState.getLastStartedTaskIds()).thenReturn(new int[1]);
when(mGestureState.getHomeIntent()).thenReturn(new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
doReturn(mActivityInterface).when(mGestureState).getContainerInterface();
}
@Before
public void setUpRecentsView() {
RECENTS_VIEW recentsView = getRecentsView();
when(recentsView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
doAnswer(answer -> {
runOnMainSync(() -> answer.<Runnable>getArgument(0).run());
return this;
}).when(recentsView).runOnPageScrollsInitialized(any());
}
@Before
public void setUpRecentsContainer() {
RecentsViewContainer recentsContainer = getRecentsContainer();
RECENTS_VIEW recentsView = getRecentsView();
when(recentsContainer.getDeviceProfile()).thenReturn(new DeviceProfile());
when(recentsContainer.getOverviewPanel()).thenReturn(recentsView);
when(recentsContainer.getDragLayer()).thenReturn(mDragLayer);
when(recentsContainer.getRootView()).thenReturn(mRootView);
when(recentsContainer.getSystemUiController()).thenReturn(mSystemUiController);
when(mActivityInterface.createActivityInitListener(any()))
.thenReturn(mActivityInitListener);
doReturn(recentsContainer).when(mActivityInterface).getCreatedContainer();
doAnswer(answer -> {
answer.<Runnable>getArgument(0).run();
return this;
}).when(recentsContainer).runOnBindToTouchInteractionService(any());
}
@Test
public void testInitWhenReady_registersActivityInitListener() {
String reasonString = "because i said so";
createSwipeHandler().initWhenReady(reasonString);
verify(mActivityInitListener).register(eq(reasonString));
}
@Test
public void testOnRecentsAnimationCanceled_unregistersActivityInitListener() {
createSwipeHandler()
.onRecentsAnimationCanceled(new HashMap<>());
runOnMainSync(() -> verify(mActivityInitListener)
.unregister(eq("AbsSwipeUpHandler.onRecentsAnimationCanceled")));
}
@Test
public void testOnConsumerAboutToBeSwitched_unregistersActivityInitListener() {
createSwipeHandler().onConsumerAboutToBeSwitched();
runOnMainSync(() -> verify(mActivityInitListener)
.unregister("AbsSwipeUpHandler.invalidateHandler"));
}
@Test
public void testOnConsumerAboutToBeSwitched_midQuickSwitch_unregistersActivityInitListener() {
createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.NEW_TASK)
.onConsumerAboutToBeSwitched();
runOnMainSync(() -> verify(mActivityInitListener)
.unregister(eq("AbsSwipeUpHandler.cancelCurrentAnimation")));
}
@Test
public void testStartNewTask_finishesRecentsAnimationController() {
SWIPE_HANDLER absSwipeUpHandler = createSwipeHandler();
onRecentsAnimationStart(absSwipeUpHandler);
runOnMainSync(() -> {
absSwipeUpHandler.startNewTask(unused -> {});
verify(mRecentsAnimationController).finish(anyBoolean(), any());
});
}
@Test
public void testHomeGesture_finishesRecentsAnimationController() {
createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME);
runOnMainSync(() -> {
verify(mRecentsAnimationController).detachNavigationBarFromApp(true);
verify(mRecentsAnimationController).finish(anyBoolean(), any(), anyBoolean());
});
}
private SWIPE_HANDLER createSwipeUpHandlerForGesture(GestureState.GestureEndTarget endTarget) {
boolean isQuickSwitch = endTarget == GestureState.GestureEndTarget.NEW_TASK;
doReturn(mState).when(mActivityInterface).stateFromGestureEndTarget(any());
SWIPE_HANDLER swipeHandler = createSwipeHandler(SystemClock.uptimeMillis(), isQuickSwitch);
swipeHandler.onActivityInit(/* alreadyOnHome= */ false);
swipeHandler.onGestureStarted(isQuickSwitch);
onRecentsAnimationStart(swipeHandler);
when(mGestureState.getRunningTaskIds(anyBoolean())).thenReturn(new int[0]);
runOnMainSync(swipeHandler::switchToScreenshot);
when(mGestureState.getEndTarget()).thenReturn(endTarget);
when(mGestureState.isRecentsAnimationRunning()).thenReturn(isQuickSwitch);
float xVelocityPxPerMs = isQuickSwitch ? 100 : 0;
float yVelocityPxPerMs = isQuickSwitch ? 0 : -100;
swipeHandler.onGestureEnded(
yVelocityPxPerMs, new PointF(xVelocityPxPerMs, yVelocityPxPerMs));
swipeHandler.onCalculateEndTarget();
runOnMainSync(swipeHandler::onSettledOnEndTarget);
return swipeHandler;
}
private void onRecentsAnimationStart(SWIPE_HANDLER absSwipeUpHandler) {
when(mActivityInterface.getOverviewWindowBounds(any(), any())).thenReturn(new Rect());
doNothing().when(mActivityInterface).setOnDeferredActivityLaunchCallback(any());
runOnMainSync(() -> absSwipeUpHandler.onRecentsAnimationStart(
mRecentsAnimationController, mRecentsAnimationTargets));
}
private static void runOnMainSync(Runnable runnable) {
InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
}
@NonNull
private SWIPE_HANDLER createSwipeHandler() {
return createSwipeHandler(SystemClock.uptimeMillis(), false);
}
@NonNull
protected abstract SWIPE_HANDLER createSwipeHandler(
long touchTimeMs, boolean continuingLastGesture);
@NonNull
protected abstract RecentsViewContainer getRecentsContainer();
@NonNull
protected abstract RECENTS_VIEW getRecentsView();
}
@@ -0,0 +1,64 @@
/*
* 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;
import androidx.test.filters.SmallTest;
import com.android.launcher3.util.LauncherMultivalentJUnit;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsState;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@SmallTest
@RunWith(LauncherMultivalentJUnit.class)
public class FallbackSwipeHandlerTestCase extends AbsSwipeUpHandlerTestCase<
RecentsActivity,
RecentsState,
FallbackRecentsView,
RecentsActivity,
FallbackActivityInterface,
FallbackSwipeHandler> {
@Mock private RecentsActivity mRecentsActivity;
@Mock private FallbackRecentsView mRecentsView;
@Override
protected FallbackSwipeHandler createSwipeHandler(
long touchTimeMs, boolean continuingLastGesture) {
return new FallbackSwipeHandler(
mContext,
mRecentsAnimationDeviceState,
mTaskAnimationManager,
mGestureState,
touchTimeMs,
continuingLastGesture,
mInputConsumerController);
}
@Override
protected RecentsActivity getRecentsContainer() {
return mRecentsActivity;
}
@Override
protected FallbackRecentsView getRecentsView() {
return mRecentsView;
}
}
@@ -0,0 +1,92 @@
/*
* 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;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
import androidx.test.filters.SmallTest;
import com.android.launcher3.Hotseat;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
import com.android.launcher3.WorkspaceStateTransitionAnimation;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.LauncherMultivalentJUnit;
import com.android.quickstep.views.RecentsView;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@SmallTest
@RunWith(LauncherMultivalentJUnit.class)
public class LauncherSwipeHandlerV2TestCase extends AbsSwipeUpHandlerTestCase<
QuickstepLauncher,
LauncherState,
RecentsView<QuickstepLauncher, LauncherState>,
QuickstepLauncher,
LauncherActivityInterface,
LauncherSwipeHandlerV2> {
@Mock private QuickstepLauncher mQuickstepLauncher;
@Mock private RecentsView<QuickstepLauncher, LauncherState> mRecentsView;
@Mock private Workspace<?> mWorkspace;
@Mock private Hotseat mHotseat;
@Mock private WorkspaceStateTransitionAnimation mTransitionAnimation;
@Before
public void setUpQuickStepLauncher() {
when(mQuickstepLauncher.createAtomicAnimationFactory())
.thenReturn(new AtomicAnimationFactory<>(0));
when(mQuickstepLauncher.getHotseat()).thenReturn(mHotseat);
doReturn(mWorkspace).when(mQuickstepLauncher).getWorkspace();
doReturn(new StateManager(mQuickstepLauncher, LauncherState.NORMAL))
.when(mQuickstepLauncher).getStateManager();
}
@Before
public void setUpWorkspace() {
when(mWorkspace.getStateTransitionAnimation()).thenReturn(mTransitionAnimation);
}
@Override
protected LauncherSwipeHandlerV2 createSwipeHandler(
long touchTimeMs, boolean continuingLastGesture) {
return new LauncherSwipeHandlerV2(
mContext,
mRecentsAnimationDeviceState,
mTaskAnimationManager,
mGestureState,
touchTimeMs,
continuingLastGesture,
mInputConsumerController);
}
@Override
protected QuickstepLauncher getRecentsContainer() {
return mQuickstepLauncher;
}
@Override
protected RecentsView<QuickstepLauncher, LauncherState> getRecentsView() {
return mRecentsView;
}
}
@@ -59,8 +59,6 @@ public class TaskAnimationManagerTest {
@Test
public void startRecentsActivity_allowBackgroundLaunch() {
assumeTrue(TaskAnimationManager.ENABLE_SHELL_TRANSITIONS);
final LauncherActivityInterface activityInterface = mock(LauncherActivityInterface.class);
final GestureState gestureState = mock(GestureState.class);
final RecentsAnimationCallbacks.RecentsAnimationListener listener =
+4
View File
@@ -77,6 +77,10 @@
<item name="materialColorError">@color/system_error_light</item>
</style>
<style name="DynamicColorsBaseLauncherTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="LauncherTheme" parent="@style/DynamicColorsBaseLauncherTheme">
<item name="android:textColorSecondary">#DE000000</item>
@@ -109,11 +109,6 @@ public abstract class BaseActivity extends Activity implements ActivityContext {
*/
public static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 4;
/**
* State flag indicating if the user will be active shortly.
*/
public static final int ACTIVITY_STATE_USER_WILL_BE_ACTIVE = 1 << 5;
/**
* State flag indicating that a state transition is in progress
*/
@@ -316,7 +311,6 @@ public abstract class BaseActivity extends Activity implements ActivityContext {
*/
public void setResumed() {
addActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE);
removeActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
}
public boolean isUserActive() {
@@ -89,7 +89,7 @@ import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.ShortcutUtil;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.IconLabelDotView;
import com.android.launcher3.views.FloatingIconViewCompanion;
import java.text.NumberFormat;
import java.util.HashMap;
@@ -101,7 +101,7 @@ import java.util.Locale;
* too aggressive.
*/
public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
IconLabelDotView, DraggableView, Reorderable {
FloatingIconViewCompanion, DraggableView, Reorderable {
public static final String TAG = "BubbleTextView";
@@ -316,6 +316,74 @@ public class DeviceProfile {
// DragController
public int flingToDeleteThresholdVelocity;
/** Used only as an alternative to mocking when null values cannot be used. */
@VisibleForTesting
public DeviceProfile() {
inv = null;
mInfo = null;
mMetrics = null;
mIconSizeSteps = null;
isTablet = false;
isPhone = false;
transposeLayoutWithOrientation = false;
isMultiDisplay = false;
isTwoPanels = false;
isPredictiveBackSwipe = false;
isQsbInline = false;
isLandscape = false;
isMultiWindowMode = false;
isGestureMode = false;
isLeftRightSplit = false;
windowX = 0;
windowY = 0;
widthPx = 0;
heightPx = 0;
availableWidthPx = 0;
availableHeightPx = 0;
rotationHint = 0;
aspectRatio = 1;
mIsScalableGrid = false;
mTypeIndex = 0;
mIsResponsiveGrid = false;
desiredWorkspaceHorizontalMarginOriginalPx = 0;
edgeMarginPx = 0;
workspaceContentScale = 0;
workspaceSpringLoadedMinNextPageVisiblePx = 0;
extraSpace = 0;
workspacePageIndicatorHeight = 0;
mWorkspacePageIndicatorOverlapWorkspace = 0;
numFolderRows = 0;
numFolderColumns = 0;
folderLabelTextScale = 0;
areNavButtonsInline = false;
mHotseatBarEdgePaddingPx = 0;
mHotseatBarWorkspaceSpacePx = 0;
hotseatQsbWidth = 0;
hotseatQsbHeight = 0;
hotseatQsbVisualHeight = 0;
hotseatQsbShadowHeight = 0;
hotseatBorderSpace = 0;
mMinHotseatIconSpacePx = 0;
mMinHotseatQsbWidthPx = 0;
mMaxHotseatIconSpacePx = 0;
inlineNavButtonsEndSpacingPx = 0;
mBubbleBarSpaceThresholdPx = 0;
numShownAllAppsColumns = 0;
overviewActionsHeight = 0;
overviewActionsTopMarginPx = 0;
overviewActionsButtonSpacing = 0;
mViewScaleProvider = null;
mDotRendererWorkSpace = null;
mDotRendererAllApps = null;
taskbarHeight = 0;
stashedTaskbarHeight = 0;
taskbarBottomMargin = 0;
taskbarIconSize = 0;
mTransientTaskbarClaimedSpace = 0;
startAlignTaskbar = false;
isTransientTaskbar = false;
}
/** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */
DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
SparseArray<DotRenderer> dotRendererCache, boolean isMultiWindowMode,
+8 -4
View File
@@ -435,6 +435,10 @@ public class Launcher extends StatefulActivity<LauncherState>
mIsColdStartupAfterReboot = sIsNewProcess
&& !LockedUserState.get(this).isUserUnlockedAtLauncherStartup();
if (mIsColdStartupAfterReboot) {
/*
* This trace is used to calculate the time from create to the point that icons are
* visible.
*/
Trace.beginAsyncSection(
COLD_STARTUP_TRACE_METHOD_NAME, COLD_STARTUP_TRACE_COOKIE);
}
@@ -2384,10 +2388,6 @@ public class Launcher extends StatefulActivity<LauncherState>
.logEnd(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION)
.log()
.reset();
if (mIsColdStartupAfterReboot) {
Trace.endAsyncSection(COLD_STARTUP_TRACE_METHOD_NAME,
COLD_STARTUP_TRACE_COOKIE);
}
});
}
@@ -2396,6 +2396,10 @@ public class Launcher extends StatefulActivity<LauncherState>
RunnableList onCompleteSignal, int workspaceItemCount, boolean isBindSync) {
mModelCallbacks.onInitialBindComplete(boundPages, pendingTasks, onCompleteSignal,
workspaceItemCount, isBindSync);
if (mIsColdStartupAfterReboot) {
Trace.endAsyncSection(COLD_STARTUP_TRACE_METHOD_NAME,
COLD_STARTUP_TRACE_COOKIE);
}
}
/**
@@ -27,6 +27,8 @@ enum class TestEvent(val event: String) {
WORKSPACE_ON_DROP("WORKSPACE_ON_DROP"),
RESIZE_FRAME_SHOWING("RESIZE_FRAME_SHOWING"),
WORKSPACE_FINISH_LOADING("WORKSPACE_FINISH_LOADING"),
SPRING_LOADED_STATE_STARTED("SPRING_LOADED_STATE_STARTED"),
SPRING_LOADED_STATE_COMPLETED("SPRING_LOADED_STATE_COMPLETED"),
}
/** Interface to create TestEventEmitters. */
@@ -83,7 +83,7 @@ import com.android.launcher3.util.Executors;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.IconLabelDotView;
import com.android.launcher3.views.FloatingIconViewCompanion;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import java.util.ArrayList;
@@ -93,7 +93,7 @@ import java.util.function.Predicate;
/**
* An icon that can appear on in the workspace representing an {@link Folder}.
*/
public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
public class FolderIcon extends FrameLayout implements FolderListener, FloatingIconViewCompanion,
DraggableView, Reorderable {
private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
@@ -20,7 +20,7 @@ import com.android.launcher3.BubbleTextView;
/**
* Views that contain {@link BubbleTextView} should implement this interface.
*/
public interface BubbleTextHolder extends IconLabelDotView {
public interface BubbleTextHolder extends FloatingIconViewCompanion {
BubbleTextView getBubbleText();
@Override
@@ -22,7 +22,7 @@ import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
import static com.android.launcher3.Utilities.getFullDrawable;
import static com.android.launcher3.Utilities.mapToRange;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible;
import static com.android.launcher3.views.FloatingIconViewCompanion.setPropertiesVisible;
import android.animation.Animator;
import android.content.Context;
@@ -516,6 +516,10 @@ public class FloatingIconView extends FrameLayout implements
// When closing an app, we want the item on the workspace to be invisible immediately
updateViewsVisibility(false /* isVisible */);
}
if (mFadeOutView instanceof FloatingIconViewCompanion fivc) {
fivc.setForceHideDot(true);
fivc.setForceHideRing(true);
}
}
@Override
@@ -653,6 +657,10 @@ public class FloatingIconView extends FrameLayout implements
if (view.mFadeOutView != null) {
view.mFadeOutView.setAlpha(1f);
}
if (view.mFadeOutView instanceof FloatingIconViewCompanion fivc) {
fivc.setForceHideDot(false);
fivc.setForceHideRing(false);
}
if (hideOriginal) {
view.updateViewsVisibility(true /* isVisible */);
@@ -674,10 +682,10 @@ public class FloatingIconView extends FrameLayout implements
private void updateViewsVisibility(boolean isVisible) {
if (mOriginalIcon != null) {
setIconAndDotVisible(mOriginalIcon, isVisible);
setPropertiesVisible(mOriginalIcon, isVisible);
}
if (mMatchVisibilityView != null) {
setIconAndDotVisible(mMatchVisibilityView, isVisible);
setPropertiesVisible(mMatchVisibilityView, isVisible);
}
}
@@ -18,19 +18,24 @@ package com.android.launcher3.views;
import android.view.View;
/**
* A view that has an icon, label, and notification dot.
* A view that can be drawn (in some capacity) via) {@link FloatingIconView}.
* This interface allows us to hide certain properties of the view that the FloatingIconView
* cannot draw, which allows us to make a seamless handoff between the FloatingIconView and
* the companion view.
*/
public interface IconLabelDotView {
public interface FloatingIconViewCompanion {
void setIconVisible(boolean visible);
void setForceHideDot(boolean hide);
default void setForceHideRing(boolean hide) {}
/**
* Sets the visibility of icon and dot of the view
*/
static void setIconAndDotVisible(View view, boolean visible) {
if (view instanceof IconLabelDotView) {
((IconLabelDotView) view).setIconVisible(visible);
((IconLabelDotView) view).setForceHideDot(!visible);
static void setPropertiesVisible(View view, boolean visible) {
if (view instanceof FloatingIconViewCompanion) {
((FloatingIconViewCompanion) view).setIconVisible(visible);
((FloatingIconViewCompanion) view).setForceHideDot(!visible);
((FloatingIconViewCompanion) view).setForceHideRing(!visible);
} else {
view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
}
@@ -17,7 +17,7 @@ package com.android.launcher3.views;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.views.FloatingIconView.getLocationBoundsForView;
import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible;
import static com.android.launcher3.views.FloatingIconViewCompanion.setPropertiesVisible;
import android.content.Context;
import android.graphics.Canvas;
@@ -237,7 +237,7 @@ public class FloatingSurfaceView extends AbstractFloatingView implements
private void setCurrentIconVisible(boolean isVisible) {
if (mIcon != null) {
setIconAndDotVisible(mIcon, isVisible);
setPropertiesVisible(mIcon, isVisible);
}
}
}
+6 -6
View File
@@ -127,9 +127,9 @@ android_test {
"com_android_launcher3_flags_lib",
],
libs: [
"android.test.base",
"android.test.runner",
"android.test.mock",
"android.test.base.stubs.system",
"android.test.runner.stubs.system",
"android.test.mock.stubs.system",
],
// Libraries used by mockito inline extended
jni_libs: [
@@ -229,9 +229,9 @@ android_robolectric_test {
"android.appwidget.flags-aconfig-java",
],
libs: [
"android.test.runner",
"android.test.base",
"android.test.mock",
"android.test.runner.stubs.system",
"android.test.base.stubs.system",
"android.test.mock.stubs.system",
"truth",
],
instrumentation_for: "Launcher3",
@@ -15,9 +15,6 @@
*/
package com.android.launcher3.ui.widget;
import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertNotNull;
import android.platform.test.annotations.PlatinumTest;
@@ -33,7 +30,6 @@ import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.util.rule.TestStabilityRule.Stability;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import org.junit.Assume;
@@ -102,7 +98,6 @@ public class TaplAddWidgetTest extends AbstractLauncherUiTest<Launcher> {
/**
* Test dragging a widget to the workspace and resize it.
*/
@Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/316910614
@PlatinumTest(focusArea = "launcher")
@Test
public void testResizeWidget() throws Throwable {