Snap for 10017868 from de1e2fe58e to udc-qpr1-release

Change-Id: I5caadfca7b18a3a103ee8549cda5d145325cacec
This commit is contained in:
Android Build Coastguard Worker
2023-04-28 01:31:33 +00:00
313 changed files with 5982 additions and 2113 deletions
@@ -218,29 +218,6 @@ public class DebugTestInformationHandler extends TestInformationHandler {
}
}
case TestProtocol.REQUEST_USE_TEST_WORKSPACE_LAYOUT: {
useTestWorkspaceLayout(
LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TEST);
return response;
}
case TestProtocol.REQUEST_USE_TEST2_WORKSPACE_LAYOUT: {
useTestWorkspaceLayout(
LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TEST2);
return response;
}
case TestProtocol.REQUEST_USE_TAPL_WORKSPACE_LAYOUT: {
useTestWorkspaceLayout(
LauncherSettings.Settings.ARG_DEFAULT_WORKSPACE_LAYOUT_TAPL);
return response;
}
case TestProtocol.REQUEST_USE_DEFAULT_WORKSPACE_LAYOUT: {
useTestWorkspaceLayout(null);
return response;
}
case TestProtocol.REQUEST_HOTSEAT_ICON_NAMES: {
return getLauncherUIProperty(Bundle::putStringArrayList, l -> {
ShortcutAndWidgetContainer hotseatIconsContainer =
@@ -278,20 +255,4 @@ public class DebugTestInformationHandler extends TestInformationHandler {
return super.call(method, arg, extras);
}
}
private void useTestWorkspaceLayout(String layout) {
final long identity = Binder.clearCallingIdentity();
try {
if (layout != null) {
LauncherSettings.Settings.call(mContext.getContentResolver(),
LauncherSettings.Settings.METHOD_SET_USE_TEST_WORKSPACE_LAYOUT_FLAG,
layout);
} else {
LauncherSettings.Settings.call(mContext.getContentResolver(),
LauncherSettings.Settings.METHOD_CLEAR_USE_TEST_WORKSPACE_LAYOUT_FLAG);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
+3 -3
View File
@@ -9,12 +9,12 @@
<string name="dialog_cancel" msgid="6464336969134856366">"ይቅር"</string>
<string name="dialog_settings" msgid="6564397136021186148">"ቅንብሮች"</string>
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"በማያ ገጹ ላይ ጽሑፍን ይተረጉሙ ወይም ያዳምጡ"</string>
<string name="niu_actions_confirmation_text" msgid="2105271481950866089">"እንደ በማያ ገዎ ላይ ያለ ጽሁፍ፣ የድር አድራሻዎች እና ቅጽበታዊ ገ እይታዎች ያሉ መረጃዎች ለGoogle ሊጋሩ ይችላሉ።\n\nምን መረጃ እንደሚያጋሩ ለመቀየር ወደ "<b>"ቅንብሮች &gt; መተግበሪያዎች &gt; ነባሪ መተግበሪያዎች &gt; ዲጂታል ረዳት መተግበሪያ"</b>" ይሂዱ።"</string>
<string name="niu_actions_confirmation_text" msgid="2105271481950866089">"እንደ በማያ ገዎ ላይ ያለ ጽሁፍ፣ የድር አድራሻዎች እና ቅጽበታዊ ገ እይታዎች ያሉ መረጃዎች ለGoogle ሊጋሩ ይችላሉ።\n\nምን መረጃ እንደሚያጋሩ ለመቀየር ወደ "<b>"ቅንብሮች &gt; መተግበሪያዎች &gt; ነባሪ መተግበሪያዎች &gt; ዲጂታል ረዳት መተግበሪያ"</b>" ይሂዱ።"</string>
<string name="assistant_not_selected_title" msgid="5017072974603345228">"ይህንን ባህሪ ለመጠቀም ረዳት ይምረጡ"</string>
<string name="assistant_not_selected_text" msgid="3244613673884359276">"በማያ ገጽዎ ላይ ጽሑፍን ለማዳመጥ ወይም ለመተርጎም በቅንብሮች ውስጥ የዲጂታል ረዳት መተግበሪያን ይምረጡ"</string>
<string name="assistant_not_supported_title" msgid="1675788067597484142">"ይህንን ባህሪ ለመጠቀም ረዳትዎን ይቀይሩ"</string>
<string name="assistant_not_supported_text" msgid="1708031078549268884">"በማያ ገጽዎ ላይ ጽሑፍን ለማዳመጥ ወይም ለመተርጎም በቅንብሮች ውስጥ የዲጂታል ረዳት መተግበሪያዎን ይቀይሩ"</string>
<string name="tooltip_listen" msgid="7634466447860989102">"በዚህ ማያ ገ ላይ ጽሁፍ ለማዳመጥ እዚህ መታ ያድርጉ"</string>
<string name="tooltip_translate" msgid="4184845868901542567">"በዚህ ማያ ገ ላይ ጽሁፍ ለመተርጎም እዚህ መታ ያድርጉ"</string>
<string name="tooltip_listen" msgid="7634466447860989102">"በዚህ ማያ ገ ላይ ጽሁፍ ለማዳመጥ እዚህ መታ ያድርጉ"</string>
<string name="tooltip_translate" msgid="4184845868901542567">"በዚህ ማያ ገ ላይ ጽሁፍ ለመተርጎም እዚህ መታ ያድርጉ"</string>
<string name="toast_p2p_app_not_shareable" msgid="7229739094132131536">"ይህ መተግበሪያ ሊጋራ አይችልም"</string>
</resources>
@@ -1,12 +1,9 @@
<?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.
@@ -14,5 +11,7 @@
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?android:attr/textColorSecondary"/>
<item
android:color="@android:color/system_neutral2_500"
android:lStar="80"/>
</selector>
@@ -0,0 +1,19 @@
<?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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<item android:color="?androidprv:attr/materialColorPrimaryContainer"/>
</selector>
@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#EFF1F2" android:state_expanded="false" />
<item android:color="#191C1D" android:state_expanded="true" />
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<item android:color="?androidprv:attr/materialColorTertiary"/>
</selector>
@@ -1,12 +1,9 @@
<?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.
@@ -14,5 +11,7 @@
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?android:attr/textColorPrimary"/>
<item
android:color="@android:color/system_neutral2_500"
android:lStar="35"/>
</selector>
@@ -17,6 +17,6 @@
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="?androidprv:attr/materialColorSurfaceContainerHigh" />
<solid android:color="?androidprv:attr/materialColorSurfaceBright" />
<corners android:radius="@dimen/keyboard_quick_switch_task_view_radius" />
</shape>
@@ -16,6 +16,6 @@
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/overviewScrimColor" />
<solid android:color="@color/quick_switch_view_background" />
<corners android:radius="@dimen/keyboard_quick_switch_view_radius" />
</shape>
@@ -23,30 +23,42 @@
android:importantForAccessibility="yes"
android:background="@drawable/keyboard_quick_switch_task_view_background"
android:clipToOutline="true"
launcher:borderColor="?androidprv:attr/materialColorSecondaryContainer">
launcher:borderColor="?androidprv:attr/materialColorOutline">
<include
layout="@layout/keyboard_quick_switch_thumbnail"
android:id="@+id/thumbnail1"
android:layout_width="0dp"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
android:layout_width="match_parent"
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/thumbnail2"/>
app:layout_constraintEnd_toEndOf="parent">
<include
layout="@layout/keyboard_quick_switch_thumbnail"
android:id="@+id/thumbnail2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:visibility="gone"
android:layout_marginStart="@dimen/keyboard_quick_switch_split_view_spacing"
<include
layout="@layout/keyboard_quick_switch_thumbnail"
android:id="@+id/thumbnail1"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/thumbnail1"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/thumbnail2"/>
<include
layout="@layout/keyboard_quick_switch_thumbnail"
android:id="@+id/thumbnail2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:visibility="gone"
android:layout_marginStart="@dimen/keyboard_quick_switch_split_view_spacing"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/thumbnail1"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
@@ -22,49 +22,46 @@
android:paddingVertical="26dp"
android:paddingHorizontal="56dp">
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/hotseat_flow"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:constraint_referenced_ids=
"hotseat_icon_1, hotseat_icon_2, hotseat_icon_3, hotseat_icon_4"
app:flow_wrapMode="aligned"
app:flow_maxElementsWrap="1"
app:flow_verticalStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<View
android:id="@+id/hotseat_icon_1"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true"
app:layout_constraintBottom_toTopOf="@id/hotseat_icon_2"
app:layout_constraintVertical_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_2"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true"
app:layout_constraintBottom_toTopOf="@id/hotseat_icon_3"
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_1"
app:layout_constraintStart_toStartOf="parent" />
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_3"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true"
app:layout_constraintBottom_toTopOf="@id/hotseat_icon_4"
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_2"
app:layout_constraintStart_toStartOf="parent" />
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_4"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_3"
app:layout_constraintBottom_toBottomOf="parent" />
android:clipToOutline="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,80 @@
<?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.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="32dp"
android:paddingStart="@dimen/gesture_tutorial_hotseat_padding_start_end"
android:paddingEnd="@dimen/gesture_tutorial_hotseat_padding_start_end">
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/hotseat_flow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:constraint_referenced_ids="hotseat_search_bar, hotseat_icon_1, hotseat_icon_2,
hotseat_icon_3, hotseat_icon_4, hotseat_icon_5"
app:flow_horizontalStyle="spread"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/hotseat_search_bar"
android:layout_width="200dp"
android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_1"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_2"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_3"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_4"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_5"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
+55
View File
@@ -0,0 +1,55 @@
<?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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/icon_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@null" />
<!--
Icon badge size is defined in Launcher3 BaseIconFactory as 0.444 of icon size.
Constraint guide starts from left, which means for a badge positioned on the right,
percent has to be 1 - 0.444 to have the same effect.
-->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/app_icon_constraint_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.556" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/app_icon_constraint_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.556" />
<ImageView
android:id="@+id/app_icon_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:contentDescription="@null"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="@id/app_icon_constraint_vertical"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/app_icon_constraint_horizontal" />
</merge>
@@ -0,0 +1,21 @@
<?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.bubbles.BubbleView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bubble_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@@ -20,35 +20,47 @@
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:background="@drawable/keyboard_quick_switch_overview_button_background"
android:clipToOutline="true"
android:importantForAccessibility="yes"
launcher:borderColor="?androidprv:attr/materialColorSecondaryContainer">
launcher:borderColor="?androidprv:attr/materialColorOutline">
<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:src="@drawable/ic_empty_recents"
<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"
app:tint="?android:attr/textColorPrimary"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
style="@style/KeyboardQuickSwitchOverview"
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="center"
app:layout_constraintTop_toBottomOf="@id/icon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintEnd_toEndOf="parent">
<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:src="@drawable/ic_empty_recents"
app:tint="?androidprv:attr/materialColorOnSurface"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
style="@style/KeyboardQuickSwitchOverview"
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="center"
app:layout_constraintTop_toBottomOf="@id/icon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
@@ -23,7 +23,17 @@
android:importantForAccessibility="yes"
android:background="@drawable/keyboard_quick_switch_task_view_background"
android:clipToOutline="true"
launcher:borderColor="?androidprv:attr/materialColorSecondaryContainer">
launcher:borderColor="?androidprv:attr/materialColorOutline">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
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_thumbnail"
@@ -49,4 +59,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.android.launcher3.taskbar.KeyboardQuickSwitchTaskView>
@@ -18,7 +18,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="@dimen/keyboard_quick_switch_view_spacing"
android:layout_marginTop="@dimen/keyboard_quick_switch_margin_top"
android:layout_marginHorizontal="@dimen/keyboard_quick_switch_margin_ends"
android:background="@drawable/keyboard_quick_switch_view_background"
@@ -44,7 +43,9 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
android:layout_height="wrap_content"
android:paddingVertical="@dimen/keyboard_quick_switch_view_spacing"
android:clipToPadding="false"/>
</HorizontalScrollView>
@@ -0,0 +1,84 @@
<?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.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="32dp"
android:paddingStart="@dimen/gesture_tutorial_hotseat_padding_start_end"
android:paddingEnd="@dimen/gesture_tutorial_hotseat_padding_start_end">
<View
android:id="@+id/hotseat_search_bar"
android:layout_width="0dp"
android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/hotseat_flow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
app:constraint_referenced_ids=
"hotseat_icon_1, hotseat_icon_2, hotseat_icon_3, hotseat_icon_4, hotseat_icon_5"
app:flow_horizontalStyle="spread_inside"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/hotseat_search_bar" />
<View
android:id="@+id/hotseat_icon_1"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_2"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_3"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_4"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_5"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -23,54 +23,45 @@
android:paddingStart="26dp"
android:paddingEnd="26dp">
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/hotseat_flow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:constraint_referenced_ids=
"hotseat_icon_1, hotseat_icon_2, hotseat_icon_3, hotseat_icon_4"
app:flow_horizontalStyle="spread_inside"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/hotseat_icon_1"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_2"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_2"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_3"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_1"
app:layout_constraintTop_toTopOf="parent" />
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_3"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toStartOf="@id/hotseat_icon_4"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_2"
app:layout_constraintTop_toTopOf="parent" />
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_4"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/hotseat_icon_3"
app:layout_constraintTop_toTopOf="parent" />
android:clipToOutline="true" />
<View
android:layout_width="0dp"
@@ -81,6 +72,6 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/hotseat_icon_1" />
app:layout_constraintTop_toBottomOf="@id/hotseat_flow" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,84 @@
<?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.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="32dp"
android:paddingStart="@dimen/gesture_tutorial_hotseat_padding_start_end"
android:paddingEnd="@dimen/gesture_tutorial_hotseat_padding_start_end">
<View
android:id="@+id/hotseat_search_bar"
android:layout_width="0dp"
android:layout_height="@dimen/gesture_tutorial_hotseat_search_height"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/hotseat_flow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/gesture_tutorial_hotseat_icon_search_margin"
app:constraint_referenced_ids=
"hotseat_icon_1, hotseat_icon_2, hotseat_icon_3, hotseat_icon_4, hotseat_icon_5"
app:flow_horizontalStyle="spread_inside"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/hotseat_search_bar" />
<View
android:id="@+id/hotseat_icon_1"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_2"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_3"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_4"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
<View
android:id="@+id/hotseat_icon_5"
android:layout_width="@dimen/gesture_tutorial_hotseat_icon_size"
android:layout_height="@dimen/gesture_tutorial_hotseat_icon_size"
android:background="@drawable/hotseat_icon_home"
android:clipToOutline="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
+1 -1
View File
@@ -24,7 +24,7 @@
android:clipChildren="false"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
launcher:borderColor="?androidprv:attr/materialColorSecondaryContainer">
launcher:borderColor="?androidprv:attr/materialColorOutline">
<com.android.quickstep.views.TaskThumbnailView
android:id="@+id/snapshot"
+1 -1
View File
@@ -25,7 +25,7 @@
android:clipToOutline="true"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
launcher:borderColor="?androidprv:attr/materialColorSecondaryContainer">
launcher:borderColor="?androidprv:attr/materialColorOutline">
<View
android:id="@+id/background"
+1 -1
View File
@@ -29,7 +29,7 @@
android:clipChildren="false"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
launcher:borderColor="?androidprv:attr/materialColorSecondaryContainer">
launcher:borderColor="?androidprv:attr/materialColorOutline">
<com.android.quickstep.views.TaskThumbnailView
android:id="@+id/snapshot"
@@ -13,16 +13,16 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/taskbar_divider_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/taskbar_icon_min_touch_size"
android:layout_height="@dimen/taskbar_icon_min_touch_size"
android:contentDescription="@string/taskbar_divider_a11y_title"
android:backgroundTint="@android:color/transparent">
<View
android:id="@+id/taskbar_divider_bar"
android:layout_height="32dp"
android:layout_width="2dp"
android:layout_gravity="center"
android:background="@drawable/bg_rounded_corner_bottom_sheet_handle" />
<!-- TODO(b/265347148): Create separate drawable -->
</FrameLayout>
android:background="@drawable/taskbar_divider_bg" />
</FrameLayout>
@@ -0,0 +1,98 @@
<?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.TaskbarDividerPopupView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/taskbar_pinning_popup_menu_width"
android:layout_height="wrap_content"
android:focusable="true"
android:background="@drawable/popup_background_material_u"
android:orientation="vertical">
<LinearLayout
android:id="@+id/taskbar_switch_option"
android:layout_width="match_parent"
android:layout_height="52dp"
android:layout_gravity="center_vertical"
android:elevation="2dp"
android:focusable="true"
android:clickable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:background="@drawable/top_rounded_popup_ripple"
android:paddingEnd="10dp"
android:paddingStart="10dp"
android:theme="@style/PopupItem">
<View
android:layout_margin="6dp"
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@drawable/ic_visibility"
android:backgroundTint="?android:attr/textColorPrimary" />
<Switch
style="@style/BaseIcon"
android:id="@+id/taskbar_pinning_switch"
android:background="@null"
android:clickable="false"
android:gravity="start|center_vertical"
android:textAlignment="viewStart"
android:paddingStart="12dp"
android:singleLine="true"
android:ellipsize="end"
android:textSize="14sp"
android:textColor="?android:attr/textColorPrimary"
android:text="@string/always_show_taskbar" />
</LinearLayout>
<LinearLayout
android:id="@+id/navigation_mode_switch_option"
android:layout_width="match_parent"
android:layout_height="52dp"
android:layout_gravity="center_vertical"
android:elevation="2dp"
android:clickable="true"
android:focusable="true"
android:background="@drawable/bottom_rounded_popup_ripple"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingEnd="10dp"
android:paddingStart="10dp"
android:theme="@style/PopupItem">
<View
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_margin="4dp"
android:background="@drawable/ic_touch"
android:backgroundTint="?android:attr/textColorPrimary" />
<com.android.launcher3.BubbleTextView
style="@style/BaseIcon"
android:id="@+id/change_navigation_mode_text"
android:gravity="start|center_vertical"
android:textAlignment="viewStart"
android:paddingStart="12dp"
android:singleLine="true"
android:ellipsize="end"
android:textSize="14sp"
android:textColor="?android:attr/textColorPrimary"
android:text="@string/change_navigation_mode" />
</LinearLayout>
</com.android.launcher3.taskbar.TaskbarDividerPopupView>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"v":"5.8.1","fr":60,"ip":0,"op":180,"w":1280,"h":800,"nm":"SUW_WelcomeScreen_Tablet_Landscape_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[288,540,0],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[25,25,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primary","cl":"primary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[56]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.986},"o":{"x":0.167,"y":0.167},"t":0,"s":[375.832,-1006.545,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.01},"t":95,"s":[375.832,-1811,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[375.832,-1006.545,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[110,110,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[75.615,-96.908],[89.338,-70.276],[111.99,-50.668],[111.764,-20.709],[122.709,7.18],[108.586,33.602],[105.316,63.383],[80.533,80.216],[63.797,105.066],[34.03,108.453],[7.663,122.679],[-20.269,111.845],[-50.226,112.189],[-69.924,89.614],[-96.61,75.997],[-103.56,46.854],[-120.861,22.395],[-113.472,-6.639],[-117.425,-36.337],[-97.389,-58.612],[-87.087,-86.745],[-58.996,-97.158],[-36.8,-117.281],[-7.086,-113.445],[21.918,-120.948],[46.446,-103.744]],"o":[[-75.615,96.909],[-89.338,70.276],[-111.99,50.668],[-111.764,20.709],[-122.709,-7.18],[-108.586,-33.602],[-105.316,-63.383],[-80.533,-80.216],[-63.797,-105.066],[-34.03,-108.453],[-7.663,-122.679],[20.269,-111.845],[50.226,-112.188],[69.924,-89.614],[96.61,-75.997],[103.56,-46.854],[120.861,-22.395],[113.472,6.64],[117.425,36.337],[97.389,58.612],[87.088,86.745],[58.995,97.158],[36.8,117.281],[7.087,113.445],[-21.918,120.948],[-46.446,103.744]],"v":[[733.209,572.105],[531.711,675.932],[383.354,847.313],[156.685,845.606],[-54.323,928.412],[-254.235,821.562],[-479.555,796.823],[-606.913,609.309],[-794.927,482.691],[-820.554,257.47],[-928.191,57.981],[-846.217,-153.353],[-848.817,-380.013],[-678.021,-529.044],[-574.99,-730.949],[-354.499,-783.537],[-169.439,-914.435],[50.234,-858.532],[274.928,-888.434],[443.46,-736.847],[656.313,-658.903],[735.094,-446.359],[887.344,-278.426],[858.327,-53.616],[915.095,165.835],[784.928,351.409]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.956862745098,0.729411764706,0.619607843137,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":720,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiary","cl":"tertiary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.248]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[57]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.172]},"t":95,"s":[75]},{"t":180,"s":[57]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.032]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[2618]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.022]},"t":95,"s":[2442]},{"t":180,"s":[2618]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.034]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[891]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.024]},"t":95,"s":[694]},{"t":180,"s":[891]}],"ix":4}},"a":{"a":0,"k":[164.438,1433.781,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[3079.125,4685.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.752941176471,0.788235294118,0.752941176471,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
@@ -0,0 +1 @@
{"v":"5.8.1","fr":60,"ip":0,"op":180,"w":800,"h":1280,"nm":"SUW_WelcomeScreen_Tablet_Portrait_Dynamic","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Null 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[288,528,0],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[25,25,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primary","cl":"primary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[56]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.07,"y":0.986},"o":{"x":0.167,"y":0.167},"t":0,"s":[999.832,-2238.545,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.773,"y":0.01},"t":95,"s":[999.832,-3043,0],"to":[0,0,0],"ti":[0,0,0]},{"t":180,"s":[999.832,-2238.545,0]}],"ix":2,"l":2},"a":{"a":0,"k":[-3514.717,-358.642,0],"ix":1,"l":2},"s":{"a":0,"k":[200,200,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[75.615,-96.908],[89.338,-70.276],[111.99,-50.668],[111.764,-20.709],[122.709,7.18],[108.586,33.602],[105.316,63.383],[80.533,80.216],[63.797,105.066],[34.03,108.453],[7.663,122.679],[-20.269,111.845],[-50.226,112.189],[-69.924,89.614],[-96.61,75.997],[-103.56,46.854],[-120.861,22.395],[-113.472,-6.639],[-117.425,-36.337],[-97.389,-58.612],[-87.087,-86.745],[-58.996,-97.158],[-36.8,-117.281],[-7.086,-113.445],[21.918,-120.948],[46.446,-103.744]],"o":[[-75.615,96.909],[-89.338,70.276],[-111.99,50.668],[-111.764,20.709],[-122.709,-7.18],[-108.586,-33.602],[-105.316,-63.383],[-80.533,-80.216],[-63.797,-105.066],[-34.03,-108.453],[-7.663,-122.679],[20.269,-111.845],[50.226,-112.188],[69.924,-89.614],[96.61,-75.997],[103.56,-46.854],[120.861,-22.395],[113.472,6.64],[117.425,36.337],[97.389,58.612],[87.088,86.745],[58.995,97.158],[36.8,117.281],[7.087,113.445],[-21.918,120.948],[-46.446,103.744]],"v":[[733.209,572.105],[531.711,675.932],[383.354,847.313],[156.685,845.606],[-54.323,928.412],[-254.235,821.562],[-479.555,796.823],[-606.913,609.309],[-794.927,482.691],[-820.554,257.47],[-928.191,57.981],[-846.217,-153.353],[-848.817,-380.013],[-678.021,-529.044],[-574.99,-730.949],[-354.499,-783.537],[-169.439,-914.435],[50.234,-858.532],[274.928,-888.434],[443.46,-736.847],[656.313,-658.903],[735.094,-446.359],[887.344,-278.426],[858.327,-53.616],[915.095,165.835],[784.928,351.409]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.956862745098,0.729411764706,0.619607843137,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-3509.952,-363.731],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Polystar 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":720,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".tertiary","cl":"tertiary","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.248]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-39]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.172]},"t":95,"s":[-21]},{"t":180,"s":[-39]}],"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.032]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[1490]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.022]},"t":95,"s":[1314]},{"t":180,"s":[1490]}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.07],"y":[1.034]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[2967]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.719],"y":[0.024]},"t":95,"s":[2770]},{"t":180,"s":[2967]}],"ix":4}},"a":{"a":0,"k":[164.438,1433.781,0],"ix":1,"l":2},"s":{"a":0,"k":[168,168,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[3079.125,4685.989],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.752941176471,0.788235294118,0.752941176471,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[164.438,1481.781],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]}
File diff suppressed because one or more lines are too long
+6 -6
View File
@@ -34,7 +34,7 @@
<string name="hotseat_edu_title_migrate" msgid="306578144424489980">"በመነሻ ገጽዎ ታችኛው ረድፍ ላይ የመተግበሪያ አስተያየት ጥቆማዎችን ያግኙ"</string>
<string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"በመነሻ ማያ ገጽዎ የተወዳጆች ረድፍ ላይ የመተግበሪያ አስተያየት ጥቆማዎችን ያግኙ"</string>
<string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"በጣም ስራ ላይ የዋሉ መተግበሪያዎችዎን በቀላሉ ከመነሻ ገጹ ሆነው ይድረሱባቸው። የአስተያየት ጥቆማዎች በእርስዎ ዕለት ተዕለት ተግባራት ላይ በመመስረት ይቀየራሉ። በታችኛው ረድፍ ላይ ያሉ መተግበሪያዎች ወደ መነሻ ገጽዎ ይወሰዳሉ።"</string>
<string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"በጣም ሥራ ላይ የዋሉ መተግበሪያዎችዎን በቀላሉ ከመነሻ ገጹ ሆነው ይድረሱባቸው። የአስተያየት ጥቆማዎች በእርስዎ ዕለት ተዕለት ተግባራት ላይ በመመሥረት ይቀየራሉ። በተወዳጆች ረድፍ ውስጥ ያሉ መተግበሪያዎች ወደ የእርስዎ መነሻ ማያ ገ ይንቀሳቀሳሉ።"</string>
<string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"በጣም ሥራ ላይ የዋሉ መተግበሪያዎችዎን በቀላሉ ከመነሻ ገጹ ሆነው ይድረሱባቸው። የአስተያየት ጥቆማዎች በእርስዎ ዕለት ተዕለት ተግባራት ላይ በመመሥረት ይቀየራሉ። በተወዳጆች ረድፍ ውስጥ ያሉ መተግበሪያዎች ወደ የእርስዎ መነሻ ማያ ገ ይንቀሳቀሳሉ።"</string>
<string name="hotseat_edu_accept" msgid="1611544083278999837">"የመተግበሪያ አስተያየት ጥቆማዎችን አግኝ"</string>
<string name="hotseat_edu_dismiss" msgid="2781161822780201689">"አይ፣ አመሰግናለሁ"</string>
<string name="hotseat_prediction_settings" msgid="6246554993566070818">"ቅንብሮች"</string>
@@ -51,8 +51,8 @@
<string name="back_gesture_feedback_swipe_in_nav_bar" msgid="1148198467090405643">"ከማያ ገጹ ታችኛው ክፍል ጋር በጣም ጠጋ ብለው አለማንሸራተትዎን ያረጋግጡ።"</string>
<string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"ከኋላ ስሜት ሰጭነት ደረጃ ለመለወጥ ወደ ቅንብሮች ይመለሱ"</string>
<string name="back_gesture_intro_title" msgid="19551256430224428">"ወደኋላ ለመመለስ ያንሸራትቱ"</string>
<string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"ወደ መጨረሻው ማያ ገ ለመመለስ ከግራ ወይም ከቀኝ ጠርዝ ወደ ማያ ገጹ መሃል ያንሸራትቱ።"</string>
<string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"ወደ መጨረሻው ማያ ገ ለመመለስ በ2 ጣቶች ከግራ ወይም ከቀኝ ጠርዝ ወደ ማያ ገጹ መሃል ያንሸራትቱ።"</string>
<string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"ወደ መጨረሻው ማያ ገ ለመመለስ ከግራ ወይም ከቀኝ ጠርዝ ወደ ማያ ገጹ መሃል ያንሸራትቱ።"</string>
<string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"ወደ መጨረሻው ማያ ገ ለመመለስ በ2 ጣቶች ከግራ ወይም ከቀኝ ጠርዝ ወደ ማያ ገጹ መሃል ያንሸራትቱ።"</string>
<string name="back_gesture_tutorial_title" msgid="1944737946101059789">"ተመለስ"</string>
<string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="1446774096007065298">"ከማያ ገጹ የታችኛው ጫፍ ወደ ላይ ማንሸራተትዎን ያረጋግጡ።"</string>
<string name="home_gesture_feedback_overview_detected" msgid="1557523944897393013">"ከመልቀቅዎ በፊት ለአፍታ እንዳልቆሙ ያረጋግጡ።"</string>
@@ -60,8 +60,8 @@
<string name="home_gesture_feedback_complete_with_follow_up" msgid="1427872029729605034">"የወደ መነሻ ሂድ ምልክትን አጠናቀዋል። ቀጥሎም ወደ ኋላ እንዴት መሄድ እንደሚችሉ ይወቁ።"</string>
<string name="home_gesture_feedback_complete_without_follow_up" msgid="8049099486868933882">"የወደ መነሻ ሂድ ምልክትን አጠናቀዋል።"</string>
<string name="home_gesture_intro_title" msgid="836590312858441830">"ወደ መነሻ ለመሄድ ያንሸራትቱ"</string>
<string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"ከእርስዎ ማያ ገ ግርጌ ላይ ወደ ላይ በጣት ጠረግ ያድርጉ። ይህ የእጅ ውዝዋዜ ሁልጊዜ ወደ መነሻ ማያ ገ ይወስድዎታል።"</string>
<string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"በ2 ጣቶች ከማያ ገጹ ግርጌ ወደ ላይ ያንሸራትቱ። ይህ የእጅ ምልክት ሁልጊዜ ወደ መነሻ ማያ ገ ይወስደዎታል።"</string>
<string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"ከእርስዎ ማያ ገ ግርጌ ላይ ወደ ላይ በጣት ጠረግ ያድርጉ። ይህ የእጅ ውዝዋዜ ሁልጊዜ ወደ መነሻ ማያ ገ ይወስድዎታል።"</string>
<string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"በ2 ጣቶች ከማያ ገጹ ግርጌ ወደ ላይ ያንሸራትቱ። ይህ የእጅ ምልክት ሁልጊዜ ወደ መነሻ ማያ ገ ይወስደዎታል።"</string>
<string name="home_gesture_tutorial_title" msgid="3126834347496917376">"ወደ መነሻ ይሂዱ"</string>
<string name="home_gesture_tutorial_subtitle" msgid="8979014952569486971">"በማንኛውም ጊዜ ወደ መነሻ ማያ ገጽዎ ለመሄድ ከማያ ገጽዎ የታችኛው ክፍል ወደ ላይ ያንሸራትቱ"</string>
<string name="overview_gesture_feedback_swipe_too_far_from_edge" msgid="3032757898111577225">"ከማያ ገጹ የታችኛው ጫፍ ወደ ላይ ማንሸራተትዎን ያረጋግጡ።"</string>
@@ -86,7 +86,7 @@
<string name="default_device_name" msgid="6660656727127422487">"መሣሪያ"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"የስርዓት አሰሳ ቅንብሮች"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"አጋራ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ቅጽበታዊ ገ እይታ"</string>
<string name="action_screenshot" msgid="8171125848358142917">"ቅጽበታዊ ገ እይታ"</string>
<string name="action_split" msgid="2098009717623550676">"ክፈል"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"የተከፈለ ማያ ገጽን ለመጠቀም ሌላ መተግበሪያ መታ ያድርጉ"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"የተከፈለ ማያ ገጽን ለመቀበል ሌላ መተግበሪያ ይምረጡ"</string>
+1 -1
View File
@@ -24,7 +24,7 @@
<string name="recents_empty_message" msgid="7040467240571714191">"কোনো শেহতীয়া বস্তু নাই"</string>
<string name="accessibility_app_usage_settings" msgid="6312864233673544149">"এপে ব্যৱহাৰ কৰা ডেটাৰ ছেটিং"</string>
<string name="recents_clear_all" msgid="5328176793634888831">"আটাইবোৰ মচক"</string>
<string name="accessibility_recent_apps" msgid="4058661986695117371">"শেহতীয়া এপসমূহ"</string>
<string name="accessibility_recent_apps" msgid="4058661986695117371">"শেহতীয়া এপ্‌সমূহ"</string>
<string name="task_view_closed" msgid="9170038230110856166">"কাৰ্য বন্ধ কৰা হ’ল"</string>
<string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; ১ মিনিট"</string>
+2 -2
View File
@@ -88,8 +88,8 @@
<string name="action_share" msgid="2648470652637092375">"Teilen"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
<string name="action_split" msgid="2098009717623550676">"Teilen"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Für „Geteilter Bildschirm“ auf weitere App tippen"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Für geteilten Bildschirm andere App auswählen"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"Für Splitscreen auf weitere App tippen"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"Für Splitscreen andere App auswählen"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"Die App oder deine Organisation lässt diese Aktion nicht zu"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Tutorial zur Bedienung überspringen?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Du findest es später auch in der <xliff:g id="NAME">%1$s</xliff:g> App"</string>
+16 -16
View File
@@ -30,11 +30,11 @@
<string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"少於 1 分鐘"</string>
<string name="time_left_for_app" msgid="3111996412933644358">"今天剩餘時間:<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="title_app_suggestions" msgid="4185902664111965088">"應用程式建議"</string>
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"的預測應用程式"</string>
<string name="all_apps_prediction_tip" msgid="2672336544844936186">"的預測應用程式"</string>
<string name="hotseat_edu_title_migrate" msgid="306578144424489980">"在主畫面底部取得應用程式建議"</string>
<string name="hotseat_edu_title_migrate_landscape" msgid="3633942953997845243">"在主畫面「我的最愛」列取得應用程式建議"</string>
<string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"在主畫面輕鬆存取常用的應用程式。系統會根據的日常安排更改建議,並將底部的應用程式移到主畫面。"</string>
<string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"在主畫面輕鬆存取最常用的應用程式。系統會根據的日常安排變更建議,「我的最愛」列中的應用程式會移至主畫面。"</string>
<string name="hotseat_edu_message_migrate" msgid="8927179260533775320">"在主畫面輕鬆存取常用的應用程式。系統會根據的日常安排更改建議,並將底部的應用程式移到主畫面。"</string>
<string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"在主畫面輕鬆存取最常用的應用程式。系統會根據的日常安排變更建議,「我的最愛」列中的應用程式會移至主畫面。"</string>
<string name="hotseat_edu_accept" msgid="1611544083278999837">"取得應用程式建議"</string>
<string name="hotseat_edu_dismiss" msgid="2781161822780201689">"不用了,謝謝"</string>
<string name="hotseat_prediction_settings" msgid="6246554993566070818">"設定"</string>
@@ -46,8 +46,8 @@
<string name="hotseat_prediction_content_description" msgid="4582028296938078419">"預測應用程式:<xliff:g id="TITLE">%1$s</xliff:g>"</string>
<string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="1711645592102201538">"請從螢幕右側或左側邊緣滑動。"</string>
<string name="back_gesture_feedback_cancelled" msgid="3274382913290074496">"請從螢幕右側或左側邊緣往中央滑動,然後放開手指。"</string>
<string name="back_gesture_feedback_complete_with_overview_follow_up" msgid="9176400654037014471">"已瞭解如何透過「由右向左滑動」手勢返回。接下來一起瞭解如何切換應用程式。"</string>
<string name="back_gesture_feedback_complete_without_follow_up" msgid="6405649621667113830">"已完成「返回」手勢的教學課程。"</string>
<string name="back_gesture_feedback_complete_with_overview_follow_up" msgid="9176400654037014471">"已瞭解如何透過「由右向左滑動」手勢返回。接下來一起瞭解如何切換應用程式。"</string>
<string name="back_gesture_feedback_complete_without_follow_up" msgid="6405649621667113830">"已完成「返回」手勢的教學課程。"</string>
<string name="back_gesture_feedback_swipe_in_nav_bar" msgid="1148198467090405643">"滑動時,手的位置不要太接近螢幕底部。"</string>
<string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"如要變更「返回」手勢的敏感度,請前往「設定」"</string>
<string name="back_gesture_intro_title" msgid="19551256430224428">"滑動即可返回"</string>
@@ -57,18 +57,18 @@
<string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="1446774096007065298">"請從螢幕底部邊緣向上滑動。"</string>
<string name="home_gesture_feedback_overview_detected" msgid="1557523944897393013">"放開手指前請勿停下來。"</string>
<string name="home_gesture_feedback_wrong_swipe_direction" msgid="6993979358080825438">"請向上滑動。"</string>
<string name="home_gesture_feedback_complete_with_follow_up" msgid="1427872029729605034">"已完成「返回主畫面」手勢的教學課程。接著,一起來瞭解如何返回上一個畫面。"</string>
<string name="home_gesture_feedback_complete_without_follow_up" msgid="8049099486868933882">"已完成「返回主畫面」手勢的教學課程。"</string>
<string name="home_gesture_feedback_complete_with_follow_up" msgid="1427872029729605034">"已完成「返回主畫面」手勢的教學課程。接著,一起來瞭解如何返回上一個畫面。"</string>
<string name="home_gesture_feedback_complete_without_follow_up" msgid="8049099486868933882">"已完成「返回主畫面」手勢的教學課程。"</string>
<string name="home_gesture_intro_title" msgid="836590312858441830">"向上滑動即可返回主畫面"</string>
<string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"從螢幕底部向上滑動。這個手勢在所有畫面下都可讓返回主畫面。"</string>
<string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"請用 2 隻手指從螢幕底部向上滑動。這個手勢在所有畫面下都可讓返回主畫面。"</string>
<string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"從螢幕底部向上滑動。這個手勢在所有畫面下都可讓返回主畫面。"</string>
<string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"請用 2 隻手指從螢幕底部向上滑動。這個手勢在所有畫面下都可讓返回主畫面。"</string>
<string name="home_gesture_tutorial_title" msgid="3126834347496917376">"返回主畫面"</string>
<string name="home_gesture_tutorial_subtitle" msgid="8979014952569486971">"只要從螢幕底部向上滑動,隨時可以返回主畫面"</string>
<string name="overview_gesture_feedback_swipe_too_far_from_edge" msgid="3032757898111577225">"請從螢幕底部邊緣向上滑動。"</string>
<string name="overview_gesture_feedback_home_detected" msgid="1411130969354020489">"請嘗試按住視窗更長時間,然後再放開。"</string>
<string name="overview_gesture_feedback_wrong_swipe_direction" msgid="6725820500906747925">"請向上滑動,然後停住。"</string>
<string name="overview_gesture_feedback_complete_with_follow_up" msgid="3544611727467765026">"已學會如何使用手勢。如要關閉手勢,請前往「設定」。"</string>
<string name="overview_gesture_feedback_complete_without_follow_up" msgid="3199486203448379152">"已完成「切換應用程式」手勢的教學課程。"</string>
<string name="overview_gesture_feedback_complete_with_follow_up" msgid="3544611727467765026">"已學會如何使用手勢。如要關閉手勢,請前往「設定」。"</string>
<string name="overview_gesture_feedback_complete_without_follow_up" msgid="3199486203448379152">"已完成「切換應用程式」手勢的教學課程。"</string>
<string name="overview_gesture_intro_title" msgid="2902054412868489378">"滑動即可切換應用程式"</string>
<string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"如要切換應用程式,請從螢幕底部向上滑動並按住,然後放開。"</string>
<string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"如要切換應用程式,請用 2 隻手指從螢幕底部向上滑動並按住,然後放開手指。"</string>
@@ -82,7 +82,7 @@
<string name="allset_title" msgid="5021126669778966707">"設定完成!"</string>
<string name="allset_hint" msgid="459504134589971527">"向上滑動即可前往主畫面"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"輕按主按鈕即可前往主畫面"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"可以開始使用 <xliff:g id="DEVICE">%1$s</xliff:g> 了"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"可以開始使用 <xliff:g id="DEVICE">%1$s</xliff:g> 了"</string>
<string name="default_device_name" msgid="6660656727127422487">"裝置"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"系統導覽設定"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"分享"</string>
@@ -90,18 +90,18 @@
<string name="action_split" msgid="2098009717623550676">"分割"</string>
<string name="toast_split_select_app" msgid="8464310533320556058">"輕按其他應用程式以使用分割螢幕"</string>
<string name="toast_split_app_unsupported" msgid="2360229567007828914">"選擇其他應用程式才能使用分割螢幕"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"應用程式或的機構不允許此操作"</string>
<string name="blocked_by_policy" msgid="2071401072261365546">"應用程式或的機構不允許此操作"</string>
<string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"要略過手勢操作教學課程嗎?"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"之後可以在「<xliff:g id="NAME">%1$s</xliff:g>」應用程式找到這些說明"</string>
<string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"之後可以在「<xliff:g id="NAME">%1$s</xliff:g>」應用程式找到這些說明"</string>
<string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"取消"</string>
<string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"略過"</string>
<string name="accessibility_rotate_button" msgid="4771825231336502943">"旋轉螢幕"</string>
<string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"工作列教學"</string>
<string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"將應用程式拖曳到一邊,即可同時使用 2 個應用程式"</string>
<string name="taskbar_edu_stashing" msgid="5645461372669217294">"慢慢向上滑動即可顯示工作列"</string>
<string name="taskbar_edu_suggestions" msgid="8215044496435527982">"根據的日常安排提供應用程式建議"</string>
<string name="taskbar_edu_suggestions" msgid="8215044496435527982">"根據的日常安排提供應用程式建議"</string>
<string name="taskbar_edu_settings_persistent" msgid="1387372982791296151">"在「設定」中啟用手勢操作後,工作列就會自動隱藏"</string>
<string name="taskbar_edu_features" msgid="3320337287472848162">"工作列助事半功倍"</string>
<string name="taskbar_edu_features" msgid="3320337287472848162">"工作列助事半功倍"</string>
<string name="taskbar_edu_close" msgid="887022990168191073">"關閉"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"完成"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"住宅"</string>
+16
View File
@@ -341,9 +341,25 @@
<dimen name="taskbar_edu_features_lottie_height">106dp</dimen>
<dimen name="taskbar_edu_features_horizontal_spacing">24dp</dimen>
<!--- Taskbar Pinning -->
<dimen name="taskbar_pinning_popup_menu_width">300dp</dimen>
<!-- Recents overview -->
<dimen name="recents_filter_icon_size">30dp</dimen>
<!-- Bubble bar -->
<dimen name="bubblebar_size">72dp</dimen>
<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_pointer_size">8dp</dimen>
<dimen name="bubblebar_icon_size">50dp</dimen>
<dimen name="bubblebar_badge_size">24dp</dimen>
<dimen name="bubblebar_icon_overlap">12dp</dimen>
<dimen name="bubblebar_icon_spacing">3dp</dimen>
<dimen name="bubblebar_icon_elevation">1dp</dimen>
<!-- Launcher splash screen -->
<!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
<!-- starting_surface_exit_animation_window_shift_length -->
+7
View File
@@ -277,6 +277,13 @@
<string name="taskbar_a11y_hidden_title">Taskbar hidden</string>
<!-- Accessibility title for the Taskbar window on phones. [CHAR_LIMIT=NONE] -->
<string name="taskbar_phone_a11y_title">Navigation bar</string>
<!-- Text in popup dialog for user to switch between always showing Taskbar or not. [CHAR LIMIT=30] -->
<string name="always_show_taskbar">Always show Taskbar</string>
<!-- Text in popup dialog for user to switch between system navigation modes. [CHAR LIMIT=30] -->
<string name="change_navigation_mode">Change navigation mode</string>
<!-- Accessibility title for the Taskbar vertical divider icon. [CHAR_LIMIT=NONE] -->
<string name="taskbar_divider_a11y_title">Taskbar Divider</string>
<!-- Label for moving drop target to the top or left side of the screen, depending on orientation (from the Taskbar only). -->
<string name="move_drop_target_top_or_left">Move to top&#47;left</string>
+3 -2
View File
@@ -14,7 +14,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<resources
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<style name="TextAppearance.GestureTutorial"
parent="android:TextAppearance.Material.Body1" />
@@ -217,7 +218,7 @@
<style name="KeyboardQuickSwitchOverview">
<item name="fontFamily">google-sans-text</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="lineHeight">20sp</item>
</style>
@@ -120,7 +120,7 @@ public class LauncherAnimationRunner extends RemoteAnimationRunnerCompat {
*/
@BinderThread
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {
public void onAnimationCancelled() {
postAsyncCallback(mHandler, () -> {
finishExistingAnimation();
getFactory().onAnimationCancelled();
@@ -235,16 +235,12 @@ public class LauncherAnimationRunner extends RemoteAnimationRunnerCompat {
RemoteAnimationTarget[] nonAppTargets,
LauncherAnimationRunner.AnimationResult result);
@Override
@UiThread
default void onAnimationCancelled(boolean isKeyguardOccluded) {
onAnimationCancelled();
}
/**
* Called when the animation is cancelled. This can happen with or without
* the create being called.
*/
default void onAnimationCancelled() { }
@Override
@UiThread
default void onAnimationCancelled() {}
}
}
@@ -1841,8 +1841,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {
mDelegate.onAnimationCancelled(isKeyguardOccluded);
public void onAnimationCancelled() {
mDelegate.onAnimationCancelled();
}
}
@@ -36,6 +36,7 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
import com.android.launcher3.anim.AlphaUpdateListener;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.keyboard.FocusIndicatorHelper;
import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
import com.android.launcher3.model.data.ItemInfo;
@@ -105,12 +106,22 @@ public class PredictionRowView<T extends Context & ActivityContext>
mActivityContext.getAppsView().getAppsStore().unregisterIconContainer(this);
}
}
// Set the predicted row in All Apps' text line to 1.
if (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get()
|| FeatureFlags.ENABLE_TWOLINE_DEVICESEARCH.get()) {
for (int i = 0; i < getChildCount(); i++) {
BubbleTextView icon = (BubbleTextView) getChildAt(i);
icon.setMaxLines(1);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(getExpectedHeight(),
MeasureSpec.EXACTLY));
updateVisibility();
}
@Override
@@ -21,7 +21,6 @@ import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.content.Context;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.GridBackupTable;
@@ -42,10 +41,7 @@ public class HotseatRestoreHelper {
context.getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
.getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
GridBackupTable backupTable = new GridBackupTable(context,
transaction.getDb(), idp.numDatabaseHotseatIcons, idp.numColumns,
idp.numRows);
GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb());
backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE);
transaction.commit();
LauncherSettings.Settings.call(context.getContentResolver(),
@@ -67,10 +63,7 @@ public class HotseatRestoreHelper {
if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) {
return;
}
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
GridBackupTable backupTable = new GridBackupTable(context,
transaction.getDb(), idp.numDatabaseHotseatIcons, idp.numColumns,
idp.numRows);
GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb());
backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true);
transaction.commit();
LauncherAppState.getInstance(context).getModel().forceReload();
@@ -166,5 +166,6 @@ public class DepthController extends BaseDepthController implements StateHandler
writer.println(prefix + "\tmInEarlyWakeUp=" + mInEarlyWakeUp);
writer.println(prefix + "\tmIgnoreStateChangesDuringMultiWindowAnimation="
+ mIgnoreStateChangesDuringMultiWindowAnimation);
writer.println(prefix + "\tmPauseBlurs=" + mPauseBlurs);
}
}
@@ -23,6 +23,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull;
@@ -45,6 +46,7 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
@Nullable private ImageView mThumbnailView1;
@Nullable private ImageView mThumbnailView2;
@Nullable private View mContent;
public KeyboardQuickSwitchTaskView(@NonNull Context context) {
this(context, null);
@@ -84,7 +86,20 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
.getColor(
R.styleable.TaskView_borderColor,
DEFAULT_BORDER_COLOR),
/* invalidateViewCallback= */ KeyboardQuickSwitchTaskView.this::invalidate);
/* invalidateViewCallback= */ KeyboardQuickSwitchTaskView.this::invalidate,
/* viewScaleTargetProvider= */ new BorderAnimator.ViewScaleTargetProvider() {
@NonNull
@Override
public View getContainerView() {
return KeyboardQuickSwitchTaskView.this;
}
@NonNull
@Override
public View getContentView() {
return mContent;
}
});
}
@Override
@@ -93,6 +108,7 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
mThumbnailView1 = findViewById(R.id.thumbnail1);
mThumbnailView2 = findViewById(R.id.thumbnail2);
mContent = findViewById(R.id.content);
}
@NonNull
@@ -93,15 +93,14 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
protected void init(TaskbarControllers taskbarControllers) {
super.init(taskbarControllers);
mTaskbarLauncherStateController.init(mControllers, mLauncher);
mTaskbarLauncherStateController.init(mControllers, mLauncher,
mControllers.getSharedState().sysuiStateFlags);
mLauncher.setTaskbarUIController(this);
onLauncherResumedOrPaused(mLauncher.hasBeenResumed(), true /* fromInit */);
onStashedInAppChanged(mLauncher.getDeviceProfile());
mTaskbarLauncherStateController.updateStateForSysuiFlags(
mControllers.getSharedState().sysuiStateFlags, true /* fromInit */);
mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
// Restore the in-app display progress from before Taskbar was recreated.
@@ -325,8 +324,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
}
@Override
public void updateStateForSysuiFlags(int sysuiFlags, boolean skipAnim) {
mTaskbarLauncherStateController.updateStateForSysuiFlags(sysuiFlags, skipAnim);
public void updateStateForSysuiFlags(int sysuiFlags) {
mTaskbarLauncherStateController.updateStateForSysuiFlags(sysuiFlags);
}
@Override
@@ -714,6 +714,8 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
* setup wizard, or normal 3 button nav.
*/
private void updateButtonLayoutSpacing() {
boolean isThreeButtonNav = mContext.isThreeButtonNav();
DeviceProfile dp = mContext.getDeviceProfile();
Resources res = mContext.getResources();
boolean isInSetup = !mContext.isUserSetupComplete();
@@ -721,7 +723,9 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
boolean isInKidsMode = mContext.isNavBarKidsModeActive();
if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
boolean isThreeButtonNav = mContext.isThreeButtonNav();
if (!isThreeButtonNav) {
return;
}
NavButtonLayoutter navButtonLayoutter =
NavButtonLayoutFactory.Companion.getUiLayoutter(
@@ -803,7 +807,7 @@ public class NavbarButtonsViewController implements TaskbarControllers.LoggableT
mNavButtonContainer.requestLayout();
mHomeButton.setOnLongClickListener(null);
} else if (mContext.isThreeButtonNav()) {
} else if (isThreeButtonNav) {
final RotateDrawable rotateDrawable = new RotateDrawable();
rotateDrawable.setDrawable(mContext.getDrawable(R.drawable.ic_sysbar_back));
rotateDrawable.setFromDegrees(0f);
@@ -30,6 +30,10 @@ import androidx.core.content.ContextCompat;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
/**
* View to render a handle that changes color based on the background to ensure contrast. Used for
* the taskbar when stashed as well as the bubble bar when stashed.
*/
public class StashedHandleView extends View {
private static final long COLOR_CHANGE_DURATION = 120;
@@ -186,7 +186,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
// Inflate views.
int taskbarLayout = DisplayController.isTransientTaskbar(this)
boolean phoneMode = TaskbarManager.isPhoneMode(mDeviceProfile);
int taskbarLayout = DisplayController.isTransientTaskbar(this) && !phoneMode
? R.layout.transient_taskbar
: R.layout.taskbar;
mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(taskbarLayout, null, false);
@@ -237,7 +238,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
? new DesktopTaskbarRecentAppsController(this)
: TaskbarRecentAppsController.DEFAULT,
new TaskbarEduTooltipController(this),
new KeyboardQuickSwitchController());
new KeyboardQuickSwitchController(),
new TaskbarDividerPopupController(this));
}
public void init(@NonNull TaskbarSharedState sharedState) {
@@ -254,6 +256,12 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
sharedState.systemBarAttrsBehavior);
onNavButtonsDarkIntensityChanged(sharedState.navButtonsDarkIntensity);
if (FLAG_HIDE_NAVBAR_WINDOW) {
// W/ the flag not set this entire class gets re-created, which resets the value of
// mIsDestroyed. We re-use the class for small-screen, so we explicitly have to mark
// this class as non-destroyed
mIsDestroyed = false;
}
if (!mAddedWindow) {
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
@@ -334,7 +342,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
public WindowManager.LayoutParams createDefaultWindowLayoutParams(int type, String title) {
DeviceProfile deviceProfile = getDeviceProfile();
// Taskbar is on the logical bottom of the screen
boolean isVerticalBarLayout = TaskbarManager.isPhoneMode(deviceProfile) &&
boolean isVerticalBarLayout = TaskbarManager.isPhoneButtonNavMode(this) &&
deviceProfile.isLandscape;
int windowFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -592,7 +600,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
mControllers.voiceInteractionWindowController.setIsVoiceInteractionWindowVisible(
(systemUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0, fromInit);
mControllers.uiController.updateStateForSysuiFlags(systemUiStateFlags, fromInit);
mControllers.uiController.updateStateForSysuiFlags(systemUiStateFlags);
}
/**
@@ -1122,4 +1130,9 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
public int getTaskbarAllAppsTopPadding() {
return mControllers.taskbarAllAppsController.getTaskbarAllAppsTopPadding();
}
@VisibleForTesting
public int getTaskbarAllAppsScroll() {
return mControllers.taskbarAllAppsController.getTaskbarAllAppsScroll();
}
}
@@ -16,11 +16,13 @@
package com.android.launcher3.taskbar
import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import com.android.launcher3.DeviceProfile
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.mapRange
@@ -61,7 +63,7 @@ class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
private val invertedLeftCornerPath: Path = Path()
private val invertedRightCornerPath: Path = Path()
private val stashedHandleWidth =
private var stashedHandleWidth =
context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width)
private val stashedHandleHeight =
@@ -86,6 +88,13 @@ class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
setCornerRoundness(DEFAULT_ROUNDNESS)
}
fun updateStashedHandleWidth(dp: DeviceProfile, res: Resources) {
stashedHandleWidth = res.getDimensionPixelSize(
if (TaskbarManager.isPhoneMode(dp)) R.dimen.taskbar_stashed_small_screen
else R.dimen.taskbar_stashed_handle_width
)
}
/**
* Sets the roundness of the round corner above Taskbar. No effect on transient Taskkbar.
*
@@ -60,6 +60,7 @@ public class TaskbarControllers {
public final TaskbarOverlayController taskbarOverlayController;
public final TaskbarEduTooltipController taskbarEduTooltipController;
public final KeyboardQuickSwitchController keyboardQuickSwitchController;
public final TaskbarDividerPopupController taskbarPinningController;
@Nullable private LoggableTaskbarController[] mControllersToLog = null;
@Nullable private BackgroundRendererController[] mBackgroundRendererControllers = null;
@@ -105,7 +106,8 @@ public class TaskbarControllers {
TaskbarSpringOnStashController taskbarSpringOnStashController,
TaskbarRecentAppsController taskbarRecentAppsController,
TaskbarEduTooltipController taskbarEduTooltipController,
KeyboardQuickSwitchController keyboardQuickSwitchController) {
KeyboardQuickSwitchController keyboardQuickSwitchController,
TaskbarDividerPopupController taskbarPinningController) {
this.taskbarActivityContext = taskbarActivityContext;
this.taskbarDragController = taskbarDragController;
this.navButtonController = navButtonController;
@@ -130,6 +132,7 @@ public class TaskbarControllers {
this.taskbarRecentAppsController = taskbarRecentAppsController;
this.taskbarEduTooltipController = taskbarEduTooltipController;
this.keyboardQuickSwitchController = keyboardQuickSwitchController;
this.taskbarPinningController = taskbarPinningController;
}
/**
@@ -163,6 +166,7 @@ public class TaskbarControllers {
taskbarTranslationController.init(this);
taskbarEduTooltipController.init(this);
keyboardQuickSwitchController.init(this);
taskbarPinningController.init(this);
mControllersToLog = new LoggableTaskbarController[] {
taskbarDragController, navButtonController, navbarButtonsViewController,
@@ -171,7 +175,7 @@ public class TaskbarControllers {
stashedHandleViewController, taskbarStashController,
taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController,
voiceInteractionWindowController, taskbarTranslationController,
taskbarEduTooltipController, keyboardQuickSwitchController
taskbarEduTooltipController, keyboardQuickSwitchController, taskbarPinningController
};
mBackgroundRendererControllers = new BackgroundRendererController[] {
taskbarDragLayerController, taskbarScrimViewController,
@@ -0,0 +1,66 @@
/*
* 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.
*/
package com.android.launcher3.taskbar
import android.view.View
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
import com.android.launcher3.taskbar.TaskbarDividerPopupView.Companion.createAndPopulate
import java.io.PrintWriter
/** Controls taskbar pinning through a popup view. */
class TaskbarDividerPopupController(private val context: TaskbarActivityContext) :
TaskbarControllers.LoggableTaskbarController {
private lateinit var controllers: TaskbarControllers
private val launcherPrefs = LauncherPrefs.get(context)
fun init(taskbarControllers: TaskbarControllers) {
controllers = taskbarControllers
}
fun showPinningView(view: View) {
context.isTaskbarWindowFullscreen = true
view.post {
val popupView = createAndPopulate(view, context)
popupView.requestFocus()
popupView.onCloseCallback = {
context.onPopupVisibilityChanged(false)
if (launcherPrefs.get(TASKBAR_PINNING)) {
animateTransientToPersistentTaskBar()
} else {
animatePersistentToTransientTaskbar()
}
}
popupView.changePreference = {
launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
}
context.onPopupVisibilityChanged(true)
popupView.show()
}
}
// TODO(b/265436799): provide animation/transition from transient taskbar to persistent one
private fun animateTransientToPersistentTaskBar() {}
// TODO(b/265436799): provide animation/transition from persistent taskbar to transient one
private fun animatePersistentToTransientTaskbar() {}
override fun dumpLogs(prefix: String, pw: PrintWriter) {
pw.println(prefix + "TaskbarPinningController:")
}
}
@@ -0,0 +1,171 @@
/*
* 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.
*/
package com.android.launcher3.taskbar
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.widget.LinearLayout
import android.widget.Switch
import androidx.core.view.postDelayed
import com.android.launcher3.R
import com.android.launcher3.popup.ArrowPopup
import com.android.launcher3.popup.RoundedArrowDrawable
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.Themes
/** Popup view with arrow for taskbar pinning */
class TaskbarDividerPopupView<T : TaskbarActivityContext>
@JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : ArrowPopup<T>(context, attrs, defStyleAttr) {
companion object {
private const val TAG = "TaskbarDividerPopupView"
private const val DIVIDER_POPUP_CLOSING_DELAY = 500L
@JvmStatic
fun createAndPopulate(
view: View,
taskbarActivityContext: TaskbarActivityContext,
): TaskbarDividerPopupView<*> {
val taskMenuViewWithArrow =
taskbarActivityContext.layoutInflater.inflate(
R.layout.taskbar_divider_popup_menu,
taskbarActivityContext.dragLayer,
false
) as TaskbarDividerPopupView<*>
return taskMenuViewWithArrow.populateForView(view)
}
}
private lateinit var dividerView: View
private val menuWidth =
context.resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_width)
private val popupCornerRadius = Themes.getDialogCornerRadius(context)
private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
private val arrowHeight = resources.getDimension(R.dimen.popup_arrow_height)
private val arrowPointRadius = resources.getDimension(R.dimen.popup_arrow_corner_radius)
private var alwaysShowTaskbarOn = !DisplayController.isTransientTaskbar(context)
private var didPreferenceChange = false
/** Callback invoked when the pinning popup view is closing. */
var onCloseCallback: () -> Unit = {}
/**
* Callback invoked when the user preference changes in popup view. Preference change will be
* based upon current value stored in [LauncherPrefs] for `TASKBAR_PINNING`
*/
var changePreference: () -> Unit = {}
init {
// This synchronizes the arrow and menu to open at the same time
mOpenChildFadeStartDelay = mOpenFadeStartDelay
mOpenChildFadeDuration = mOpenFadeDuration
mCloseFadeStartDelay = mCloseChildFadeStartDelay
mCloseFadeDuration = mCloseChildFadeDuration
}
override fun isOfType(type: Int): Boolean = type and TYPE_TASKBAR_PINNING_POPUP != 0
override fun getTargetObjectLocation(outPos: Rect) {
popupContainer.getDescendantRectRelativeToSelf(dividerView, outPos)
}
@SuppressLint("UseSwitchCompatOrMaterialCode")
override fun onFinishInflate() {
super.onFinishInflate()
val taskbarSwitchOption = findViewById<LinearLayout>(R.id.taskbar_switch_option)
val alwaysShowTaskbarSwitch = findViewById<Switch>(R.id.taskbar_pinning_switch)
alwaysShowTaskbarSwitch.isChecked = alwaysShowTaskbarOn
taskbarSwitchOption.setOnClickListener {
alwaysShowTaskbarSwitch.isClickable = true
alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
onClickAlwaysShowTaskbarSwitchOption()
}
}
/** Orient object as usual and then center object horizontally. */
override fun orientAboutObject() {
super.orientAboutObject()
x = mTempRect.centerX() - menuWidth / 2f
}
override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
if (ev?.action == MotionEvent.ACTION_DOWN) {
if (!popupContainer.isEventOverView(this, ev)) {
close(true)
}
} else if (popupContainer.isEventOverView(dividerView, ev)) {
return true
}
return false
}
private fun populateForView(view: View): TaskbarDividerPopupView<*> {
dividerView = view
return this
}
override fun addArrow() {
super.addArrow()
// Change arrow location to the middle of popup.
mArrow.x = (dividerView.x + dividerView.width / 2) - (mArrowWidth / 2)
}
override fun updateArrowColor() {
if (!Gravity.isVertical(mGravity)) {
mArrow.background =
RoundedArrowDrawable(
arrowWidth,
arrowHeight,
arrowPointRadius,
popupCornerRadius,
measuredWidth.toFloat(),
measuredHeight.toFloat(),
(measuredWidth - arrowWidth) / 2, // arrowOffsetX
0f, // arrowOffsetY
false, // isPointingUp
true, // leftAligned
Themes.getAttrColor(context, R.attr.popupColorPrimary),
)
elevation = mElevation
mArrow.elevation = mElevation
}
}
override fun closeComplete() {
if (didPreferenceChange) {
onCloseCallback()
}
super.closeComplete()
}
private fun onClickAlwaysShowTaskbarSwitchOption() {
didPreferenceChange = true
changePreference()
// Allow switch animation to finish and then close the popup.
postDelayed(DIVIDER_POPUP_CLOSING_DELAY) { close(true) }
}
}
@@ -71,7 +71,7 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
mControllerCallbacks = callbacks;
mBackgroundRenderer.updateStashedHandleWidth(mActivity.getDeviceProfile(), getResources());
recreateControllers();
}
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.taskbar
import android.graphics.PorterDuff.Mode.SRC_ATOP
import android.graphics.PorterDuffColorFilter
import android.os.Bundle
import android.view.View
import android.view.View.GONE
@@ -27,14 +25,13 @@ import android.view.accessibility.AccessibilityNodeInfo
import androidx.annotation.IntDef
import androidx.annotation.LayoutRes
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieProperty.COLOR_FILTER
import com.airbnb.lottie.model.KeyPath
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
import com.android.quickstep.util.LottieAnimationColorUtils
import java.io.PrintWriter
/** First EDU step for swiping up to show transient Taskbar. */
@@ -239,11 +236,5 @@ private fun LottieAnimationView.supportLightTheme() {
return
}
addLottieOnCompositionLoadedListener {
DARK_TO_LIGHT_COLORS.forEach { (key, color) ->
addValueCallback(KeyPath("**", key, "**"), COLOR_FILTER) {
PorterDuffColorFilter(context.getColor(color), SRC_ATOP)
}
}
}
LottieAnimationColorUtils.updateColors(this, DARK_TO_LIGHT_COLORS, context.theme)
}
@@ -21,6 +21,7 @@ import android.os.Binder
import android.os.IBinder
import android.view.InsetsFrameProvider
import android.view.InsetsFrameProvider.SOURCE_DISPLAY
import android.view.InsetsSource.FLAG_SUPPRESS_SCRIM
import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME
import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION
@@ -86,7 +87,8 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
if (context.isGestureNav) {
windowLayoutParams.providedInsets =
arrayOf(
InsetsFrameProvider(insetsOwner, 0, navigationBars()),
InsetsFrameProvider(insetsOwner, 0, navigationBars())
.setFlags(FLAG_SUPPRESS_SCRIM, FLAG_SUPPRESS_SCRIM),
InsetsFrameProvider(insetsOwner, 0, tappableElement()),
InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
@@ -210,7 +210,9 @@ public class TaskbarLauncherStateController {
}
};
public void init(TaskbarControllers controllers, QuickstepLauncher launcher) {
/** Initializes the controller instance, and applies the initial state immediately. */
public void init(TaskbarControllers controllers, QuickstepLauncher launcher,
int sysuiStateFlags) {
mCanSyncViews = false;
mControllers = controllers;
@@ -229,6 +231,8 @@ public class TaskbarLauncherStateController {
mLauncher.getStateManager().addStateListener(mStateListener);
mLauncherState = launcher.getStateManager().getState();
updateStateForSysuiFlags(sysuiStateFlags, /*applyState*/ false);
applyState(0);
mCanSyncViews = true;
@@ -298,7 +302,11 @@ public class TaskbarLauncherStateController {
}
/** SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values. */
public void updateStateForSysuiFlags(int systemUiStateFlags, boolean skipAnim) {
public void updateStateForSysuiFlags(int systemUiStateFlags) {
updateStateForSysuiFlags(systemUiStateFlags, /* applyState */ true);
}
private void updateStateForSysuiFlags(int systemUiStateFlags, boolean applyState) {
final boolean prevIsAwake = hasAnyFlag(FLAG_AWAKE);
final boolean currIsAwake = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_AWAKE);
@@ -322,9 +330,7 @@ public class TaskbarLauncherStateController {
|| (systemUiStateFlags & SYSUI_STATE_WAKEFULNESS_MASK) != WAKEFULNESS_AWAKE;
updateStateForFlag(FLAG_TASKBAR_HIDDEN, isTaskbarHidden);
if (skipAnim) {
applyState(0);
} else {
if (applyState) {
applyState();
}
}
@@ -361,10 +367,6 @@ public class TaskbarLauncherStateController {
applyState(duration, true);
}
public Animator applyState(boolean start) {
return applyState(mControllers.taskbarStashController.getStashDuration(), start);
}
public Animator applyState(long duration, boolean start) {
if (mControllers.taskbarActivityContext.isDestroyed()) {
return null;
@@ -420,7 +422,7 @@ public class TaskbarLauncherStateController {
}
}
if (hasAnyFlag(changedFlags, FLAGS_LAUNCHER_ACTIVE | FLAG_AWAKE)) {
if (hasAnyFlag(changedFlags, FLAGS_LAUNCHER_ACTIVE)) {
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -642,14 +644,8 @@ public class TaskbarLauncherStateController {
long resetDuration = mControllers.taskbarStashController.isInApp()
? duration
: duration / 2;
boolean shouldReset =
mControllers.taskbarTranslationController.shouldResetBackToZero(resetDuration);
boolean goingToLauncher = isAnimatingToLauncher();
boolean isNormalState = mLauncherState == LauncherState.NORMAL;
// Taskbar should always reset when animating to launcher in normal state to ensure there
// is no jump during the handoff to the hotseat.
if ((goingToLauncher && isNormalState)
|| (shouldReset && (goingToLauncher || isNormalState))) {
if (!mControllers.taskbarTranslationController.willAnimateToZeroBefore(resetDuration)
&& (isAnimatingToLauncher() || mLauncherState == LauncherState.NORMAL)) {
animatorSet.play(mControllers.taskbarTranslationController
.createAnimToResetTranslation(resetDuration));
}
@@ -20,6 +20,8 @@ import static android.content.pm.PackageManager.FEATURE_PC;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
@@ -32,6 +34,7 @@ import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
@@ -48,6 +51,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
@@ -133,6 +137,13 @@ public class TaskbarManager {
private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver =
new SimpleBroadcastReceiver(this::showTaskbarFromBroadcast);
private final SharedPreferences.OnSharedPreferenceChangeListener
mTaskbarPinningPreferenceChangeListener = (sharedPreferences, key) -> {
if (TASKBAR_PINNING_KEY.equals(key)) {
recreateTaskbar();
}
};
@SuppressLint("WrongConstant")
public TaskbarManager(TouchInteractionService service) {
mDisplayController = DisplayController.INSTANCE.get(service);
@@ -168,9 +179,14 @@ public class TaskbarManager {
DeviceProfile oldDp = mTaskbarActivityContext.getDeviceProfile();
boolean isOrientationChange =
(configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0;
int newOrientation = newConfig.windowConfiguration.getRotation();
int oldOrientation = mOldConfig.windowConfiguration.getRotation();
int oldWidth = isOrientationChange ? oldDp.heightPx : oldDp.widthPx;
int oldHeight = isOrientationChange ? oldDp.widthPx : oldDp.heightPx;
if (dp.widthPx == oldWidth && dp.heightPx == oldHeight) {
if ((dp.widthPx == oldWidth && dp.heightPx == oldHeight)
|| (newOrientation == oldOrientation)) {
configDiffForRecreate &= ~ActivityInfo.CONFIG_SCREEN_SIZE;
}
}
@@ -244,6 +260,8 @@ public class TaskbarManager {
private void destroyExistingTaskbar() {
debugWhyTaskbarNotDestroyed("destroyExistingTaskbar: " + mTaskbarActivityContext);
if (mTaskbarActivityContext != null) {
LauncherPrefs.get(mContext).removeListener(mTaskbarPinningPreferenceChangeListener,
TASKBAR_PINNING);
mTaskbarActivityContext.onDestroy();
if (!FLAG_HIDE_NAVBAR_WINDOW) {
mTaskbarActivityContext = null;
@@ -380,6 +398,10 @@ public class TaskbarManager {
mTaskbarActivityContext.setUIController(
createTaskbarUIControllerForActivity(mActivity));
}
// We to wait until user unlocks the device to attach listener.
LauncherPrefs.get(mContext).addListener(mTaskbarPinningPreferenceChangeListener,
TASKBAR_PINNING);
}
public void onSystemUiFlagsChanged(int systemUiStateFlags) {
@@ -137,6 +137,7 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba
if (folder != null) {
folder.iterateOverItems(op);
}
mControllers.taskbarAllAppsController.updateNotificationDots(updatedDots);
}
/**
@@ -18,11 +18,12 @@ package com.android.launcher3.taskbar;
import static android.view.HapticFeedbackConstants.LONG_PRESS;
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.FORCE_PERSISTENT_TASKBAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
@@ -324,7 +325,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
// that taskbar unstashes when going to 3 button mode (supportsVisualStashing() false).
boolean isManuallyStashedInApp = supportsVisualStashing()
&& !isTransientTaskbar
&& !FORCE_PERSISTENT_TASKBAR.get()
&& !ENABLE_TASKBAR_PINNING.get()
&& mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible;
updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
@@ -353,7 +354,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
* Returns whether the user can manually stash the taskbar based on the current device state.
*/
protected boolean supportsManualStashing() {
if (FORCE_PERSISTENT_TASKBAR.get()) {
if (ENABLE_TASKBAR_PINNING.get() && mPrefs.getBoolean(TASKBAR_PINNING_KEY, false)) {
return false;
}
return supportsVisualStashing()
@@ -54,7 +54,6 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
private boolean mHasSprungOnceThisGesture;
private @Nullable ValueAnimator mSpringBounce;
private boolean mGestureEnded;
private boolean mGestureInProgress;
private boolean mAnimationToHomeRunning;
private final boolean mIsTransientTaskbar;
@@ -124,7 +123,6 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
private void reset() {
mGestureEnded = false;
mGestureInProgress = false;
mHasSprungOnceThisGesture = false;
}
@@ -136,24 +134,18 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
}
/**
* Returns {@code true} if we should reset the animation back to zero.
*
* Returns {@code false} if there is a gesture in progress, or if we are already animating
* to 0 within the specified duration.
* Returns true if we will animate to zero before the input duration.
*/
public boolean shouldResetBackToZero(long duration) {
if (mGestureInProgress) {
return false;
}
public boolean willAnimateToZeroBefore(long duration) {
if (mSpringBounce != null && mSpringBounce.isRunning()) {
long springDuration = mSpringBounce.getDuration();
long current = mSpringBounce.getCurrentPlayTime();
return (springDuration - current >= duration);
return (springDuration - current < duration);
}
if (mTranslationYForSwipe.isAnimatingToValue(0)) {
return mTranslationYForSwipe.getRemainingTime() >= duration;
return mTranslationYForSwipe.getRemainingTime() < duration;
}
return true;
return false;
}
/**
@@ -196,7 +188,6 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
mAnimationToHomeRunning = false;
cancelSpringIfExists();
reset();
mGestureInProgress = true;
}
/**
* Called when there is movement to move the taskbar.
@@ -220,7 +211,6 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
mGestureEnded = true;
startSpring();
}
mGestureInProgress = false;
}
}
@@ -232,7 +222,6 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
pw.println(prefix + "\tmHasSprungOnceThisGesture=" + mHasSprungOnceThisGesture);
pw.println(prefix + "\tmAnimationToHomeRunning=" + mAnimationToHomeRunning);
pw.println(prefix + "\tmGestureEnded=" + mGestureEnded);
pw.println(prefix + "\tmGestureInProgress=" + mGestureInProgress);
pw.println(prefix + "\tmSpringBounce is running=" + (mSpringBounce != null
&& mSpringBounce.isRunning()));
}
@@ -127,7 +127,7 @@ public class TaskbarUIController {
/**
* SysUI flags updated, see QuickStepContract.SYSUI_STATE_* values.
*/
public void updateStateForSysuiFlags(int sysuiFlags, boolean skipAnim){
public void updateStateForSysuiFlags(int sysuiFlags) {
}
/**
@@ -114,7 +114,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
mActivityContext = ActivityContext.lookupContext(context);
mIconLayoutBounds = mActivityContext.getTransientTaskbarBounds();
Resources resources = getResources();
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivityContext);
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivityContext)
&& !TaskbarManager.isPhoneMode(mActivityContext.getDeviceProfile());
mIsRtl = Utilities.isRtl(resources);
mTransientTaskbarMinWidth = mContext.getResources().getDimension(
R.dimen.transient_taskbar_min_width);
@@ -217,7 +218,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
mAllAppsButton.setOnClickListener(mControllerCallbacks.getAllAppsButtonClickListener());
}
if (mTaskbarDivider != null) {
//TODO(b/265434705): set long press listener
mTaskbarDivider.setOnLongClickListener(
mControllerCallbacks.getTaskbarDividerLongClickListener());
}
}
@@ -641,6 +641,13 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
};
}
public View.OnLongClickListener getTaskbarDividerLongClickListener() {
return v -> {
mControllers.taskbarPinningController.showPinningView(v);
return true;
};
}
public View.OnLongClickListener getIconOnLongClickListener() {
return mControllers.taskbarDragController::startDragOnLongClick;
}
@@ -24,8 +24,10 @@ import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.launcher3.util.PackageUserKey;
import java.util.List;
import java.util.function.Predicate;
/**
* Handles the all apps overlay window initialization, updates, and its data.
@@ -91,6 +93,13 @@ public final class TaskbarAllAppsController {
}
}
/** Updates the current notification dots. */
public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
if (mAppsView != null) {
mAppsView.getAppsStore().updateNotificationDots(updatedDots);
}
}
/** Opens the {@link TaskbarAllAppsContainerView} in a new window. */
public void show() {
show(true);
@@ -135,10 +144,15 @@ public final class TaskbarAllAppsController {
overlayContext.getDragController().setDisallowLongClick(mDisallowLongClick);
}
@VisibleForTesting
public int getTaskbarAllAppsTopPadding() {
// Allow null-pointer since this should only be null if the apps view is not showing.
return mAppsView.getActiveRecyclerView().getClipBounds().top;
}
@VisibleForTesting
public int getTaskbarAllAppsScroll() {
// Allow null-pointer since this should only be null if the apps view is not showing.
return mAppsView.getActiveRecyclerView().computeVerticalScrollOffset();
}
}
@@ -71,13 +71,6 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
} else {
mTranslationShift = TRANSLATION_SHIFT_OPENED;
}
if (FeatureFlags.ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION.get()) {
mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(mViewOutlineProvider);
mAppsView.getAppsRecyclerViewContainer().setClipToOutline(true);
findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, this);
}
}
/** The apps container inside this view. */
@@ -88,9 +81,6 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
@Override
protected void handleClose(boolean animate) {
handleClose(animate, mAllAppsCallbacks.getCloseDuration());
if (FeatureFlags.ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION.get()) {
findOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(this);
}
}
@Override
@@ -121,12 +111,29 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mActivityContext.addOnDeviceProfileChangeListener(this);
if (FeatureFlags.ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION.get()) {
mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(mViewOutlineProvider);
mAppsView.getAppsRecyclerViewContainer().setClipToOutline(true);
OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
if (dispatcher != null) {
dispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT, this);
}
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mActivityContext.removeOnDeviceProfileChangeListener(this);
if (FeatureFlags.ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION.get()) {
mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(null);
mAppsView.getAppsRecyclerViewContainer().setClipToOutline(false);
OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
if (dispatcher != null) {
dispatcher.unregisterOnBackInvokedCallback(this);
}
}
}
@Override
@@ -0,0 +1,134 @@
/*
* 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.
*/
package com.android.launcher3.taskbar.bubbles
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.graphics.drawable.ShapeDrawable
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.mapToRange
import com.android.launcher3.anim.Interpolators
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.wm.shell.common.TriangleShape
/** Drawable for the background of the bubble bar. */
class BubbleBarBackground(context: TaskbarActivityContext, private val backgroundHeight: Float) :
Drawable() {
private val DARK_THEME_SHADOW_ALPHA = 51f
private val LIGHT_THEME_SHADOW_ALPHA = 25f
private val paint: Paint = Paint()
private val pointerSize: Float
private val shadowAlpha: Float
private var shadowBlur = 0f
private var keyShadowDistance = 0f
private var arrowPositionX: Float = 0f
private var showingArrow: Boolean = false
private var arrowDrawable: ShapeDrawable
init {
paint.color = context.getColor(R.color.taskbar_background)
paint.flags = Paint.ANTI_ALIAS_FLAG
paint.style = Paint.Style.FILL
val res = context.resources
shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
pointerSize = res.getDimension(R.dimen.bubblebar_pointer_size)
shadowAlpha =
if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA
else LIGHT_THEME_SHADOW_ALPHA
arrowDrawable =
ShapeDrawable(TriangleShape.create(pointerSize, pointerSize, /* pointUp= */ true))
arrowDrawable.setBounds(0, 0, pointerSize.toInt(), pointerSize.toInt())
arrowDrawable.paint.flags = Paint.ANTI_ALIAS_FLAG
arrowDrawable.paint.style = Paint.Style.FILL
arrowDrawable.paint.color = context.getColor(R.color.taskbar_background)
}
fun showArrow(show: Boolean) {
showingArrow = show
}
fun setArrowPosition(x: Float) {
arrowPositionX = x
}
/** Draws the background with the given paint and height, on the provided canvas. */
override fun draw(canvas: Canvas) {
canvas.save()
// TODO (b/277359345): Should animate the alpha similar to taskbar (see TaskbarDragLayer)
// Draw shadows.
val newShadowAlpha =
mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
paint.setShadowLayer(
shadowBlur,
0f,
keyShadowDistance,
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
)
arrowDrawable.paint.setShadowLayer(
shadowBlur,
0f,
keyShadowDistance,
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
)
// Draw background.
val radius = backgroundHeight / 2f
canvas.drawRoundRect(
0f,
0f,
canvas.width.toFloat(),
canvas.height.toFloat(),
radius,
radius,
paint
)
if (showingArrow) {
// Draw arrow.
val transX = arrowPositionX - pointerSize / 2f
canvas.translate(transX, -pointerSize)
arrowDrawable.draw(canvas)
}
canvas.restore()
}
override fun getOpacity(): Int {
return paint.alpha
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
}
override fun setColorFilter(colorFilter: ColorFilter?) {
paint.colorFilter = colorFilter
}
}
@@ -0,0 +1,36 @@
/*
* 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.
*/
package com.android.launcher3.taskbar.bubbles
import android.graphics.Bitmap
import android.graphics.Path
import com.android.wm.shell.common.bubbles.BubbleInfo
/** Contains state info about a bubble in the bubble bar as well as presentation information. */
data class BubbleBarBubble(
val info: BubbleInfo,
val view: BubbleView,
val badge: Bitmap,
val icon: Bitmap,
val dotColor: Int,
val dotPath: Path,
val appName: String
) {
fun getKey(): String {
return info.key
}
}
@@ -0,0 +1,435 @@
/*
* 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.
*/
package com.android.launcher3.taskbar.bubbles;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_PERSONS_DATA;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static com.android.launcher3.icons.FastBitmapDrawable.WHITE_SCRIM_ALPHA;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import android.annotation.BinderThread;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
import android.util.PathParser;
import android.view.LayoutInflater;
import com.android.internal.graphics.ColorUtils;
import com.android.launcher3.R;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.quickstep.SystemUiProxy;
import com.android.wm.shell.bubbles.IBubblesListener;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.common.bubbles.BubbleInfo;
import com.android.wm.shell.common.bubbles.RemovedBubble;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* This registers a listener with SysUIProxy to get information about changes to the bubble
* stack state from WMShell (SysUI). The controller is also responsible for loading the necessary
* information to render each of the bubbles & dispatches changes to
* {@link BubbleBarViewController} which will then update {@link BubbleBarView} as needed.
*
* For details around the behavior of the bubble bar, see {@link BubbleBarView}.
*/
public class BubbleBarController extends IBubblesListener.Stub {
private static final String TAG = BubbleBarController.class.getSimpleName();
private static final boolean DEBUG = false;
// Whether bubbles are showing in the bubble bar from launcher
public static final boolean BUBBLE_BAR_ENABLED =
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
private static final int MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
| SYSUI_STATE_IME_SHOWING
| SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED
| SYSUI_STATE_IME_SWITCHER_SHOWING;
private static final int MASK_HIDE_HANDLE_VIEW = SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
private static final int MASK_SYSUI_LOCKED = SYSUI_STATE_BOUNCER_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
private final Context mContext;
private final BubbleBarView mBarView;
private final ArrayMap<String, BubbleBarBubble> mBubbles = new ArrayMap<>();
private static final Executor BUBBLE_STATE_EXECUTOR = Executors.newSingleThreadExecutor(
new SimpleThreadFactory("BubbleStateUpdates-", THREAD_PRIORITY_BACKGROUND));
private final Executor mMainExecutor;
private final LauncherApps mLauncherApps;
private final BubbleIconFactory mIconFactory;
private BubbleBarBubble mSelectedBubble;
private BubbleBarViewController mBubbleBarViewController;
private BubbleStashController mBubbleStashController;
private BubbleStashedHandleViewController mBubbleStashedHandleViewController;
/**
* Similar to {@link BubbleBarUpdate} but rather than {@link BubbleInfo}s it uses
* {@link BubbleBarBubble}s so that it can be used to update the views.
*/
private static class BubbleBarViewUpdate {
boolean expandedChanged;
boolean expanded;
String selectedBubbleKey;
String suppressedBubbleKey;
String unsuppressedBubbleKey;
List<RemovedBubble> removedBubbles;
List<String> bubbleKeysInOrder;
// These need to be loaded in the background
BubbleBarBubble addedBubble;
BubbleBarBubble updatedBubble;
List<BubbleBarBubble> currentBubbles;
BubbleBarViewUpdate(BubbleBarUpdate update) {
expandedChanged = update.expandedChanged;
expanded = update.expanded;
selectedBubbleKey = update.selectedBubbleKey;
suppressedBubbleKey = update.suppressedBubbleKey;
unsuppressedBubbleKey = update.unsupressedBubbleKey;
removedBubbles = update.removedBubbles;
bubbleKeysInOrder = update.bubbleKeysInOrder;
}
}
public BubbleBarController(Context context, BubbleBarView bubbleView) {
mContext = context;
mBarView = bubbleView; // Need the view for inflating bubble views.
// TODO: register the listener with SysUiProxu
mMainExecutor = MAIN_EXECUTOR;
mLauncherApps = context.getSystemService(LauncherApps.class);
mIconFactory = new BubbleIconFactory(context,
context.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size),
context.getResources().getDimensionPixelSize(R.dimen.bubblebar_badge_size),
context.getResources().getColor(R.color.important_conversation),
context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
}
public void onDestroy() {
// TODO: unregister the listener with SysUiProxy
}
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
bubbleControllers.runAfterInit(() -> {
mBubbleBarViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
mBubbleStashedHandleViewController.setHiddenForBubbles(!BUBBLE_BAR_ENABLED);
});
}
/**
* Updates the bubble bar, handle bar, and stash controllers based on sysui state flags.
*/
public void updateStateForSysuiFlags(int flags) {
boolean hideBubbleBar = (flags & MASK_HIDE_BUBBLE_BAR) != 0;
mBubbleBarViewController.setHiddenForSysui(hideBubbleBar);
boolean hideHandleView = (flags & MASK_HIDE_HANDLE_VIEW) != 0;
mBubbleStashedHandleViewController.setHiddenForSysui(hideHandleView);
boolean sysuiLocked = (flags & MASK_SYSUI_LOCKED) != 0;
mBubbleStashController.onSysuiLockedStateChange(sysuiLocked);
}
//
// Bubble data changes
//
@BinderThread
@Override
public void onBubbleStateChange(Bundle bundle) {
bundle.setClassLoader(BubbleBarUpdate.class.getClassLoader());
BubbleBarUpdate update = bundle.getParcelable("update", BubbleBarUpdate.class);
BubbleBarViewUpdate viewUpdate = new BubbleBarViewUpdate(update);
if (update.addedBubble != null
|| update.updatedBubble != null
|| !update.currentBubbleList.isEmpty()) {
// We have bubbles to load
BUBBLE_STATE_EXECUTOR.execute(() -> {
if (update.addedBubble != null) {
viewUpdate.addedBubble = populateBubble(update.addedBubble, mContext, mBarView);
}
if (update.updatedBubble != null) {
viewUpdate.updatedBubble =
populateBubble(update.updatedBubble, mContext, mBarView);
}
if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) {
List<BubbleBarBubble> currentBubbles = new ArrayList<>();
for (int i = 0; i < update.currentBubbleList.size(); i++) {
BubbleBarBubble b =
populateBubble(update.currentBubbleList.get(i), mContext, mBarView);
currentBubbles.add(b);
}
viewUpdate.currentBubbles = currentBubbles;
}
mMainExecutor.execute(() -> applyViewChanges(viewUpdate));
});
} else {
// No bubbles to load, immediately apply the changes.
BUBBLE_STATE_EXECUTOR.execute(
() -> mMainExecutor.execute(() -> applyViewChanges(viewUpdate)));
}
}
private void applyViewChanges(BubbleBarViewUpdate update) {
final boolean isCollapsed = (update.expandedChanged && !update.expanded)
|| (!update.expandedChanged && !mBubbleBarViewController.isExpanded());
BubbleBarBubble bubbleToSelect = null;
if (!update.removedBubbles.isEmpty()) {
for (int i = 0; i < update.removedBubbles.size(); i++) {
RemovedBubble removedBubble = update.removedBubbles.get(i);
BubbleBarBubble bubble = mBubbles.remove(removedBubble.getKey());
if (bubble != null) {
mBubbleBarViewController.removeBubble(bubble);
} else {
Log.w(TAG, "trying to remove bubble that doesn't exist: "
+ removedBubble.getKey());
}
}
}
if (update.addedBubble != null) {
mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
mBubbleBarViewController.addBubble(update.addedBubble);
if (isCollapsed) {
// If we're collapsed, the most recently added bubble will be selected.
bubbleToSelect = update.addedBubble;
}
}
if (update.currentBubbles != null && !update.currentBubbles.isEmpty()) {
// Iterate in reverse because new bubbles are added in front and the list is in order.
for (int i = update.currentBubbles.size() - 1; i >= 0; i--) {
BubbleBarBubble bubble = update.currentBubbles.get(i);
if (bubble != null) {
mBubbles.put(bubble.getKey(), bubble);
mBubbleBarViewController.addBubble(bubble);
if (isCollapsed) {
// If we're collapsed, the most recently added bubble will be selected.
bubbleToSelect = bubble;
}
} else {
Log.w(TAG, "trying to add bubble but null after loading! "
+ update.addedBubble.getKey());
}
}
}
// Adds and removals have happened, update visibility before any other visual changes.
mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
mBubbleStashedHandleViewController.setHiddenForBubbles(mBubbles.isEmpty());
if (update.updatedBubble != null) {
// TODO: (b/269670235) handle updates:
// (1) if content / icons change -- requires reload & add back in place
// (2) if showing update dot changes -- tell the view to hide / show the dot
}
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
// Create the new list
List<BubbleBarBubble> newOrder = update.bubbleKeysInOrder.stream()
.map(mBubbles::get).filter(Objects::nonNull).toList();
if (!newOrder.isEmpty()) {
mBubbleBarViewController.reorderBubbles(newOrder);
}
}
if (update.suppressedBubbleKey != null) {
// TODO: (b/273316505) handle suppression
}
if (update.unsuppressedBubbleKey != null) {
// TODO: (b/273316505) handle suppression
}
if (update.selectedBubbleKey != null) {
if (mSelectedBubble != null
&& !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
BubbleBarBubble newlySelected = mBubbles.get(update.selectedBubbleKey);
if (newlySelected != null) {
bubbleToSelect = newlySelected;
} else {
Log.w(TAG, "trying to select bubble that doesn't exist:"
+ update.selectedBubbleKey);
}
}
}
if (bubbleToSelect != null) {
setSelectedBubble(bubbleToSelect);
}
if (update.expandedChanged) {
if (update.expanded != mBubbleBarViewController.isExpanded()) {
mBubbleBarViewController.setExpandedFromSysui(update.expanded);
} else {
Log.w(TAG, "expansion was changed but is the same");
}
}
}
/**
* Sets the bubble that should be selected. This notifies the views, it does not notify
* WMShell that the selection has changed, that should go through
* {@link SystemUiProxy#showBubble}.
*/
public void setSelectedBubble(BubbleBarBubble b) {
if (!Objects.equals(b, mSelectedBubble)) {
if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey());
mSelectedBubble = b;
mBubbleBarViewController.updateSelectedBubble(mSelectedBubble);
}
}
/**
* Returns the selected bubble or null if no bubble is selected.
*/
@Nullable
public String getSelectedBubbleKey() {
if (mSelectedBubble != null) {
return mSelectedBubble.getKey();
}
return null;
}
//
// Loading data for the bubbles
//
@Nullable
private BubbleBarBubble populateBubble(BubbleInfo b, Context context, BubbleBarView bbv) {
String appName;
Bitmap badgeBitmap;
Bitmap bubbleBitmap;
Path dotPath;
int dotColor;
boolean isImportantConvo = false; // TODO: (b/269671451) needs to be added to BubbleInfo
ShortcutRequest.QueryResult result = new ShortcutRequest(context,
new UserHandle(b.getUserId()))
.forPackage(b.getPackageName(), b.getShortcutId())
.query(FLAG_MATCH_DYNAMIC
| FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
| FLAG_MATCH_CACHED
| FLAG_GET_PERSONS_DATA);
ShortcutInfo shortcutInfo = result.size() > 0 ? result.get(0) : null;
if (shortcutInfo == null) {
Log.w(TAG, "No shortcutInfo found for bubble: " + b.getKey()
+ " with shortcutId: " + b.getShortcutId());
}
ApplicationInfo appInfo;
try {
appInfo = mLauncherApps.getApplicationInfo(
b.getPackageName(),
0,
new UserHandle(b.getUserId()));
} catch (PackageManager.NameNotFoundException e) {
// If we can't find package... don't think we should show the bubble.
Log.w(TAG, "Unable to find packageName: " + b.getPackageName());
return null;
}
if (appInfo == null) {
Log.w(TAG, "Unable to find appInfo: " + b.getPackageName());
return null;
}
PackageManager pm = context.getPackageManager();
appName = String.valueOf(appInfo.loadLabel(pm));
Drawable appIcon = appInfo.loadUnbadgedIcon(pm);
Drawable badgedIcon = pm.getUserBadgedIcon(appIcon, new UserHandle(b.getUserId()));
// Badged bubble image
Drawable bubbleDrawable = mIconFactory.getBubbleDrawable(context, shortcutInfo,
b.getIcon());
if (bubbleDrawable == null) {
// Default to app icon
bubbleDrawable = appIcon;
}
BitmapInfo badgeBitmapInfo = mIconFactory.getBadgeBitmap(badgedIcon, isImportantConvo);
badgeBitmap = badgeBitmapInfo.icon;
float[] bubbleBitmapScale = new float[1];
bubbleBitmap = mIconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale);
// Dot color & placement
Path iconPath = PathParser.createPathFromPathData(
context.getResources().getString(
com.android.internal.R.string.config_icon_mask));
Matrix matrix = new Matrix();
float scale = bubbleBitmapScale[0];
float radius = BubbleView.DEFAULT_PATH_SIZE / 2f;
matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
radius /* pivot y */);
iconPath.transform(matrix);
dotPath = iconPath;
dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
Color.WHITE, WHITE_SCRIM_ALPHA);
LayoutInflater inflater = LayoutInflater.from(context);
BubbleView bubbleView = (BubbleView) inflater.inflate(
R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
bubbleView.setBubble(bubble);
return bubble;
}
}
@@ -0,0 +1,304 @@
/*
* 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.
*/
package com.android.launcher3.taskbar.bubbles;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.android.launcher3.R;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.views.ActivityContext;
import java.util.List;
/**
* The view that holds all the bubble views. Modifying this view should happen through
* {@link BubbleBarViewController}. Updates to the bubbles themselves (adds, removes, updates,
* selection) should happen through {@link BubbleBarController} which is the source of truth
* for state information about the bubbles.
* <p>
* The bubble bar has a couple of visual states:
* - stashed as a handle
* - unstashed but collapsed, in this state the bar is showing but the bubbles are stacked within it
* - unstashed and expanded, in this state the bar is showing and the bubbles are shown in a row
* with one of the bubbles being selected. Additionally, WMShell will display the expanded bubble
* view above the bar.
* <p>
* The bubble bar has some behavior related to taskbar:
* - When taskbar is unstashed, bubble bar will also become unstashed (but in its "collapsed"
* state)
* - When taskbar is stashed, bubble bar will also become stashed (unless bubble bar is in its
* "expanded" state)
* - When bubble bar is in its "expanded" state, taskbar becomes stashed
* <p>
* If there are no bubbles, the bubble bar and bubble stashed handle are not shown. Additionally
* the bubble bar and stashed handle are not shown on lockscreen.
* <p>
* When taskbar is in persistent or 3 button nav mode, the bubble bar is not available, and instead
* the bubbles are shown fully by WMShell in their floating mode.
*/
public class BubbleBarView extends FrameLayout {
private static final String TAG = BubbleBarView.class.getSimpleName();
// TODO: (b/273594744) calculate the amount of space we have and base the max on that
// if it's smaller than 5.
private static final int MAX_BUBBLES = 5;
private final TaskbarActivityContext mActivityContext;
private final BubbleBarBackground mBubbleBarBackground;
// The current bounds of all the bubble bar.
private final Rect mBubbleBarBounds = new Rect();
// The amount the bubbles overlap when they are stacked in the bubble bar
private final float mIconOverlapAmount;
// The spacing between the bubbles when they are expanded in the bubble bar
private final float mIconSpacing;
// The size of a bubble in the bar
private final float mIconSize;
// The elevation of the bubbles within the bar
private final float mBubbleElevation;
// Whether the bar is expanded (i.e. the bubble activity is being displayed).
private boolean mIsBarExpanded = false;
// The currently selected bubble view.
private BubbleView mSelectedBubbleView;
// The click listener when the bubble bar is collapsed.
private View.OnClickListener mOnClickListener;
private final Rect mTempRect = new Rect();
// We don't reorder the bubbles when they are expanded as it could be jarring for the user
// this runnable will be populated with any reordering of the bubbles that should be applied
// once they are collapsed.
@Nullable
private Runnable mReorderRunnable;
public BubbleBarView(Context context) {
this(context, null);
}
public BubbleBarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mActivityContext = ActivityContext.lookupContext(context);
mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
mIconSpacing = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
mIconSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
mBubbleElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_elevation);
setClipToPadding(false);
mBubbleBarBackground = new BubbleBarBackground(mActivityContext,
getResources().getDimensionPixelSize(R.dimen.bubblebar_size));
setBackgroundDrawable(mBubbleBarBackground);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mBubbleBarBounds.left = left;
mBubbleBarBounds.top = top;
mBubbleBarBounds.right = right;
mBubbleBarBounds.bottom = bottom;
// The bubble bar handle is aligned to the bottom edge of the screen so scale towards that.
setPivotX(getWidth());
setPivotY(getHeight());
// Position the views
updateChildrenRenderNodeProperties();
}
/**
* Returns the bounds of the bubble bar.
*/
public Rect getBubbleBarBounds() {
return mBubbleBarBounds;
}
// TODO: (b/273592694) animate it
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (getChildCount() + 1 > MAX_BUBBLES) {
removeViewInLayout(getChildAt(getChildCount() - 1));
}
super.addView(child, index, params);
}
/**
* Updates the z order, positions, and badge visibility of the bubble views in the bar based
* on the expanded state.
*/
// TODO: (b/273592694) animate it
private void updateChildrenRenderNodeProperties() {
int bubbleCount = getChildCount();
final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
for (int i = 0; i < bubbleCount; i++) {
BubbleView bv = (BubbleView) getChildAt(i);
bv.setTranslationY(ty);
if (mIsBarExpanded) {
final float tx = i * (mIconSize + mIconSpacing);
bv.setTranslationX(tx);
bv.setZ(0);
bv.showBadge();
} else {
bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
bv.setTranslationX(i * mIconOverlapAmount);
if (i > 0) {
bv.hideBadge();
} else {
bv.showBadge();
}
}
}
}
/**
* Reorders the views to match the provided list.
*/
public void reorder(List<BubbleView> viewOrder) {
if (isExpanded()) {
mReorderRunnable = () -> doReorder(viewOrder);
} else {
doReorder(viewOrder);
}
}
// TODO: (b/273592694) animate it
private void doReorder(List<BubbleView> viewOrder) {
if (!isExpanded()) {
for (int i = 0; i < viewOrder.size(); i++) {
View child = viewOrder.get(i);
if (child != null) {
removeViewInLayout(child);
addViewInLayout(child, i, child.getLayoutParams());
}
}
updateChildrenRenderNodeProperties();
}
}
/**
* Sets which bubble view should be shown as selected.
*/
// TODO: (b/273592694) animate it
public void setSelectedBubble(BubbleView view) {
mSelectedBubbleView = view;
updateArrowForSelected();
invalidate();
}
private void updateArrowForSelected() {
if (mSelectedBubbleView == null) {
Log.w(TAG, "trying to update selection arrow without a selected view!");
return;
}
final int index = indexOfChild(mSelectedBubbleView);
// Find the center of the bubble when it's expanded, set the arrow position to it.
final float tx = getPaddingStart() + index * (mIconSize + mIconSpacing) + mIconSize / 2f;
mBubbleBarBackground.setArrowPosition(tx);
}
@Override
public void setOnClickListener(View.OnClickListener listener) {
mOnClickListener = listener;
setOrUnsetClickListener();
}
/**
* The click listener used for the bubble view gets added / removed depending on whether
* the bar is expanded or collapsed, this updates whether the listener is set based on state.
*/
private void setOrUnsetClickListener() {
super.setOnClickListener(mIsBarExpanded ? null : mOnClickListener);
}
/**
* Sets whether the bubble bar is expanded or collapsed.
*/
// TODO: (b/273592694) animate it
public void setExpanded(boolean isBarExpanded) {
if (mIsBarExpanded != isBarExpanded) {
mIsBarExpanded = isBarExpanded;
updateArrowForSelected();
setOrUnsetClickListener();
if (!isBarExpanded && mReorderRunnable != null) {
mReorderRunnable.run();
mReorderRunnable = null;
}
mBubbleBarBackground.showArrow(mIsBarExpanded);
requestLayout(); // trigger layout to reposition views & update size for expansion
}
}
/**
* Returns whether the bubble bar is expanded.
*/
public boolean isExpanded() {
return mIsBarExpanded;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int childCount = getChildCount();
final float iconWidth = mIsBarExpanded
? (childCount * (mIconSize + mIconSpacing))
: mIconSize + ((childCount - 1) * mIconOverlapAmount);
final int totalWidth = (int) iconWidth + getPaddingStart() + getPaddingEnd();
setMeasuredDimension(totalWidth, MeasureSpec.getSize(heightMeasureSpec));
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
measureChild(child, (int) mIconSize, (int) mIconSize);
}
}
/**
* Returns whether the given MotionEvent, *in screen coordinates*, is within bubble bar
* touch bounds.
*/
public boolean isEventOverAnyItem(MotionEvent ev) {
if (getVisibility() == View.VISIBLE) {
getBoundsOnScreen(mTempRect);
return mTempRect.contains((int) ev.getX(), (int) ev.getY());
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (!mIsBarExpanded) {
// When the bar is collapsed, all taps on it should expand it.
return true;
}
return super.onInterceptTouchEvent(ev);
}
}
@@ -0,0 +1,297 @@
/*
* 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.
*/
package com.android.launcher3.taskbar.bubbles;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import android.graphics.Rect;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiValueAlpha;
import java.util.List;
import java.util.Objects;
/**
* Controller for {@link BubbleBarView}. Manages the visibility of the bubble bar as well as
* responding to changes in bubble state provided by BubbleBarController.
*/
public class BubbleBarViewController {
private static final String TAG = BubbleBarViewController.class.getSimpleName();
private final TaskbarActivityContext mActivity;
private final BubbleBarView mBarView;
private final int mIconSize;
// Initialized in init.
private BubbleStashController mBubbleStashController;
private BubbleBarController mBubbleBarController;
private View.OnClickListener mBubbleClickListener;
private View.OnClickListener mBubbleBarClickListener;
// 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 mBubbleBarTranslationY = new AnimatedFloat(
this::updateTranslationY);
// Modified when swipe up is happening on the bubble bar or task bar.
private float mBubbleBarSwipeUpTranslationY;
// Whether the bar is hidden for a sysui state.
private boolean mHiddenForSysui;
// Whether the bar is hidden because there are no bubbles.
private boolean mHiddenForNoBubbles;
public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) {
mActivity = activity;
mBarView = barView;
mBubbleBarAlpha = new MultiValueAlpha(mBarView, 1 /* num alpha channels */);
mBubbleBarAlpha.setUpdateVisibility(true);
mIconSize = activity.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
}
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleBarController = bubbleControllers.bubbleBarController;
mActivity.addOnDeviceProfileChangeListener(dp ->
mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight
);
mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight;
mBubbleBarScale.updateValue(1f);
mBubbleClickListener = v -> onBubbleClicked(v);
mBubbleBarClickListener = v -> setExpanded(true);
mBarView.setOnClickListener(mBubbleBarClickListener);
// TODO: when barView layout changes tell taskbarInsetsController the insets have changed.
}
private void onBubbleClicked(View v) {
BubbleBarBubble bubble = ((BubbleView) v).getBubble();
if (bubble == null) {
Log.e(TAG, "bubble click listener, bubble was null");
}
final String currentlySelected = mBubbleBarController.getSelectedBubbleKey();
if (mBarView.isExpanded() && Objects.equals(bubble.getKey(), currentlySelected)) {
// Tapping the currently selected bubble while expanded collapses the view.
setExpanded(false);
mBubbleStashController.stashBubbleBar();
} else {
mBubbleBarController.setSelectedBubble(bubble);
// TODO: Tell SysUi to show the expanded view for this bubble.
}
}
//
// The below animators are exposed to BubbleStashController so it can manage the stashing
// animation.
//
public MultiPropertyFactory<View> getBubbleBarAlpha() {
return mBubbleBarAlpha;
}
public AnimatedFloat getBubbleBarScale() {
return mBubbleBarScale;
}
public AnimatedFloat getBubbleBarTranslationY() {
return mBubbleBarTranslationY;
}
/**
* Whether the bubble bar is visible or not.
*/
public boolean isBubbleBarVisible() {
return mBarView.getVisibility() == VISIBLE;
}
/**
* The bounds of the bubble bar.
*/
public Rect getBubbleBarBounds() {
return mBarView.getBubbleBarBounds();
}
/**
* When the bubble bar is not stashed, it can be collapsed (the icons are in a stack) or
* expanded (the icons are in a row). This indicates whether the bubble bar is expanded.
*/
public boolean isExpanded() {
return mBarView.isExpanded();
}
/**
* Whether the motion event is within the bounds of the bubble bar.
*/
public boolean isEventOverAnyItem(MotionEvent ev) {
return mBarView.isEventOverAnyItem(ev);
}
//
// Visibility of the bubble bar
//
/**
* Returns whether the bubble bar is hidden because there are no bubbles.
*/
public boolean isHiddenForNoBubbles() {
return mHiddenForNoBubbles;
}
/**
* Sets whether the bubble bar should be hidden because there are no bubbles.
*/
public void setHiddenForBubbles(boolean hidden) {
if (mHiddenForNoBubbles != hidden) {
mHiddenForNoBubbles = hidden;
updateVisibilityForStateChange();
}
}
/**
* Sets whether the bubble bar should be hidden due to SysUI state (e.g. on lockscreen).
*/
public void setHiddenForSysui(boolean hidden) {
if (mHiddenForSysui != hidden) {
mHiddenForSysui = hidden;
updateVisibilityForStateChange();
}
}
// TODO: (b/273592694) animate it
private void updateVisibilityForStateChange() {
if (!mHiddenForSysui && !mBubbleStashController.isStashed() && !mHiddenForNoBubbles) {
mBarView.setVisibility(VISIBLE);
} else {
mBarView.setVisibility(INVISIBLE);
}
}
//
// Modifying view related properties.
//
/**
* Sets the translation of the bubble bar during the swipe up gesture.
*/
public void setTranslationYForSwipe(float transY) {
mBubbleBarSwipeUpTranslationY = transY;
updateTranslationY();
}
private void updateTranslationY() {
mBarView.setTranslationY(mBubbleBarTranslationY.value
+ mBubbleBarSwipeUpTranslationY);
}
/**
* Applies scale properties for the entire bubble bar.
*/
private void updateScale() {
float scale = mBubbleBarScale.value;
mBarView.setScaleX(scale);
mBarView.setScaleY(scale);
}
//
// Manipulating the specific bubble views in the bar
//
/**
* Removes the provided bubble from the bubble bar.
*/
public void removeBubble(BubbleBarBubble b) {
if (b != null) {
mBarView.removeView(b.getView());
} else {
Log.w(TAG, "removeBubble, bubble was null!");
}
}
/**
* Adds the provided bubble to the bubble bar.
*/
public void addBubble(BubbleBarBubble b) {
if (b != null) {
mBarView.addView(b.getView(), 0, new FrameLayout.LayoutParams(mIconSize, mIconSize));
b.getView().setOnClickListener(mBubbleClickListener);
} else {
Log.w(TAG, "addBubble, bubble was null!");
}
}
/**
* Reorders the bubbles based on the provided list.
*/
public void reorderBubbles(List<BubbleBarBubble> newOrder) {
List<BubbleView> viewList = newOrder.stream().filter(Objects::nonNull)
.map(BubbleBarBubble::getView).toList();
mBarView.reorder(viewList);
}
/**
* Updates the selected bubble.
*/
public void updateSelectedBubble(BubbleBarBubble newlySelected) {
mBarView.setSelectedBubble(newlySelected.getView());
}
/**
* Sets whether the bubble bar should be expanded (not unstashed, but have the contents
* within it expanded). This method notifies SystemUI that the bubble bar is expanded and
* showing a selected bubble. This method should ONLY be called from UI events originating
* from Launcher.
*/
public void setExpanded(boolean isExpanded) {
if (isExpanded != mBarView.isExpanded()) {
mBarView.setExpanded(isExpanded);
if (!isExpanded) {
// TODO: Tell SysUi to collapse the bubble
} else {
final String selectedKey = mBubbleBarController.getSelectedBubbleKey();
if (selectedKey != null) {
// TODO: Tell SysUi to show the bubble
} else {
Log.w(TAG, "trying to expand bubbles when there isn't one selected");
}
// TODO: Tell taskbar stash controller to stash without bubbles following
}
}
}
/**
* Sets whether the bubble bar should be expanded. This method is used in response to UI events
* from SystemUI.
*/
public void setExpandedFromSysui(boolean isExpanded) {
if (!isExpanded) {
mBubbleStashController.stashBubbleBar();
} else {
mBubbleStashController.showBubbleBar(true /* expand the bubbles */);
}
}
}
@@ -0,0 +1,80 @@
/*
* 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.
*/
package com.android.launcher3.taskbar.bubbles;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.util.RunnableList;
/**
* Hosts various bubble controllers to facilitate passing between one another.
*/
public class BubbleControllers {
public final BubbleBarController bubbleBarController;
public final BubbleBarViewController bubbleBarViewController;
public final BubbleStashController bubbleStashController;
public final BubbleStashedHandleViewController bubbleStashedHandleViewController;
private final RunnableList mPostInitRunnables = new RunnableList();
/**
* Want to add a new controller? Don't forget to:
* * Call init
* * Call onDestroy
*/
public BubbleControllers(
BubbleBarController bubbleBarController,
BubbleBarViewController bubbleBarViewController,
BubbleStashController bubbleStashController,
BubbleStashedHandleViewController bubbleStashedHandleViewController) {
this.bubbleBarController = bubbleBarController;
this.bubbleBarViewController = bubbleBarViewController;
this.bubbleStashController = bubbleStashController;
this.bubbleStashedHandleViewController = bubbleStashedHandleViewController;
}
/**
* Initializes all controllers. Note that controllers can now reference each other through this
* BubbleControllers instance, but should be careful to only access things that were created
* in constructors for now, as some controllers may still be waiting for init().
*/
public void init(TaskbarControllers taskbarControllers) {
bubbleBarController.init(taskbarControllers, this);
bubbleBarViewController.init(taskbarControllers, this);
bubbleStashedHandleViewController.init(taskbarControllers, this);
bubbleStashController.init(taskbarControllers, this);
mPostInitRunnables.executeAllAndDestroy();
}
/**
* If all controllers are already initialized, runs the given callback immediately. Otherwise,
* queues it to run after calling init() on all controllers. This should likely be used in any
* case where one controller is telling another controller to do something inside init().
*/
public void runAfterInit(Runnable runnable) {
// If this has been executed in init, it automatically runs adds to it.
mPostInitRunnables.add(runnable);
}
/**
* Cleans up all controllers.
*/
public void onDestroy() {
bubbleStashedHandleViewController.onDestroy();
bubbleBarController.onDestroy();
}
}
@@ -0,0 +1,277 @@
/*
* 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.
*/
package com.android.launcher3.taskbar.bubbles;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.Nullable;
import android.view.InsetsController;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.taskbar.StashedHandleViewController;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.util.MultiPropertyFactory;
/**
* Coordinates between controllers such as BubbleBarView and BubbleHandleViewController to
* create a cohesive animation between stashed/unstashed states.
*/
public class BubbleStashController {
private static final String TAG = BubbleStashController.class.getSimpleName();
/**
* How long to stash/unstash.
*/
public static final long BAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE;
/**
* The scale bubble bar animates to when being stashed.
*/
private static final float STASHED_BAR_SCALE = 0.5f;
protected final TaskbarActivityContext mActivity;
// Initialized in init.
private TaskbarControllers mControllers;
private BubbleBarViewController mBarViewController;
private BubbleStashedHandleViewController mHandleViewController;
private TaskbarStashController mTaskbarStashController;
private MultiPropertyFactory.MultiProperty mIconAlphaForStash;
private AnimatedFloat mIconScaleForStash;
private AnimatedFloat mIconTranslationYForStash;
private MultiPropertyFactory.MultiProperty mBubbleStashedHandleAlpha;
private boolean mRequestedStashState;
private boolean mRequestedExpandedState;
private boolean mIsStashed;
private int mStashedHeight;
private int mUnstashedHeight;
private boolean mBubblesShowingOnHome;
private boolean mBubblesShowingOnOverview;
@Nullable
private AnimatorSet mAnimator;
public BubbleStashController(TaskbarActivityContext activity) {
mActivity = activity;
}
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
mControllers = controllers;
mBarViewController = bubbleControllers.bubbleBarViewController;
mHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
mTaskbarStashController = controllers.taskbarStashController;
mIconAlphaForStash = mBarViewController.getBubbleBarAlpha().get(0);
mIconScaleForStash = mBarViewController.getBubbleBarScale();
mIconTranslationYForStash = mBarViewController.getBubbleBarTranslationY();
mBubbleStashedHandleAlpha = mHandleViewController.getStashedHandleAlpha().get(
StashedHandleViewController.ALPHA_INDEX_STASHED);
mStashedHeight = mHandleViewController.getStashedHeight();
mUnstashedHeight = mHandleViewController.getUnstashedHeight();
bubbleControllers.runAfterInit(() -> {
if (mTaskbarStashController.isStashed()) {
stashBubbleBar();
} else {
showBubbleBar(false /* expandBubbles */);
}
});
}
/**
* Returns the touchable height of the bubble bar based on it's stashed state.
*/
public int getTouchableHeight() {
return mIsStashed ? mStashedHeight : mUnstashedHeight;
}
/**
* Returns whether the bubble bar is currently stashed.
*/
public boolean isStashed() {
return mIsStashed;
}
/**
* Called when launcher enters or exits the home page. Bubbles are unstashed on home.
*/
public void setBubblesShowingOnHome(boolean onHome) {
if (mBubblesShowingOnHome != onHome) {
mBubblesShowingOnHome = onHome;
if (mBubblesShowingOnHome) {
showBubbleBar(/* expanded= */ false);
} else if (!mBarViewController.isExpanded()) {
stashBubbleBar();
}
}
}
/** Whether bubbles are showing on the launcher home page. */
public boolean isBubblesShowingOnHome() {
return mBubblesShowingOnHome;
}
// TODO: when tapping on an app in overview, this is a bit delayed compared to taskbar stashing
/** Called when launcher enters or exits overview. Bubbles are unstashed on overview. */
public void setBubblesShowingOnOverview(boolean onOverview) {
if (mBubblesShowingOnOverview != onOverview) {
mBubblesShowingOnOverview = onOverview;
if (!mBubblesShowingOnOverview && !mBarViewController.isExpanded()) {
stashBubbleBar();
}
}
}
/** Called when sysui locked state changes, when locked, bubble bar is stashed. */
public void onSysuiLockedStateChange(boolean isSysuiLocked) {
if (isSysuiLocked) {
// TODO: should the normal path flip mBubblesOnHome / check if this is needed
// If we're locked, we're no longer showing on home.
mBubblesShowingOnHome = false;
mBubblesShowingOnOverview = false;
stashBubbleBar();
}
}
/**
* Stashes the bubble bar if allowed based on other state (e.g. on home and overview the
* bar does not stash).
*/
public void stashBubbleBar() {
mRequestedStashState = true;
mRequestedExpandedState = false;
updateStashedAndExpandedState();
}
/**
* Shows the bubble bar, and expands bubbles depending on {@param expandBubbles}.
*/
public void showBubbleBar(boolean expandBubbles) {
mRequestedStashState = false;
mRequestedExpandedState = expandBubbles;
updateStashedAndExpandedState();
}
private void updateStashedAndExpandedState() {
if (mBarViewController.isHiddenForNoBubbles()) {
// If there are no bubbles the bar and handle are invisible, nothing to do here.
return;
}
boolean isStashed = mRequestedStashState
&& !mBubblesShowingOnHome
&& !mBubblesShowingOnOverview;
if (mIsStashed != isStashed) {
mIsStashed = isStashed;
if (mAnimator != null) {
mAnimator.cancel();
}
mAnimator = createStashAnimator(mIsStashed, BAR_STASH_DURATION);
mAnimator.start();
onIsStashedChanged();
}
if (mBarViewController.isExpanded() != mRequestedExpandedState) {
mBarViewController.setExpanded(mRequestedExpandedState);
}
}
/**
* Create a stash animation.
*
* @param isStashed whether it's a stash animation or an unstash animation
* @param duration duration of the animation
* @return the animation
*/
private AnimatorSet createStashAnimator(boolean isStashed, long duration) {
AnimatorSet animatorSet = new AnimatorSet();
final float stashTranslation = (mUnstashedHeight - mStashedHeight) / 2f;
AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
// Not exactly half and may overlap. See [first|second]HalfDurationScale below.
AnimatorSet firstHalfAnimatorSet = new AnimatorSet();
AnimatorSet secondHalfAnimatorSet = new AnimatorSet();
final float firstHalfDurationScale;
final float secondHalfDurationScale;
if (isStashed) {
firstHalfDurationScale = 0.75f;
secondHalfDurationScale = 0.5f;
fullLengthAnimatorSet.play(mIconTranslationYForStash.animateToValue(stashTranslation));
firstHalfAnimatorSet.playTogether(
mIconAlphaForStash.animateToValue(0),
mIconScaleForStash.animateToValue(STASHED_BAR_SCALE));
secondHalfAnimatorSet.playTogether(
mBubbleStashedHandleAlpha.animateToValue(1));
} else {
firstHalfDurationScale = 0.5f;
secondHalfDurationScale = 0.75f;
// If we're on home, adjust the translation so the bubble bar aligns with hotseat.
final float hotseatTransY = mActivity.getDeviceProfile().getTaskbarOffsetY();
final float translationY = mBubblesShowingOnHome ? hotseatTransY : 0;
fullLengthAnimatorSet.playTogether(
mIconScaleForStash.animateToValue(1),
mIconTranslationYForStash.animateToValue(translationY));
firstHalfAnimatorSet.playTogether(
mBubbleStashedHandleAlpha.animateToValue(0)
);
secondHalfAnimatorSet.playTogether(
mIconAlphaForStash.animateToValue(1)
);
}
fullLengthAnimatorSet.play(mHandleViewController.createRevealAnimToIsStashed(isStashed));
fullLengthAnimatorSet.setDuration(duration);
firstHalfAnimatorSet.setDuration((long) (duration * firstHalfDurationScale));
secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale));
secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale)));
animatorSet.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
secondHalfAnimatorSet);
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mAnimator = null;
mControllers.runAfterInit(() -> {
if (isStashed) {
mBarViewController.setExpanded(false);
}
});
}
});
return animatorSet;
}
private void onIsStashedChanged() {
mControllers.runAfterInit(() -> {
mHandleViewController.onIsStashedChanged();
// TODO: when stash changes tell taskbarInsetsController the insets have changed.
});
}
}
@@ -0,0 +1,262 @@
/*
* 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.
*/
package com.android.launcher3.taskbar.bubbles;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.res.Resources;
import android.graphics.Outline;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewOutlineProvider;
import com.android.launcher3.R;
import com.android.launcher3.anim.RevealOutlineAnimation;
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.taskbar.StashedHandleView;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarControllers;
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;
/**
* Handles properties/data collection, then passes the results to our stashed handle View to render.
*/
public class BubbleStashedHandleViewController {
private final TaskbarActivityContext mActivity;
private final StashedHandleView mStashedHandleView;
private final MultiValueAlpha mTaskbarStashedHandleAlpha;
// Initialized in init.
private BubbleBarViewController mBarViewController;
private BubbleStashController mBubbleStashController;
private RegionSamplingHelper mRegionSamplingHelper;
private int mBarSize;
private int mStashedHandleWidth;
private int mStashedHandleHeight;
// The bounds we want to clip to in the settled state when showing the stashed handle.
private final Rect mStashedHandleBounds = new Rect();
// 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;
// XXX: if there are more of these maybe do state flags instead
private boolean mHiddenForSysui;
private boolean mHiddenForNoBubbles;
private boolean mHiddenForHomeButtonDisabled;
public BubbleStashedHandleViewController(TaskbarActivityContext activity,
StashedHandleView stashedHandleView) {
mActivity = activity;
mStashedHandleView = stashedHandleView;
mTaskbarStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1);
}
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
mBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleStashController = bubbleControllers.bubbleStashController;
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;
mTaskbarStashedHandleAlpha.get(0).setValue(0);
final int stashedTaskbarHeight = 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);
}
});
mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
new RegionSamplingHelper.SamplingCallback() {
@Override
public void onRegionDarknessChanged(boolean isRegionDark) {
mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */);
}
@Override
public Rect getSampledRegion(View sampledView) {
return mStashedHandleView.getSampledRegion();
}
}, Executors.UI_HELPER_EXECUTOR);
mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> {
// 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.
Rect bubblebarRect = mBarViewController.getBubbleBarBounds();
final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
mStashedHandleBounds.set(
bubblebarRect.right - mStashedHandleWidth,
stashedCenterY - mStashedHandleHeight / 2,
bubblebarRect.right,
stashedCenterY + mStashedHandleHeight / 2);
mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
view.setPivotX(view.getWidth());
view.setPivotY(view.getHeight() - stashedTaskbarHeight / 2f);
});
}
public void onDestroy() {
mRegionSamplingHelper.stopAndDestroy();
mRegionSamplingHelper = null;
}
/**
* Returns the height of the stashed handle.
*/
public int getStashedHeight() {
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.
*/
public void setHiddenForSysui(boolean hidden) {
if (mHiddenForSysui != hidden) {
mHiddenForSysui = hidden;
updateVisibilityForStateChange();
}
}
/**
* Called when the handle should be hidden (or shown) because there are no bubbles
* (or 1+ bubbles).
*/
public void setHiddenForBubbles(boolean hidden) {
if (mHiddenForNoBubbles != hidden) {
mHiddenForNoBubbles = hidden;
updateVisibilityForStateChange();
}
}
/**
* Called when the home button is enabled / disabled. Bubbles don't show if home is disabled.
*/
// TODO: is this needed for bubbles?
public void setIsHomeButtonDisabled(boolean homeDisabled) {
mHiddenForHomeButtonDisabled = homeDisabled;
updateVisibilityForStateChange();
}
// TODO: (b/273592694) animate it?
private void updateVisibilityForStateChange() {
if (!mHiddenForSysui && !mHiddenForHomeButtonDisabled && !mHiddenForNoBubbles) {
mStashedHandleView.setVisibility(VISIBLE);
} else {
mStashedHandleView.setVisibility(INVISIBLE);
}
updateRegionSampling();
}
/**
* Called when bubble bar is stash state changes so that updates to the stashed handle color
* can be started or stopped.
*/
public void onIsStashedChanged() {
updateRegionSampling();
}
private void updateRegionSampling() {
boolean handleVisible = mStashedHandleView.getVisibility() == VISIBLE
&& mBubbleStashController.isStashed();
mRegionSamplingHelper.setWindowVisible(handleVisible);
if (handleVisible) {
mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
} else {
mRegionSamplingHelper.stop();
}
}
/**
* Sets the translation of the stashed handle during the swipe up gesture.
*/
public void setTranslationYForSwipe(float transY) {
mStashedHandleView.setTranslationY(transY);
}
/**
* Used by {@link BubbleStashController} to animate the handle when stashing or un stashing.
*/
public MultiPropertyFactory<View> getStashedHandleAlpha() {
return mTaskbarStashedHandleAlpha;
}
/**
* Creates and returns an Animator that updates the stashed handle shape and size.
* When stashed, the shape is a thin rounded pill. When unstashed, the shape morphs into
* the size of where the bubble bar icons will be.
*/
public Animator createRevealAnimToIsStashed(boolean isStashed) {
Rect bubbleBarBounds = new Rect(mBarViewController.getBubbleBarBounds());
// 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;
final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
stashedHandleRadius, stashedHandleRadius, bubbleBarBounds, mStashedHandleBounds);
boolean isReversed = !isStashed;
boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
mWasLastRevealAnimReversed = isReversed;
if (changingDirection) {
mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
}
ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView,
isReversed, mStartProgressForNextRevealAnim);
revealAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction();
}
});
return revealAnim;
}
}
@@ -0,0 +1,134 @@
/*
* 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.
*/
package com.android.launcher3.taskbar.bubbles;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Outline;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.launcher3.R;
import com.android.launcher3.icons.IconNormalizer;
// TODO: (b/276978250) This is will be similar to WMShell's BadgedImageView, it'd be nice to share.
// TODO: (b/269670235) currently this doesn't show the 'update dot'
/**
* View that displays a bubble icon, along with an app badge on either the left or
* right side of the view.
*/
public class BubbleView extends ConstraintLayout {
// TODO: (b/269670235) currently we don't render the 'update dot', this will be used for that.
public static final int DEFAULT_PATH_SIZE = 100;
private final ImageView mBubbleIcon;
private final ImageView mAppIcon;
private final int mBubbleSize;
// TODO: (b/273310265) handle RTL
private boolean mOnLeft = false;
private BubbleBarBubble mBubble;
public BubbleView(Context context) {
this(context, null);
}
public BubbleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public BubbleView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
// We manage positioning the badge ourselves
setLayoutDirection(LAYOUT_DIRECTION_LTR);
LayoutInflater.from(context).inflate(R.layout.bubble_view, this);
mBubbleSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
mBubbleIcon = findViewById(R.id.icon_view);
mAppIcon = findViewById(R.id.app_icon_view);
setFocusable(true);
setClickable(true);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
BubbleView.this.getOutline(outline);
}
});
}
private void getOutline(Outline outline) {
final int normalizedSize = IconNormalizer.getNormalizedCircleSize(mBubbleSize);
final int inset = (mBubbleSize - normalizedSize) / 2;
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
}
/** Sets the bubble being rendered in this view. */
void setBubble(BubbleBarBubble bubble) {
mBubble = bubble;
mBubbleIcon.setImageBitmap(bubble.getIcon());
mAppIcon.setImageBitmap(bubble.getBadge());
}
/** Returns the bubble being rendered in this view. */
@Nullable
BubbleBarBubble getBubble() {
return mBubble;
}
/** Shows the app badge on this bubble. */
void showBadge() {
Bitmap appBadgeBitmap = mBubble.getBadge();
if (appBadgeBitmap == null) {
mAppIcon.setVisibility(GONE);
return;
}
int translationX;
if (mOnLeft) {
translationX = -(mBubble.getIcon().getWidth() - appBadgeBitmap.getWidth());
} else {
translationX = 0;
}
mAppIcon.setTranslationX(translationX);
mAppIcon.setVisibility(VISIBLE);
}
/** Hides the app badge on this bubble. */
void hideBadge() {
mAppIcon.setVisibility(GONE);
}
@Override
public String toString() {
return "BubbleView{" + mBubble + "}";
}
}
@@ -402,8 +402,9 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
PredictedAppIcon icon = (PredictedAppIcon) LayoutInflater.from(parent.getContext())
.inflate(R.layout.predicted_app_icon, parent, false);
icon.applyFromWorkspaceItem(info);
icon.setOnClickListener(ItemClickHandler.INSTANCE);
icon.setOnFocusChangeListener(Launcher.getLauncher(parent.getContext()).getFocusHandler());
Launcher launcher = Launcher.getLauncher(parent.getContext());
icon.setOnClickListener(launcher.getItemOnClickListener());
icon.setOnFocusChangeListener(launcher.getFocusHandler());
return icon;
}
@@ -56,6 +56,10 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
return RemoteViews.startPendingIntent(hostView, pendingIntent,
remoteResponse.getLaunchOptions(view));
}
if (mLauncher.getSplitToWorkspaceController().handleSecondWidgetSelectionForSplit(view,
pendingIntent)) {
return true;
}
Pair<Intent, ActivityOptions> options = remoteResponse.getLaunchOptions(view);
ActivityOptionsWrapper activityOptions = mLauncher.getAppTransitionManager()
.getActivityLaunchOptions(hostView);
@@ -74,6 +74,7 @@ import android.hardware.SensorManager;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManager;
import android.media.permission.SafeCloseable;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.IBinder;
@@ -91,6 +92,7 @@ import android.window.SplashScreen;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.app.viewcapture.SettingsAwareViewCapture;
import com.android.launcher3.AbstractFloatingView;
@@ -764,6 +766,7 @@ public class QuickstepLauncher extends Launcher {
mActiveOnBackAnimationCallback.onBackStarted(backEvent);
}
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Override
public void onBackInvoked() {
// Recreate mActiveOnBackAnimationCallback if necessary to avoid NPE
@@ -970,8 +973,8 @@ public class QuickstepLauncher extends Launcher {
return mTaskbarUIController;
}
public SplitSelectStateController getSplitSelectStateController() {
return mSplitSelectStateController;
public SplitToWorkspaceController getSplitToWorkspaceController() {
return mSplitToWorkspaceController;
}
public <T extends OverviewActionsView> T getActionsView() {
@@ -1178,6 +1181,11 @@ public class QuickstepLauncher extends Launcher {
}
}
@Override
public void tryClearAccessibilityFocus(View view) {
view.clearAccessibilityFocus();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@@ -16,12 +16,10 @@
package com.android.launcher3.uioverrides.flags;
import static com.android.launcher3.config.FeatureFlags.FLAGS_PREF_NAME;
import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
import static com.android.launcher3.uioverrides.flags.FlagsFactory.TEAMFOOD_FLAG;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Process;
import android.text.Html;
@@ -52,31 +50,27 @@ public final class FlagTogglerPrefUi {
private final PreferenceFragmentCompat mFragment;
private final Context mContext;
private final SharedPreferences mSharedPreferences;
private final PreferenceDataStore mDataStore = new PreferenceDataStore() {
@Override
public void putBoolean(String key, boolean value) {
mSharedPreferences.edit().putBoolean(key, value).apply();
FlagsFactory.getSharedPreferences().edit().putBoolean(key, value).apply();
updateMenu();
}
@Override
public boolean getBoolean(String key, boolean defaultValue) {
return mSharedPreferences.getBoolean(key, defaultValue);
return FlagsFactory.getSharedPreferences().getBoolean(key, defaultValue);
}
};
public FlagTogglerPrefUi(PreferenceFragmentCompat fragment) {
mFragment = fragment;
mContext = fragment.getActivity();
mSharedPreferences = mContext.getSharedPreferences(
FLAGS_PREF_NAME, Context.MODE_PRIVATE);
}
public void applyTo(PreferenceGroup parent) {
Set<String> modifiedPrefs = mSharedPreferences.getAll().keySet();
Set<String> modifiedPrefs = FlagsFactory.getSharedPreferences().getAll().keySet();
List<DebugFlag> flags = FlagsFactory.getDebugFlags();
flags.sort((f1, f2) -> {
// Sort first by any prefs that the user has changed, then alphabetically.
@@ -102,7 +96,7 @@ public final class FlagTogglerPrefUi {
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
holder.itemView.setOnLongClickListener(v -> {
mSharedPreferences.edit().remove(flag.key).apply();
FlagsFactory.getSharedPreferences().edit().remove(flag.key).apply();
setChecked(getFlagStateFromSharedPrefs(flag));
updateSummary(this, flag);
updateMenu();
@@ -133,7 +127,7 @@ public final class FlagTogglerPrefUi {
private void updateSummary(SwitchPreference switchPreference, DebugFlag flag) {
String summary = flag.defaultValue == TEAMFOOD
? "<font color='blue'><b>[TEAMFOOD]</b> </font>" : "";
if (mSharedPreferences.contains(flag.key)) {
if (FlagsFactory.getSharedPreferences().contains(flag.key)) {
summary += "<font color='red'><b>[OVERRIDDEN]</b> </font>";
}
if (!TextUtils.isEmpty(summary)) {
@@ -156,7 +150,7 @@ public final class FlagTogglerPrefUi {
public void onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_apply_flags) {
mSharedPreferences.edit().commit();
FlagsFactory.getSharedPreferences().edit().commit();
Log.e(TAG,
"Killing launcher process " + Process.myPid() + " to apply new flag values");
System.exit(0);
@@ -21,7 +21,6 @@ import static android.app.ActivityThread.currentApplication;
import static com.android.launcher3.BuildConfig.IS_DEBUG_DEVICE;
import static com.android.launcher3.config.FeatureFlags.FlagState.DISABLED;
import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.content.Context;
@@ -30,6 +29,8 @@ import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.launcher3.config.FeatureFlags.BooleanFlag;
import com.android.launcher3.config.FeatureFlags.FlagState;
import com.android.launcher3.config.FeatureFlags.IntFlag;
@@ -52,10 +53,11 @@ public class FlagsFactory {
private static final FlagsFactory INSTANCE = new FlagsFactory();
private static final boolean FLAG_AUTO_APPLY_ENABLED = true;
public static final String FLAGS_PREF_NAME = "featureFlags";
private static final String FLAGS_PREF_NAME = "featureFlags";
public static final String NAMESPACE_LAUNCHER = "launcher";
private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
private static SharedPreferences sSharedPreferences;
static final BooleanFlag TEAMFOOD_FLAG = getReleaseFlag(
0, "LAUNCHER_TEAMFOOD", DISABLED, "Enable this flag to opt-in all team food flags");
@@ -93,10 +95,8 @@ public class FlagsFactory {
public static BooleanFlag getDebugFlag(
int bugId, String key, FlagState flagState, String description) {
if (IS_DEBUG_DEVICE) {
SharedPreferences prefs = currentApplication()
.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
boolean defaultValue = getEnabledValue(flagState);
boolean currentValue = prefs.getBoolean(key, defaultValue);
boolean currentValue = getSharedPreferences().getBoolean(key, defaultValue);
DebugFlag flag = new DebugFlag(key, description, flagState, currentValue);
sDebugFlags.add(flag);
return flag;
@@ -115,9 +115,7 @@ public class FlagsFactory {
boolean defaultValueInCode = getEnabledValue(flagState);
boolean defaultValue = DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, key, defaultValueInCode);
if (IS_DEBUG_DEVICE) {
SharedPreferences prefs = currentApplication()
.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
boolean currentValue = prefs.getBoolean(key, defaultValue);
boolean currentValue = getSharedPreferences().getBoolean(key, defaultValue);
DebugFlag flag = new DeviceFlag(key, description, flagState, currentValue,
defaultValueInCode);
sDebugFlags.add(flag);
@@ -145,6 +143,17 @@ public class FlagsFactory {
}
}
/** Returns the SharedPreferences instance backing Debug FeatureFlags. */
@NonNull
static SharedPreferences getSharedPreferences() {
if (sSharedPreferences == null) {
sSharedPreferences = currentApplication()
.createDeviceProtectedStorageContext()
.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE);
}
return sSharedPreferences;
}
/**
* Dumps the current flags state to the print writer
*/
@@ -15,11 +15,15 @@
*/
package com.android.launcher3.uioverrides.touchcontrollers;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
@@ -106,6 +110,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
newCancelListener(this::clearState);
private boolean mNoIntercept;
private Boolean mIsTrackpadFourFingerSwipe;
private LauncherState mStartState;
private boolean mIsHomeScreenVisible = true;
@@ -131,7 +136,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
int action = ev.getActionMasked();
if (action == ACTION_DOWN) {
mIsTrackpadFourFingerSwipe = null;
mNoIntercept = !canInterceptTouch(ev);
if (mNoIntercept) {
return false;
@@ -140,6 +147,13 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
// Only detect horizontal swipe for intercept, then we will allow swipe up as well.
mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT,
false /* ignoreSlopWhenSettling */);
} else if (isTrackpadMultiFingerSwipe(ev) && mIsTrackpadFourFingerSwipe == null
&& action == ACTION_MOVE) {
mIsTrackpadFourFingerSwipe = isTrackpadFourFingerSwipe(ev);
mNoIntercept = !mIsTrackpadFourFingerSwipe;
if (mNoIntercept) {
return false;
}
}
if (mNoIntercept) {
@@ -162,9 +176,6 @@ public class NoButtonQuickSwitchTouchController implements TouchController,
if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) {
return false;
}
if (isTrackpadMultiFingerSwipe(ev)) {
return false;
}
int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
return false;
@@ -21,7 +21,6 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN;
import android.graphics.PointF;
@@ -106,8 +105,7 @@ public class StatusBarTouchController implements TouchController {
// Currently input dispatcher will not do touch transfer if there are more than
// one touch pointer. Hence, even if slope passed, only set the slippery flag
// when there is single touch event. (context: InputDispatcher.cpp line 1445)
if (dy > mTouchSlop && dy > Math.abs(dx) && (isTrackpadMotionEvent(ev)
|| ev.getPointerCount() == 1)) {
if (dy > mTouchSlop && dy > Math.abs(dx) && ev.getPointerCount() == 1) {
ev.setAction(ACTION_DOWN);
dispatchTouchEvent(ev);
setWindowSlippery(true);
@@ -161,8 +159,7 @@ public class StatusBarTouchController implements TouchController {
} else {
// For NORMAL state, only listen if the event originated above the navbar height
DeviceProfile dp = mLauncher.getDeviceProfile();
if (!isTrackpadMotionEvent(ev) && ev.getY() > (mLauncher.getDragLayer().getHeight()
- dp.getInsets().bottom)) {
if (ev.getY() > (mLauncher.getDragLayer().getHeight() - dp.getInsets().bottom)) {
return false;
}
}
@@ -105,6 +105,7 @@ import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.taskbar.TaskbarUIController;
import com.android.launcher3.tracing.InputConsumerProto;
import com.android.launcher3.tracing.SwipeHandlerProto;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.TraceHelper;
@@ -624,7 +625,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
mActivityInterface);
});
notifyGestureStartedAsync();
notifyGestureStarted();
}
private void onDeferredActivityLaunch() {
@@ -961,7 +962,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
}
});
}
notifyGestureStartedAsync();
notifyGestureStarted();
setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
if (mIsTransientTaskbar && !mTaskbarAlreadyOpen && !isLikelyToStartNewTask) {
@@ -994,7 +995,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
* Notifies the launcher that the swipe gesture has started. This can be called multiple times.
*/
@UiThread
private void notifyGestureStartedAsync() {
private void notifyGestureStarted() {
final T curActivity = mActivity;
if (curActivity != null) {
// Once the gesture starts, we can no longer transition home through the button, so
@@ -2063,8 +2064,8 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
mRecentsAnimationTargets));
// Disable scrolling in RecentsView for trackpad gestures.
if (!mGestureState.isTrackpadGesture()) {
// Disable scrolling in RecentsView for trackpad 3-finger swipe up gesture.
if (!mGestureState.isThreeFingerTrackpadGesture()) {
mRecentsViewScrollLinked = true;
}
}
@@ -2168,7 +2169,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
ActiveGestureLog.INSTANCE.addLog("Unexpected task appeared"
+ " id=" + taskInfo.taskId
+ " pkg=" + taskInfo.baseIntent.getComponent().getPackageName());
finishRecentsAnimationOnTasksAppeared();
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
} else if (handleTaskAppeared(appearedTaskTargets)) {
Optional<RemoteAnimationTarget> taskTargetOptional =
Arrays.stream(appearedTaskTargets)
@@ -2176,17 +2177,22 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
targetCompat.taskId == mGestureState.getLastStartedTaskId())
.findFirst();
if (!taskTargetOptional.isPresent()) {
finishRecentsAnimationOnTasksAppeared();
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
RemoteAnimationTarget taskTarget = taskTargetOptional.get();
TaskView taskView = mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) {
finishRecentsAnimationOnTasksAppeared();
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
}
ViewGroup splashView = mActivity.getDragLayer();
final QuickstepLauncher quickstepLauncher = mActivity instanceof QuickstepLauncher
? (QuickstepLauncher) mActivity : null;
if (quickstepLauncher != null) {
quickstepLauncher.getDepthController().pauseBlursOnWindows(true);
}
// When revealing the app with launcher splash screen, make the app visible
// and behind the splash view before the splash is animated away.
@@ -2194,7 +2200,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
new SurfaceTransactionApplier(splashView);
SurfaceTransaction transaction = new SurfaceTransaction();
for (RemoteAnimationTarget target : appearedTaskTargets) {
transaction.forSurface(target.leash).setAlpha(1).setLayer(-1);
transaction.forSurface(target.leash).setAlpha(1).setLayer(-1).setShow();
}
surfaceApplier.scheduleApply(transaction);
@@ -2206,16 +2212,25 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finishRecentsAnimationOnTasksAppeared();
// Hiding launcher which shows the app surface behind, then
// finishing recents to the app. After transition finish, showing
// the views on launcher again, so it can be visible when next
// animation starts.
splashView.setAlpha(0);
if (quickstepLauncher != null) {
quickstepLauncher.getDepthController()
.pauseBlursOnWindows(false);
}
finishRecentsAnimationOnTasksAppeared(() -> splashView.setAlpha(1));
}
});
}
}
}
private void finishRecentsAnimationOnTasksAppeared() {
private void finishRecentsAnimationOnTasksAppeared(Runnable onFinishComplete) {
if (mRecentsAnimationController != null) {
mRecentsAnimationController.finish(false /* toRecents */, null /* onFinishComplete */);
mRecentsAnimationController.finish(false /* toRecents */, onFinishComplete);
}
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimationOnTasksAppeared");
}
@@ -15,6 +15,9 @@
*/
package com.android.quickstep;
import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.MotionEventsUtils.isTrackpadThreeFingerSwipe;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
@@ -27,6 +30,7 @@ import android.annotation.Nullable;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import com.android.launcher3.statemanager.BaseState;
@@ -139,8 +143,30 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
private final BaseActivityInterface mActivityInterface;
private final MultiStateCallback mStateCallback;
private final int mGestureId;
private boolean mIsTrackpadGesture;
public enum TrackpadGestureType {
NONE,
// Assigned before we know whether it's a 3-finger or 4-finger gesture.
MULTI_FINGER,
THREE_FINGER,
FOUR_FINGER;
public static TrackpadGestureType getTrackpadGestureType(MotionEvent event) {
if (!isTrackpadMultiFingerSwipe(event)) {
return TrackpadGestureType.NONE;
}
if (isTrackpadThreeFingerSwipe(event)) {
return TrackpadGestureType.THREE_FINGER;
}
if (isTrackpadFourFingerSwipe(event)) {
return TrackpadGestureType.FOUR_FINGER;
}
return TrackpadGestureType.MULTI_FINGER;
}
}
private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE;
private CachedTaskInfo mRunningTask;
private GestureEndTarget mEndTarget;
private RemoteAnimationTarget mLastAppearedTaskTarget;
@@ -249,17 +275,22 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL
}
/**
* Sets if the gesture is is from the trackpad.
* Sets if the gesture is is from the trackpad, if so, whether 3-finger, or 4-finger
*/
public void setIsTrackpadGesture(boolean isTrackpadGesture) {
mIsTrackpadGesture = isTrackpadGesture;
public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) {
mTrackpadGestureType = trackpadGestureType;
}
/**
* @return if the gesture is from the trackpad.
*/
public boolean isTrackpadGesture() {
return mIsTrackpadGesture;
return mTrackpadGestureType != TrackpadGestureType.NONE;
}
public boolean isThreeFingerTrackpadGesture() {
return mTrackpadGestureType == TrackpadGestureType.THREE_FINGER;
}
public boolean isFourFingerTrackpadGesture() {
return mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER;
}
/**
@@ -141,8 +141,8 @@ public class LauncherBackAnimationController {
@Override
public void onBackCancelled() {
handler.post(() -> {
resetPositionAnimated();
mProgressAnimator.reset();
mProgressAnimator.onBackCancelled(
LauncherBackAnimationController.this::resetPositionAnimated);
});
}
@@ -192,7 +192,7 @@ public class LauncherBackAnimationController {
}
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {}
public void onAnimationCancelled() {}
};
SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback, runner);
@@ -249,7 +249,7 @@ public class OverviewCommandHelper {
}
GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE,
false /* isTrackpadGesture */);
GestureState.TrackpadGestureType.NONE);
gestureState.setHandlingAtomicEvent(true);
AbsSwipeUpHandler interactionHandler = mService.getSwipeUpHandlerFactory()
.newHandler(gestureState, cmd.createTime);
@@ -125,6 +125,13 @@ public class QuickstepTestInformationHandler extends TestInformationHandler {
.getTaskbarAllAppsTopPadding());
}
case TestProtocol.REQUEST_TASKBAR_APPS_LIST_SCROLL_Y: {
return getTISBinderUIProperty(Bundle::putInt, tisBinder ->
tisBinder.getTaskbarManager()
.getCurrentActivityContext()
.getTaskbarAllAppsScroll());
}
case TestProtocol.REQUEST_ENABLE_BLOCK_TIMEOUT:
runOnTISBinder(tisBinder -> {
enableBlockingTimeout(tisBinder, true);
@@ -150,10 +150,17 @@ public class RecentsAnimationController {
@UiThread
public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
if (mFinishRequested) {
// If finishing, add to pending finish callbacks, otherwise, if finished, adding to the
// destroyed RunnableList will just trigger the callback to be called immediately
mPendingFinishCallbacks.add(callback);
finishController(toRecents, callback, sendUserLeaveHint, false /* forceFinish */);
}
@UiThread
public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint,
boolean forceFinish) {
mPendingFinishCallbacks.add(callback);
if (!forceFinish && mFinishRequested) {
// If finish has already been requested, then add the callback to the pending list.
// If already finished, then adding it to the destroyed RunnableList will just
// trigger the callback to be called immediately
return;
}
ActiveGestureLog.INSTANCE.addLog(
@@ -165,15 +172,19 @@ public class RecentsAnimationController {
mFinishRequested = true;
mFinishTargetIsLauncher = toRecents;
mOnFinishedListener.accept(this);
mPendingFinishCallbacks.add(callback);
UI_HELPER_EXECUTOR.execute(() -> {
Runnable finishCb = () -> {
mController.finish(toRecents, sendUserLeaveHint);
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
InteractionJankMonitorWrapper.end(
InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
MAIN_EXECUTOR.execute(mPendingFinishCallbacks::executeAllAndDestroy);
});
};
if (forceFinish) {
finishCb.run();
} else {
UI_HELPER_EXECUTOR.execute(finishCb);
}
}
/**
@@ -98,7 +98,7 @@ public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
private @SystemUiStateFlags int mSystemUiStateFlags;
private @SystemUiStateFlags int mSystemUiStateFlags = QuickStepContract.SYSUI_STATE_AWAKE;
private NavigationMode mMode = THREE_BUTTONS;
private NavBarPosition mNavBarPosition;
@@ -967,7 +967,7 @@ public class SystemUiProxy implements ISystemUiProxy {
IRemoteAnimationRunner runner) {
mBackToLauncherCallback = callback;
mBackToLauncherRunner = runner;
if (mBackAnimation == null) {
if (mBackAnimation == null || mBackToLauncherCallback == null) {
return;
}
try {
@@ -115,7 +115,7 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
}
}
// But force-finish it anyways
finishRunningRecentsAnimation(false /* toHome */);
finishRunningRecentsAnimation(false /* toHome */, true /* forceFinish */);
if (mCallbacks != null) {
// If mCallbacks still != null, that means we are getting this startRecentsAnimation()
@@ -291,13 +291,26 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn
* Finishes the running recents animation.
*/
public void finishRunningRecentsAnimation(boolean toHome) {
finishRunningRecentsAnimation(toHome, false /* forceFinish */);
}
/**
* Finishes the running recents animation.
* @param forceFinish will synchronously finish the controller
*/
private void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish) {
if (mController != null) {
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "finishRunningRecentsAnimation", toHome);
mCallbacks.notifyAnimationCanceled();
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
? mController::finishAnimationToHome
: mController::finishAnimationToApp);
if (forceFinish) {
mController.finishController(toHome, null, false /* sendUserLeaveHint */,
true /* forceFinish */);
} else {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
? mController::finishAnimationToHome
: mController::finishAnimationToApp);
}
cleanUpRecentsAnimation();
}
}
@@ -128,19 +128,19 @@ public interface TaskShortcutFactory {
* A menu item, "Save app pair", that allows the user to preserve the current app combination as
* a single persistent icon on the Home screen, allowing for quick split screen initialization.
*/
class SaveAppPairSystemShortcut extends SystemShortcut {
class SaveAppPairSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
private final TaskView mTaskView;
public SaveAppPairSystemShortcut(BaseDraggingActivity target, TaskView taskView) {
super(R.drawable.ic_save_app_pair, R.string.save_app_pair, target,
public SaveAppPairSystemShortcut(BaseDraggingActivity activity, TaskView taskView) {
super(R.drawable.ic_save_app_pair, R.string.save_app_pair, activity,
taskView.getItemInfo(), taskView);
mTaskView = taskView;
}
@Override
public void onClick(View view) {
// TODO (b/274189428): Call "saveAppPair" function in new AppPairController class
((RecentsView) mTarget.getOverviewPanel())
.getSplitSelectController().getAppPairsController().saveAppPair(mTaskView);
}
}
@@ -243,7 +243,17 @@ public final class TaskViewUtils {
TOUCH_RESPONSE_INTERPOLATOR);
out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0,
TOUCH_RESPONSE_INTERPOLATOR);
out.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
final SurfaceTransaction showTransaction = new SurfaceTransaction();
for (int i = targets.apps.length - 1; i >= 0; --i) {
showTransaction.getTransaction().show(targets.apps[i].leash);
}
applier.scheduleApply(showTransaction);
}
});
out.addOnFrameCallback(() -> {
for (RemoteTargetHandle handle : remoteTargetHandles) {
handle.getTaskViewSimulator().apply(handle.getTransformParams());
@@ -24,11 +24,11 @@ import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE;
import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TRACKPAD_GESTURE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.GestureState.DEFAULT_STATE;
import static com.android.quickstep.GestureState.TrackpadGestureType.getTrackpadGestureType;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
@@ -77,11 +77,11 @@ import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -647,7 +647,7 @@ public class TouchInteractionService extends Service
// onConsumerInactive and wipe the previous gesture state
GestureState prevGestureState = new GestureState(mGestureState);
GestureState newGestureState = createGestureState(mGestureState,
isTrackpadMultiFingerSwipe(event));
getTrackpadGestureType(event));
newGestureState.setSwipeUpStartTimeMs(SystemClock.uptimeMillis());
mConsumer.onConsumerAboutToBeSwitched();
mGestureState = newGestureState;
@@ -656,7 +656,7 @@ public class TouchInteractionService extends Service
} else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode()
&& mDeviceState.canTriggerAssistantAction(event)) {
mGestureState = createGestureState(mGestureState,
isTrackpadMultiFingerSwipe(event));
getTrackpadGestureType(event));
// Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we
// should not interrupt it. QuickSwitch assumes that interruption can only
// happen if the next gesture is also quick switch.
@@ -678,7 +678,9 @@ public class TouchInteractionService extends Service
if (mUncheckedConsumer != InputConsumer.NO_OP) {
switch (event.getActionMasked()) {
case ACTION_DOWN, ACTION_UP ->
case ACTION_DOWN:
// fall through
case ACTION_UP:
ActiveGestureLog.INSTANCE.addLog(
/* event= */ "onMotionEvent(" + (int) event.getRawX() + ", "
+ (int) event.getRawY() + "): "
@@ -687,15 +689,18 @@ public class TouchInteractionService extends Service
/* gestureEvent= */ event.getActionMasked() == ACTION_DOWN
? MOTION_DOWN
: MOTION_UP);
case ACTION_MOVE ->
break;
case ACTION_MOVE:
ActiveGestureLog.INSTANCE.addLog("onMotionEvent: "
+ MotionEvent.actionToString(event.getActionMasked()) + ","
+ MotionEvent.classificationToString(event.getClassification())
+ ", pointerCount: " + event.getPointerCount(), MOTION_MOVE);
default ->
break;
default: {
ActiveGestureLog.INSTANCE.addLog("onMotionEvent: "
+ MotionEvent.actionToString(event.getActionMasked()) + ","
+ MotionEvent.classificationToString(event.getClassification()));
}
}
}
@@ -708,9 +713,13 @@ public class TouchInteractionService extends Service
event.setAction(ACTION_CANCEL);
}
// Skip ACTION_POINTER_DOWN and ACTION_POINTER_UP events from trackpad.
if (!mGestureState.isTrackpadGesture() || (action != ACTION_POINTER_DOWN
&& action != ACTION_POINTER_UP)) {
if (mGestureState.isTrackpadGesture() && (action == ACTION_POINTER_DOWN
|| action == ACTION_POINTER_UP)) {
// Skip ACTION_POINTER_DOWN and ACTION_POINTER_UP events from trackpad.
if (action == ACTION_POINTER_DOWN) {
mGestureState.setTrackpadGestureType(getTrackpadGestureType(event));
}
} else {
mUncheckedConsumer.onMotionEvent(event);
}
@@ -744,7 +753,7 @@ public class TouchInteractionService extends Service
}
public GestureState createGestureState(GestureState previousGestureState,
boolean isTrackpadGesture) {
GestureState.TrackpadGestureType trackpadGestureType) {
final GestureState gestureState;
TopTaskTracker.CachedTaskInfo taskInfo;
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
@@ -761,7 +770,7 @@ public class TouchInteractionService extends Service
taskInfo = TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false);
gestureState.updateRunningTask(taskInfo);
}
gestureState.setIsTrackpadGesture(isTrackpadGesture);
gestureState.setTrackpadGestureType(trackpadGestureType);
// Log initial state for the gesture.
ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current running task package name=")
@@ -835,12 +844,18 @@ public class TouchInteractionService extends Service
// If Taskbar is present, we listen for long press to unstash it.
TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext();
if (tac != null && canStartSystemGesture) {
reasonString.append(NEWLINE_PREFIX)
.append(reasonPrefix)
.append(SUBSTRING_PREFIX)
.append("TaskbarActivityContext != null, using TaskbarStashInputConsumer");
base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat, tac);
if (tac != null) {
// Present always on large screen or on small screen w/ flag
DeviceProfile dp = tac.getDeviceProfile();
boolean useTaskbarConsumer = dp.isTaskbarPresent && !TaskbarManager.isPhoneMode(dp);
if (canStartSystemGesture && useTaskbarConsumer) {
reasonString.append(NEWLINE_PREFIX)
.append(reasonPrefix)
.append(SUBSTRING_PREFIX)
.append("TaskbarActivityContext != null, "
+ "using TaskbarStashInputConsumer");
base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat, tac);
}
}
if (mDeviceState.isBubblesExpanded()) {
@@ -860,7 +875,6 @@ public class TouchInteractionService extends Service
}
if (ENABLE_TRACKPAD_GESTURE.get() && mGestureState.isTrackpadGesture()
&& mGestureState.getActivityInterface().isResumed()
&& !previousGestureState.isRecentsAnimationRunning()) {
reasonString = newCompoundString(reasonPrefix)
.append(SUBSTRING_PREFIX)
@@ -203,8 +203,24 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
}
int edgeFlags = ev.getEdgeFlags();
ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
// Disable scrolling in RecentsView for trackpad gestures.
if (!mGestureState.isTrackpadGesture()) {
if (mGestureState.isTrackpadGesture()) {
// Disable scrolling in RecentsView for 3-finger trackpad gesture. We don't know if a
// trackpad motion event is 3-finger or 4-finger with the U API until ACTION_MOVE (we
// skip ACTION_POINTER_UP events in TouchInteractionService), so in order to make sure
// that RecentsView always get a closed sequence of motion events and yet disable
// 3-finger scroll, we do the following (1) always dispatch ACTION_DOWN and ACTION_UP
// trackpad multi-finger motion events. (2) only dispatch 4-finger ACTION_MOVE motion
// events.
switch (ev.getActionMasked()) {
case ACTION_MOVE -> {
if (mGestureState.isFourFingerTrackpadGesture()) {
mRecentsViewDispatcher.dispatchEvent(ev);
}
}
default -> mRecentsViewDispatcher.dispatchEvent(ev);
}
} else {
mRecentsViewDispatcher.dispatchEvent(ev);
}
ev.setEdgeFlags(edgeFlags);
@@ -294,8 +310,14 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
boolean haveNotPassedSlopOnContinuedGesture =
!mPassedSlopOnThisGesture && mPassedPilferInputSlop;
double degrees = Math.toDegrees(Math.atan(upDist / horizontalDist));
boolean isLikelyToStartNewTask = haveNotPassedSlopOnContinuedGesture
|| degrees <= OVERVIEW_MIN_DEGREES;
// Regarding degrees >= -OVERVIEW_MIN_DEGREES - Trackpad gestures can start anywhere
// on the screen, allowing downward swipes. We want to impose the same angle in that
// scenario.
boolean swipeWithinQuickSwitchRange = degrees <= OVERVIEW_MIN_DEGREES
&& (!mGestureState.isTrackpadGesture() || degrees >= -OVERVIEW_MIN_DEGREES);
boolean isLikelyToStartNewTask =
haveNotPassedSlopOnContinuedGesture || swipeWithinQuickSwitchRange;
if (!mPassedPilferInputSlop) {
if (passedSlop) {
@@ -306,9 +328,12 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
// Do not allow quick switch for trackpad 3-finger gestures
// TODO(b/261815244): might need to impose stronger conditions for the swipe
// angle
boolean noQuickSwitchForTrackpadGesture = mGestureState.isTrackpadGesture()
&& isLikelyToStartNewTask;
if (isHorizontalSwipeWhenDisabled || noQuickSwitchForTrackpadGesture) {
boolean noQuickSwitchForThreeFingerGesture = isLikelyToStartNewTask
&& mGestureState.isThreeFingerTrackpadGesture();
boolean noQuickstepForFourFingerGesture = !isLikelyToStartNewTask
&& mGestureState.isFourFingerTrackpadGesture();
if (isHorizontalSwipeWhenDisabled || noQuickSwitchForThreeFingerGesture
|| noQuickstepForFourFingerGesture) {
forceCancelGesture(ev);
break;
}
@@ -33,6 +33,7 @@ public class StatusBarInputConsumer extends DelegateInputConsumer {
private final SystemUiProxy mSystemUiProxy;
private final float mTouchSlop;
private final PointF mDown = new PointF();
private boolean mHasPassedTouchSlop;
public StatusBarInputConsumer(Context context, InputConsumer delegate,
InputMonitorCompat inputMonitor) {
@@ -53,13 +54,21 @@ public class StatusBarInputConsumer extends DelegateInputConsumer {
mDelegate.onMotionEvent(ev);
switch (ev.getActionMasked()) {
case ACTION_DOWN -> mDown.set(ev.getX(), ev.getY());
case ACTION_DOWN -> {
mDown.set(ev.getX(), ev.getY());
mHasPassedTouchSlop = false;
}
case ACTION_MOVE -> {
float displacementY = ev.getY() - mDown.y;
if (displacementY > mTouchSlop) {
setActive(ev);
ev.setAction(ACTION_DOWN);
dispatchTouchEvent(ev);
if (!mHasPassedTouchSlop) {
float displacementY = ev.getY() - mDown.y;
if (Math.abs(displacementY) > mTouchSlop) {
mHasPassedTouchSlop = true;
if (displacementY > 0) {
setActive(ev);
ev.setAction(ACTION_DOWN);
dispatchTouchEvent(ev);
}
}
}
}
}
@@ -26,6 +26,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
@@ -63,11 +64,13 @@ import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.util.Executors;
import com.android.quickstep.GestureState;
import com.android.quickstep.TouchInteractionService.TISBinder;
import com.android.quickstep.util.LottieAnimationColorUtils;
import com.android.quickstep.util.TISBindHelper;
import com.airbnb.lottie.LottieAnimationView;
import java.net.URISyntaxException;
import java.util.Map;
/**
* A page shows after SUW flow to hint users to swipe up from the bottom of the screen to go home
@@ -82,6 +85,9 @@ public class AllSetActivity extends Activity {
private static final String EXTRA_ACCENT_COLOR_LIGHT_MODE = "suwColorAccentLight";
private static final String EXTRA_DEVICE_NAME = "suwDeviceName";
private static final String LOTTIE_PRIMARY_COLOR_TOKEN = ".primary";
private static final String LOTTIE_TERTIARY_COLOR_TOKEN = ".tertiary";
private static final float HINT_BOTTOM_FACTOR = 1 - .94f;
private static final int MAX_SWIPE_DURATION = 350;
@@ -114,7 +120,8 @@ public class AllSetActivity extends Activity {
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
int mode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
Resources resources = getResources();
int mode = resources.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
boolean isDarkTheme = mode == Configuration.UI_MODE_NIGHT_YES;
Intent intent = getIntent();
int accentColor = intent.getIntExtra(
@@ -126,7 +133,7 @@ public class AllSetActivity extends Activity {
mBackground = new BgDrawable(this);
findViewById(R.id.root_view).setBackground(mBackground);
mContentView = findViewById(R.id.content_view);
mSwipeUpShift = getResources().getDimension(R.dimen.allset_swipe_up_shift);
mSwipeUpShift = resources.getDimension(R.dimen.allset_swipe_up_shift);
TextView subtitle = findViewById(R.id.subtitle);
String suwDeviceName = intent.getStringExtra(EXTRA_DEVICE_NAME);
@@ -188,8 +195,15 @@ public class AllSetActivity extends Activity {
// There's a bug in the currently used external Lottie library (v5.2.0), and it doesn't load
// the correct animation from the raw resources when configuration changes, so we need to
// manually load the resource and pass it to Lottie.
mAnimatedBackground.setAnimation(getResources().openRawResource(R.raw.all_set_page_bg),
mAnimatedBackground.setAnimation(resources.openRawResource(R.raw.all_set_page_bg),
null);
LottieAnimationColorUtils.updateColors(
mAnimatedBackground,
Map.of(LOTTIE_PRIMARY_COLOR_TOKEN, R.color.all_set_bg_primary,
LOTTIE_TERTIARY_COLOR_TOKEN, R.color.all_set_bg_tertiary),
getTheme());
startBackgroundAnimation();
}
@@ -233,13 +233,19 @@ abstract class TutorialController implements BackGestureAttemptCallback,
@LayoutRes
protected int getMockHotseatResId() {
return mTutorialFragment.isLargeScreen()
? (mTutorialFragment.isFoldable()
if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) {
return mTutorialFragment.isLargeScreen()
? mTutorialFragment.isFoldable()
? R.layout.redesigned_gesture_tutorial_foldable_mock_hotseat
: R.layout.redesigned_gesture_tutorial_tablet_mock_hotseat
: R.layout.redesigned_gesture_tutorial_mock_hotseat;
} else {
return mTutorialFragment.isLargeScreen()
? mTutorialFragment.isFoldable()
? R.layout.gesture_tutorial_foldable_mock_hotseat
: R.layout.gesture_tutorial_tablet_mock_hotseat)
: (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()
? R.layout.redesigned_gesture_tutorial_mock_hotseat
: R.layout.gesture_tutorial_mock_hotseat);
: R.layout.gesture_tutorial_tablet_mock_hotseat
: R.layout.gesture_tutorial_mock_hotseat;
}
}
@LayoutRes
@@ -0,0 +1,104 @@
/*
* Copyright 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.
*/
package com.android.quickstep.util;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.content.Context;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.quickstep.views.TaskView;
/**
* Mini controller class that handles app pair interactions: saving, modifying, deleting, etc.
*/
public class AppPairsController {
private static final int POINT_THREE_RATIO = 0;
private static final int POINT_FIVE_RATIO = 1;
private static final int POINT_SEVEN_RATIO = 2;
/**
* Used to calculate {@link #complement(int)}
*/
private static final int FULL_RATIO = 2;
private static final int LEFT_TOP = 0;
private static final int RIGHT_BOTTOM = 1 << 2;
// TODO (jeremysim b/274189428): Support saving different ratios in future.
public int DEFAULT_RATIO = POINT_FIVE_RATIO;
private final Context mContext;
private final SplitSelectStateController mSplitSelectStateController;
public AppPairsController(Context context,
SplitSelectStateController splitSelectStateController) {
mContext = context;
mSplitSelectStateController = splitSelectStateController;
}
/**
* Creates a new app pair ItemInfo and adds it to the workspace
*/
public void saveAppPair(TaskView taskView) {
TaskView.TaskIdAttributeContainer[] attributes = taskView.getTaskIdAttributeContainers();
WorkspaceItemInfo app1 = attributes[0].getItemInfo().clone();
WorkspaceItemInfo app2 = attributes[1].getItemInfo().clone();
app1.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
app2.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
app1.rank = DEFAULT_RATIO + LEFT_TOP;
app2.rank = complement(DEFAULT_RATIO) + RIGHT_BOTTOM;
FolderInfo newAppPair = FolderInfo.createAppPair(app1, app2);
// TODO (jeremysim b/274189428): Generate default title here.
newAppPair.title = "App pair 1";
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
MODEL_EXECUTOR.execute(() -> {
newAppPair.contents.forEach(member -> {
member.title = "";
member.bitmap = iconCache.getDefaultIcon(newAppPair.user);
iconCache.getTitleAndIcon(member, member.usingLowResIcon());
});
MAIN_EXECUTOR.execute(() -> {
LauncherAccessibilityDelegate delegate =
Launcher.getLauncher(mContext).getAccessibilityDelegate();
if (delegate != null) {
MAIN_EXECUTOR.execute(() -> delegate.addToWorkspace(newAppPair, true));
}
});
});
}
/**
* Used to calculate the "opposite" side of the split ratio, so we can know how big the split
* apps are supposed to be. This math works because POINT_THREE_RATIO is internally represented
* by 0, POINT_FIVE_RATIO is represented by 1, and POINT_SEVEN_RATIO is represented by 2. There
* are no other supported ratios for now.
*/
private int complement(int ratio1) {
int ratio2 = FULL_RATIO - ratio1;
return ratio2;
}
}
@@ -74,6 +74,10 @@ public class BaseDepthController {
// Hints that there is potentially content behind Launcher and that we shouldn't optimize by
// marking the launcher surface as opaque. Only used in certain Launcher states.
private boolean mHasContentBehindLauncher;
/** Pause applying depth and blur, can be used when something behind the Launcher. */
protected boolean mPauseBlurs;
/**
* Last blur value, in pixels, that was applied.
* For debugging purposes.
@@ -104,6 +108,13 @@ public class BaseDepthController {
mHasContentBehindLauncher = hasContentBehindLauncher;
}
public void pauseBlursOnWindows(boolean pause) {
if (pause != mPauseBlurs) {
mPauseBlurs = pause;
applyDepthAndBlur();
}
}
protected void applyDepthAndBlur() {
float depth = mDepth;
IBinder windowToken = mLauncher.getRootView().getWindowToken();
@@ -121,9 +132,9 @@ public class BaseDepthController {
return;
}
boolean hasOpaqueBg = mLauncher.getScrimView().isFullyOpaque();
boolean isSurfaceOpaque = !mHasContentBehindLauncher && hasOpaqueBg;
boolean isSurfaceOpaque = mPauseBlurs || (!mHasContentBehindLauncher && hasOpaqueBg);
mCurrentBlur = !mCrossWindowBlursEnabled || hasOpaqueBg
mCurrentBlur = !mCrossWindowBlursEnabled || hasOpaqueBg || mPauseBlurs
? 0 : (int) (depth * mMaxBlurRadius);
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()
.setBackgroundBlurRadius(mSurface, mCurrentBlur)
@@ -16,17 +16,18 @@
package com.android.quickstep.util;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.ColorInt;
import android.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Px;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.anim.Interpolators;
@@ -35,7 +36,9 @@ import com.android.launcher3.anim.Interpolators;
* Utility class for drawing a rounded-rect border around a view.
* <p>
* To use this class:
* 1. Create an instance in the target view.
* 1. Create an instance in the target view. NOTE: The border will animate outwards from the
* provided border bounds. If the border will not be visible outside of those bounds, then a
* {@link ViewScaleTargetProvider} must be provided in the constructor.
* 2. Override the target view's {@link android.view.View#draw(Canvas)} method and call
* {@link BorderAnimator#drawBorder(Canvas)} after {@code super.draw(canvas)}.
* 3. Call {@link BorderAnimator#buildAnimator(boolean)} and start the animation or call
@@ -56,12 +59,13 @@ public final class BorderAnimator {
@Px private final int mBorderWidthPx;
@Px private final int mBorderRadiusPx;
@NonNull private final Runnable mInvalidateViewCallback;
@Nullable private final ViewScaleTargetProvider mViewScaleTargetProvider;
private final long mAppearanceDurationMs;
private final long mDisappearanceDurationMs;
@NonNull private final Interpolator mInterpolator;
@NonNull private final Paint mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int mAlignmentAdjustment;
private float mAlignmentAdjustment;
@Nullable private Animator mRunningBorderAnimation;
@@ -76,6 +80,22 @@ public final class BorderAnimator {
borderRadiusPx,
borderColor,
invalidateViewCallback,
/* viewScaleTargetProvider= */ null);
}
public BorderAnimator(
@NonNull BorderBoundsBuilder borderBoundsBuilder,
int borderWidthPx,
int borderRadiusPx,
@ColorInt int borderColor,
@NonNull Runnable invalidateViewCallback,
@Nullable ViewScaleTargetProvider viewScaleTargetProvider) {
this(borderBoundsBuilder,
borderWidthPx,
borderRadiusPx,
borderColor,
invalidateViewCallback,
viewScaleTargetProvider,
DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
DEFAULT_DISAPPEARANCE_ANIMATION_DURATION_MS,
DEFAULT_INTERPOLATOR);
@@ -87,6 +107,7 @@ public final class BorderAnimator {
int borderRadiusPx,
@ColorInt int borderColor,
@NonNull Runnable invalidateViewCallback,
@Nullable ViewScaleTargetProvider viewScaleTargetProvider,
long appearanceDurationMs,
long disappearanceDurationMs,
@NonNull Interpolator interpolator) {
@@ -94,6 +115,7 @@ public final class BorderAnimator {
mBorderWidthPx = borderWidthPx;
mBorderRadiusPx = borderRadiusPx;
mInvalidateViewCallback = invalidateViewCallback;
mViewScaleTargetProvider = viewScaleTargetProvider;
mAppearanceDurationMs = appearanceDurationMs;
mDisappearanceDurationMs = disappearanceDurationMs;
mInterpolator = interpolator;
@@ -106,16 +128,14 @@ public final class BorderAnimator {
private void updateOutline() {
float interpolatedProgress = mInterpolator.getInterpolation(
mBorderAnimationProgress.value);
mAlignmentAdjustment = (int) Utilities.mapBoundToRange(
mBorderAnimationProgress.value,
/* lowerBound= */ 0f,
/* upperBound= */ 1f,
/* toMin= */ 0f,
/* toMax= */ (float) (mBorderWidthPx / 2f),
mInterpolator);
float borderWidth = mBorderWidthPx * interpolatedProgress;
// Outset the border by half the width to create an outwards-growth animation
mAlignmentAdjustment = (-borderWidth / 2f)
// Inset the border if we are scaling the container up
+ (mViewScaleTargetProvider == null ? 0 : mBorderWidthPx);
mBorderPaint.setAlpha(Math.round(255 * interpolatedProgress));
mBorderPaint.setStrokeWidth(Math.round(mBorderWidthPx * interpolatedProgress));
mBorderPaint.setStrokeWidth(borderWidth);
mInvalidateViewCallback.run();
}
@@ -126,13 +146,16 @@ public final class BorderAnimator {
* calling super.
*/
public void drawBorder(Canvas canvas) {
// Increase the radius if we are scaling the container up
float radiusAdjustment = mViewScaleTargetProvider == null
? -mAlignmentAdjustment : mAlignmentAdjustment;
canvas.drawRoundRect(
/* left= */ mBorderBounds.left + mAlignmentAdjustment,
/* top= */ mBorderBounds.top + mAlignmentAdjustment,
/* right= */ mBorderBounds.right - mAlignmentAdjustment,
/* bottom= */ mBorderBounds.bottom - mAlignmentAdjustment,
/* rx= */ mBorderRadiusPx - mAlignmentAdjustment,
/* ry= */ mBorderRadiusPx - mAlignmentAdjustment,
/* rx= */ mBorderRadiusPx + radiusAdjustment,
/* ry= */ mBorderRadiusPx + radiusAdjustment,
/* paint= */ mBorderPaint);
}
@@ -146,22 +169,81 @@ public final class BorderAnimator {
mRunningBorderAnimation.setDuration(
isAppearing ? mAppearanceDurationMs : mDisappearanceDurationMs);
mRunningBorderAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
setViewScales();
}
});
mRunningBorderAnimation.addListener(
AnimatorListeners.forEndCallback(() -> mRunningBorderAnimation = null));
AnimatorListeners.forEndCallback(() -> {
mRunningBorderAnimation = null;
if (isAppearing) {
return;
}
resetViewScales();
}));
return mRunningBorderAnimation;
}
/**
* Immediately shows/hides the border without an animation.
*
* <p>
* To animate the appearance/disappearance, see {@link BorderAnimator#buildAnimator(boolean)}
*/
public void setBorderVisible(boolean visible) {
if (mRunningBorderAnimation != null) {
mRunningBorderAnimation.end();
}
mBorderBoundsBuilder.updateBorderBounds(mBorderBounds);
if (visible) {
setViewScales();
}
mBorderAnimationProgress.updateValue(visible ? 1f : 0f);
if (!visible) {
resetViewScales();
}
}
private void setViewScales() {
if (mViewScaleTargetProvider == null) {
return;
}
View container = mViewScaleTargetProvider.getContainerView();
float width = container.getWidth();
float height = container.getHeight();
// scale up just enough to make room for the border
float scaleX = 1f + ((2 * mBorderWidthPx) / width);
float scaleY = 1f + ((2 * mBorderWidthPx) / height);
container.setPivotX(width / 2);
container.setPivotY(height / 2);
container.setScaleX(scaleX);
container.setScaleY(scaleY);
View contentView = mViewScaleTargetProvider.getContentView();
contentView.setPivotX(contentView.getWidth() / 2f);
contentView.setPivotY(contentView.getHeight() / 2f);
contentView.setScaleX(1f / scaleX);
contentView.setScaleY(1f / scaleY);
}
private void resetViewScales() {
if (mViewScaleTargetProvider == null) {
return;
}
View container = mViewScaleTargetProvider.getContainerView();
container.setPivotX(container.getWidth());
container.setPivotY(container.getHeight());
container.setScaleX(1f);
container.setScaleY(1f);
View contentView = mViewScaleTargetProvider.getContentView();
contentView.setPivotX(contentView.getWidth() / 2f);
contentView.setPivotY(contentView.getHeight() / 2f);
contentView.setScaleX(1f);
contentView.setScaleY(1f);
}
/**
@@ -174,4 +256,25 @@ public final class BorderAnimator {
*/
void updateBorderBounds(Rect rect);
}
/**
* Provider for scaling target views for the beginning and end of this animation.
*/
public interface ViewScaleTargetProvider {
/**
* Returns the content view's container. This view will be scaled up to make room for the
* border.
*/
@NonNull
View getContainerView();
/**
* Returns the content view. This view will be scaled down reciprocally to the container's
* up-scaling to maintain its original size. This should be the view containing all of the
* content being surrounded by the border.
*/
@NonNull
View getContentView();
}
}
@@ -0,0 +1,90 @@
/*
* 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.
*/
package com.android.quickstep.util;
import static com.airbnb.lottie.LottieProperty.COLOR_FILTER;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.airbnb.lottie.LottieAnimationView;
import com.airbnb.lottie.model.KeyPath;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/** Utility class for programmatically updating Lottie animation tokenized colors. */
public final class LottieAnimationColorUtils {
private LottieAnimationColorUtils() {}
/**
* Updates the given Lottie animation's tokenized colors according to the given mapping.
* <p>
* Use this method signature only when {@code tokenToColorCodeMap} maps to packed ARBG color
* integers.
* <p>
* @param animationView {@link LottieAnimationView} whose animation's colors need to be updated
* @param tokenToColorCodeMap A mapping from the color tokens used in the Lottie file used in
* {@code animationView} to packed ARBG color integers.
*/
public static void updateColors(
@NonNull LottieAnimationView animationView,
@NonNull Map<String, Integer> tokenToColorCodeMap) {
updateColors(animationView, tokenToColorCodeMap, null);
}
/**
* Updates the given Lottie animation's tokenized colors according to the given mapping.
* <p>
* Use this method signature with a non-null theme only when {@code tokenToColorCodeMap} maps
* to color resource references.
* <p>
* @param animationView {@link LottieAnimationView} whose animation's colors need to be updated
* @param tokenToColorCodeMap A mapping from the color tokens used in the Lottie file used in
* {@code animationView} to packed ARBG color integers or color
* resource references.
* @param theme {@link Theme} to be used when resolving color resource references. {@code null}
* iff {@code tokenToColorCodeMap} maps to packed ARBG color integers.
*/
public static void updateColors(
@NonNull LottieAnimationView animationView,
@NonNull Map<String, Integer> tokenToColorCodeMap,
@Nullable Theme theme) {
Resources resources = animationView.getResources();
final Map<String, Integer> tokenToColorMap = theme == null
// tokenToColorCodeMap maps directly to ARBG values
? tokenToColorCodeMap
// tokenToColorCodeMap maps to color references, build a mapping to resolved colors
: tokenToColorCodeMap.keySet().stream().collect(Collectors.toMap(
Function.identity(),
token -> resources.getColor(tokenToColorCodeMap.get(token), theme)));
animationView.addLottieOnCompositionLoadedListener(
composition -> tokenToColorMap.forEach(
(token, color) -> animationView.addValueCallback(
new KeyPath("**", token, "**"),
COLOR_FILTER,
frameInfo -> new PorterDuffColorFilter(
color, PorterDuff.Mode.SRC_ATOP))));
}
}

Some files were not shown because too many files have changed in this diff Show More