From fa3d403840dffe0a6ba000b1691cfd40f426916e Mon Sep 17 00:00:00 2001 From: nift4 <nift4@protonmail.com> Date: Wed, 5 Apr 2023 12:38:05 +0200 Subject: [PATCH] Power menu styles: Initial checkin for U [1/3] 2024/05/07 - ikeramat & dhina17: port to u-qpr2 Squashed: - SystemUI: power menu styles refresh Change-Id: I8e4a29ab0f65e3051b898e49e96ed0b2ef43acca --- .../drawable/global_actions_background.xml | 22 + .../global_actions_column_seascape.xml | 68 ++ .../layout-land/global_actions_grid_item.xml | 61 ++ .../global_actions_grid_seascape.xml | 79 ++ .../global_actions_grid_v2.xml | 68 ++ .../layout-sw600dp/global_actions_grid_v2.xml | 76 ++ .../res/layout/global_actions_grid.xml | 3 +- .../res/layout/global_actions_grid_item.xml | 60 ++ .../res/layout/global_actions_grid_v2.xml | 41 + .../res/layout/global_actions_lock_view.xml | 35 + .../global_actions_power_dialog_flow.xml | 2 +- .../SystemUI/res/values-sw392dp/dimens.xml | 8 + .../SystemUI/res/values-sw410dp/dimens.xml | 8 + .../SystemUI/res/values-sw600dp/config.xml | 6 + .../SystemUI/res/values-sw600dp/dimens.xml | 3 + packages/SystemUI/res/values/colors.xml | 12 + packages/SystemUI/res/values/config.xml | 6 + packages/SystemUI/res/values/dimens.xml | 11 + .../GlobalActionsColumnLayout.java | 3 +- .../globalactions/GlobalActionsDialog.java | 807 ++++++++++++++++++ .../GlobalActionsDialogLite.java | 160 +++- .../globalactions/GlobalActionsImpl.java | 33 +- .../GlobalActionsPowerDialog.java | 5 +- .../globalactions/MinHeightScrollView.java | 43 + .../interactor/FooterActionsInteractor.kt | 15 +- .../GlobalActionsDialogTest.java | 601 +++++++++++++ .../android/server/policy/GlobalActions.java | 6 +- .../server/policy/PhoneWindowManager.java | 9 + 28 files changed, 2206 insertions(+), 45 deletions(-) create mode 100644 packages/SystemUI/res/drawable/global_actions_background.xml create mode 100644 packages/SystemUI/res/layout-land/global_actions_column_seascape.xml create mode 100644 packages/SystemUI/res/layout-land/global_actions_grid_item.xml create mode 100644 packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml create mode 100644 packages/SystemUI/res/layout-sw600dp-land/global_actions_grid_v2.xml create mode 100644 packages/SystemUI/res/layout-sw600dp/global_actions_grid_v2.xml create mode 100644 packages/SystemUI/res/layout/global_actions_grid_item.xml create mode 100644 packages/SystemUI/res/layout/global_actions_grid_v2.xml create mode 100644 packages/SystemUI/res/layout/global_actions_lock_view.xml create mode 100644 packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java create mode 100644 packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java diff --git a/packages/SystemUI/res/drawable/global_actions_background.xml b/packages/SystemUI/res/drawable/global_actions_background.xml new file mode 100644 index 000000000000..ebabcebf4240 --- /dev/null +++ b/packages/SystemUI/res/drawable/global_actions_background.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +* Copyright 2021, The Android Open Source Project +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@color/global_actions_extra_background"/> + <corners android:radius="@dimen/global_actions_corner_radius" /> +</shape> diff --git a/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml b/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml new file mode 100644 index 000000000000..412beb789deb --- /dev/null +++ b/packages/SystemUI/res/layout-land/global_actions_column_seascape.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 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.systemui.globalactions.GlobalActionsColumnLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/global_actions_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:clipToPadding="false" + android:theme="@style/Theme.SystemUI.QuickSettings" + android:gravity="center_horizontal | bottom" + android:clipChildren="false" +> + <LinearLayout + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:padding="0dp" + android:orientation="horizontal" + > + <!-- Grid of action items --> + <com.android.systemui.globalactions.ListGridLayout + android:id="@android:id/list" + android:layout_gravity="bottom|left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginBottom="@dimen/global_actions_grid_side_margin" + android:translationZ="@dimen/global_actions_translate" + android:paddingLeft="@dimen/global_actions_grid_vertical_padding" + android:paddingRight="@dimen/global_actions_grid_vertical_padding" + android:paddingTop="@dimen/global_actions_grid_horizontal_padding" + android:paddingBottom="@dimen/global_actions_grid_horizontal_padding" + android:background="?android:attr/colorBackgroundFloating" + /> + <!-- For separated items--> + <LinearLayout + android:id="@+id/separated_button" + android:layout_gravity="top|left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="@dimen/global_actions_grid_side_margin" + android:layout_marginBottom="@dimen/global_actions_grid_side_margin" + android:paddingLeft="@dimen/global_actions_grid_vertical_padding" + android:paddingRight="@dimen/global_actions_grid_vertical_padding" + android:paddingTop="@dimen/global_actions_grid_horizontal_padding" + android:paddingBottom="@dimen/global_actions_grid_horizontal_padding" + android:orientation="horizontal" + android:background="?android:attr/colorBackgroundFloating" + android:translationZ="@dimen/global_actions_translate" + /> + </LinearLayout> + +</com.android.systemui.globalactions.GlobalActionsColumnLayout> diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml new file mode 100644 index 000000000000..0f9deaa3c569 --- /dev/null +++ b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2019 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 + --> + +<!-- RelativeLayouts have an issue enforcing minimum heights, so just + work around this for now with LinearLayouts. --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:layout_marginTop="@dimen/global_actions_grid_item_side_margin" + android:layout_marginBottom="@dimen/global_actions_grid_item_side_margin" + android:layout_marginLeft="@dimen/global_actions_grid_item_vertical_margin" + android:layout_marginRight="@dimen/global_actions_grid_item_vertical_margin" +> + <LinearLayout + android:layout_width="@dimen/global_actions_grid_item_height" + android:layout_height="@dimen/global_actions_grid_item_width" + android:gravity="top|center_horizontal" + android:orientation="vertical" + > + <ImageView + android:id="@*android:id/icon" + android:layout_width="@dimen/global_actions_grid_item_icon_width" + android:layout_height="@dimen/global_actions_grid_item_icon_height" + android:layout_marginTop="@dimen/global_actions_grid_item_icon_top_margin" + android:layout_marginBottom="@dimen/global_actions_grid_item_icon_bottom_margin" + android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin" + android:scaleType="centerInside" + android:tint="@color/global_actions_text" + /> + + <TextView + android:id="@*android:id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" + android:singleLine="true" + android:gravity="center" + android:textSize="12dp" + android:textColor="@color/global_actions_text" + android:textAppearance="?android:attr/textAppearanceSmall" + /> + </LinearLayout> +</LinearLayout> diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml new file mode 100644 index 000000000000..e52ad2acadc0 --- /dev/null +++ b/packages/SystemUI/res/layout-land/global_actions_grid_seascape.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="utf-8"?> +<com.android.systemui.globalactions.GlobalActionsGridLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/global_actions_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:theme="@style/Theme.SystemUI.QuickSettings" + android:gravity="left | center_vertical" + android:clipChildren="false" + android:clipToPadding="false" + android:paddingLeft="@dimen/global_actions_grid_container_shadow_offset" + android:layout_marginLeft="@dimen/global_actions_grid_container_negative_shadow_offset" +> + <LinearLayout + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:padding="0dp" + android:orientation="vertical" + android:clipChildren="false" + android:clipToPadding="false" + android:layout_marginLeft="@dimen/global_actions_grid_container_bottom_margin" + > + <!-- For separated items--> + <LinearLayout + android:id="@+id/separated_button" + android:layout_gravity="top|left" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/global_actions_grid_side_margin" + android:layout_marginBottom="@dimen/global_actions_grid_side_margin" + android:paddingLeft="@dimen/global_actions_grid_vertical_padding" + android:paddingRight="@dimen/global_actions_grid_vertical_padding" + android:paddingTop="@dimen/global_actions_grid_horizontal_padding" + android:paddingBottom="@dimen/global_actions_grid_horizontal_padding" + android:orientation="horizontal" + android:layoutDirection="rtl" + android:background="?android:attr/colorBackgroundFloating" + android:gravity="center" + android:translationZ="@dimen/global_actions_translate" + /> + <!-- Grid of action items --> + <com.android.systemui.globalactions.ListGridLayout + android:id="@android:id/list" + android:layout_gravity="bottom|left" + android:gravity="right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_marginBottom="@dimen/global_actions_grid_side_margin" + android:translationZ="@dimen/global_actions_translate" + android:paddingLeft="@dimen/global_actions_grid_vertical_padding" + android:paddingRight="@dimen/global_actions_grid_vertical_padding" + android:paddingTop="@dimen/global_actions_grid_horizontal_padding" + android:paddingBottom="@dimen/global_actions_grid_horizontal_padding" + android:background="?android:attr/colorBackgroundFloating" + > + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layoutDirection="locale" + /> + </com.android.systemui.globalactions.ListGridLayout> + </LinearLayout> + +</com.android.systemui.globalactions.GlobalActionsGridLayout> diff --git a/packages/SystemUI/res/layout-sw600dp-land/global_actions_grid_v2.xml b/packages/SystemUI/res/layout-sw600dp-land/global_actions_grid_v2.xml new file mode 100644 index 000000000000..953a29e3a07e --- /dev/null +++ b/packages/SystemUI/res/layout-sw600dp-land/global_actions_grid_v2.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/global_actions_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal"> + + <LinearLayout + android:layout_weight="1" + android:layout_height="match_parent" + android:layout_width="0dp" + android:clipChildren="false" + android:orientation="vertical" + android:clipToPadding="false" + android:id="@+id/controls_pane" + > + <LinearLayout + android:id="@+id/global_actions_controls" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"/> + </LinearLayout> + + <LinearLayout + android:layout_weight="1" + android:layout_height="match_parent" + android:layout_width="0dp" + android:orientation="vertical" + android:id="@+id/nfc_pane" + > + <include layout="@layout/global_actions_view" /> + + <include layout="@layout/global_actions_lock_view" /> + + <LinearLayout + android:id="@+id/global_actions_grid_root" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:orientation="vertical" + android:clipToPadding="false"> + + <FrameLayout + android:id="@+id/global_actions_wallet" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + </LinearLayout> + + </LinearLayout> +</LinearLayout> diff --git a/packages/SystemUI/res/layout-sw600dp/global_actions_grid_v2.xml b/packages/SystemUI/res/layout-sw600dp/global_actions_grid_v2.xml new file mode 100644 index 000000000000..6ffcef7b24e6 --- /dev/null +++ b/packages/SystemUI/res/layout-sw600dp/global_actions_grid_v2.xml @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/global_actions_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_weight="1" + android:layout_height="0dp" + android:layout_width="match_parent" + android:orientation="vertical" + android:id="@+id/nfc_pane" + > + + <include layout="@layout/global_actions_view" /> + + <include layout="@layout/global_actions_lock_view" /> + + + <com.android.systemui.globalactions.MinHeightScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:scrollbars="none"> + + <LinearLayout + android:id="@+id/global_actions_grid_root" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:orientation="vertical" + android:clipToPadding="false"> + + <FrameLayout + android:id="@+id/global_actions_wallet" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + </LinearLayout> + </com.android.systemui.globalactions.MinHeightScrollView> + + </LinearLayout> + + <LinearLayout + android:layout_weight="1" + android:layout_height="0dp" + android:layout_width="match_parent" + android:orientation="vertical" + android:id="@+id/controls_pane" + android:clipToPadding="false" + android:clipChildren="false"> + <LinearLayout + android:id="@+id/global_actions_controls" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"/> + </LinearLayout> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid.xml b/packages/SystemUI/res/layout/global_actions_grid.xml index 8c621b92a513..33d573d58a8d 100644 --- a/packages/SystemUI/res/layout/global_actions_grid.xml +++ b/packages/SystemUI/res/layout/global_actions_grid.xml @@ -7,7 +7,6 @@ android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false" - android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset" > <FrameLayout @@ -92,4 +91,4 @@ </LinearLayout> </com.android.systemui.globalactions.GlobalActionsGridLayout> -</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml new file mode 100644 index 000000000000..31c7cbf6ff1b --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 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. +--> + +<!-- RelativeLayouts have an issue enforcing minimum heights, so just + work around this for now with LinearLayouts. --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:layout_marginTop="@dimen/global_actions_grid_item_vertical_margin" + android:layout_marginBottom="@dimen/global_actions_grid_item_vertical_margin" + android:layout_marginLeft="@dimen/global_actions_grid_item_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_item_side_margin" +> + <LinearLayout + android:layout_width="@dimen/global_actions_grid_item_width" + android:layout_height="@dimen/global_actions_grid_item_height" + android:gravity="top|center_horizontal" + android:orientation="vertical" + > + <ImageView + android:id="@*android:id/icon" + android:layout_width="@dimen/global_actions_grid_item_icon_width" + android:layout_height="@dimen/global_actions_grid_item_icon_height" + android:layout_marginTop="@dimen/global_actions_grid_item_icon_top_margin" + android:layout_marginBottom="@dimen/global_actions_grid_item_icon_bottom_margin" + android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin" + android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin" + android:scaleType="centerInside" + android:tint="@color/global_actions_text" + /> + + <TextView + android:id="@*android:id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" + android:singleLine="true" + android:gravity="center" + android:textSize="12dp" + android:textColor="@color/global_actions_text" + android:textAppearance="?android:attr/textAppearanceSmall" + /> + </LinearLayout> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml new file mode 100644 index 000000000000..30ffc32ce1f8 --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/global_actions_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" +> + + <include layout="@layout/global_actions_view" /> + + <include layout="@layout/global_actions_lock_view" /> + + <com.android.systemui.globalactions.MinHeightScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:scrollbars="none"> + + <LinearLayout + android:id="@+id/global_actions_grid_root" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipChildren="false" + android:orientation="vertical" + android:clipToPadding="false"> + + <FrameLayout + android:id="@+id/global_actions_wallet" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + <LinearLayout + android:id="@+id/global_actions_controls" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"/> + + </LinearLayout> + </com.android.systemui.globalactions.MinHeightScrollView> +</LinearLayout> diff --git a/packages/SystemUI/res/layout/global_actions_lock_view.xml b/packages/SystemUI/res/layout/global_actions_lock_view.xml new file mode 100644 index 000000000000..eccc63688065 --- /dev/null +++ b/packages/SystemUI/res/layout/global_actions_lock_view.xml @@ -0,0 +1,35 @@ +<!-- + ~ Copyright (C) 2020 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:id="@+id/global_actions_lock_message_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone"> + <TextView + android:id="@+id/global_actions_lock_message" + style="@style/TextAppearance.Control.Title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="@dimen/global_actions_side_margin" + android:drawablePadding="12dp" + android:gravity="center" + android:text="@string/global_action_lock_message" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.35"/> +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/packages/SystemUI/res/layout/global_actions_power_dialog_flow.xml b/packages/SystemUI/res/layout/global_actions_power_dialog_flow.xml index 478aa5b894a0..1d27e56a06cd 100644 --- a/packages/SystemUI/res/layout/global_actions_power_dialog_flow.xml +++ b/packages/SystemUI/res/layout/global_actions_power_dialog_flow.xml @@ -20,7 +20,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" - android:background="@drawable/global_actions_lite_background" + android:background="@android:color/transparent" android:padding="@dimen/global_actions_lite_padding" android:clipChildren="false" android:clipToPadding="false"> diff --git a/packages/SystemUI/res/values-sw392dp/dimens.xml b/packages/SystemUI/res/values-sw392dp/dimens.xml index 96af3c13f32e..2d77cf6b6975 100644 --- a/packages/SystemUI/res/values-sw392dp/dimens.xml +++ b/packages/SystemUI/res/values-sw392dp/dimens.xml @@ -20,8 +20,16 @@ <dimen name="global_actions_grid_horizontal_padding">3dp</dimen> <dimen name="global_actions_grid_item_side_margin">10dp</dimen> + <dimen name="global_actions_grid_item_vertical_margin">6dp</dimen> + <dimen name="global_actions_grid_item_width">72dp</dimen> <dimen name="global_actions_grid_item_height">72dp</dimen> + <dimen name="global_actions_grid_item_icon_width">22dp</dimen> + <dimen name="global_actions_grid_item_icon_height">22dp</dimen> + <dimen name="global_actions_grid_item_icon_top_margin">14dp</dimen> + <dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen> + <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen> + <!-- Home Controls --> <dimen name="global_actions_side_margin">16dp</dimen> diff --git a/packages/SystemUI/res/values-sw410dp/dimens.xml b/packages/SystemUI/res/values-sw410dp/dimens.xml index ff6e005a94c7..f08e985a3077 100644 --- a/packages/SystemUI/res/values-sw410dp/dimens.xml +++ b/packages/SystemUI/res/values-sw410dp/dimens.xml @@ -25,8 +25,16 @@ <dimen name="global_actions_grid_horizontal_padding">4dp</dimen> <dimen name="global_actions_grid_item_side_margin">12dp</dimen> + <dimen name="global_actions_grid_item_vertical_margin">8dp</dimen> + <dimen name="global_actions_grid_item_width">72dp</dimen> <dimen name="global_actions_grid_item_height">72dp</dimen> + <dimen name="global_actions_grid_item_icon_width">24dp</dimen> + <dimen name="global_actions_grid_item_icon_height">24dp</dimen> + <dimen name="global_actions_grid_item_icon_top_margin">18dp</dimen> + <dimen name="global_actions_grid_item_icon_side_margin">24dp</dimen> + <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen> + <!-- Biometric Auth pattern view size, better to align keyguard_security_width --> <dimen name="biometric_auth_pattern_view_size">348dp</dimen> </resources> diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index 1f671ac4c875..9c804f451f98 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -32,6 +32,12 @@ <!-- orientation of the dead zone when touches have recently occurred elsewhere on screen --> <integer name="navigation_bar_deadzone_orientation">0</integer> + <!-- Whether wallet view is shown in landscape / seascape orientations --> + <bool name="global_actions_show_landscape_wallet_view">true</bool> + + <!-- Max number of columns for quick controls area --> + <integer name="controls_max_columns">4</integer> + <!-- How many lines to show in the security footer --> <integer name="qs_security_footer_maxLines">1</integer> diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 1e54fc9e1445..9204278a851f 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -64,6 +64,9 @@ <!-- Text size for user name in user switcher --> <dimen name="kg_user_switcher_text_size">18sp</dimen> + <!-- TODO(himanshujaju) - add comments --> + <dimen name="global_actions_wallet_top_margin">5dp</dimen> + <dimen name="global_actions_grid_item_layout_height">80dp</dimen> <dimen name="qs_brightness_margin_bottom">16dp</dimen> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 774a3662fca6..436cc4e66c23 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -34,6 +34,17 @@ <!-- The color of the background in the grid of the Global Actions menu --> <color name="global_actions_grid_background">#F1F3F4</color> + <color name="global_actions_extra_background">@*android:color/primary_device_default_dark</color> + + <!-- The color of the text in the Global Actions menu --> + <color name="global_actions_text">@color/GM2_grey_700</color> + + <!-- The color of the text in the Global Actions menu --> + <color name="global_actions_alert_text">@color/GM2_red_700</color> + + <!-- The color of the background of the emergency button when home controls are visible --> + <color name="global_actions_emergency_background">@color/GM2_red_400</color> + <color name="global_actions_emergency_text">@color/GM2_grey_100</color> <!-- Colors for Power Menu Lite --> <color name="global_actions_lite_background">@*android:color/primary_device_default_light</color> @@ -155,6 +166,7 @@ <color name="GM2_grey_900">#202124</color> <color name="GM2_red_300">#F28B82</color> + <color name="GM2_red_400">#EE675C</color> <color name="GM2_red_500">#EA4335</color> <color name="GM2_red_600">#B3261E</color> <color name="GM2_red_700">#C5221F</color> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 550a4a2573c3..e4f4c2973d9e 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -565,6 +565,9 @@ <!-- Max number of columns for quick controls area --> <integer name="controls_max_columns">2</integer> + <!-- Max number of columns for power menu --> + <integer name="power_menu_max_columns">3</integer> + <!-- Max number of columns for power menu lite --> <integer name="power_menu_lite_max_columns">2</integer> <!-- Max number of rows for power menu lite --> @@ -593,6 +596,9 @@ <!-- content URL in a notification when ACTION_BATTERY_CHANGED.EXTRA_PRESENT field is false --> <string translatable="false" name="config_batteryStateUnknownUrl"></string> + <!-- Whether wallet view is shown in landscape / seascape orientations --> + <bool name="global_actions_show_landscape_wallet_view">false</bool> + <!-- Package name of the preferred system app to perform eSOS action --> <string name="config_preferredEmergencySosPackage" translatable="false"></string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a0f236c72143..1fc8b0c03117 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -992,14 +992,25 @@ <dimen name="global_actions_grid_item_layout_height">98dp</dimen> <dimen name="global_actions_grid_item_side_margin">5dp</dimen> + <dimen name="global_actions_grid_item_vertical_margin">4dp</dimen> + <dimen name="global_actions_grid_item_width">64dp</dimen> <dimen name="global_actions_grid_item_height">64dp</dimen> + <dimen name="global_actions_grid_item_icon_width">20dp</dimen> + <dimen name="global_actions_grid_item_icon_height">20dp</dimen> + <dimen name="global_actions_grid_item_icon_top_margin">12dp</dimen> + <dimen name="global_actions_grid_item_icon_side_margin">22dp</dimen> + <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen> + <!-- Margins at the left and right of the power menu and home controls widgets. --> <dimen name="global_actions_side_margin">10dp</dimen> <!-- Amount to shift the layout when exiting/entering for controls activities --> <dimen name="global_actions_controls_y_translation">20dp</dimen> + <!-- Shift quick access wallet down in Global Actions when Controls are unavailable --> + <dimen name="global_actions_wallet_top_margin">40dp</dimen> + <!-- Shutdown and restart actions are larger in power options dialog --> <dimen name="global_actions_power_dialog_item_height">128dp</dimen> <dimen name="global_actions_power_dialog_item_width">128dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsColumnLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsColumnLayout.java index a3065d3cca59..78b9054b813e 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsColumnLayout.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsColumnLayout.java @@ -42,7 +42,8 @@ public class GlobalActionsColumnLayout extends GlobalActionsLayout { protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - post(() -> updateSnap()); + // TODO re-add this when adding animation for it + // post(() -> updateSnap()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java new file mode 100644 index 000000000000..197e94be5cd9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -0,0 +1,807 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2022 droid-ng + * + * 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.systemui.globalactions; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; + +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; +import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE; +import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.Nullable; +import android.app.IActivityManager; +import android.app.PendingIntent; +import android.app.admin.DevicePolicyManager; +import android.app.trust.TrustManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.PackageManager; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.UserManager; +import android.provider.Settings; +import android.service.dreams.IDreamManager; +import android.telecom.TelecomManager; +import android.transition.AutoTransition; +import android.transition.TransitionManager; +import android.transition.TransitionSet; +import android.util.Log; +import android.view.IWindowManager; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LifecycleOwner; + +import com.android.app.animation.Interpolators; +import com.android.internal.R; +import com.android.systemui.animation.Expandable; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.view.RotationPolicy; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.animation.DialogLaunchAnimator; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.controls.dagger.ControlsComponent; +import com.android.systemui.controls.ControlsServiceInfo; +import com.android.systemui.controls.controller.ControlsController; +import com.android.systemui.controls.dagger.ControlsComponent; +import com.android.systemui.controls.management.ControlsAnimations; +import com.android.systemui.controls.ui.ControlsUiController; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.model.SysUiState; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.GlobalActions.GlobalActionsManager; +import com.android.systemui.plugins.GlobalActionsPanelPlugin; +import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.shade.ShadeController; +import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.phone.CentralSurfaces; +import com.android.systemui.statusbar.phone.LightBarController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.window.StatusBarWindowController; +import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.android.systemui.util.RingerModeTracker; +import com.android.systemui.util.leak.RotationUtils; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.settings.SecureSettings; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Executor; + +import javax.inject.Inject; +import javax.inject.Provider; + +/** + * Helper to show the global actions dialog. Each item is an {@link Action} that may show depending + * on whether the keyguard is showing, and whether the device is provisioned. + * This version includes wallet and controls. + */ +public class GlobalActionsDialog extends GlobalActionsDialogLite + implements DialogInterface.OnDismissListener, + DialogInterface.OnShowListener, + ConfigurationController.ConfigurationListener, + GlobalActionsPanelPlugin.Callbacks, + LifecycleOwner { + + private static final String TAG = "GlobalActionsDialog"; + + public static final String PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"; + public static final String PREFS_CONTROLS_FILE = "controls_prefs"; + private static final int SEEDING_MAX = 2; + + private final LockPatternUtils mLockPatternUtils; + private final KeyguardStateController mKeyguardStateController; + private final ActivityStarter mActivityStarter; + private final SysuiColorExtractor mSysuiColorExtractor; + private final IStatusBarService mCentralSurfacesService; + private final NotificationShadeWindowController mNotificationShadeWindowController; + private GlobalActionsPanelPlugin mWalletPlugin; + private Optional<ControlsUiController> mControlsUiControllerOptional; + private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>(); + private ControlsComponent mControlsComponent; + private Optional<ControlsController> mControlsControllerOptional; + private UserContextProvider mUserContextProvider; + @VisibleForTesting + boolean mShowLockScreenCardsAndControls = false; + + private final KeyguardStateController.Callback mKeyguardStateControllerListener = + new KeyguardStateController.Callback() { + @Override + public void onUnlockedChanged() { + if (mDialog != null) { + ActionsDialog dialog = (ActionsDialog) mDialog; + boolean unlocked = mKeyguardStateController.isUnlocked(); + if (dialog.mWalletViewController != null) { + dialog.mWalletViewController.onDeviceLockStateChanged(!unlocked); + } + if (!dialog.isShowingControls() && mControlsComponent.getVisibility() == AVAILABLE) { + dialog.showControls(mControlsUiControllerOptional.get()); + } + if (unlocked) { + dialog.hideLockMessage(); + } + } + } + }; + + private final ContentObserver mSettingsObserver = new ContentObserver(mMainHandler) { + @Override + public void onChange(boolean selfChange) { + onPowerMenuLockScreenSettingsChanged(); + } + }; + + /** + * @param context everything needs a context :( + */ + @Inject + public GlobalActionsDialog( + Context context, + GlobalActionsManager windowManagerFuncs, + AudioManager audioManager, + IDreamManager iDreamManager, + DevicePolicyManager devicePolicyManager, + LockPatternUtils lockPatternUtils, + BroadcastDispatcher broadcastDispatcher, + GlobalSettings globalSettings, + SecureSettings secureSettings, + @NonNull VibratorHelper vibrator, + @Main Resources resources, + ConfigurationController configurationController, + UserTracker userTracker, + ActivityStarter activityStarter, + KeyguardStateController keyguardStateController, + UserManager userManager, + TrustManager trustManager, + IActivityManager iActivityManager, + @Nullable TelecomManager telecomManager, + MetricsLogger metricsLogger, + SysuiColorExtractor colorExtractor, + IStatusBarService statusBarService, + NotificationShadeWindowController notificationShadeWindowController, + StatusBarWindowController statusBarOptional, + IWindowManager iWindowManager, + LightBarController lightBarController, + @Background Executor backgroundExecutor, + UiEventLogger uiEventLogger, + RingerModeTracker ringerModeTracker, + @Main Handler handler, + UserContextProvider userContextProvider, + PackageManager packageManager, + ShadeController shadeController, + KeyguardUpdateMonitor keyguardUpdateMonitor, + DialogLaunchAnimator dialogLaunchAnimator, + SelectedUserInteractor selectedUserInteractor, + ControlsComponent controlsComponent) { + super(context, + windowManagerFuncs, + audioManager, + iDreamManager, + devicePolicyManager, + lockPatternUtils, + broadcastDispatcher, + globalSettings, + secureSettings, + vibrator, + resources, + configurationController, + userTracker, + keyguardStateController, + userManager, + trustManager, + iActivityManager, + telecomManager, + metricsLogger, + colorExtractor, + statusBarService, + lightBarController, + notificationShadeWindowController, + statusBarOptional, + iWindowManager, + backgroundExecutor, + uiEventLogger, + ringerModeTracker, + handler, + packageManager, + shadeController, + keyguardUpdateMonitor, + dialogLaunchAnimator, + selectedUserInteractor, + controlsComponent); + + mLockPatternUtils = lockPatternUtils; + mKeyguardStateController = keyguardStateController; + mSysuiColorExtractor = colorExtractor; + mCentralSurfacesService = statusBarService; + mNotificationShadeWindowController = notificationShadeWindowController; + mControlsComponent = controlsComponent; + mControlsUiControllerOptional = controlsComponent.getControlsUiController(); + mControlsControllerOptional = controlsComponent.getControlsController(); + mUserContextProvider = userContextProvider; + mActivityStarter = activityStarter; + + mKeyguardStateController.addCallback(mKeyguardStateControllerListener); + + if (mControlsComponent.getControlsListingController().isPresent()) { + mControlsComponent.getControlsListingController().get() + .addCallback(list -> { + mControlsServiceInfos = new ArrayList(list); + // This callback may occur after the dialog has been shown. If so, add + // controls into the already visible space or show the lock msg if needed. + if (mDialog != null) { + ActionsDialog dialog = (ActionsDialog) mDialog; + if (!dialog.isShowingControls() + && mControlsComponent.getVisibility() == AVAILABLE) { + dialog.showControls(mControlsUiControllerOptional.get()); + } else if (shouldShowLockMessage(dialog)) { + dialog.showLockMessage(); + } + } + }); + } + + // Listen for changes to show controls on the power menu while locked + onPowerMenuLockScreenSettingsChanged(); + mGlobalSettings.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), + false /* notifyForDescendants */, + mSettingsObserver); + } + + @Override + protected void onRefresh() { + super.onRefresh(); + } + + @Override + public void destroy() { + super.destroy(); + mKeyguardStateController.removeCallback(mKeyguardStateControllerListener); + mGlobalSettings.unregisterContentObserver(mSettingsObserver); + } + + /** + * See if any available control service providers match one of the preferred components. If + * they do, and there are no current favorites for that component, query the preferred + * component for a limited number of suggested controls. + */ + private void seedFavorites() { + if (!mControlsControllerOptional.isPresent() + || mControlsServiceInfos.isEmpty()) { + return; + } + + String[] preferredControlsPackages = getContext().getResources() + .getStringArray(com.android.systemui.res.R.array.config_controlsPreferredPackages); + + SharedPreferences prefs = mUserContextProvider.getUserContext() + .getSharedPreferences(PREFS_CONTROLS_FILE, Context.MODE_PRIVATE); + Set<String> seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, + Collections.emptySet()); + + List<ComponentName> componentsToSeed = new ArrayList<>(); + for (int i = 0; i < Math.min(SEEDING_MAX, preferredControlsPackages.length); i++) { + String pkg = preferredControlsPackages[i]; + for (ControlsServiceInfo info : mControlsServiceInfos) { + if (!pkg.equals(info.componentName.getPackageName())) continue; + if (seededPackages.contains(pkg)) { + break; + } else if (mControlsControllerOptional.get() + .countFavoritesForComponent(info.componentName) > 0) { + // When there are existing controls but no saved preference, assume it + // is out of sync, perhaps through a device restore, and update the + // preference + addPackageToSeededSet(prefs, pkg); + break; + } + componentsToSeed.add(info.componentName); + break; + } + } + + if (componentsToSeed.isEmpty()) return; + + mControlsControllerOptional.get().seedFavoritesForComponents( + componentsToSeed, + (response) -> { + Log.d(TAG, "Controls seeded: " + response); + if (response.getAccepted()) { + addPackageToSeededSet(prefs, response.getPackageName()); + } + }); + } + + private void addPackageToSeededSet(SharedPreferences prefs, String pkg) { + Set<String> seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, + Collections.emptySet()); + Set<String> updatedPkgs = new HashSet<>(seededPackages); + updatedPkgs.add(pkg); + prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply(); + } + + /** + * Show the global actions dialog (creating if necessary) + * + * @param keyguardShowing True if keyguard is showing + */ + @Override + public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned, + @Nullable Expandable v, GlobalActionsPanelPlugin walletPlugin) { + mWalletPlugin = walletPlugin; + super.showOrHideDialog(keyguardShowing, isDeviceProvisioned, null); + } + + @Override + protected void handleShow(@Nullable Expandable expandable) { + seedFavorites(); + super.handleShow(null); + } + + /** + * Returns the maximum number of power menu items to show based on which GlobalActions + * layout is being used. + */ + @VisibleForTesting + @Override + protected int getMaxShownPowerItems() { + return getContext().getResources().getInteger( + com.android.systemui.res.R.integer.power_menu_max_columns); + } + + /** + * Create the global actions dialog. + * + * @return A new dialog. + */ + @Override + protected ActionsDialogLite createDialog() { + initDialogItems(); + + ControlsUiController uiController = null; + if (mControlsComponent.getVisibility() == AVAILABLE) { + uiController = mControlsUiControllerOptional.get(); + } + ActionsDialog dialog = new ActionsDialog(getContext(), mAdapter, mOverflowAdapter, + this::getWalletViewController, mSysuiColorExtractor, + mCentralSurfacesService, mLightBarController, mKeyguardStateController, + mNotificationShadeWindowController, mStatusBarWindowController, + controlsAvailable(), uiController, this::onRefresh, mKeyguardShowing, + mPowerAdapter, mRestartAdapter, mUsersAdapter, mUiEventLogger, + mShadeController, mKeyguardUpdateMonitor, + mLockPatternUtils, mSelectedUserInteractor); + + dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. + dialog.setOnDismissListener(this); + dialog.setOnShowListener(this); + + return dialog; + } + + @Nullable + private GlobalActionsPanelPlugin.PanelViewController getWalletViewController() { + if (mWalletPlugin == null) { + return null; + } + return mWalletPlugin.onPanelShown(this, !mKeyguardStateController.isUnlocked()); + } + + /** + * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is + * called when the quick access wallet requests that an intent be started (with lock screen + * shown first if needed). + */ + @Override + public void startPendingIntentDismissingKeyguard(PendingIntent pendingIntent) { + mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent); + } + + @Override + protected int getEmergencyTextColor(Context context, boolean dummy) { + return context.getResources().getColor( + com.android.systemui.res.R.color.global_actions_emergency_text); + } + + @Override + protected int getEmergencyIconColor(Context context, boolean dummy) { + return getContext().getResources().getColor( + com.android.systemui.res.R.color.global_actions_emergency_text); + } + + @Override + protected int getEmergencyBackgroundColor(Context context, boolean dummy) { + return getContext().getResources().getColor( + com.android.systemui.res.R.color.global_actions_emergency_background); + } + + @Override + protected int getGridItemLayoutResource() { + return com.android.systemui.res.R.layout.global_actions_grid_item_v2; + } + + @VisibleForTesting + class ActionsDialog extends ActionsDialogLite { + + private final Provider<GlobalActionsPanelPlugin.PanelViewController> mWalletFactory; + @Nullable private GlobalActionsPanelPlugin.PanelViewController mWalletViewController; + private ResetOrientationData mResetOrientationData; + private final boolean mControlsAvailable; + + private ControlsUiController mControlsUiController; + private ViewGroup mControlsView; + @VisibleForTesting ViewGroup mLockMessageContainer; + private TextView mLockMessage; + + ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter, + Provider<GlobalActionsPanelPlugin.PanelViewController> walletFactory, + SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, + LightBarController lightBarController, + KeyguardStateController keyguardStateController, + NotificationShadeWindowController notificationShadeWindowController, + StatusBarWindowController statusBarOptional, boolean controlsAvailable, + @Nullable ControlsUiController controlsUiController, Runnable onRotateCallback, + boolean keyguardShowing, MyPowerOptionsAdapter powerAdapter, + MyRestartOptionsAdapter restartAdapter, MyUsersAdapter usersAdapter, + UiEventLogger uiEventLogger, ShadeController shadeController, + KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils, + SelectedUserInteractor selectedUserInteractor) { + super(context, com.android.systemui.res.R.style.Theme_SystemUI_Dialog_GlobalActions, + adapter, overflowAdapter, sysuiColorExtractor, statusBarService, + lightBarController, keyguardStateController, notificationShadeWindowController, + statusBarOptional, onRotateCallback, keyguardShowing, powerAdapter, + restartAdapter, usersAdapter, uiEventLogger, shadeController, + keyguardUpdateMonitor, lockPatternUtils, selectedUserInteractor); + mControlsAvailable = controlsAvailable; + mControlsUiController = controlsUiController; + mWalletFactory = walletFactory; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + initializeLayout(); + if (shouldShowLockMessage()) { + showLockMessage(); + } + } + + private boolean shouldShowLockMessage() { + return GlobalActionsDialog.this.shouldShowLockMessage(this); + } + + private boolean isShowingControls() { + return mControlsUiController != null; + } + + private void showControls(ControlsUiController controller) { + mControlsUiController = controller; + mControlsUiController.show(mControlsView, this::dismissForControlsActivity, + null /* activityContext */); + } + + private boolean isWalletViewAvailable() { + return mWalletViewController != null && mWalletViewController.getPanelContent() != null; + } + + private void initializeWalletView() { + if (mWalletFactory == null) { + return; + } + mWalletViewController = mWalletFactory.get(); + if (!isWalletViewAvailable()) { + return; + } + + boolean isLandscapeWalletViewShown = mContext.getResources().getBoolean( + com.android.systemui.res.R.bool.global_actions_show_landscape_wallet_view); + + int rotation = RotationUtils.getRotation(mContext); + boolean rotationLocked = RotationPolicy.isRotationLocked(mContext); + if (rotation != RotationUtils.ROTATION_NONE) { + if (rotationLocked) { + if (mResetOrientationData == null) { + mResetOrientationData = new ResetOrientationData(); + mResetOrientationData.locked = true; + mResetOrientationData.rotation = rotation; + } + + // Unlock rotation, so user can choose to rotate to portrait to see the panel. + // This call is posted so that the rotation does not change until post-layout, + // otherwise onConfigurationChanged() may not get invoked. + mGlobalActionsLayout.post(() -> + RotationPolicy.setRotationLockAtAngle( + mContext, false, RotationUtils.ROTATION_NONE, TAG)); + + if (!isLandscapeWalletViewShown) { + return; + } + } + } else { + if (!rotationLocked) { + if (mResetOrientationData == null) { + mResetOrientationData = new ResetOrientationData(); + mResetOrientationData.locked = false; + } + } + + boolean shouldLockRotation = !isLandscapeWalletViewShown; + if (rotationLocked != shouldLockRotation) { + // Locks the screen to portrait if the landscape / seascape orientation does not + // show the wallet view, so the user doesn't accidentally hide the panel. + // This call is posted so that the rotation does not change until post-layout, + // otherwise onConfigurationChanged() may not get invoked. + mGlobalActionsLayout.post(() -> + RotationPolicy.setRotationLockAtAngle( + mContext, shouldLockRotation, RotationUtils.ROTATION_NONE, TAG)); + } + } + + // Disable rotation suggestions, if enabled + setRotationSuggestionsEnabled(false); + + FrameLayout panelContainer = + findViewById(com.android.systemui.res.R.id.global_actions_wallet); + FrameLayout.LayoutParams panelParams = + new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT); + if (!mControlsAvailable) { + panelParams.topMargin = mContext.getResources().getDimensionPixelSize( + com.android.systemui.res.R.dimen.global_actions_wallet_top_margin); + } + View walletView = mWalletViewController.getPanelContent(); + panelContainer.addView(walletView, panelParams); + // Smooth transitions when wallet is resized, which can happen when a card is added + ViewGroup root = findViewById(com.android.systemui.res.R.id.global_actions_grid_root); + if (root != null) { + walletView.addOnLayoutChangeListener((v, l, t, r, b, ol, ot, or, ob) -> { + int oldHeight = ob - ot; + int newHeight = b - t; + if (oldHeight > 0 && oldHeight != newHeight) { + TransitionSet transition = new AutoTransition() + .setDuration(250) + .setOrdering(TransitionSet.ORDERING_TOGETHER); + TransitionManager.beginDelayedTransition(root, transition); + } + }); + } + } + + @Override + protected int getLayoutResource() { + return com.android.systemui.res.R.layout.global_actions_grid_v2; + } + + @Override + protected void initializeLayout() { + super.initializeLayout(); + mControlsView = findViewById(com.android.systemui.res.R.id.global_actions_controls); + mLockMessageContainer = requireViewById( + com.android.systemui.res.R.id.global_actions_lock_message_container); + mLockMessage = + requireViewById(com.android.systemui.res.R.id.global_actions_lock_message); + initializeWalletView(); + getWindow().setBackgroundDrawable(mBackgroundDrawable); + } + + @Override + public void show() { + super.show(); + if (mControlsUiController != null) { + mControlsUiController.show(mControlsView, this::dismissForControlsActivity, + null /* activityContext */); + } + + mBackgroundDrawable.setAlpha(0); + float xOffset = mGlobalActionsLayout.getAnimationOffsetX(); + ObjectAnimator alphaAnimator = + ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f); + alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); + alphaAnimator.setDuration(183); + alphaAnimator.addUpdateListener((animation) -> { + float animatedValue = animation.getAnimatedFraction(); + int alpha = (int) (animatedValue * mScrimAlpha * 255); + mBackgroundDrawable.setAlpha(alpha); + }); + + ObjectAnimator xAnimator = + ObjectAnimator.ofFloat(mContainer, "translationX", xOffset, 0f); + xAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); + xAnimator.setDuration(350); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(alphaAnimator, xAnimator); + animatorSet.start(); + } + + @Override + public void dismiss() { + dismissWallet(); + if (mControlsUiController != null) mControlsUiController.closeDialogs(false); + if (mControlsUiController != null) mControlsUiController.hide(mControlsView); + mContainer.setTranslationX(0); + ObjectAnimator alphaAnimator = + ObjectAnimator.ofFloat(mContainer, "alpha", 1f, 0f); + alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); + alphaAnimator.setDuration(233); + alphaAnimator.addUpdateListener((animation) -> { + float animatedValue = 1f - animation.getAnimatedFraction(); + int alpha = (int) (animatedValue * mScrimAlpha * 255); + mBackgroundDrawable.setAlpha(alpha); + }); + + float xOffset = mGlobalActionsLayout.getAnimationOffsetX(); + ObjectAnimator xAnimator = + ObjectAnimator.ofFloat(mContainer, "translationX", 0f, xOffset); + xAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); + xAnimator.setDuration(350); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(alphaAnimator, xAnimator); + animatorSet.addListener(new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + completeDismiss(); + } + }); + + animatorSet.start(); + } + + void completeDismiss() { + resetOrientation(); + super.dismiss(); + } + + private void dismissForControlsActivity() { + dismissWallet(); + if (mControlsUiController != null) mControlsUiController.closeDialogs(false); + if (mControlsUiController != null) mControlsUiController.hide(mControlsView); + ViewGroup root = (ViewGroup) mGlobalActionsLayout.getParent(); + ControlsAnimations.exitAnimation(root, this::completeDismiss).start(); + } + + private void dismissWallet() { + if (mWalletViewController != null) { + mWalletViewController.onDismissed(); + // The wallet controller should not be re-used after being dismissed. + mWalletViewController = null; + } + } + + private void resetOrientation() { + if (mResetOrientationData != null) { + RotationPolicy.setRotationLockAtAngle(mContext, mResetOrientationData.locked, + mResetOrientationData.rotation, TAG); + } + setRotationSuggestionsEnabled(true); + } + + @Override + public void refreshDialog() { + // ensure dropdown menus are dismissed before re-initializing the dialog + dismissWallet(); + if (mControlsUiController != null) { + mControlsUiController.hide(mControlsView); + } + + super.refreshDialog(); + if (mControlsUiController != null) { + mControlsUiController.show(mControlsView, this::dismissForControlsActivity, + null /* activityContext */); + } + } + + void hideLockMessage() { + if (mLockMessageContainer.getVisibility() == View.VISIBLE) { + mLockMessageContainer.animate().alpha(0).setDuration(150).setListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mLockMessageContainer.setVisibility(View.GONE); + } + }).start(); + } + } + + void showLockMessage() { + Drawable lockIcon = mContext.getDrawable(com.android.internal.R.drawable.ic_lock); + lockIcon.setTint(mContext.getColor(com.android.systemui.R.color.control_primary_text)); + mLockMessage.setCompoundDrawablesWithIntrinsicBounds(null, lockIcon, null, null); + mLockMessageContainer.setVisibility(View.VISIBLE); + } + + private class ResetOrientationData { + public boolean locked; + public int rotation; + } + } + + /** + * Determines whether or not debug mode has been activated for the Global Actions Panel. + */ + private static boolean isPanelDebugModeEnabled(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.GLOBAL_ACTIONS_PANEL_DEBUG_ENABLED, 0) == 1; + } + + /** + * Determines whether or not the Global Actions menu should be forced to use the newer + * grid-style layout. + */ + private static boolean isForceGridEnabled(Context context) { + return isPanelDebugModeEnabled(context); + } + + private boolean controlsAvailable() { + return isDeviceProvisioned() + && mControlsComponent.isEnabled() + && !mControlsServiceInfos.isEmpty(); + } + + private boolean shouldShowLockMessage(ActionsDialog dialog) { + return mControlsComponent.getVisibility() == AVAILABLE_AFTER_UNLOCK + || isWalletAvailableAfterUnlock(dialog); + } + + // Temporary while we move items out of the power menu + private boolean isWalletAvailableAfterUnlock(ActionsDialog dialog) { + boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id) + == STRONG_AUTH_REQUIRED_AFTER_BOOT; + return !mKeyguardStateController.isUnlocked() + && (!mShowLockScreenCardsAndControls || isLockedAfterBoot) + && dialog.isWalletViewAvailable(); + } + + private void onPowerMenuLockScreenSettingsChanged() { + mShowLockScreenCardsAndControls = mSecureSettings.getInt( + Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0; + } + + @Override + protected boolean shouldForceDark() { + return true; + } + + @Override + protected boolean shouldUseControlsLayout() { + return true; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index d70815f0b88b..dc083d97ec47 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -61,6 +61,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RectF; @@ -96,6 +97,8 @@ import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowInsetsController; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; @@ -158,6 +161,7 @@ import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; import com.android.systemui.util.EmergencyDialerConstants; import com.android.systemui.util.RingerModeTracker; +import com.android.systemui.util.leak.RotationUtils; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; @@ -213,7 +217,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final IDreamManager mDreamManager; private final DevicePolicyManager mDevicePolicyManager; private final LockPatternUtils mLockPatternUtils; - private final SelectedUserInteractor mSelectedUserInteractor; + protected final SelectedUserInteractor mSelectedUserInteractor; private final KeyguardStateController mKeyguardStateController; private final BroadcastDispatcher mBroadcastDispatcher; protected final GlobalSettings mGlobalSettings; @@ -226,7 +230,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final IActivityManager mIActivityManager; private final TelecomManager mTelecomManager; private final MetricsLogger mMetricsLogger; - private final UiEventLogger mUiEventLogger; + protected final UiEventLogger mUiEventLogger; private final LineageGlobalActions mLineageGlobalActions; // Used for RingerModeTracker @@ -258,7 +262,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene // Power menu customizations private String[] mActions; - private boolean mKeyguardShowing = false; + protected boolean mKeyguardShowing = false; private boolean mDeviceProvisioned = false; private ToggleState mAirplaneState = ToggleState.Off; private boolean mIsWaitingForEcmExit = false; @@ -271,7 +275,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private final IStatusBarService mStatusBarService; protected final LightBarController mLightBarController; protected final NotificationShadeWindowController mNotificationShadeWindowController; - private final StatusBarWindowController mStatusBarWindowController; + protected final StatusBarWindowController mStatusBarWindowController; private final IWindowManager mIWindowManager; private final Executor mBackgroundExecutor; private final RingerModeTracker mRingerModeTracker; @@ -279,8 +283,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected Handler mMainHandler; private int mSmallestScreenWidthDp; private int mOrientation; - private final ShadeController mShadeController; - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + protected final ShadeController mShadeController; + protected final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final DialogLaunchAnimator mDialogLaunchAnimator; private final ControlsComponent mControlsComponent; @@ -481,7 +485,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mConfigurationController.removeCallback(this); } - protected Context getContext() { + public Context getContext() { return mContext; } @@ -493,6 +497,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene return mKeyguardUpdateMonitor; } + public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned, + @Nullable Expandable v, GlobalActionsPanelPlugin walletPlugin) { + showOrHideDialog(keyguardShowing, isDeviceProvisioned, v); + } + /** * Show the global actions dialog (creating if necessary) or hide it if it's already showing. * @@ -514,7 +523,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mDialog.dismiss(); mDialog = null; } else { - handleShow(expandable); + handleShow(shouldUseControlsLayout() ? expandable : null); } } @@ -949,6 +958,34 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } } + protected int getEmergencyTextColor(Context context, boolean alternate) { + if (alternate) { + return context.getResources().getColor( + com.android.systemui.res.R.color.global_actions_alert_text); + } + return context.getResources().getColor( + com.android.systemui.res.R.color.global_actions_lite_text); + } + + protected int getEmergencyIconColor(Context context, boolean alternate) { + if (alternate) { + return context.getResources().getColor( + com.android.systemui.res.R.color.global_actions_alert_text); + } + return context.getResources().getColor( + com.android.systemui.res.R.color.global_actions_lite_emergency_icon); + } + + protected int getEmergencyBackgroundColor(Context context, boolean alternate) { + if (alternate) { + return context.getResources().getColor( + com.android.systemui.res.R.color.global_actions_emergency_background); + } + return context.getResources().getColor( + com.android.systemui.res.R.color.global_actions_lite_emergency_background); + } + + @VisibleForTesting protected abstract class EmergencyAction extends SinglePressAction { EmergencyAction(int iconResId, int messageResId) { @@ -957,7 +994,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public boolean shouldBeSeparated() { - return false; + return !useGridLayout() && !shouldUseControlsLayout(); } @Override @@ -994,18 +1031,18 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } protected int getEmergencyTextColor(Context context) { - return context.getResources().getColor( - com.android.systemui.res.R.color.global_actions_lite_text); + return GlobalActionsDialogLite.this.getEmergencyTextColor( + context, !shouldUseControlsLayout()); } protected int getEmergencyIconColor(Context context) { - return context.getResources().getColor( - com.android.systemui.res.R.color.global_actions_lite_emergency_icon); + return GlobalActionsDialogLite.this.getEmergencyIconColor( + context, !shouldUseControlsLayout()); } protected int getEmergencyBackgroundColor(Context context) { - return context.getResources().getColor( - com.android.systemui.res.R.color.global_actions_lite_emergency_background); + return GlobalActionsDialogLite.this.getEmergencyBackgroundColor( + context, !shouldUseControlsLayout()); } private class EmergencyAffordanceAction extends EmergencyAction { @@ -1805,8 +1842,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene Log.w(TAG, "No power options action found at position: " + position); return null; } - int viewLayoutResource = - com.android.systemui.res.R.layout.global_actions_grid_item_lite; + int viewLayoutResource = getGridItemLayoutResource(); View view = convertView != null ? convertView : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false); view.setOnClickListener(v -> onClickItem(position)); @@ -2178,6 +2214,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } protected int getGridItemLayoutResource() { + if (!shouldUseControlsLayout()) { + return com.android.systemui.res.R.layout.global_actions_grid_item; + } return com.android.systemui.res.R.layout.global_actions_grid_item_lite; } @@ -2635,7 +2674,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } @VisibleForTesting - static class ActionsDialogLite extends SystemUIDialog implements DialogInterface, + class ActionsDialogLite extends SystemUIDialog implements DialogInterface, ColorExtractor.OnColorsChangedListener { protected final Context mContext; @@ -2696,7 +2735,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (distanceY < 0 && distanceY > distanceX + if (e1 != null && distanceY < 0 && distanceY > distanceX && e1.getY() <= mStatusBarWindowController.getStatusBarHeight()) { // Downwards scroll from top openShadeAndDismiss(); @@ -2775,8 +2814,22 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mBlurUtils = new BlurUtils(mContext.getResources(), CrossWindowBlurListeners.getInstance(), new DumpManager()); + // Window initialization + Window window = getWindow(); + window.requestFeature(Window.FEATURE_NO_TITLE); + // Inflate the decor view, so the attributes below are not overwritten by the theme. + window.getDecorView(); + window.setLayout(MATCH_PARENT, MATCH_PARENT); + window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + window.addFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); + window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); + window.getAttributes().setFitInsetsTypes(0 /* types */); if (mBlurUtils.supportsBlursOnWindows()) { - Window window = getWindow(); // Enable blur behind // Enable dim behind since we are setting some amount dim for the blur. window.addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND @@ -2858,13 +2911,14 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } public void showPowerOptionsMenu() { - mPowerOptionsDialog = GlobalActionsPowerDialog.create(mContext, mPowerOptionsAdapter); + mPowerOptionsDialog = GlobalActionsPowerDialog.create( + mContext, mPowerOptionsAdapter, shouldForceDark()); mPowerOptionsDialog.show(); } public void showRestartOptionsMenu() { mRestartOptionsDialog = GlobalActionsPowerDialog.create(mContext, - mRestartOptionsAdapter); + mRestartOptionsAdapter, shouldForceDark()); mRestartOptionsDialog.show(); } @@ -2874,11 +2928,28 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } public void showUsersMenu() { - mUsersDialog = GlobalActionsPowerDialog.create(mContext, mUsersAdapter); + mUsersDialog = GlobalActionsPowerDialog.create( + mContext, mUsersAdapter, shouldForceDark()); mUsersDialog.show(); } protected int getLayoutResource() { + if (!shouldUseControlsLayout()) { + int rotation = RotationUtils.getRotation(mContext); + if (rotation == RotationUtils.ROTATION_SEASCAPE) { + if (useGridLayout()) { + return com.android.systemui.res.R.layout.global_actions_grid_seascape; + } else { + return com.android.systemui.res.R.layout.global_actions_column_seascape; + } + } else { + if (useGridLayout()) { + return com.android.systemui.res.R.layout.global_actions_grid; + } else { + return com.android.systemui.res.R.layout.global_actions_column; + } + } + } return com.android.systemui.res.R.layout.global_actions_grid_lite; } @@ -2898,7 +2969,19 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene }); mGlobalActionsLayout.setRotationListener(this::onRotate); mGlobalActionsLayout.setAdapter(mAdapter); + ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView(); + root.setOnApplyWindowInsetsListener((v, windowInsets) -> { + Insets i = windowInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.navigationBars() | WindowInsets.Type.systemGestures() + | WindowInsets.Type.displayCutout()); + root.setPadding(i.left, i.top, i.right, i.bottom); + return WindowInsets.CONSUMED; + }); mContainer = findViewById(com.android.systemui.res.R.id.global_actions_container); + // Some legacy dialog layouts don't have the outer container + if (mContainer == null) { + mContainer = mGlobalActionsLayout; + } mContainer.setOnTouchListener((v, event) -> { mGestureDetector.onTouchEvent(event); return v.onTouchEvent(event); @@ -2925,12 +3008,14 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene if (mBackgroundDrawable == null) { mBackgroundDrawable = new ScrimDrawable(); - mScrimAlpha = 1.0f; } // Set dim only when blur is enabled. if (mBlurUtils.supportsBlursOnWindows()) { getWindow().setDimAmount(0.54f); + mScrimAlpha = 0.0f; + } else { + mScrimAlpha = 1.0f; } // If user entered from the lock screen and smart lock was enabled, disable it @@ -3008,12 +3093,17 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene return; } ((ScrimDrawable) mBackgroundDrawable).setColor(Color.BLACK, animate); - View decorView = getWindow().getDecorView(); + WindowInsetsController insetController = getWindow().getInsetsController(); if (colors.supportsDarkText()) { - decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR - | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + insetController.setSystemBarsAppearance( + WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS + | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, + WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS + | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS); } else { - decorView.setSystemUiVisibility(0); + insetController.setSystemBarsAppearance(0, + WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS + | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS); } } @@ -3213,4 +3303,18 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene refreshDialog(); } } + + protected boolean shouldForceDark() { + return false; + } + + protected boolean shouldUseControlsLayout() { + return Settings.Secure.getInt( + mContext.getContentResolver(), LMOSettings.Secure.POWER_MENU_TYPE, 0) == 0; + } + + protected boolean useGridLayout() { + return Settings.Secure.getInt( + mContext.getContentResolver(), LMOSettings.Secure.POWER_MENU_TYPE, 0) == 3; + } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index e565ee115b25..81052cbd85a7 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -17,13 +17,18 @@ package com.android.systemui.globalactions; import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS; import android.content.Context; +import android.provider.Settings; import com.android.systemui.plugins.GlobalActions; +import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.libremobileos.providers.LMOSettings; + import javax.inject.Inject; public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks { @@ -31,39 +36,56 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks private final Context mContext; private final KeyguardStateController mKeyguardStateController; private final DeviceProvisionedController mDeviceProvisionedController; + private final ExtensionController.Extension<GlobalActionsPanelPlugin> mWalletPluginProvider; private final BlurUtils mBlurUtils; private final CommandQueue mCommandQueue; - private final GlobalActionsDialogLite mGlobalActionsDialog; + private final GlobalActionsDialog mGlobalActionsDialog; + private final GlobalActionsDialogLite mGlobalActionsDialogLite; private boolean mDisabled; private ShutdownUi mShutdownUi; + private boolean fullDialog = true; @Inject public GlobalActionsImpl(Context context, CommandQueue commandQueue, - GlobalActionsDialogLite globalActionsDialog, BlurUtils blurUtils, + GlobalActionsDialogLite globalActionsDialogLite, + GlobalActionsDialog globalActionsDialog, + BlurUtils blurUtils, KeyguardStateController keyguardStateController, DeviceProvisionedController deviceProvisionedController, - ShutdownUi shutdownUi) { + ShutdownUi shutdownUi, + ExtensionController extensionController) { mContext = context; mGlobalActionsDialog = globalActionsDialog; + mGlobalActionsDialogLite = globalActionsDialogLite; mKeyguardStateController = keyguardStateController; mDeviceProvisionedController = deviceProvisionedController; mCommandQueue = commandQueue; mBlurUtils = blurUtils; mCommandQueue.addCallback(this); mShutdownUi = shutdownUi; + mWalletPluginProvider = extensionController + .newExtension(GlobalActionsPanelPlugin.class) + .withPlugin(GlobalActionsPanelPlugin.class) + .build(); } @Override public void destroy() { mCommandQueue.removeCallback(this); mGlobalActionsDialog.destroy(); + mGlobalActionsDialogLite.destroy(); } @Override public void showGlobalActions(GlobalActionsManager manager) { if (mDisabled) return; - mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(), - mDeviceProvisionedController.isDeviceProvisioned(), null /* view */); + fullDialog = Settings.Secure.getInt( + mContext.getContentResolver(), LMOSettings.Secure.POWER_MENU_TYPE, 0) == 1; + GlobalActionsDialogLite globalActionsDialog = + fullDialog ? mGlobalActionsDialog : mGlobalActionsDialogLite; + globalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(), + mDeviceProvisionedController.isDeviceProvisioned(), + null /* view */, mWalletPluginProvider.get()); } @Override @@ -78,6 +100,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks mDisabled = disabled; if (disabled) { mGlobalActionsDialog.dismissDialog(); + mGlobalActionsDialogLite.dismissDialog(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPowerDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPowerDialog.java index 3fc3476aa428..c9a662941573 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPowerDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsPowerDialog.java @@ -40,7 +40,7 @@ public class GlobalActionsPowerDialog { /** * Create a dialog for displaying Shut Down and Restart actions. */ - public static Dialog create(@NonNull Context context, ListAdapter adapter) { + public static Dialog create(@NonNull Context context, ListAdapter adapter, boolean forceDark) { ViewGroup listView = (ViewGroup) LayoutInflater.from(context).inflate( com.android.systemui.res.R.layout.global_actions_power_dialog_flow, null); @@ -78,7 +78,8 @@ public class GlobalActionsPowerDialog { window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); window.setTitle(""); // prevent Talkback from speaking first item name twice window.setBackgroundDrawable(res.getDrawable( - com.android.systemui.res.R.drawable.global_actions_lite_background, + forceDark ? com.android.systemui.res.R.drawable.global_actions_background + : com.android.systemui.res.R.drawable.global_actions_lite_background, context.getTheme())); window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); if (blurUtils.supportsBlursOnWindows()) { diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java b/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java new file mode 100644 index 000000000000..622fa658f1b0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/globalactions/MinHeightScrollView.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 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.systemui.globalactions; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ScrollView; + +/** + * When measured, this view sets the minimum height of its first child to be equal to its own + * target height. + * + * This ensures fall-through click handlers can be placed on this view's child component. + */ +public class MinHeightScrollView extends ScrollView { + public MinHeightScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + View firstChild = getChildAt(0); + if (firstChild != null) { + firstChild.setMinimumHeight(MeasureSpec.getSize(heightMeasureSpec)); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt index 8e307408ba86..6e2470c3b4e7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt @@ -43,6 +43,7 @@ import com.android.systemui.security.data.repository.SecurityRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.user.data.repository.UserSwitcherRepository import com.android.systemui.user.domain.interactor.UserSwitcherInteractor +import com.libremobileos.providers.LMOSettings import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow @@ -153,11 +154,15 @@ constructor( expandable: Expandable, ) { uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS) - globalActionsDialogLite.showOrHideDialog( - /* keyguardShowing= */ false, - /* isDeviceProvisioned= */ true, - expandable, - ) + if (Settings.Secure.getInt(globalActionsDialogLite.context.getContentResolver(), + LMOSettings.Secure.POWER_MENU_TYPE, 0) == 0) + globalActionsDialogLite.showOrHideDialog( + /* keyguardShowing= */ false, + /* isDeviceProvisioned= */ true, + expandable, + ) + else + globalActionsDialogLite.context.sendBroadcast(Intent("android.intent.action.POWER_MENU")) } override fun showSettings(expandable: Expandable) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java new file mode 100644 index 000000000000..d3bb2e56db1a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2020 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.systemui.globalactions; + +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.IActivityManager; +import android.app.admin.DevicePolicyManager; +import android.app.trust.TrustManager; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.content.res.Resources; +import android.graphics.Color; +import android.media.AudioManager; +import android.os.Handler; +import android.os.RemoteException; +import android.os.UserManager; +import android.service.dreams.IDreamManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.IWindowManager; +import android.view.View; +import android.view.WindowManagerPolicyConstants; +import android.widget.FrameLayout; + +import androidx.test.filters.SmallTest; + +import com.android.internal.colorextraction.ColorExtractor; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.colorextraction.SysuiColorExtractor; +import com.android.systemui.controls.controller.ControlsController; +import com.android.systemui.controls.dagger.ControlsComponent; +import com.android.systemui.controls.management.ControlsListingController; +import com.android.systemui.controls.ui.ControlsUiController; +import com.android.systemui.model.SysUiState; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.GlobalActions; +import com.android.systemui.plugins.GlobalActionsPanelPlugin; +import com.android.systemui.settings.UserContextProvider; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.telephony.TelephonyListenerManager; +import com.android.systemui.util.RingerModeLiveData; +import com.android.systemui.util.RingerModeTracker; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.settings.SecureSettings; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Executor; +import java.util.regex.Pattern; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class GlobalActionsDialogTest extends SysuiTestCase { + private static final long UI_TIMEOUT_MILLIS = 5000; // 5 sec + private static final Pattern CANCEL_BUTTON = + Pattern.compile("cancel", Pattern.CASE_INSENSITIVE); + + private GlobalActionsDialog mGlobalActionsDialog; + + @Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs; + @Mock private AudioManager mAudioManager; + @Mock private IDreamManager mDreamManager; + @Mock private DevicePolicyManager mDevicePolicyManager; + @Mock private LockPatternUtils mLockPatternUtils; + @Mock private BroadcastDispatcher mBroadcastDispatcher; + @Mock private TelephonyListenerManager mTelephonyListenerManager; + @Mock private GlobalSettings mGlobalSettings; + @Mock private Resources mResources; + @Mock private ConfigurationController mConfigurationController; + @Mock private ActivityStarter mActivityStarter; + @Mock private KeyguardStateController mKeyguardStateController; + @Mock private UserManager mUserManager; + @Mock private TrustManager mTrustManager; + @Mock private IActivityManager mActivityManager; + @Mock private MetricsLogger mMetricsLogger; + @Mock private SysuiColorExtractor mColorExtractor; + @Mock private IStatusBarService mStatusBarService; + @Mock private NotificationShadeWindowController mNotificationShadeWindowController; + @Mock private ControlsUiController mControlsUiController; + @Mock private IWindowManager mWindowManager; + @Mock private Executor mBackgroundExecutor; + @Mock private ControlsListingController mControlsListingController; + @Mock private ControlsController mControlsController; + @Mock private UiEventLogger mUiEventLogger; + @Mock private RingerModeTracker mRingerModeTracker; + @Mock private RingerModeLiveData mRingerModeLiveData; + @Mock private SysUiState mSysUiState; + @Mock GlobalActionsPanelPlugin mWalletPlugin; + @Mock GlobalActionsPanelPlugin.PanelViewController mWalletController; + @Mock private Handler mHandler; + @Mock private UserContextProvider mUserContextProvider; + @Mock private UserTracker mUserTracker; + @Mock private PackageManager mPackageManager; + @Mock private SecureSettings mSecureSettings; + @Mock private StatusBar mStatusBar; + @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private ControlsComponent mControlsComponent; + + private TestableLooper mTestableLooper; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mTestableLooper = TestableLooper.get(this); + allowTestableLooperAsMainThread(); + + when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); + when(mResources.getConfiguration()).thenReturn( + getContext().getResources().getConfiguration()); + when(mUserContextProvider.getUserContext()).thenReturn(mContext); + mControlsComponent = new ControlsComponent( + true, + mContext, + () -> mControlsController, + () -> mControlsUiController, + () -> mControlsListingController, + mLockPatternUtils, + mKeyguardStateController, + mUserTracker, + mSecureSettings + ); + + mGlobalActionsDialog = new GlobalActionsDialog(mContext, + mWindowManagerFuncs, + mAudioManager, + mDreamManager, + mDevicePolicyManager, + mLockPatternUtils, + mBroadcastDispatcher, + mTelephonyListenerManager, + mGlobalSettings, + mSecureSettings, + null, + mResources, + mConfigurationController, + mActivityStarter, + mKeyguardStateController, + mUserManager, + mTrustManager, + mActivityManager, + null, + mMetricsLogger, + mColorExtractor, + mStatusBarService, + mNotificationShadeWindowController, + mWindowManager, + mBackgroundExecutor, + mUiEventLogger, + mRingerModeTracker, + mSysUiState, + mHandler, + mControlsComponent, + mUserContextProvider + mPackageManager, + Optional.of(mStatusBar), + mKeyguardUpdateMonitor + ); + mGlobalActionsDialog.setZeroDialogPressDelayForTesting(); + + ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors(); + backdropColors.setMainColor(Color.BLACK); + when(mColorExtractor.getNeutralColors()).thenReturn(backdropColors); + when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); + } + + @Test + public void testShouldLogShow() { + mGlobalActionsDialog.onShow(null); + mTestableLooper.processAllMessages(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN); + } + + @Test + public void testShouldLogDismiss() { + mGlobalActionsDialog.onDismiss(mGlobalActionsDialog.mDialog); + mTestableLooper.processAllMessages(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_CLOSE); + } + + @Test + public void testShouldLogBugreportPress() throws InterruptedException { + GlobalActionsDialog.BugReportAction bugReportAction = + mGlobalActionsDialog.makeBugReportActionForTesting(); + bugReportAction.onPress(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_PRESS); + } + + @Test + public void testShouldLogBugreportLongPress() { + GlobalActionsDialog.BugReportAction bugReportAction = + mGlobalActionsDialog.makeBugReportActionForTesting(); + bugReportAction.onLongPress(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_BUGREPORT_LONG_PRESS); + } + + @Test + public void testShouldLogEmergencyDialerPress() { + GlobalActionsDialog.EmergencyDialerAction emergencyDialerAction = + mGlobalActionsDialog.makeEmergencyDialerActionForTesting(); + emergencyDialerAction.onPress(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_EMERGENCY_DIALER_PRESS); + } + + @Test + public void testShouldLogScreenshotPress() { + GlobalActionsDialog.ScreenshotAction screenshotAction = + mGlobalActionsDialog.makeScreenshotActionForTesting(); + screenshotAction.onPress(); + verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_SCREENSHOT_PRESS); + } + + @Test + public void testShouldShowScreenshot() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.integer.config_navBarInteractionMode, + WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON); + + GlobalActionsDialog.ScreenshotAction screenshotAction = + mGlobalActionsDialog.makeScreenshotActionForTesting(); + assertThat(screenshotAction.shouldShow()).isTrue(); + } + + @Test + public void testShouldNotShowScreenshot() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.integer.config_navBarInteractionMode, + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON); + + GlobalActionsDialog.ScreenshotAction screenshotAction = + mGlobalActionsDialog.makeScreenshotActionForTesting(); + assertThat(screenshotAction.shouldShow()).isFalse(); + } + + private void verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent event) { + mTestableLooper.processAllMessages(); + verify(mUiEventLogger, times(1)) + .log(event); + } + + @SafeVarargs + private static <T> void assertItemsOfType(List<T> stuff, Class<? extends T>... classes) { + assertThat(stuff).hasSize(classes.length); + for (int i = 0; i < stuff.size(); i++) { + assertThat(stuff.get(i)).isInstanceOf(classes[i]); + } + } + + @Test + public void testCreateActionItems_maxThree_noOverflow() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow 3 items to be shown + doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // ensure items are not blocked by keyguard or device provisioning + doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertItemsOfType(mGlobalActionsDialog.mItems, + GlobalActionsDialog.EmergencyAction.class, + GlobalActionsDialog.ShutDownAction.class, + GlobalActionsDialog.RestartAction.class); + assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty(); + assertThat(mGlobalActionsDialog.mPowerItems).isEmpty(); + } + + @Test + public void testCreateActionItems_maxThree_condensePower() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow 3 items to be shown + doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // ensure items are not blocked by keyguard or device provisioning + doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); + // make sure lockdown action will be shown + doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertItemsOfType(mGlobalActionsDialog.mItems, + GlobalActionsDialog.EmergencyAction.class, + GlobalActionsDialog.LockDownAction.class, + GlobalActionsDialog.PowerOptionsAction.class); + assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty(); + assertItemsOfType(mGlobalActionsDialog.mPowerItems, + GlobalActionsDialog.ShutDownAction.class, + GlobalActionsDialog.RestartAction.class); + } + + @Test + public void testCreateActionItems_maxThree_condensePower_splitPower() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow 3 items to be shown + doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // make sure lockdown action will be shown + doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); + // make sure bugreport also shown + doReturn(true).when(mGlobalActionsDialog).shouldDisplayBugReport(any()); + // ensure items are not blocked by keyguard or device provisioning + doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_BUGREPORT, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertItemsOfType(mGlobalActionsDialog.mItems, + GlobalActionsDialog.EmergencyAction.class, + GlobalActionsDialog.LockDownAction.class, + GlobalActionsDialog.PowerOptionsAction.class); + assertItemsOfType(mGlobalActionsDialog.mOverflowItems, + GlobalActionsDialog.BugReportAction.class); + assertItemsOfType(mGlobalActionsDialog.mPowerItems, + GlobalActionsDialog.ShutDownAction.class, + GlobalActionsDialog.RestartAction.class); + } + + @Test + public void testCreateActionItems_maxFour_condensePower() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow 3 items to be shown + doReturn(4).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // make sure lockdown action will be shown + doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); + // ensure items are not blocked by keyguard or device provisioning + doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + GlobalActionsDialog.GLOBAL_ACTION_KEY_SCREENSHOT + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertItemsOfType(mGlobalActionsDialog.mItems, + GlobalActionsDialog.EmergencyAction.class, + GlobalActionsDialog.LockDownAction.class, + GlobalActionsDialog.PowerOptionsAction.class, + GlobalActionsDialog.ScreenshotAction.class); + assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty(); + assertItemsOfType(mGlobalActionsDialog.mPowerItems, + GlobalActionsDialog.ShutDownAction.class, + GlobalActionsDialog.RestartAction.class); + } + + @Test + public void testCreateActionItems_maxThree_doNotCondensePower() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow 3 items to be shown + doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // make sure lockdown action will be shown + doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); + // make sure bugreport is also shown + doReturn(true).when(mGlobalActionsDialog).shouldDisplayBugReport(any()); + // ensure items are not blocked by keyguard or device provisioning + doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_BUGREPORT, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertItemsOfType(mGlobalActionsDialog.mItems, + GlobalActionsDialog.EmergencyAction.class, + GlobalActionsDialog.ShutDownAction.class, + GlobalActionsDialog.BugReportAction.class); + assertItemsOfType(mGlobalActionsDialog.mOverflowItems, + GlobalActionsDialog.LockDownAction.class); + assertThat(mGlobalActionsDialog.mPowerItems).isEmpty(); + } + + @Test + public void testCreateActionItems_maxAny() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow any number of power menu items to be shown + doReturn(Integer.MAX_VALUE).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // ensure items are not blocked by keyguard or device provisioning + doReturn(true).when(mGlobalActionsDialog).shouldShowAction(any()); + // make sure lockdown action will be shown + doReturn(true).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertItemsOfType(mGlobalActionsDialog.mItems, + GlobalActionsDialog.EmergencyAction.class, + GlobalActionsDialog.ShutDownAction.class, + GlobalActionsDialog.RestartAction.class, + GlobalActionsDialog.LockDownAction.class); + assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty(); + assertThat(mGlobalActionsDialog.mPowerItems).isEmpty(); + } + + @Test + public void testCreateActionItems_maxThree_lockdownDisabled_doesNotShowLockdown() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow only 3 items to be shown + doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); + // make sure lockdown action will NOT be shown + doReturn(false).when(mGlobalActionsDialog).shouldDisplayLockdown(any()); + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + // lockdown action not allowed + GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertItemsOfType(mGlobalActionsDialog.mItems, + GlobalActionsDialog.EmergencyAction.class, + GlobalActionsDialog.ShutDownAction.class, + GlobalActionsDialog.RestartAction.class); + assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty(); + assertThat(mGlobalActionsDialog.mPowerItems).isEmpty(); + } + + @Test + public void testCreateActionItems_shouldShowAction_excludeBugReport() { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + // allow only 3 items to be shown + doReturn(3).when(mGlobalActionsDialog).getMaxShownPowerItems(); + doReturn(true).when(mGlobalActionsDialog).shouldDisplayBugReport(any()); + // exclude bugreport in shouldShowAction to demonstrate how any button can be removed + doAnswer( + invocation -> !(invocation.getArgument(0) + instanceof GlobalActionsDialog.BugReportAction)) + .when(mGlobalActionsDialog).shouldShowAction(any()); + + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + // bugreport action not allowed + GlobalActionsDialog.GLOBAL_ACTION_KEY_BUGREPORT, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + mGlobalActionsDialog.createActionItems(); + + assertItemsOfType(mGlobalActionsDialog.mItems, + GlobalActionsDialog.EmergencyAction.class, + GlobalActionsDialog.ShutDownAction.class, + GlobalActionsDialog.RestartAction.class); + assertThat(mGlobalActionsDialog.mOverflowItems).isEmpty(); + assertThat(mGlobalActionsDialog.mPowerItems).isEmpty(); + } + + @Test + public void testShouldShowLockScreenMessage() throws RemoteException { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + mGlobalActionsDialog.mDialog = null; + when(mKeyguardStateController.isUnlocked()).thenReturn(false); + when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo()); + when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED); + mGlobalActionsDialog.mShowLockScreenCardsAndControls = false; + setupDefaultActions(); + when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController); + when(mWalletController.getPanelContent()).thenReturn(new FrameLayout(mContext)); + + mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); + + GlobalActionsDialog.ActionsDialog dialog = + (GlobalActionsDialog.ActionsDialog) mGlobalActionsDialog.mDialog; + assertThat(dialog).isNotNull(); + assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.VISIBLE); + + // Dismiss the dialog so that it does not pollute other tests + mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); + } + + @Test + public void testShouldNotShowLockScreenMessage_whenWalletOrControlsShownOnLockScreen() + throws RemoteException { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + mGlobalActionsDialog.mDialog = null; + when(mKeyguardStateController.isUnlocked()).thenReturn(false); + when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo()); + when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED); + mGlobalActionsDialog.mShowLockScreenCardsAndControls = true; + setupDefaultActions(); + when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController); + when(mWalletController.getPanelContent()).thenReturn(new FrameLayout(mContext)); + + mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); + + GlobalActionsDialog.ActionsDialog dialog = + (GlobalActionsDialog.ActionsDialog) mGlobalActionsDialog.mDialog; + assertThat(dialog).isNotNull(); + assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.GONE); + + // Dismiss the dialog so that it does not pollute other tests + mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); + } + + @Test + public void testShouldNotShowLockScreenMessage_whenControlsAndWalletBothDisabled() + throws RemoteException { + mGlobalActionsDialog = spy(mGlobalActionsDialog); + mGlobalActionsDialog.mDialog = null; + when(mKeyguardStateController.isUnlocked()).thenReturn(false); + + when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo()); + when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED); + mGlobalActionsDialog.mShowLockScreenCardsAndControls = true; + setupDefaultActions(); + when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController); + when(mWalletController.getPanelContent()).thenReturn(null); + when(mControlsUiController.getAvailable()).thenReturn(false); + + mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); + + GlobalActionsDialog.ActionsDialog dialog = + (GlobalActionsDialog.ActionsDialog) mGlobalActionsDialog.mDialog; + assertThat(dialog).isNotNull(); + assertThat(dialog.mLockMessageContainer.getVisibility()).isEqualTo(View.GONE); + + // Dismiss the dialog so that it does not pollute other tests + mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); + } + + private UserInfo newUserInfo() { + return new UserInfo(0, null, null, UserInfo.FLAG_PRIMARY, null); + } + + private void setupDefaultActions() { + String[] actions = { + GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY, + GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, + GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART, + }; + doReturn(actions).when(mGlobalActionsDialog).getDefaultActions(); + } +} diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java index 76a714c47f2e..07d2f0336217 100644 --- a/services/core/java/com/android/server/policy/GlobalActions.java +++ b/services/core/java/com/android/server/policy/GlobalActions.java @@ -16,12 +16,15 @@ package com.android.server.policy; import android.content.Context; import android.os.Handler; +import android.provider.Settings; import android.util.Slog; import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs; import com.android.server.policy.GlobalActionsProvider; +import com.libremobileos.providers.LMOSettings; + class GlobalActions implements GlobalActionsProvider.GlobalActionsListener { private static final String TAG = "GlobalActions"; @@ -64,7 +67,8 @@ class GlobalActions implements GlobalActionsProvider.GlobalActionsListener { mKeyguardShowing = keyguardShowing; mDeviceProvisioned = deviceProvisioned; mShowing = true; - if (mGlobalActionsAvailable) { + if (mGlobalActionsAvailable && Settings.Secure.getInt(mContext.getContentResolver(), + LMOSettings.Secure.POWER_MENU_TYPE, 0) != 4) { mHandler.postDelayed(mShowTimeout, 5000); mGlobalActionsProvider.showGlobalActions(); } else { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 2c96300f9f7b..69a1f7a1f0ad 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -2676,6 +2676,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); mContext.registerReceiver(mMultiuserReceiver, filter); + // register power menu broadcast + filter = new IntentFilter("android.intent.action.POWER_MENU"); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + showGlobalActionsInternal(); + } + }, filter); + mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); mHapticFeedbackVibrationProvider = new HapticFeedbackVibrationProvider(mContext.getResources(), mVibrator); -- GitLab