summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/TaskInfo.java3
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_floating_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_more_button.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml28
-rw-r--r--libs/WindowManager/Shell/res/drawable/handle_menu_background.xml30
-rw-r--r--libs/WindowManager/Shell/res/layout/caption_handle_menu.xml49
-rw-r--r--libs/WindowManager/Shell/res/layout/caption_window_decoration.xml11
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml10
-rw-r--r--libs/WindowManager/Shell/res/values/styles.xml7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java105
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java130
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java90
-rw-r--r--packages/SystemUI/res/values/integers.xml2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl19
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java548
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt550
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java106
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt116
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthCredentialViewTest.kt163
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java172
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java178
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt102
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt166
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt67
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt89
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt67
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt1
-rw-r--r--services/core/java/com/android/server/wm/Dimmer.java38
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java88
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java4
-rw-r--r--services/core/java/com/android/server/wm/LockTaskController.java15
-rw-r--r--services/core/java/com/android/server/wm/Transition.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DimmerTests.java44
91 files changed, 3082 insertions, 1291 deletions
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index b09463e3074b..3bf3067f8410 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -449,7 +449,8 @@ public class TaskInfo {
&& isVisible == that.isVisible
&& isSleeping == that.isSleeping
&& Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId)
- && parentTaskId == that.parentTaskId;
+ && parentTaskId == that.parentTaskId
+ && Objects.equals(topActivity, that.topActivity);
}
/**
diff --git a/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
new file mode 100644
index 000000000000..8779cc09715b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M5.958,37.708Q4.458,37.708 3.354,36.604Q2.25,35.5 2.25,34V18.292Q2.25,16.792 3.354,15.688Q4.458,14.583 5.958,14.583H9.5V5.958Q9.5,4.458 10.625,3.354Q11.75,2.25 13.208,2.25H34Q35.542,2.25 36.646,3.354Q37.75,4.458 37.75,5.958V21.667Q37.75,23.167 36.646,24.271Q35.542,25.375 34,25.375H30.5V34Q30.5,35.5 29.396,36.604Q28.292,37.708 26.792,37.708ZM5.958,34H26.792Q26.792,34 26.792,34Q26.792,34 26.792,34V21.542H5.958V34Q5.958,34 5.958,34Q5.958,34 5.958,34ZM30.5,21.667H34Q34,21.667 34,21.667Q34,21.667 34,21.667V9.208H13.208V14.583H26.833Q28.375,14.583 29.438,15.667Q30.5,16.75 30.5,18.25Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
new file mode 100644
index 000000000000..ea0fbb0e5d33
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M18.167,21.875H29.833V10.208H18.167ZM7.875,35.833Q6.375,35.833 5.271,34.729Q4.167,33.625 4.167,32.125V7.875Q4.167,6.375 5.271,5.271Q6.375,4.167 7.875,4.167H32.125Q33.625,4.167 34.729,5.271Q35.833,6.375 35.833,7.875V32.125Q35.833,33.625 34.729,34.729Q33.625,35.833 32.125,35.833ZM7.875,32.125H32.125Q32.125,32.125 32.125,32.125Q32.125,32.125 32.125,32.125V7.875Q32.125,7.875 32.125,7.875Q32.125,7.875 32.125,7.875H7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125ZM7.875,7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125V7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
new file mode 100644
index 000000000000..c55cbe2d054c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M34.042,14.625V9.333Q34.042,9.333 34.042,9.333Q34.042,9.333 34.042,9.333H28.708V5.708H33.917Q35.458,5.708 36.562,6.833Q37.667,7.958 37.667,9.458V14.625ZM2.375,14.625V9.458Q2.375,7.958 3.479,6.833Q4.583,5.708 6.125,5.708H11.292V9.333H6Q6,9.333 6,9.333Q6,9.333 6,9.333V14.625ZM28.708,34.25V30.667H34.042Q34.042,30.667 34.042,30.667Q34.042,30.667 34.042,30.667V25.333H37.667V30.542Q37.667,32 36.562,33.125Q35.458,34.25 33.917,34.25ZM6.125,34.25Q4.583,34.25 3.479,33.125Q2.375,32 2.375,30.542V25.333H6V30.667Q6,30.667 6,30.667Q6,30.667 6,30.667H11.292V34.25ZM9.333,27.292V12.667H30.708V27.292ZM12.917,23.708H27.125V16.25H12.917ZM12.917,23.708V16.25V23.708Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_more_button.xml b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
new file mode 100644
index 000000000000..447df43dfddd
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="6.0"
+ android:translateY="6.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M8.083,22.833Q6.917,22.833 6.104,22Q5.292,21.167 5.292,20Q5.292,18.833 6.125,18Q6.958,17.167 8.125,17.167Q9.292,17.167 10.125,18Q10.958,18.833 10.958,20Q10.958,21.167 10.125,22Q9.292,22.833 8.083,22.833ZM20,22.833Q18.833,22.833 18,22Q17.167,21.167 17.167,20Q17.167,18.833 18,18Q18.833,17.167 20,17.167Q21.167,17.167 22,18Q22.833,18.833 22.833,20Q22.833,21.167 22,22Q21.167,22.833 20,22.833ZM31.875,22.833Q30.708,22.833 29.875,22Q29.042,21.167 29.042,20Q29.042,18.833 29.875,18Q30.708,17.167 31.917,17.167Q33.083,17.167 33.896,18Q34.708,18.833 34.708,20Q34.708,21.167 33.875,22Q33.042,22.833 31.875,22.833Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
new file mode 100644
index 000000000000..c334a543a86a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+>
+ <group android:translateX="6.0"
+ android:translateY="8.0">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M18 14L13 14L13 2L18 2L18 14ZM20 14L20 2C20 0.9 19.1 -3.93402e-08 18 -8.74228e-08L13 -3.0598e-07C11.9 -3.54062e-07 11 0.9 11 2L11 14C11 15.1 11.9 16 13 16L18 16C19.1 16 20 15.1 20 14ZM7 14L2 14L2 2L7 2L7 14ZM9 14L9 2C9 0.9 8.1 -5.20166e-07 7 -5.68248e-07L2 -7.86805e-07C0.9 -8.34888e-07 -3.93403e-08 0.9 -8.74228e-08 2L-6.11959e-07 14C-6.60042e-07 15.1 0.9 16 2 16L7 16C8.1 16 9 15.1 9 14Z"/> </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml b/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml
new file mode 100644
index 000000000000..e307f007e4a4
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="210.0dp"
+ android:height="64.0dp"
+ android:tint="@color/decor_button_light_color"
+>
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18.3334 14L13.3334 14L13.3334 2L18.3334 2L18.3334 14ZM20.3334 14L20.3334 2C20.3334 0.9 19.4334 -3.93402e-08 18.3334 -8.74228e-08L13.3334 -3.0598e-07C12.2334 -3.54062e-07 11.3334 0.9 11.3334 2L11.3334 14C11.3334 15.1 12.2334 16 13.3334 16L18.3334 16C19.4334 16 20.3334 15.1 20.3334 14ZM7.33337 14L2.33337 14L2.33337 2L7.33337 2L7.33337 14ZM9.33337 14L9.33337 2C9.33337 0.899999 8.43337 -5.20166e-07 7.33337 -5.68248e-07L2.33337 -7.86805e-07C1.23337 -8.34888e-07 0.333374 0.899999 0.333374 2L0.333373 14C0.333373 15.1 1.23337 16 2.33337 16L7.33337 16C8.43337 16 9.33337 15.1 9.33337 14Z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
new file mode 100644
index 000000000000..d9a140b810f8
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <!--
+ ~ Copyright (C) 2022 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.wm.shell.windowdecor.WindowDecorLinearLayout
+xmlns:android="http://schemas.android.com/apk/res/android"
+android:id="@+id/handle_menu"
+android:layout_width="wrap_content"
+android:layout_height="wrap_content"
+android:gravity="center_horizontal"
+android:background="@drawable/decor_caption_title">
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/fullscreen_button"
+ android:contentDescription="@string/fullscreen_text"
+ android:background="@drawable/caption_fullscreen_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/split_screen_button"
+ android:contentDescription="@string/split_screen_text"
+ android:background="@drawable/caption_split_screen_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/floating_button"
+ android:contentDescription="@string/float_button_text"
+ android:background="@drawable/caption_floating_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/desktop_button"
+ android:contentDescription="@string/desktop_text"
+ android:background="@drawable/caption_desktop_button"/>
+ <Button
+ style="@style/CaptionButtonStyle"
+ android:id="@+id/more_button"
+ android:contentDescription="@string/more_button_text"
+ android:background="@drawable/caption_more_button"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
index 38cd5702f134..51e634c17532 100644
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
@@ -19,14 +19,10 @@
android:id="@+id/caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center_horizontal"
android:background="@drawable/decor_caption_title">
<Button
+ style="@style/CaptionButtonStyle"
android:id="@+id/back_button"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_margin="5dp"
- android:padding="4dp"
android:contentDescription="@string/back_button_text"
android:background="@drawable/decor_back_button_dark"
/>
@@ -39,11 +35,8 @@
android:contentDescription="@string/handle_text"
android:background="@drawable/decor_handle_dark"/>
<Button
+ style="@style/CaptionButtonStyle"
android:id="@+id/close_window"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_margin="5dp"
- android:padding="4dp"
android:contentDescription="@string/close_button_text"
android:background="@drawable/decor_close_button_dark"/>
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index d8a507469722..9fab3a1eb4b9 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -197,4 +197,14 @@
<string name="back_button_text">Back</string>
<!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
<string name="handle_text">Handle</string>
+ <!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
+ <string name="fullscreen_text">Fullscreen</string>
+ <!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] -->
+ <string name="desktop_text">Desktop Mode</string>
+ <!-- Accessibility text for the handle split screen button [CHAR LIMIT=NONE] -->
+ <string name="split_screen_text">Split Screen</string>
+ <!-- Accessibility text for the handle more options button [CHAR LIMIT=NONE] -->
+ <string name="more_button_text">More</string>
+ <!-- Accessibility text for the handle floating window button [CHAR LIMIT=NONE] -->
+ <string name="float_button_text">Float</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 19f7c3ef4364..a8597210d72e 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -30,6 +30,13 @@
<item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
</style>
+ <style name="CaptionButtonStyle">
+ <item name="android:layout_width">32dp</item>
+ <item name="android:layout_height">32dp</item>
+ <item name="android:layout_margin">5dp</item>
+ <item name="android:padding">4dp</item>
+ </style>
+
<style name="DockedDividerBackground">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/split_divider_bar_width</item>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 5b7d141591ea..295a2e3c4244 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -86,9 +86,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private static final int FLING_ENTER_DURATION = 350;
private static final int FLING_EXIT_DURATION = 350;
- private final int mDividerWindowWidth;
- private final int mDividerInsets;
- private final int mDividerSize;
+ private int mDividerWindowWidth;
+ private int mDividerInsets;
+ private int mDividerSize;
private final Rect mTempRect = new Rect();
private final Rect mRootBounds = new Rect();
@@ -131,6 +131,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mContext = context.createConfigurationContext(configuration);
mOrientation = configuration.orientation;
mRotation = configuration.windowConfiguration.getRotation();
+ mDensity = configuration.densityDpi;
mSplitLayoutHandler = splitLayoutHandler;
mDisplayImeController = displayImeController;
mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
@@ -139,24 +140,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
- final Resources resources = context.getResources();
- mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
- mDividerInsets = getDividerInsets(resources, context.getDisplay());
- mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
+ updateDividerConfig(mContext);
mRootBounds.set(configuration.windowConfiguration.getBounds());
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
resetDividerPosition();
- mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide);
+ mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
updateInvisibleRect();
}
- private int getDividerInsets(Resources resources, Display display) {
+ private void updateDividerConfig(Context context) {
+ final Resources resources = context.getResources();
+ final Display display = context.getDisplay();
final int dividerInset = resources.getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_insets);
-
int radius = 0;
RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT);
radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
@@ -167,7 +166,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
- return Math.max(dividerInset, radius);
+ mDividerInsets = Math.max(dividerInset, radius);
+ mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
+ mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
}
/** Gets bounds of the primary split with screen based coordinate. */
@@ -309,6 +310,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mRotation = rotation;
mDensity = density;
mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
+ updateDividerConfig(mContext);
initDividerPosition(mTempRect);
updateInvisibleRect();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index 16f1d1c2944c..f81c9f80830a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.RemoteAction;
@@ -70,6 +71,11 @@ public interface PipMenuController {
void setAppActions(List<RemoteAction> appActions, RemoteAction closeAction);
/**
+ * Wait until the next frame to run the given Runnable.
+ */
+ void runWithNextFrame(@NonNull Runnable runnable);
+
+ /**
* Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a
* need to synchronize the movements on the same frame as PiP.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f170e774739f..2d7c5ce6feb5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -179,8 +179,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// This is necessary in case there was a resize animation ongoing when exit PIP
// started, in which case the first resize will be skipped to let the exit
// operation handle the final resize out of PIP mode. See b/185306679.
- finishResize(tx, destinationBounds, direction, animationType);
- sendOnPipTransitionFinished(direction);
+ finishResizeDelayedIfNeeded(() -> {
+ finishResize(tx, destinationBounds, direction, animationType);
+ sendOnPipTransitionFinished(direction);
+ });
}
}
@@ -196,6 +198,34 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
};
+ /**
+ * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
+ *
+ * This is done to avoid a race condition between the last transaction applied in
+ * onAnimationUpdate and the finishResize in onAnimationEnd. finishResize creates a
+ * WindowContainerTransaction, which is to be applied by WmCore later. It may happen that it
+ * gets applied before the transaction created by the last onAnimationUpdate. As a result of
+ * this, the PiP surface may get scaled after the new bounds are applied by WmCore, which
+ * makes the PiP surface have unexpected bounds. To avoid this, we delay the finishResize
+ * operation until the next frame. This aligns the last onAnimationUpdate transaction with the
+ * WCT application.
+ *
+ * The race only happens when the PiP surface transaction has to be synced with the PiP menu
+ * due to the necessity for a delay when syncing the PiP surface, the PiP menu surface and
+ * the PiP menu contents.
+ */
+ private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
+ if (!shouldSyncPipTransactionWithMenu()) {
+ finishResizeRunnable.run();
+ return;
+ }
+ mPipMenuController.runWithNextFrame(finishResizeRunnable);
+ }
+
+ private boolean shouldSyncPipTransactionWithMenu() {
+ return mPipMenuController.isMenuVisible();
+ }
+
@VisibleForTesting
final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
new PipTransitionController.PipTransitionCallback() {
@@ -221,7 +251,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@Override
public boolean handlePipTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, Rect destinationBounds) {
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(leash, tx, destinationBounds);
return true;
}
@@ -1223,7 +1253,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceTransactionHelper
.crop(tx, mLeash, toBounds)
.round(tx, mLeash, mPipTransitionState.isInPip());
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
@@ -1265,7 +1295,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceTransactionHelper
.scale(tx, mLeash, startBounds, toBounds, degrees)
.round(tx, mLeash, startBounds, toBounds);
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 281ea530e9e1..27902b2231ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -305,6 +305,18 @@ public class PhonePipMenuController implements PipMenuController {
showResizeHandle);
}
+ @Override
+ public void runWithNextFrame(Runnable runnable) {
+ if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+ runnable.run();
+ }
+
+ mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
+ mMainHandler.post(runnable);
+ });
+ mPipMenuView.invalidate();
+ }
+
/**
* Move the PiP menu, which does a translation and possibly a scale transformation.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 4ce45e142c64..7d4b43be4f73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -466,6 +466,18 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
@Override
+ public void runWithNextFrame(Runnable runnable) {
+ if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+ runnable.run();
+ }
+
+ mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
+ mMainHandler.post(runnable);
+ });
+ mPipMenuView.invalidate();
+ }
+
+ @Override
public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction,
Rect pipDestBounds) {
if (DEBUG) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index dca516a327b0..36dd8edaa8b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -26,11 +26,16 @@ import android.app.ActivityTaskManager;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Handler;
+import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
+import android.view.InputChannel;
import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -64,8 +69,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final SyncTransactionQueue mSyncQueue;
private FreeformTaskTransitionStarter mTransitionStarter;
private DesktopModeController mDesktopModeController;
+ private EventReceiver mEventReceiver;
+ private InputMonitor mInputMonitor;
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
+ private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
public CaptionWindowDecorViewModel(
Context context,
@@ -108,12 +116,19 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
- TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
+ TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration,
+ mDragStartListener);
CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
setupWindowDecorationForTransition(taskInfo, startT, finishT);
+ if (mInputMonitor == null) {
+ mInputMonitor = InputManager.getInstance().monitorGestureInput(
+ "caption-touch", mContext.getDisplayId());
+ mEventReceiver = new EventReceiver(
+ mInputMonitor.getInputChannel(), Looper.myLooper());
+ }
return true;
}
@@ -165,6 +180,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
@Override
public void onClick(View v) {
+ CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
if (id == R.id.close_window) {
WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -176,6 +192,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
} else if (id == R.id.back_button) {
injectBackKey();
+ } else if (id == R.id.caption_handle) {
+ decoration.createHandleMenu();
+ } else if (id == R.id.desktop_button) {
+ mDesktopModeController.setDesktopModeActive(true);
+ decoration.closeHandleMenu();
+ } else if (id == R.id.fullscreen_button) {
+ mDesktopModeController.setDesktopModeActive(false);
+ decoration.closeHandleMenu();
+ decoration.setButtonVisibility();
}
}
private void injectBackKey() {
@@ -257,6 +282,36 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
}
+ // InputEventReceiver to listen for touch input outside of caption bounds
+ private class EventReceiver extends InputEventReceiver {
+ EventReceiver(InputChannel channel, Looper looper) {
+ super(channel, looper);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = false;
+ if (event instanceof MotionEvent
+ && ((MotionEvent) event).getActionMasked() == MotionEvent.ACTION_UP) {
+ handled = true;
+ CaptionWindowDecorViewModel.this.handleMotionEvent((MotionEvent) event);
+ }
+ finishInputEvent(event, handled);
+ }
+ }
+
+ // If any input received is outside of caption bounds, turn off handle menu
+ private void handleMotionEvent(MotionEvent ev) {
+ int size = mWindowDecorByTaskId.size();
+ for (int i = 0; i < size; i++) {
+ CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
+ if (decoration != null) {
+ decoration.closeHandleMenuIfNeeded(ev);
+ }
+ }
+ }
+
+
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
return DesktopModeStatus.IS_SUPPORTED
@@ -264,4 +319,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
+
+ private class DragStartListenerImpl implements TaskPositioner.DragStartListener{
+ @Override
+ public void onDragStart(int taskId) {
+ mWindowDecorByTaskId.get(taskId).closeHandleMenu();
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 9d61c14e1435..03cad043ed67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -20,10 +20,14 @@ import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.view.Choreographer;
+import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
@@ -58,6 +62,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
private boolean mDesktopActive;
+ private AdditionalWindow mHandleMenu;
+
CaptionWindowDecoration(
Context context,
DisplayController displayController,
@@ -123,7 +129,20 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
if (isDragResizeable) {
mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
}
+ final Resources resources = mDecorWindowContext.getResources();
+ final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ final int captionHeight = loadDimensionPixelSize(resources,
+ mRelayoutParams.mCaptionHeightId);
+ final int captionWidth = loadDimensionPixelSize(resources,
+ mRelayoutParams.mCaptionWidthId);
+ final int captionLeft = taskBounds.width() / 2
+ - captionWidth / 2;
+ final int captionTop = taskBounds.top
+ <= captionHeight / 2 ? 0 : -captionHeight / 2;
+ mRelayoutParams.setCaptionPosition(captionLeft, captionTop);
+
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
+ taskInfo = null; // Clear it just in case we use it accidentally
mTaskOrganizer.applyTransaction(wct);
@@ -137,15 +156,14 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
}
// If this task is not focused, do not show caption.
- setCaptionVisibility(taskInfo.isFocused);
+ setCaptionVisibility(mTaskInfo.isFocused);
// Only handle should show if Desktop Mode is inactive.
boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
- if (mDesktopActive != desktopCurrentStatus && taskInfo.isFocused) {
+ if (mDesktopActive != desktopCurrentStatus && mTaskInfo.isFocused) {
mDesktopActive = desktopCurrentStatus;
setButtonVisibility();
}
- taskInfo = null; // Clear it just in case we use it accidentally
if (!isDragResizeable) {
closeDragResizeListener();
@@ -184,9 +202,22 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
back.setOnClickListener(mOnCaptionButtonClickListener);
View handle = caption.findViewById(R.id.caption_handle);
handle.setOnTouchListener(mOnCaptionTouchListener);
+ handle.setOnClickListener(mOnCaptionButtonClickListener);
setButtonVisibility();
}
+ private void setupHandleMenu() {
+ View menu = mHandleMenu.mWindowViewHost.getView();
+ View fullscreen = menu.findViewById(R.id.fullscreen_button);
+ fullscreen.setOnClickListener(mOnCaptionButtonClickListener);
+ View desktop = menu.findViewById(R.id.desktop_button);
+ desktop.setOnClickListener(mOnCaptionButtonClickListener);
+ View split = menu.findViewById(R.id.split_screen_button);
+ split.setOnClickListener(mOnCaptionButtonClickListener);
+ View more = menu.findViewById(R.id.more_button);
+ more.setOnClickListener(mOnCaptionButtonClickListener);
+ }
+
/**
* Sets caption visibility based on task focus.
*
@@ -194,8 +225,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
*/
private void setCaptionVisibility(boolean visible) {
int v = visible ? View.VISIBLE : View.GONE;
- View caption = mResult.mRootView.findViewById(R.id.caption);
- caption.setVisibility(v);
+ View captionView = mResult.mRootView.findViewById(R.id.caption);
+ captionView.setVisibility(v);
+ if (!visible) closeHandleMenu();
}
/**
@@ -203,6 +235,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
*
*/
public void setButtonVisibility() {
+ mDesktopActive = DesktopModeStatus.isActive(mContext);
int v = mDesktopActive ? View.VISIBLE : View.GONE;
View caption = mResult.mRootView.findViewById(R.id.caption);
View back = caption.findViewById(R.id.back_button);
@@ -220,6 +253,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
}
+ public boolean isHandleMenuActive() {
+ return mHandleMenu != null;
+ }
+
private void closeDragResizeListener() {
if (mDragResizeListener == null) {
return;
@@ -228,9 +265,67 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mDragResizeListener = null;
}
+ /**
+ * Create and display handle menu window
+ */
+ public void createHandleMenu() {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ final Resources resources = mDecorWindowContext.getResources();
+ int x = mRelayoutParams.mCaptionX;
+ int y = mRelayoutParams.mCaptionY;
+ int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ String namePrefix = "Caption Menu";
+ mHandleMenu = addWindow(R.layout.caption_handle_menu, namePrefix, t,
+ x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
+ width, height);
+ mSyncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+ });
+ setupHandleMenu();
+ }
+
+ /**
+ * Close the handle menu window
+ */
+ public void closeHandleMenu() {
+ if (!isHandleMenuActive()) return;
+ mHandleMenu.releaseView();
+ mHandleMenu = null;
+ }
+
+ @Override
+ void releaseViews() {
+ closeHandleMenu();
+ super.releaseViews();
+ }
+
+ /**
+ * Close an open handle menu if input is outside of menu coordinates
+ * @param ev the tapped point to compare against
+ * @return
+ */
+ public void closeHandleMenuIfNeeded(MotionEvent ev) {
+ if (mHandleMenu != null) {
+ Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
+ .positionInParent;
+ final Resources resources = mDecorWindowContext.getResources();
+ ev.offsetLocation(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
+ ev.offsetLocation(-positionInParent.x, -positionInParent.y);
+ int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ if (!(ev.getX() >= 0 && ev.getY() >= 0
+ && ev.getX() <= width && ev.getY() <= height)) {
+ closeHandleMenu();
+ }
+ }
+ }
+
@Override
public void close() {
closeDragResizeListener();
+ closeHandleMenu();
super.close();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index 27c10114ac0e..f0f2db7ded80 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -42,14 +42,18 @@ class TaskPositioner implements DragResizeCallback {
private final Rect mResizeTaskBounds = new Rect();
private int mCtrlType;
+ private DragStartListener mDragStartListener;
- TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) {
+ TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+ DragStartListener dragStartListener) {
mTaskOrganizer = taskOrganizer;
mWindowDecoration = windowDecoration;
+ mDragStartListener = dragStartListener;
}
@Override
public void onDragResizeStart(int ctrlType, float x, float y) {
+ mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
mCtrlType = ctrlType;
mTaskBoundsAtDragStart.set(
@@ -97,4 +101,12 @@ class TaskPositioner implements DragResizeCallback {
mTaskOrganizer.applyTransaction(wct);
}
}
+
+ interface DragStartListener {
+ /**
+ * Inform the implementing class that a drag resize has started
+ * @param taskId id of this positioner's {@link WindowDecoration}
+ */
+ void onDragStart(int taskId);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index b314163802ca..7ecb3f3f6355 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -200,16 +200,17 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
final Resources resources = mDecorWindowContext.getResources();
- final int decorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
- final int decorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
+ outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
+ outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
outResult.mWidth = taskBounds.width()
+ loadDimensionPixelSize(resources, params.mOutsetRightId)
- - decorContainerOffsetX;
+ - outResult.mDecorContainerOffsetX;
outResult.mHeight = taskBounds.height()
+ loadDimensionPixelSize(resources, params.mOutsetBottomId)
- - decorContainerOffsetY;
+ - outResult.mDecorContainerOffsetY;
startT.setPosition(
- mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
+ mDecorationContainerSurface,
+ outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
.setWindowCrop(mDecorationContainerSurface,
outResult.mWidth, outResult.mHeight)
// TODO(b/244455401): Change the z-order when it's better organized
@@ -252,14 +253,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId);
- //Prevent caption from going offscreen if task is too high up
- final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2;
-
startT.setPosition(
- mCaptionContainerSurface, -decorContainerOffsetX
- + taskBounds.width() / 2 - captionWidth / 2,
- -decorContainerOffsetY - captionYPos)
- .setWindowCrop(mCaptionContainerSurface, taskBounds.width(), captionHeight)
+ mCaptionContainerSurface,
+ -outResult.mDecorContainerOffsetX + params.mCaptionX,
+ -outResult.mDecorContainerOffsetY + params.mCaptionY)
+ .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
.show(mCaptionContainerSurface);
if (mCaptionWindowManager == null) {
@@ -292,7 +290,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Caption insets
mCaptionInsetsRect.set(taskBounds);
mCaptionInsetsRect.bottom =
- mCaptionInsetsRect.top + captionHeight - captionYPos;
+ mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect,
CAPTION_INSETS_TYPES);
} else {
@@ -302,10 +300,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Task surface itself
Point taskPosition = mTaskInfo.positionInParent;
mTaskSurfaceCrop.set(
- decorContainerOffsetX,
- decorContainerOffsetY,
- outResult.mWidth + decorContainerOffsetX,
- outResult.mHeight + decorContainerOffsetY);
+ outResult.mDecorContainerOffsetX,
+ outResult.mDecorContainerOffsetY,
+ outResult.mWidth + outResult.mDecorContainerOffsetX,
+ outResult.mHeight + outResult.mDecorContainerOffsetY);
startT.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
.setCrop(mTaskSurface, mTaskSurfaceCrop);
@@ -326,7 +324,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return true;
}
- private void releaseViews() {
+ void releaseViews() {
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
@@ -369,20 +367,60 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
releaseViews();
}
- private static int loadDimensionPixelSize(Resources resources, int resourceId) {
+ static int loadDimensionPixelSize(Resources resources, int resourceId) {
if (resourceId == Resources.ID_NULL) {
return 0;
}
return resources.getDimensionPixelSize(resourceId);
}
- private static float loadDimension(Resources resources, int resourceId) {
+ static float loadDimension(Resources resources, int resourceId) {
if (resourceId == Resources.ID_NULL) {
return 0;
}
return resources.getDimension(resourceId);
}
+ /**
+ * Create a window associated with this WindowDecoration.
+ * Note that subclass must dispose of this when the task is hidden/closed.
+ * @param layoutId layout to make the window from
+ * @param t the transaction to apply
+ * @param xPos x position of new window
+ * @param yPos y position of new window
+ * @param width width of new window
+ * @param height height of new window
+ * @return
+ */
+ AdditionalWindow addWindow(int layoutId, String namePrefix,
+ SurfaceControl.Transaction t, int xPos, int yPos, int width, int height) {
+ final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
+ SurfaceControl windowSurfaceControl = builder
+ .setName(namePrefix + " of Task=" + mTaskInfo.taskId)
+ .setContainerLayer()
+ .setParent(mDecorationContainerSurface)
+ .build();
+ View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
+
+ t.setPosition(
+ windowSurfaceControl, xPos, yPos)
+ .setWindowCrop(windowSurfaceControl, width, height)
+ .show(windowSurfaceControl);
+ final WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(width, height,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+ lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
+ lp.setTrustedOverlay();
+ WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
+ windowSurfaceControl, null /* hostInputToken */);
+ SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
+ .create(mDecorWindowContext, mDisplay, windowManager);
+ viewHost.setView(v, lp);
+ return new AdditionalWindow(windowSurfaceControl, viewHost,
+ mSurfaceControlTransactionSupplier);
+ }
+
static class RelayoutParams{
RunningTaskInfo mRunningTaskInfo;
int mLayoutResId;
@@ -395,6 +433,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
int mOutsetLeftId;
int mOutsetRightId;
+ int mCaptionX;
+ int mCaptionY;
+
void setOutsets(int leftId, int topId, int rightId, int bottomId) {
mOutsetLeftId = leftId;
mOutsetTopId = topId;
@@ -402,6 +443,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mOutsetBottomId = bottomId;
}
+ void setCaptionPosition(int left, int top) {
+ mCaptionX = left;
+ mCaptionY = top;
+ }
+
void reset() {
mLayoutResId = Resources.ID_NULL;
mCaptionHeightId = Resources.ID_NULL;
@@ -412,6 +458,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mOutsetBottomId = Resources.ID_NULL;
mOutsetLeftId = Resources.ID_NULL;
mOutsetRightId = Resources.ID_NULL;
+
+ mCaptionX = 0;
+ mCaptionY = 0;
}
}
@@ -419,10 +468,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
int mWidth;
int mHeight;
T mRootView;
+ int mDecorContainerOffsetX;
+ int mDecorContainerOffsetY;
void reset() {
mWidth = 0;
mHeight = 0;
+ mDecorContainerOffsetX = 0;
+ mDecorContainerOffsetY = 0;
mRootView = null;
}
}
@@ -432,4 +485,41 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return new SurfaceControlViewHost(c, d, wmm);
}
}
+
+ /**
+ * Subclass for additional windows associated with this WindowDecoration
+ */
+ static class AdditionalWindow {
+ SurfaceControl mWindowSurface;
+ SurfaceControlViewHost mWindowViewHost;
+ Supplier<SurfaceControl.Transaction> mTransactionSupplier;
+
+ private AdditionalWindow(SurfaceControl surfaceControl,
+ SurfaceControlViewHost surfaceControlViewHost,
+ Supplier<SurfaceControl.Transaction> transactionSupplier) {
+ mWindowSurface = surfaceControl;
+ mWindowViewHost = surfaceControlViewHost;
+ mTransactionSupplier = transactionSupplier;
+ }
+
+ void releaseView() {
+ WindowlessWindowManager windowManager = mWindowViewHost.getWindowlessWM();
+
+ if (mWindowViewHost != null) {
+ mWindowViewHost.release();
+ mWindowViewHost = null;
+ }
+ windowManager = null;
+ final SurfaceControl.Transaction t = mTransactionSupplier.get();
+ boolean released = false;
+ if (mWindowSurface != null) {
+ t.remove(mWindowSurface);
+ mWindowSurface = null;
+ released = true;
+ }
+ if (released) {
+ t.apply();
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 4d37e5dbc4dc..15181b1549f5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
@@ -64,6 +65,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
@@ -102,12 +104,14 @@ public class WindowDecorationTests extends ShellTestCase {
private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
private SurfaceControl.Transaction mMockSurfaceControlStartT;
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
+ private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
@Before
public void setUp() {
mMockSurfaceControlStartT = createMockSurfaceControlTransaction();
mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
+ mMockSurfaceControlAddWindowT = createMockSurfaceControlTransaction();
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
.create(any(), any(), any());
@@ -227,8 +231,8 @@ public class WindowDecorationTests extends ShellTestCase {
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -46, 8);
- verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
+ verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
+ verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 432, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
@@ -242,7 +246,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockView).setTaskFocusState(true);
verify(mMockWindowContainerTransaction)
.addRectInsetsProvider(taskInfo.token,
- new Rect(100, 300, 400, 332),
+ new Rect(100, 300, 400, 364),
new int[] { InsetsState.ITYPE_CAPTION_BAR });
}
@@ -366,6 +370,71 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
}
+ @Test
+ public void testAddWindow() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+ createMockSurfaceControlBuilder(taskBackgroundSurface);
+ mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+ final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder captionContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(captionContainerSurface);
+ mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+ final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mMockSurfaceControlTransactions.add(t);
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .build();
+ taskInfo.isFocused = true;
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ mRelayoutParams.setOutsets(
+ R.dimen.test_window_decor_left_outset,
+ R.dimen.test_window_decor_top_outset,
+ R.dimen.test_window_decor_right_outset,
+ R.dimen.test_window_decor_bottom_outset);
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ windowDecor.relayout(taskInfo);
+
+ final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder additionalWindowSurfaceBuilder =
+ createMockSurfaceControlBuilder(additionalWindowSurface);
+ mMockSurfaceControlBuilders.add(additionalWindowSurfaceBuilder);
+
+ WindowDecoration.AdditionalWindow additionalWindow = windowDecor.addTestWindow();
+
+ verify(additionalWindowSurfaceBuilder).setContainerLayer();
+ verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
+ verify(additionalWindowSurfaceBuilder).build();
+ verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
+ verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, 432, 64);
+ verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
+ verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
+ .create(any(), eq(defaultDisplay), any());
+ assertThat(additionalWindow.mWindowViewHost).isNotNull();
+
+ additionalWindow.releaseView();
+
+ assertThat(additionalWindow.mWindowViewHost).isNull();
+ assertThat(additionalWindow.mWindowSurface).isNull();
+ }
+
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
@@ -429,5 +498,20 @@ public class WindowDecorationTests extends ShellTestCase {
relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
+
+ private WindowDecoration.AdditionalWindow addTestWindow() {
+ final Resources resources = mDecorWindowContext.getResources();
+ int x = mRelayoutParams.mCaptionX;
+ int y = mRelayoutParams.mCaptionY;
+ int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+ int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+ String name = "Test Window";
+ WindowDecoration.AdditionalWindow additionalWindow =
+ addWindow(R.layout.caption_handle_menu, name, mMockSurfaceControlAddWindowT,
+ x - mRelayoutResult.mDecorContainerOffsetX,
+ y - mRelayoutResult.mDecorContainerOffsetY,
+ width, height);
+ return additionalWindow;
+ }
}
}
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index e30d4415a0c4..8d4431520c75 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -35,4 +35,6 @@
<!-- Percentage of displacement for items in QQS to guarantee matching with bottom of clock at
fade_out_complete_frame -->
<dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen>
+
+ <integer name="qs_carrier_max_em">7</integer>
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index e743ec87bd1c..bfbe88c475ac 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -20,6 +20,7 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
import android.view.MotionEvent;
+import android.view.SurfaceControl;
import com.android.systemui.shared.recents.ISystemUiProxy;
oneway interface IOverviewProxy {
@@ -44,12 +45,6 @@ oneway interface IOverviewProxy {
void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) = 8;
/**
- * Sent when there was an action on one of the onboarding tips view.
- * TODO: Move this implementation to SystemUI completely
- */
- void onTip(int actionType, int viewType) = 10;
-
- /**
* Sent when device assistant changes its default assistant whether it is available or not.
*/
void onAssistantAvailable(boolean available) = 13;
@@ -60,13 +55,6 @@ oneway interface IOverviewProxy {
void onAssistantVisibilityChanged(float visibility) = 14;
/**
- * Sent when back is triggered.
- * TODO: Move this implementation to SystemUI completely
- */
- void onBackAction(boolean completed, int downX, int downY, boolean isButton,
- boolean gestureSwipeLeft) = 15;
-
- /**
* Sent when some system ui state changes.
*/
void onSystemUiStateChanged(int stateFlags) = 16;
@@ -115,4 +103,9 @@ oneway interface IOverviewProxy {
* Sent when split keyboard shortcut is triggered to enter stage split.
*/
void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
+
+ /**
+ * Sent when the surface for navigation bar is created or changed
+ */
+ void onNavigationBarSurface(in SurfaceControl surface) = 26;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 2b2b05ce2fbf..b99b72bb4275 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -106,9 +106,6 @@ interface ISystemUiProxy {
/** Sets home rotation enabled. */
void setHomeRotationEnabled(boolean enabled) = 45;
- /** Notifies that a swipe-up gesture has started */
- oneway void notifySwipeUpGestureStarted() = 46;
-
/** Notifies when taskbar status updated */
oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 80c6c48cb7ee..37da2c7d3379 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -120,7 +120,7 @@ public class AuthContainerView extends LinearLayout
@VisibleForTesting final BiometricCallback mBiometricCallback;
@Nullable private AuthBiometricView mBiometricView;
- @Nullable private AuthCredentialView mCredentialView;
+ @VisibleForTesting @Nullable AuthCredentialView mCredentialView;
private final AuthPanelController mPanelController;
private final FrameLayout mFrameLayout;
private final ImageView mBackgroundView;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
index f9e44a0c1724..85cb39849f1f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.content.Context;
import android.util.AttributeSet;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
@@ -34,7 +35,7 @@ import java.util.List;
*/
public class AuthCredentialPatternView extends AuthCredentialView {
- private LockPatternView mLockPatternView;
+ @VisibleForTesting LockPatternView mLockPatternView;
private class UnlockPatternListener implements LockPatternView.OnPatternListener {
@@ -93,9 +94,7 @@ public class AuthCredentialPatternView extends AuthCredentialView {
@Override
protected void onErrorTimeoutFinish() {
super.onErrorTimeoutFinish();
- // select to enable marquee unless a screen reader is enabled
- mLockPatternView.setEnabled(!mAccessibilityManager.isEnabled()
- || !mAccessibilityManager.isTouchExplorationEnabled());
+ mLockPatternView.setEnabled(true);
}
public AuthCredentialPatternView(Context context, AttributeSet attrs) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 5958e6a436f1..157f14fb4c51 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -47,6 +47,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -98,7 +99,7 @@ public abstract class AuthCredentialView extends LinearLayout {
protected int mUserId;
protected long mOperationId;
protected int mEffectiveUserId;
- protected ErrorTimer mErrorTimer;
+ @VisibleForTesting ErrorTimer mErrorTimer;
protected @Background DelayableExecutor mBackgroundExecutor;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 96fe65f8fd40..65fcd760360c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -60,7 +60,9 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -119,6 +121,7 @@ public class UdfpsController implements DozeReceiver {
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final VibratorHelper mVibrator;
+ @NonNull private final FeatureFlags mFeatureFlags;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@@ -130,6 +133,7 @@ public class UdfpsController implements DozeReceiver {
@NonNull private final LatencyTracker mLatencyTracker;
@VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
@NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
+ @NonNull private final BouncerInteractor mBouncerInteractor;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -212,7 +216,8 @@ public class UdfpsController implements DozeReceiver {
mUnlockedScreenOffAnimationController,
mUdfpsDisplayMode, requestId, reason, callback,
(view, event, fromUdfpsView) -> onTouch(requestId, event,
- fromUdfpsView), mActivityLaunchAnimator)));
+ fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
+ mBouncerInteractor)));
}
@Override
@@ -590,6 +595,7 @@ public class UdfpsController implements DozeReceiver {
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
+ @NonNull FeatureFlags featureFlags,
@NonNull FalsingManager falsingManager,
@NonNull PowerManager powerManager,
@NonNull AccessibilityManager accessibilityManager,
@@ -608,7 +614,8 @@ public class UdfpsController implements DozeReceiver {
@NonNull LatencyTracker latencyTracker,
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
@NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
- @BiometricsBackground Executor biometricsExecutor) {
+ @BiometricsBackground Executor biometricsExecutor,
+ @NonNull BouncerInteractor bouncerInteractor) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -638,6 +645,8 @@ public class UdfpsController implements DozeReceiver {
mActivityLaunchAnimator = activityLaunchAnimator;
mAlternateTouchProvider = alternateTouchProvider.orElse(null);
mBiometricExecutor = biometricsExecutor;
+ mFeatureFlags = featureFlags;
+ mBouncerInteractor = bouncerInteractor;
mOrientationListener = new BiometricDisplayListener(
context,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 7d0109686351..d70861ac5f19 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -48,6 +48,8 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -70,29 +72,31 @@ const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui"
*/
@UiThread
class UdfpsControllerOverlay @JvmOverloads constructor(
- private val context: Context,
- fingerprintManager: FingerprintManager,
- private val inflater: LayoutInflater,
- private val windowManager: WindowManager,
- private val accessibilityManager: AccessibilityManager,
- private val statusBarStateController: StatusBarStateController,
- private val shadeExpansionStateManager: ShadeExpansionStateManager,
- private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val dialogManager: SystemUIDialogManager,
- private val dumpManager: DumpManager,
- private val transitionController: LockscreenShadeTransitionController,
- private val configurationController: ConfigurationController,
- private val systemClock: SystemClock,
- private val keyguardStateController: KeyguardStateController,
- private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
- private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
- val requestId: Long,
- @ShowReason val requestReason: Int,
- private val controllerCallback: IUdfpsOverlayControllerCallback,
- private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
- private val activityLaunchAnimator: ActivityLaunchAnimator,
- private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
+ private val context: Context,
+ fingerprintManager: FingerprintManager,
+ private val inflater: LayoutInflater,
+ private val windowManager: WindowManager,
+ private val accessibilityManager: AccessibilityManager,
+ private val statusBarStateController: StatusBarStateController,
+ private val shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val dialogManager: SystemUIDialogManager,
+ private val dumpManager: DumpManager,
+ private val transitionController: LockscreenShadeTransitionController,
+ private val configurationController: ConfigurationController,
+ private val systemClock: SystemClock,
+ private val keyguardStateController: KeyguardStateController,
+ private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+ private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
+ val requestId: Long,
+ @ShowReason val requestReason: Int,
+ private val controllerCallback: IUdfpsOverlayControllerCallback,
+ private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
+ private val activityLaunchAnimator: ActivityLaunchAnimator,
+ private val featureFlags: FeatureFlags,
+ private val bouncerInteractor: BouncerInteractor,
+ private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -246,7 +250,9 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
unlockedScreenOffAnimationController,
dialogManager,
controller,
- activityLaunchAnimator
+ activityLaunchAnimator,
+ featureFlags,
+ bouncerInteractor
)
}
REASON_AUTH_BP -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
deleted file mode 100644
index 4d7f89d7b727..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.content.res.Configuration;
-import android.util.MathUtils;
-import android.view.MotionEvent;
-
-import com.android.keyguard.BouncerPanelExpansionCalculator;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.shade.ShadeExpansionListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.time.SystemClock;
-
-import java.io.PrintWriter;
-
-/**
- * Class that coordinates non-HBM animations during keyguard authentication.
- */
-public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> {
- public static final String TAG = "UdfpsKeyguardViewCtrl";
- @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
- @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController;
- @NonNull private final ConfigurationController mConfigurationController;
- @NonNull private final SystemClock mSystemClock;
- @NonNull private final KeyguardStateController mKeyguardStateController;
- @NonNull private final UdfpsController mUdfpsController;
- @NonNull private final UnlockedScreenOffAnimationController
- mUnlockedScreenOffAnimationController;
- @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
- private final ValueAnimator mUnlockedScreenOffDozeAnimator = ValueAnimator.ofFloat(0f, 1f);
-
- private boolean mShowingUdfpsBouncer;
- private boolean mUdfpsRequested;
- private float mQsExpansion;
- private boolean mFaceDetectRunning;
- private int mStatusBarState;
- private float mTransitionToFullShadeProgress;
- private float mLastDozeAmount;
- private long mLastUdfpsBouncerShowTime = -1;
- private float mPanelExpansionFraction;
- private boolean mLaunchTransitionFadingAway;
- private boolean mIsLaunchingActivity;
- private float mActivityLaunchProgress;
-
- /**
- * hidden amount of pin/pattern/password bouncer
- * {@link KeyguardBouncer#EXPANSION_VISIBLE} (0f) to
- * {@link KeyguardBouncer#EXPANSION_HIDDEN} (1f)
- */
- private float mInputBouncerHiddenAmount;
- private boolean mIsGenericBouncerShowing; // whether UDFPS bouncer or input bouncer is visible
-
- protected UdfpsKeyguardViewController(
- @NonNull UdfpsKeyguardView view,
- @NonNull StatusBarStateController statusBarStateController,
- @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
- @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
- @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
- @NonNull DumpManager dumpManager,
- @NonNull LockscreenShadeTransitionController transitionController,
- @NonNull ConfigurationController configurationController,
- @NonNull SystemClock systemClock,
- @NonNull KeyguardStateController keyguardStateController,
- @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
- @NonNull SystemUIDialogManager systemUIDialogManager,
- @NonNull UdfpsController udfpsController,
- @NonNull ActivityLaunchAnimator activityLaunchAnimator) {
- super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
- dumpManager);
- mKeyguardViewManager = statusBarKeyguardViewManager;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mLockScreenShadeTransitionController = transitionController;
- mConfigurationController = configurationController;
- mSystemClock = systemClock;
- mKeyguardStateController = keyguardStateController;
- mUdfpsController = udfpsController;
- mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
- mActivityLaunchAnimator = activityLaunchAnimator;
-
- mUnlockedScreenOffDozeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- mUnlockedScreenOffDozeAnimator.setInterpolator(Interpolators.ALPHA_IN);
- mUnlockedScreenOffDozeAnimator.addUpdateListener(
- new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mView.onDozeAmountChanged(
- animation.getAnimatedFraction(),
- (float) animation.getAnimatedValue(),
- UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF);
- }
- });
- }
-
- @Override
- @NonNull protected String getTag() {
- return "UdfpsKeyguardViewController";
- }
-
- @Override
- public void onInit() {
- super.onInit();
- mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- }
-
- @Override
- protected void onViewAttached() {
- super.onViewAttached();
- final float dozeAmount = getStatusBarStateController().getDozeAmount();
- mLastDozeAmount = dozeAmount;
- mStateListener.onDozeAmountChanged(dozeAmount, dozeAmount);
- getStatusBarStateController().addCallback(mStateListener);
-
- mUdfpsRequested = false;
-
- mLaunchTransitionFadingAway = mKeyguardStateController.isLaunchTransitionFadingAway();
- mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
- mStatusBarState = getStatusBarStateController().getState();
- mQsExpansion = mKeyguardViewManager.getQsExpansion();
- updateGenericBouncerVisibility();
- mConfigurationController.addCallback(mConfigurationListener);
- getShadeExpansionStateManager().addExpansionListener(mShadeExpansionListener);
- updateScaleFactor();
- mView.updatePadding();
- updateAlpha();
- updatePauseAuth();
-
- mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
- mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this);
- mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
- }
-
- @Override
- protected void onViewDetached() {
- super.onViewDetached();
- mFaceDetectRunning = false;
-
- mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback);
- getStatusBarStateController().removeCallback(mStateListener);
- mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
- mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
- mConfigurationController.removeCallback(mConfigurationListener);
- getShadeExpansionStateManager().removeExpansionListener(mShadeExpansionListener);
- if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
- mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
- }
- mActivityLaunchAnimator.removeListener(mActivityLaunchAnimatorListener);
- }
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- super.dump(pw, args);
- pw.println("mShowingUdfpsBouncer=" + mShowingUdfpsBouncer);
- pw.println("mFaceDetectRunning=" + mFaceDetectRunning);
- pw.println("mStatusBarState=" + StatusBarState.toString(mStatusBarState));
- pw.println("mTransitionToFullShadeProgress=" + mTransitionToFullShadeProgress);
- pw.println("mQsExpansion=" + mQsExpansion);
- pw.println("mIsGenericBouncerShowing=" + mIsGenericBouncerShowing);
- pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount);
- pw.println("mPanelExpansionFraction=" + mPanelExpansionFraction);
- pw.println("unpausedAlpha=" + mView.getUnpausedAlpha());
- pw.println("mUdfpsRequested=" + mUdfpsRequested);
- pw.println("mLaunchTransitionFadingAway=" + mLaunchTransitionFadingAway);
- pw.println("mLastDozeAmount=" + mLastDozeAmount);
-
- mView.dump(pw);
- }
-
- /**
- * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
- * @return whether the udfpsBouncer has been newly shown or hidden
- */
- private boolean showUdfpsBouncer(boolean show) {
- if (mShowingUdfpsBouncer == show) {
- return false;
- }
-
- boolean udfpsAffordanceWasNotShowing = shouldPauseAuth();
- mShowingUdfpsBouncer = show;
- if (mShowingUdfpsBouncer) {
- mLastUdfpsBouncerShowTime = mSystemClock.uptimeMillis();
- }
- if (mShowingUdfpsBouncer) {
- if (udfpsAffordanceWasNotShowing) {
- mView.animateInUdfpsBouncer(null);
- }
-
- if (mKeyguardStateController.isOccluded()) {
- mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
- }
-
- mView.announceForAccessibility(mView.getContext().getString(
- R.string.accessibility_fingerprint_bouncer));
- } else {
- mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
- }
-
- updateGenericBouncerVisibility();
- updateAlpha();
- updatePauseAuth();
- return true;
- }
-
- /**
- * Returns true if the fingerprint manager is running but we want to temporarily pause
- * authentication. On the keyguard, we may want to show udfps when the shade
- * is expanded, so this can be overridden with the showBouncer method.
- */
- public boolean shouldPauseAuth() {
- if (mShowingUdfpsBouncer) {
- return false;
- }
-
- if (mUdfpsRequested && !getNotificationShadeVisible()
- && (!mIsGenericBouncerShowing
- || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE)
- && mKeyguardStateController.isShowing()) {
- return false;
- }
-
- if (mLaunchTransitionFadingAway) {
- return true;
- }
-
- // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
- // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
- // delayed. However, we still animate in the UDFPS affordance with the
- // mUnlockedScreenOffDozeAnimator.
- if (mStatusBarState != KEYGUARD && mLastDozeAmount == 0f) {
- return true;
- }
-
- if (mInputBouncerHiddenAmount < .5f) {
- return true;
- }
-
- if (mView.getUnpausedAlpha() < (255 * .1)) {
- return true;
- }
-
- return false;
- }
-
- @Override
- public boolean listenForTouchesOutsideView() {
- return true;
- }
-
- @Override
- public void onTouchOutsideView() {
- maybeShowInputBouncer();
- }
-
- /**
- * If we were previously showing the udfps bouncer, hide it and instead show the regular
- * (pin/pattern/password) bouncer.
- *
- * Does nothing if we weren't previously showing the UDFPS bouncer.
- */
- private void maybeShowInputBouncer() {
- if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
- mKeyguardViewManager.showBouncer(true);
- }
- }
-
- /**
- * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside
- * of the udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password
- * bouncer.
- */
- private boolean hasUdfpsBouncerShownWithMinTime() {
- return (mSystemClock.uptimeMillis() - mLastUdfpsBouncerShowTime) > 200;
- }
-
- /**
- * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
- * transitioning yet, while 1.0f means we've fully dragged down.
- *
- * For example, start swiping down to expand the notification shade from the empty space in
- * the middle of the lock screen.
- */
- public void setTransitionToFullShadeProgress(float progress) {
- mTransitionToFullShadeProgress = progress;
- updateAlpha();
- }
-
- /**
- * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's
- * alpha is based on the doze amount.
- */
- @Override
- public void updateAlpha() {
- // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
- // then the keyguard is occluded by some application - so instead use the input bouncer
- // hidden amount to determine the fade.
- float expansion = mUdfpsRequested ? mInputBouncerHiddenAmount : mPanelExpansionFraction;
-
- int alpha = mShowingUdfpsBouncer ? 255
- : (int) MathUtils.constrain(
- MathUtils.map(.5f, .9f, 0f, 255f, expansion),
- 0f, 255f);
-
- if (!mShowingUdfpsBouncer) {
- // swipe from top of the lockscreen to expand full QS:
- alpha *= (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(mQsExpansion));
-
- // swipe from the middle (empty space) of lockscreen to expand the notification shade:
- alpha *= (1.0f - mTransitionToFullShadeProgress);
-
- // Fade out the icon if we are animating an activity launch over the lockscreen and the
- // activity didn't request the UDFPS.
- if (mIsLaunchingActivity && !mUdfpsRequested) {
- alpha *= (1.0f - mActivityLaunchProgress);
- }
-
- // Fade out alpha when a dialog is shown
- // Fade in alpha when a dialog is hidden
- alpha *= mView.getDialogSuggestedAlpha();
- }
- mView.setUnpausedAlpha(alpha);
- }
-
- /**
- * Updates mIsGenericBouncerShowing (whether any bouncer is showing) and updates the
- * mInputBouncerHiddenAmount to reflect whether the input bouncer is fully showing or not.
- */
- private void updateGenericBouncerVisibility() {
- mIsGenericBouncerShowing = mKeyguardViewManager.isBouncerShowing(); // includes altBouncer
- final boolean altBouncerShowing = mKeyguardViewManager.isShowingAlternateAuth();
- if (altBouncerShowing || !mKeyguardViewManager.bouncerIsOrWillBeShowing()) {
- mInputBouncerHiddenAmount = 1f;
- } else if (mIsGenericBouncerShowing) {
- // input bouncer is fully showing
- mInputBouncerHiddenAmount = 0f;
- }
- }
-
- /**
- * Update the scale factor based on the device's resolution.
- */
- private void updateScaleFactor() {
- if (mUdfpsController != null && mUdfpsController.mOverlayParams != null) {
- mView.setScaleFactor(mUdfpsController.mOverlayParams.getScaleFactor());
- }
- }
-
- private final StatusBarStateController.StateListener mStateListener =
- new StatusBarStateController.StateListener() {
- @Override
- public void onDozeAmountChanged(float linear, float eased) {
- if (mLastDozeAmount < linear) {
- showUdfpsBouncer(false);
- }
- mUnlockedScreenOffDozeAnimator.cancel();
- final boolean animatingFromUnlockedScreenOff =
- mUnlockedScreenOffAnimationController.isAnimationPlaying();
- if (animatingFromUnlockedScreenOff && linear != 0f) {
- // we manually animate the fade in of the UDFPS icon since the unlocked
- // screen off animation prevents the doze amounts to be incrementally eased in
- mUnlockedScreenOffDozeAnimator.start();
- } else {
- mView.onDozeAmountChanged(linear, eased,
- UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
- }
-
- mLastDozeAmount = linear;
- updatePauseAuth();
- }
-
- @Override
- public void onStateChanged(int statusBarState) {
- mStatusBarState = statusBarState;
- updateAlpha();
- updatePauseAuth();
- }
- };
-
- private final StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor =
- new StatusBarKeyguardViewManager.AlternateAuthInterceptor() {
- @Override
- public boolean showAlternateAuthBouncer() {
- return showUdfpsBouncer(true);
- }
-
- @Override
- public boolean hideAlternateAuthBouncer() {
- return showUdfpsBouncer(false);
- }
-
- @Override
- public boolean isShowingAlternateAuthBouncer() {
- return mShowingUdfpsBouncer;
- }
-
- @Override
- public void requestUdfps(boolean request, int color) {
- mUdfpsRequested = request;
- mView.requestUdfps(request, color);
- updateAlpha();
- updatePauseAuth();
- }
-
- @Override
- public boolean isAnimating() {
- return false;
- }
-
- /**
- * Set the amount qs is expanded. Forxample, swipe down from the top of the
- * lock screen to start the full QS expansion.
- */
- @Override
- public void setQsExpansion(float qsExpansion) {
- mQsExpansion = qsExpansion;
- updateAlpha();
- updatePauseAuth();
- }
-
- @Override
- public boolean onTouch(MotionEvent event) {
- if (mTransitionToFullShadeProgress != 0) {
- return false;
- }
- return mUdfpsController.onTouch(event);
- }
-
- @Override
- public void setBouncerExpansionChanged(float expansion) {
- mInputBouncerHiddenAmount = expansion;
- updateAlpha();
- updatePauseAuth();
- }
-
- /**
- * Only called on primary auth bouncer changes, not on whether the UDFPS bouncer
- * visibility changes.
- */
- @Override
- public void onBouncerVisibilityChanged() {
- updateGenericBouncerVisibility();
- updateAlpha();
- updatePauseAuth();
- }
-
- @Override
- public void dump(PrintWriter pw) {
- pw.println(getTag());
- }
- };
-
- private final ConfigurationController.ConfigurationListener mConfigurationListener =
- new ConfigurationController.ConfigurationListener() {
- @Override
- public void onUiModeChanged() {
- mView.updateColor();
- }
-
- @Override
- public void onThemeChanged() {
- mView.updateColor();
- }
-
- @Override
- public void onConfigChanged(Configuration newConfig) {
- updateScaleFactor();
- mView.updatePadding();
- mView.updateColor();
- }
- };
-
- private final ShadeExpansionListener mShadeExpansionListener = new ShadeExpansionListener() {
- @Override
- public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
- float fraction = event.getFraction();
- mPanelExpansionFraction =
- mKeyguardViewManager.isBouncerInTransit() ? BouncerPanelExpansionCalculator
- .aboutToShowBouncerProgress(fraction) : fraction;
- updateAlpha();
- updatePauseAuth();
- }
- };
-
- private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onLaunchTransitionFadingAwayChanged() {
- mLaunchTransitionFadingAway =
- mKeyguardStateController.isLaunchTransitionFadingAway();
- updatePauseAuth();
- }
- };
-
- private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener =
- new ActivityLaunchAnimator.Listener() {
- @Override
- public void onLaunchAnimationStart() {
- mIsLaunchingActivity = true;
- mActivityLaunchProgress = 0f;
- updateAlpha();
- }
-
- @Override
- public void onLaunchAnimationEnd() {
- mIsLaunchingActivity = false;
- updateAlpha();
- }
-
- @Override
- public void onLaunchAnimationProgress(float linearProgress) {
- mActivityLaunchProgress = linearProgress;
- updateAlpha();
- }
- };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
new file mode 100644
index 000000000000..5bae2dc502d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.animation.ValueAnimator
+import android.content.res.Configuration
+import android.util.MathUtils
+import android.view.MotionEvent
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateAuthInterceptor
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** Class that coordinates non-HBM animations during keyguard authentication. */
+open class UdfpsKeyguardViewController
+constructor(
+ private val view: UdfpsKeyguardView,
+ statusBarStateController: StatusBarStateController,
+ shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val keyguardViewManager: StatusBarKeyguardViewManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ dumpManager: DumpManager,
+ private val lockScreenShadeTransitionController: LockscreenShadeTransitionController,
+ private val configurationController: ConfigurationController,
+ private val systemClock: SystemClock,
+ private val keyguardStateController: KeyguardStateController,
+ private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+ systemUIDialogManager: SystemUIDialogManager,
+ private val udfpsController: UdfpsController,
+ private val activityLaunchAnimator: ActivityLaunchAnimator,
+ featureFlags: FeatureFlags,
+ private val bouncerInteractor: BouncerInteractor
+) :
+ UdfpsAnimationViewController<UdfpsKeyguardView>(
+ view,
+ statusBarStateController,
+ shadeExpansionStateManager,
+ systemUIDialogManager,
+ dumpManager
+ ) {
+ private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
+ private var showingUdfpsBouncer = false
+ private var udfpsRequested = false
+ private var qsExpansion = 0f
+ private var faceDetectRunning = false
+ private var statusBarState = 0
+ private var transitionToFullShadeProgress = 0f
+ private var lastDozeAmount = 0f
+ private var lastUdfpsBouncerShowTime: Long = -1
+ private var panelExpansionFraction = 0f
+ private var launchTransitionFadingAway = false
+ private var isLaunchingActivity = false
+ private var activityLaunchProgress = 0f
+ private val unlockedScreenOffDozeAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong()
+ interpolator = Interpolators.ALPHA_IN
+ addUpdateListener { animation ->
+ view.onDozeAmountChanged(
+ animation.animatedFraction,
+ animation.animatedValue as Float,
+ UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF
+ )
+ }
+ }
+ /**
+ * Hidden amount of input (pin/pattern/password) bouncer. This is used
+ * [KeyguardBouncer.EXPANSION_VISIBLE] (0f) to [KeyguardBouncer.EXPANSION_HIDDEN] (1f). Only
+ * used for the non-modernBouncer.
+ */
+ private var inputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN
+ private var inputBouncerExpansion = 0f // only used for modernBouncer
+
+ private val stateListener: StatusBarStateController.StateListener =
+ object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ if (lastDozeAmount < linear) {
+ showUdfpsBouncer(false)
+ }
+ unlockedScreenOffDozeAnimator.cancel()
+ val animatingFromUnlockedScreenOff =
+ unlockedScreenOffAnimationController.isAnimationPlaying()
+ if (animatingFromUnlockedScreenOff && linear != 0f) {
+ // we manually animate the fade in of the UDFPS icon since the unlocked
+ // screen off animation prevents the doze amounts to be incrementally eased in
+ unlockedScreenOffDozeAnimator.start()
+ } else {
+ view.onDozeAmountChanged(
+ linear,
+ eased,
+ UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
+ )
+ }
+ lastDozeAmount = linear
+ updatePauseAuth()
+ }
+
+ override fun onStateChanged(statusBarState: Int) {
+ this@UdfpsKeyguardViewController.statusBarState = statusBarState
+ updateAlpha()
+ updatePauseAuth()
+ }
+ }
+
+ private val bouncerExpansionCallback: BouncerExpansionCallback =
+ object : BouncerExpansionCallback {
+ override fun onExpansionChanged(expansion: Float) {
+ inputBouncerHiddenAmount = expansion
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ override fun onVisibilityChanged(isVisible: Boolean) {
+ updateBouncerHiddenAmount()
+ updateAlpha()
+ updatePauseAuth()
+ }
+ }
+
+ private val configurationListener: ConfigurationController.ConfigurationListener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onUiModeChanged() {
+ view.updateColor()
+ }
+
+ override fun onThemeChanged() {
+ view.updateColor()
+ }
+
+ override fun onConfigChanged(newConfig: Configuration) {
+ updateScaleFactor()
+ view.updatePadding()
+ view.updateColor()
+ }
+ }
+
+ private val shadeExpansionListener = ShadeExpansionListener { (fraction) ->
+ panelExpansionFraction =
+ if (keyguardViewManager.isBouncerInTransit) {
+ aboutToShowBouncerProgress(fraction)
+ } else {
+ fraction
+ }
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ private val keyguardStateControllerCallback: KeyguardStateController.Callback =
+ object : KeyguardStateController.Callback {
+ override fun onLaunchTransitionFadingAwayChanged() {
+ launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
+ updatePauseAuth()
+ }
+ }
+
+ private val activityLaunchAnimatorListener: ActivityLaunchAnimator.Listener =
+ object : ActivityLaunchAnimator.Listener {
+ override fun onLaunchAnimationStart() {
+ isLaunchingActivity = true
+ activityLaunchProgress = 0f
+ updateAlpha()
+ }
+
+ override fun onLaunchAnimationEnd() {
+ isLaunchingActivity = false
+ updateAlpha()
+ }
+
+ override fun onLaunchAnimationProgress(linearProgress: Float) {
+ activityLaunchProgress = linearProgress
+ updateAlpha()
+ }
+ }
+
+ private val statusBarKeyguardViewManagerCallback: KeyguardViewManagerCallback =
+ object : KeyguardViewManagerCallback {
+ override fun onQSExpansionChanged(qsExpansion: Float) {
+ this@UdfpsKeyguardViewController.qsExpansion = qsExpansion
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ /**
+ * Forward touches to the UdfpsController. This allows the touch to start from outside
+ * the sensor area and then slide their finger into the sensor area.
+ */
+ override fun onTouch(event: MotionEvent) {
+ // Don't forward touches if the shade has already started expanding.
+ if (transitionToFullShadeProgress != 0f) {
+ return
+ }
+ udfpsController.onTouch(event)
+ }
+ }
+
+ private val alternateAuthInterceptor: AlternateAuthInterceptor =
+ object : AlternateAuthInterceptor {
+ override fun showAlternateAuthBouncer(): Boolean {
+ return showUdfpsBouncer(true)
+ }
+
+ override fun hideAlternateAuthBouncer(): Boolean {
+ return showUdfpsBouncer(false)
+ }
+
+ override fun isShowingAlternateAuthBouncer(): Boolean {
+ return showingUdfpsBouncer
+ }
+
+ override fun requestUdfps(request: Boolean, color: Int) {
+ udfpsRequested = request
+ view.requestUdfps(request, color)
+ updateAlpha()
+ updatePauseAuth()
+ }
+
+ override fun dump(pw: PrintWriter) {
+ pw.println(tag)
+ }
+ }
+
+ override val tag: String
+ get() = TAG
+
+ override fun onInit() {
+ super.onInit()
+ keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+ }
+
+ init {
+ if (isModernBouncerEnabled) {
+ view.repeatWhenAttached {
+ // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
+ // can make the view not visible; and we still want to listen for events
+ // that may make the view visible again.
+ repeatOnLifecycle(Lifecycle.State.CREATED) { listenForBouncerExpansion(this) }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+ return scope.launch {
+ bouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
+ inputBouncerExpansion = bouncerExpansion
+ updateAlpha()
+ updatePauseAuth()
+ }
+ }
+ }
+
+ public override fun onViewAttached() {
+ super.onViewAttached()
+ val dozeAmount = statusBarStateController.dozeAmount
+ lastDozeAmount = dozeAmount
+ stateListener.onDozeAmountChanged(dozeAmount, dozeAmount)
+ statusBarStateController.addCallback(stateListener)
+ udfpsRequested = false
+ launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
+ keyguardStateController.addCallback(keyguardStateControllerCallback)
+ statusBarState = statusBarStateController.state
+ qsExpansion = keyguardViewManager.qsExpansion
+ keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
+ if (!isModernBouncerEnabled) {
+ val bouncer = keyguardViewManager.bouncer
+ bouncer?.expansion?.let {
+ bouncerExpansionCallback.onExpansionChanged(it)
+ bouncer.addBouncerExpansionCallback(bouncerExpansionCallback)
+ }
+ updateBouncerHiddenAmount()
+ }
+ configurationController.addCallback(configurationListener)
+ shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
+ updateScaleFactor()
+ view.updatePadding()
+ updateAlpha()
+ updatePauseAuth()
+ keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+ lockScreenShadeTransitionController.udfpsKeyguardViewController = this
+ activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
+ }
+
+ override fun onViewDetached() {
+ super.onViewDetached()
+ faceDetectRunning = false
+ keyguardStateController.removeCallback(keyguardStateControllerCallback)
+ statusBarStateController.removeCallback(stateListener)
+ keyguardViewManager.removeAlternateAuthInterceptor(alternateAuthInterceptor)
+ keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
+ configurationController.removeCallback(configurationListener)
+ shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
+ if (lockScreenShadeTransitionController.udfpsKeyguardViewController === this) {
+ lockScreenShadeTransitionController.udfpsKeyguardViewController = null
+ }
+ activityLaunchAnimator.removeListener(activityLaunchAnimatorListener)
+ keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
+ if (!isModernBouncerEnabled) {
+ keyguardViewManager.bouncer?.removeBouncerExpansionCallback(bouncerExpansionCallback)
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<String>) {
+ super.dump(pw, args)
+ pw.println("isModernBouncerEnabled=$isModernBouncerEnabled")
+ pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
+ pw.println("faceDetectRunning=$faceDetectRunning")
+ pw.println("statusBarState=" + StatusBarState.toString(statusBarState))
+ pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress")
+ pw.println("qsExpansion=$qsExpansion")
+ pw.println("panelExpansionFraction=$panelExpansionFraction")
+ pw.println("unpausedAlpha=" + view.unpausedAlpha)
+ pw.println("udfpsRequestedByApp=$udfpsRequested")
+ pw.println("launchTransitionFadingAway=$launchTransitionFadingAway")
+ pw.println("lastDozeAmount=$lastDozeAmount")
+ if (isModernBouncerEnabled) {
+ pw.println("inputBouncerExpansion=$inputBouncerExpansion")
+ } else {
+ pw.println("inputBouncerHiddenAmount=$inputBouncerHiddenAmount")
+ }
+ view.dump(pw)
+ }
+
+ /**
+ * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
+ * @return whether the udfpsBouncer has been newly shown or hidden
+ */
+ private fun showUdfpsBouncer(show: Boolean): Boolean {
+ if (showingUdfpsBouncer == show) {
+ return false
+ }
+ val udfpsAffordanceWasNotShowing = shouldPauseAuth()
+ showingUdfpsBouncer = show
+ if (showingUdfpsBouncer) {
+ lastUdfpsBouncerShowTime = systemClock.uptimeMillis()
+ }
+ if (showingUdfpsBouncer) {
+ if (udfpsAffordanceWasNotShowing) {
+ view.animateInUdfpsBouncer(null)
+ }
+ if (keyguardStateController.isOccluded) {
+ keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true)
+ }
+ view.announceForAccessibility(
+ view.context.getString(R.string.accessibility_fingerprint_bouncer)
+ )
+ } else {
+ keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
+ }
+ updateBouncerHiddenAmount()
+ updateAlpha()
+ updatePauseAuth()
+ return true
+ }
+
+ /**
+ * Returns true if the fingerprint manager is running but we want to temporarily pause
+ * authentication. On the keyguard, we may want to show udfps when the shade is expanded, so
+ * this can be overridden with the showBouncer method.
+ */
+ override fun shouldPauseAuth(): Boolean {
+ if (showingUdfpsBouncer) {
+ return false
+ }
+ if (
+ udfpsRequested &&
+ !notificationShadeVisible &&
+ !isInputBouncerFullyVisible() &&
+ keyguardStateController.isShowing
+ ) {
+ return false
+ }
+ if (launchTransitionFadingAway) {
+ return true
+ }
+
+ // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
+ // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
+ // delayed. However, we still animate in the UDFPS affordance with the
+ // mUnlockedScreenOffDozeAnimator.
+ if (statusBarState != StatusBarState.KEYGUARD && lastDozeAmount == 0f) {
+ return true
+ }
+ if (isBouncerExpansionGreaterThan(.5f)) {
+ return true
+ }
+ return view.unpausedAlpha < 255 * .1
+ }
+
+ fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean {
+ return if (isModernBouncerEnabled) {
+ inputBouncerExpansion >= bouncerExpansionThreshold
+ } else {
+ inputBouncerHiddenAmount < bouncerExpansionThreshold
+ }
+ }
+
+ fun isInputBouncerFullyVisible(): Boolean {
+ return if (isModernBouncerEnabled) {
+ inputBouncerExpansion == 1f
+ } else {
+ keyguardViewManager.isBouncerShowing && !keyguardViewManager.isShowingAlternateAuth
+ }
+ }
+
+ override fun listenForTouchesOutsideView(): Boolean {
+ return true
+ }
+
+ override fun onTouchOutsideView() {
+ maybeShowInputBouncer()
+ }
+
+ /**
+ * If we were previously showing the udfps bouncer, hide it and instead show the regular
+ * (pin/pattern/password) bouncer.
+ *
+ * Does nothing if we weren't previously showing the UDFPS bouncer.
+ */
+ private fun maybeShowInputBouncer() {
+ if (showingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
+ keyguardViewManager.showBouncer(true)
+ }
+ }
+
+ /**
+ * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside of the
+ * udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password bouncer.
+ */
+ private fun hasUdfpsBouncerShownWithMinTime(): Boolean {
+ return systemClock.uptimeMillis() - lastUdfpsBouncerShowTime > 200
+ }
+
+ /**
+ * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
+ * transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down
+ * to expand the notification shade from the empty space in the middle of the lock screen.
+ */
+ fun setTransitionToFullShadeProgress(progress: Float) {
+ transitionToFullShadeProgress = progress
+ updateAlpha()
+ }
+
+ /**
+ * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's alpha is
+ * based on the doze amount.
+ */
+ override fun updateAlpha() {
+ // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
+ // then the keyguard is occluded by some application - so instead use the input bouncer
+ // hidden amount to determine the fade.
+ val expansion = if (udfpsRequested) getInputBouncerHiddenAmt() else panelExpansionFraction
+ var alpha: Int =
+ if (showingUdfpsBouncer) 255
+ else MathUtils.constrain(MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f).toInt()
+ if (!showingUdfpsBouncer) {
+ // swipe from top of the lockscreen to expand full QS:
+ alpha =
+ (alpha * (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(qsExpansion)))
+ .toInt()
+
+ // swipe from the middle (empty space) of lockscreen to expand the notification shade:
+ alpha = (alpha * (1.0f - transitionToFullShadeProgress)).toInt()
+
+ // Fade out the icon if we are animating an activity launch over the lockscreen and the
+ // activity didn't request the UDFPS.
+ if (isLaunchingActivity && !udfpsRequested) {
+ alpha = (alpha * (1.0f - activityLaunchProgress)).toInt()
+ }
+
+ // Fade out alpha when a dialog is shown
+ // Fade in alpha when a dialog is hidden
+ alpha = (alpha * view.dialogSuggestedAlpha).toInt()
+ }
+ view.unpausedAlpha = alpha
+ }
+
+ private fun getInputBouncerHiddenAmt(): Float {
+ return if (isModernBouncerEnabled) {
+ 1f - inputBouncerExpansion
+ } else {
+ inputBouncerHiddenAmount
+ }
+ }
+
+ /** Update the scale factor based on the device's resolution. */
+ private fun updateScaleFactor() {
+ udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
+ }
+
+ private fun updateBouncerHiddenAmount() {
+ if (isModernBouncerEnabled) {
+ return
+ }
+ val altBouncerShowing = keyguardViewManager.isShowingAlternateAuth
+ if (altBouncerShowing || !keyguardViewManager.bouncerIsOrWillBeShowing()) {
+ inputBouncerHiddenAmount = 1f
+ } else if (keyguardViewManager.isBouncerShowing) {
+ // input bouncer is fully showing
+ inputBouncerHiddenAmount = 0f
+ }
+ }
+
+ companion object {
+ const val TAG = "UdfpsKeyguardViewController"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 344fb76dca04..844a311a988b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -232,11 +232,13 @@ object Flags {
@JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
// 1100 - windowing
+ @JvmField
@Keep
val WM_ENABLE_SHELL_TRANSITIONS =
SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false)
/** b/170163464: animate bubbles expanded view collapse with home gesture */
+ @JvmField
@Keep
val BUBBLES_HOME_GESTURE =
SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true)
@@ -258,40 +260,50 @@ object Flags {
@Keep
val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false)
+ @JvmField
@Keep
val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false)
+ @JvmField
@Keep
val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false)
+ @JvmField
@Keep
val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false)
+ @JvmField
@Keep
val SHOW_FLOATING_TASKS_AS_BUBBLES =
SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false)
+ @JvmField
@Keep
val ENABLE_FLING_TO_DISMISS_BUBBLE =
SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true)
+ @JvmField
@Keep
val ENABLE_FLING_TO_DISMISS_PIP =
SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true)
+ @JvmField
@Keep
val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false)
// 1200 - predictive back
+ @JvmField
@Keep
val WM_ENABLE_PREDICTIVE_BACK =
SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true)
+ @JvmField
@Keep
val WM_ENABLE_PREDICTIVE_BACK_ANIM =
SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false)
+ @JvmField
@Keep
val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false)
@@ -301,7 +313,7 @@ object Flags {
// 1300 - screenshots
// TODO(b/254512719): Tracking Bug
- @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300)
+ @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300, true)
// TODO(b/254513155): Tracking Bug
@JvmField val SCREENSHOT_WORK_PROFILE_POLICY = UnreleasedFlag(1301)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index 99ae85d7a548..80c6130955c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data
import android.view.KeyEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
import java.lang.ref.WeakReference
import javax.inject.Inject
@@ -45,4 +46,9 @@ interface BouncerViewDelegate {
fun dispatchBackKeyEventPreIme(): Boolean
fun showNextSecurityScreenOrFinish(): Boolean
fun resume()
+ fun setDismissAction(
+ onDismissAction: ActivityStarter.OnDismissAction?,
+ cancelAction: Runnable?,
+ )
+ fun willDismissWithActions(): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 543389e0a7cd..0046256c677b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -21,10 +21,9 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
-import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
+import com.android.systemui.statusbar.phone.KeyguardBouncer
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -41,9 +40,15 @@ constructor(
/** Determines if we want to instantaneously show the bouncer instead of translating. */
private val _isScrimmed = MutableStateFlow(false)
val isScrimmed = _isScrimmed.asStateFlow()
- /** Set amount of how much of the bouncer is showing on the screen */
- private val _expansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
- val expansionAmount = _expansionAmount.asStateFlow()
+ /**
+ * Set how much of the panel is showing on the screen.
+ * ```
+ * 0f = panel fully hidden = bouncer fully showing
+ * 1f = panel fully showing = bouncer fully hidden
+ * ```
+ */
+ private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncer.EXPANSION_HIDDEN)
+ val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
private val _isVisible = MutableStateFlow(false)
val isVisible = _isVisible.asStateFlow()
private val _show = MutableStateFlow<KeyguardBouncerModel?>(null)
@@ -54,8 +59,6 @@ constructor(
val hide = _hide.asStateFlow()
private val _startingToHide = MutableStateFlow(false)
val startingToHide = _startingToHide.asStateFlow()
- private val _onDismissAction = MutableStateFlow<BouncerCallbackActionsModel?>(null)
- val onDismissAction = _onDismissAction.asStateFlow()
private val _disappearAnimation = MutableStateFlow<Runnable?>(null)
val startingDisappearAnimation = _disappearAnimation.asStateFlow()
private val _keyguardPosition = MutableStateFlow(0f)
@@ -96,8 +99,8 @@ constructor(
_isScrimmed.value = isScrimmed
}
- fun setExpansion(expansion: Float) {
- _expansionAmount.value = expansion
+ fun setPanelExpansion(panelExpansion: Float) {
+ _panelExpansionAmount.value = panelExpansion
}
fun setVisible(isVisible: Boolean) {
@@ -120,10 +123,6 @@ constructor(
_startingToHide.value = startingToHide
}
- fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) {
- _onDismissAction.value = bouncerCallbackActionsModel
- }
-
fun setStartDisappearAnimation(runnable: Runnable?) {
_disappearAnimation.value = runnable
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
index 2af9318d92ec..dbb0352c2187 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
@@ -30,7 +30,6 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
@@ -40,6 +39,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
@@ -77,7 +77,7 @@ constructor(
KeyguardBouncerModel(
promptReason = repository.bouncerPromptReason ?: 0,
errorMessage = repository.bouncerErrorMessage,
- expansionAmount = repository.expansionAmount.value
+ expansionAmount = repository.panelExpansionAmount.value
)
)
repository.setShowingSoon(false)
@@ -90,14 +90,22 @@ constructor(
val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {}
val isVisible: Flow<Boolean> = repository.isVisible
val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
- val expansionAmount: Flow<Float> = repository.expansionAmount
val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
val startingDisappearAnimation: Flow<Runnable> =
repository.startingDisappearAnimation.filterNotNull()
- val onDismissAction: Flow<BouncerCallbackActionsModel> =
- repository.onDismissAction.filterNotNull()
val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
val keyguardPosition: Flow<Float> = repository.keyguardPosition
+ val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
+ /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
+ val bouncerExpansion: Flow<Float> = //
+ combine(repository.panelExpansionAmount, repository.isVisible) { expansionAmount, isVisible
+ ->
+ if (isVisible) {
+ 1f - expansionAmount
+ } else {
+ 0f
+ }
+ }
// TODO(b/243685699): Move isScrimmed logic to data layer.
// TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
@@ -128,7 +136,7 @@ constructor(
Trace.beginSection("KeyguardBouncer#show")
repository.setScrimmed(isScrimmed)
if (isScrimmed) {
- setExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+ setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
}
if (resumeBouncer) {
@@ -149,7 +157,6 @@ constructor(
}
keyguardStateController.notifyBouncerShowing(true)
callbackInteractor.dispatchStartingToShow()
-
Trace.endSection()
}
@@ -168,7 +175,6 @@ constructor(
keyguardStateController.notifyBouncerShowing(false /* showing */)
cancelShowRunnable()
repository.setShowingSoon(false)
- repository.setOnDismissAction(null)
repository.setVisible(false)
repository.setHide(true)
repository.setShow(null)
@@ -176,14 +182,17 @@ constructor(
}
/**
- * Sets the panel expansion which is calculated further upstream. Expansion is from 0f to 1f
- * where 0f => showing and 1f => hiding
+ * Sets the panel expansion which is calculated further upstream. Panel expansion is from 0f
+ * (panel fully hidden) to 1f (panel fully showing). As the panel shows (from 0f => 1f), the
+ * bouncer hides and as the panel becomes hidden (1f => 0f), the bouncer starts to show.
+ * Therefore, a panel expansion of 1f represents the bouncer fully hidden and a panel expansion
+ * of 0f represents the bouncer fully showing.
*/
- fun setExpansion(expansion: Float) {
- val oldExpansion = repository.expansionAmount.value
+ fun setPanelExpansion(expansion: Float) {
+ val oldExpansion = repository.panelExpansionAmount.value
val expansionChanged = oldExpansion != expansion
if (repository.startingDisappearAnimation.value == null) {
- repository.setExpansion(expansion)
+ repository.setPanelExpansion(expansion)
}
if (
@@ -227,7 +236,7 @@ constructor(
onDismissAction: ActivityStarter.OnDismissAction?,
cancelAction: Runnable?
) {
- repository.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+ bouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
}
/** Update the resources of the views. */
@@ -282,7 +291,7 @@ constructor(
/** Returns whether bouncer is fully showing. */
fun isFullyShowing(): Boolean {
return (repository.showingSoon.value || repository.isVisible.value) &&
- repository.expansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
+ repository.panelExpansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
repository.startingDisappearAnimation.value == null
}
@@ -294,8 +303,8 @@ constructor(
/** If bouncer expansion is between 0f and 1f non-inclusive. */
fun isInTransit(): Boolean {
return repository.showingSoon.value ||
- repository.expansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
- repository.expansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
+ repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
+ repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
}
/** Return whether bouncer is animating away. */
@@ -305,7 +314,7 @@ constructor(
/** Return whether bouncer will dismiss with actions */
fun willDismissWithAction(): Boolean {
- return repository.onDismissAction.value?.onDismissAction != null
+ return bouncerView.delegate?.willDismissWithActions() == true
}
/** Returns whether the bouncer should be full screen. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index df260148751c..a22958b74bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -29,6 +29,7 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.collect
@@ -75,6 +76,17 @@ object KeyguardBouncerViewBinder {
hostViewController.showPrimarySecurityScreen()
hostViewController.onResume()
}
+
+ override fun setDismissAction(
+ onDismissAction: ActivityStarter.OnDismissAction?,
+ cancelAction: Runnable?
+ ) {
+ hostViewController.setOnDismissAction(onDismissAction, cancelAction)
+ }
+
+ override fun willDismissWithActions(): Boolean {
+ return hostViewController.hasDismissActions()
+ }
}
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -122,15 +134,6 @@ object KeyguardBouncerViewBinder {
}
launch {
- viewModel.setDismissAction.collect {
- hostViewController.setOnDismissAction(
- it.onDismissAction,
- it.cancelAction
- )
- }
- }
-
- launch {
viewModel.startDisappearAnimation.collect {
hostViewController.startDisappearAnimation(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 9ad52117bfc6..9a9284371074 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -20,7 +20,6 @@ import android.view.View
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
@@ -38,7 +37,7 @@ constructor(
private val interactor: BouncerInteractor,
) {
/** Observe on bouncer expansion amount. */
- val bouncerExpansionAmount: Flow<Float> = interactor.expansionAmount
+ val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
/** Observe on bouncer visibility. */
val isBouncerVisible: Flow<Boolean> = interactor.isVisible
@@ -63,9 +62,6 @@ constructor(
/** Observe whether bouncer is starting to hide. */
val startingToHide: Flow<Unit> = interactor.startingToHide
- /** Observe whether we want to set the dismiss action to the bouncer. */
- val setDismissAction: Flow<BouncerCallbackActionsModel> = interactor.onDismissAction
-
/** Observe whether we want to start the disappear animation. */
val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 592a156b9498..85d15dca12cb 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -85,7 +85,11 @@ import android.view.InsetsVisibilities;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewRootImpl.SurfaceChangedCallback;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.InternalInsetsInfo;
import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
@@ -354,15 +358,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
@Override
- public void onQuickStepStarted() {
- // Use navbar dragging as a signal to hide the rotate button
- mView.getRotationButtonController().setRotateSuggestionButtonState(false);
-
- // Hide the notifications panel when quick step starts
- mShadeController.collapsePanel(true /* animate */);
- }
-
- @Override
public void onPrioritizedRotation(@Surface.Rotation int rotation) {
mStartingQuickSwitchRotation = rotation;
if (rotation == -1) {
@@ -475,6 +470,24 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
};
+ private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback =
+ new SurfaceChangedCallback() {
+ @Override
+ public void surfaceCreated(Transaction t) {
+ notifyNavigationBarSurface();
+ }
+
+ @Override
+ public void surfaceDestroyed() {
+ notifyNavigationBarSurface();
+ }
+
+ @Override
+ public void surfaceReplaced(Transaction t) {
+ notifyNavigationBarSurface();
+ }
+ };
+
@Inject
NavigationBar(
NavigationBarView navigationBarView,
@@ -702,6 +715,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
mOnComputeInternalInsetsListener);
+ mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback);
+ notifyNavigationBarSurface();
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -780,6 +795,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mHandler.removeCallbacks(mEnableLayoutTransitions);
mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
mPipOptional.ifPresent(mView::removePipExclusionBoundsChangeListener);
+ ViewRootImpl viewRoot = mView.getViewRootImpl();
+ if (viewRoot != null) {
+ viewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
+ }
mFrame = null;
mOrientationHandle = null;
}
@@ -933,6 +952,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
}
+ private void notifyNavigationBarSurface() {
+ ViewRootImpl viewRoot = mView.getViewRootImpl();
+ SurfaceControl surface = viewRoot != null ? viewRoot.getSurfaceControl() : null;
+ mOverviewProxyService.onNavigationBarSurfaceChanged(surface);
+ }
+
private int deltaRotation(int oldRotation, int newRotation) {
int delta = newRotation - oldRotation;
if (delta < 0) delta += 4;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index 622f5a279a5f..83c2a5de5c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -412,10 +412,6 @@ public class KeyButtonView extends ImageView implements ButtonInterface {
logSomePresses(action, flags);
if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
Log.i(TAG, "Back button event: " + KeyEvent.actionToString(action));
- if (action == MotionEvent.ACTION_UP) {
- mOverviewProxyService.notifyBackAction((flags & KeyEvent.FLAG_CANCELED) == 0,
- -1, -1, true /* isButton */, false /* gestureSwipeLeft */);
- }
}
final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 709467ffd3b5..c319a827da44 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -287,8 +287,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mBackAnimation.setTriggerBack(true);
}
- mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
- (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
logGesture(mInRejectedExclusion
? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED
: SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
@@ -300,8 +298,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mBackAnimation.setTriggerBack(false);
}
logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE);
- mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
- (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
}
@Override
@@ -785,9 +781,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
if (mExcludeRegion.contains(x, y)) {
if (withinRange) {
- // Log as exclusion only if it is in acceptable range in the first place.
- mOverviewProxyService.notifyBackAction(
- false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge);
// We don't have the end point for logging purposes.
mEndPoint.x = -1;
mEndPoint.y = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index 703b95a082dc..b5ceeaed4904 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -19,6 +19,7 @@ package com.android.systemui.qs.carrier;
import android.annotation.StyleRes;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
@@ -33,6 +34,7 @@ import com.android.settingslib.Utils;
import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.util.LargeScreenUtils;
import java.util.Objects;
@@ -72,6 +74,7 @@ public class QSCarrier extends LinearLayout {
mMobileSignal = findViewById(R.id.mobile_signal);
mCarrierText = findViewById(R.id.qs_carrier_text);
mSpacer = findViewById(R.id.spacer);
+ updateResources();
}
/**
@@ -142,4 +145,20 @@ public class QSCarrier extends LinearLayout {
public void updateTextAppearance(@StyleRes int resId) {
FontSizeUtils.updateFontSizeFromStyle(mCarrierText, resId);
}
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateResources();
+ }
+
+ private void updateResources() {
+ boolean useLargeScreenHeader =
+ LargeScreenUtils.shouldUseLargeScreenShadeHeader(getResources());
+ mCarrierText.setMaxEms(
+ useLargeScreenHeader
+ ? Integer.MAX_VALUE
+ : getResources().getInteger(R.integer.qs_carrier_max_em)
+ );
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index e9a6c25c0e6d..1f92b12c1a83 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -140,7 +140,7 @@ public class QSIconViewImpl extends QSIconView {
iv.setTag(R.id.qs_icon_tag, icon);
iv.setTag(R.id.qs_slash_tag, state.slash);
iv.setPadding(0, padding, 0, padding);
- if (d instanceof Animatable2) {
+ if (shouldAnimate && d instanceof Animatable2) {
Animatable2 a = (Animatable2) d;
a.start();
if (state.isTransient) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 2bc343c1ca5e..46c4f410d078 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -64,6 +64,7 @@ import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputMethodManager;
@@ -149,6 +150,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
private final UiEventLogger mUiEventLogger;
private Region mActiveNavBarRegion;
+ private SurfaceControl mNavigationBarSurface;
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
@@ -218,17 +220,15 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
@Override
- public void onBackPressed() throws RemoteException {
+ public void onBackPressed() {
verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
-
- notifyBackAction(true, -1, -1, true, false);
});
}
@Override
- public void onImeSwitcherPressed() throws RemoteException {
+ public void onImeSwitcherPressed() {
// TODO(b/204901476) We're intentionally using DEFAULT_DISPLAY for now since
// Launcher/Taskbar isn't display aware.
mContext.getSystemService(InputMethodManager.class)
@@ -317,12 +317,6 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
@Override
- public void notifySwipeUpGestureStarted() {
- verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () ->
- notifySwipeUpGestureStartedInternal());
- }
-
- @Override
public void notifyPrioritizedRotation(@Surface.Rotation int rotation) {
verifyCallerAndClearCallingIdentityPostMain("notifyPrioritizedRotation", () ->
notifyPrioritizedRotationInternal(rotation));
@@ -444,6 +438,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
Log.e(TAG_OPS, "Failed to call onInitialize()", e);
}
dispatchNavButtonBounds();
+ dispatchNavigationBarSurface();
// Force-update the systemui state flags
updateSystemUiStateFlags();
@@ -598,11 +593,18 @@ public class OverviewProxyService extends CurrentUserTracker implements
.commitUpdate(mContext.getDisplayId());
}
- public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton,
- boolean gestureSwipeLeft) {
+ /**
+ * Called when the navigation bar surface is created or changed
+ */
+ public void onNavigationBarSurfaceChanged(SurfaceControl navbarSurface) {
+ mNavigationBarSurface = navbarSurface;
+ dispatchNavigationBarSurface();
+ }
+
+ private void dispatchNavigationBarSurface() {
try {
if (mOverviewProxy != null) {
- mOverviewProxy.onBackAction(completed, downX, downY, isButton, gestureSwipeLeft);
+ mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface);
}
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to notify back action", e);
@@ -801,24 +803,12 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
}
- public void notifyQuickStepStarted() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onQuickStepStarted();
- }
- }
-
private void notifyPrioritizedRotationInternal(@Surface.Rotation int rotation) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onPrioritizedRotation(rotation);
}
}
- public void notifyQuickScrubStarted() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onQuickScrubStarted();
- }
- }
-
private void notifyAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
mConnectionCallbacks.get(i).onAssistantProgress(progress);
@@ -837,12 +827,6 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
}
- private void notifySwipeUpGestureStartedInternal() {
- for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
- mConnectionCallbacks.get(i).onSwipeUpGestureStarted();
- }
- }
-
public void notifyAssistantVisibilityChanged(float visibility) {
try {
if (mOverviewProxy != null) {
@@ -1006,23 +990,20 @@ public class OverviewProxyService extends CurrentUserTracker implements
pw.print(" mWindowCornerRadius="); pw.println(mWindowCornerRadius);
pw.print(" mSupportsRoundedCornersOnWindows="); pw.println(mSupportsRoundedCornersOnWindows);
pw.print(" mActiveNavBarRegion="); pw.println(mActiveNavBarRegion);
+ pw.print(" mNavigationBarSurface="); pw.println(mNavigationBarSurface);
pw.print(" mNavBarMode="); pw.println(mNavBarMode);
mSysUiState.dump(pw, args);
}
public interface OverviewProxyListener {
default void onConnectionChanged(boolean isConnected) {}
- default void onQuickStepStarted() {}
- default void onSwipeUpGestureStarted() {}
default void onPrioritizedRotation(@Surface.Rotation int rotation) {}
default void onOverviewShown(boolean fromHome) {}
- default void onQuickScrubStarted() {}
/** Notify the recents app (overview) is started by 3-button navigation. */
default void onToggleRecentApps() {}
default void onHomeRotationEnabled(boolean enabled) {}
default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {}
default void onTaskbarAutohideSuspend(boolean suspend) {}
- default void onSystemUiStateChanged(int sysuiStateFlags) {}
default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
default void onAssistantGestureCompletion(float velocity) {}
default void startAssistant(Bundle bundle) {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 31900ecd9478..fac2a97e65a3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -4643,7 +4643,7 @@ public final class NotificationPanelViewController {
mUpdateFlingVelocity = vel;
}
} else if (!mCentralSurfaces.isBouncerShowing()
- && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
+ && !mStatusBarKeyguardViewManager.isShowingAlternateAuth()
&& !mKeyguardStateController.isKeyguardGoingAway()) {
onEmptySpaceClick();
onTrackingStopped(true);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 65bd58d0d801..1e63b2dd134f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -284,7 +284,7 @@ public class NotificationShadeWindowViewController {
return true;
}
- if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
// capture all touches if the alt auth bouncer is showing
return true;
}
@@ -322,7 +322,7 @@ public class NotificationShadeWindowViewController {
handled = !mService.isPulsing();
}
- if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
// eat the touch
handled = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 87ef92a28d5d..408293cffd99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -496,9 +496,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
return;
}
- final float smallCornerRadius =
- getResources().getDimension(R.dimen.notification_corner_radius_small)
- / getResources().getDimension(R.dimen.notification_corner_radius);
final float viewEnd = viewStart + anv.getActualHeight();
final float cornerAnimationDistance = mCornerAnimationDistance
* mAmbientState.getExpansionFraction();
@@ -509,7 +506,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
final float changeFraction = MathUtils.saturate(
(viewEnd - cornerAnimationTop) / cornerAnimationDistance);
anv.requestBottomRoundness(
- anv.isLastInSection() ? 1f : changeFraction,
+ /* value = */ anv.isLastInSection() ? 1f : changeFraction,
/* animate = */ false,
SourceType.OnScroll);
@@ -517,7 +514,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
// Fast scroll skips frames and leaves corners with unfinished rounding.
// Reset top and bottom corners outside of animation bounds.
anv.requestBottomRoundness(
- anv.isLastInSection() ? 1f : smallCornerRadius,
+ /* value = */ anv.isLastInSection() ? 1f : 0f,
/* animate = */ false,
SourceType.OnScroll);
}
@@ -527,16 +524,16 @@ public class NotificationShelf extends ActivatableNotificationView implements
final float changeFraction = MathUtils.saturate(
(viewStart - cornerAnimationTop) / cornerAnimationDistance);
anv.requestTopRoundness(
- anv.isFirstInSection() ? 1f : changeFraction,
- false,
+ /* value = */ anv.isFirstInSection() ? 1f : changeFraction,
+ /* animate = */ false,
SourceType.OnScroll);
} else if (viewStart < cornerAnimationTop) {
// Fast scroll skips frames and leaves corners with unfinished rounding.
// Reset top and bottom corners outside of animation bounds.
anv.requestTopRoundness(
- anv.isFirstInSection() ? 1f : smallCornerRadius,
- false,
+ /* value = */ anv.isFirstInSection() ? 1f : 0f,
+ /* animate = */ false,
SourceType.OnScroll);
}
}
@@ -976,6 +973,16 @@ public class NotificationShelf extends ActivatableNotificationView implements
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
}
+ /**
+ * This method resets the OnScroll roundness of a view to 0f
+ *
+ * Note: This should be the only class that handles roundness {@code SourceType.OnScroll}
+ */
+ public static void resetOnScrollRoundness(ExpandableView expandableView) {
+ expandableView.requestTopRoundness(0f, false, SourceType.OnScroll);
+ expandableView.requestBottomRoundness(0f, false, SourceType.OnScroll);
+ }
+
public class ShelfState extends ExpandableViewState {
private boolean hasItemsInStableShelf;
private ExpandableView firstViewInShelf;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 26f0ad9eca87..0554fb5b3689 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -44,6 +44,7 @@ import com.android.internal.widget.NotificationExpandButton;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.NotificationGroupingUtil;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -308,6 +309,11 @@ public class NotificationChildrenContainer extends ViewGroup
row.setContentTransformationAmount(0, false /* isLastChild */);
row.setNotificationFaded(mContainingNotificationIsFaded);
+
+ // This is a workaround, the NotificationShelf should be the owner of `OnScroll` roundness.
+ // Here we should reset the `OnScroll` roundness only on top-level rows.
+ NotificationShelf.resetOnScrollRoundness(row);
+
// It doesn't make sense to keep old animations around, lets cancel them!
ExpandableViewState viewState = row.getViewState();
if (viewState != null) {
@@ -1377,8 +1383,12 @@ public class NotificationChildrenContainer extends ViewGroup
if (child.getVisibility() == View.GONE) {
continue;
}
+ child.requestTopRoundness(
+ /* value = */ 0f,
+ /* animate = */ isShown(),
+ SourceType.DefaultValue);
child.requestBottomRoundness(
- last ? getBottomRoundness() : 0f,
+ /* value = */ last ? getBottomRoundness() : 0f,
/* animate = */ isShown(),
SourceType.DefaultValue);
last = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 9bb4132490d4..b2a9509a03b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -64,6 +64,11 @@ public class KeyguardBouncer {
private static final String TAG = "KeyguardBouncer";
static final long BOUNCER_FACE_DELAY = 1200;
public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f;
+ /**
+ * Values for the bouncer expansion represented as the panel expansion.
+ * Panel expansion 1f = panel fully showing = bouncer fully hidden
+ * Panel expansion 0f = panel fully hiding = bouncer fully showing
+ */
public static final float EXPANSION_HIDDEN = 1f;
public static final float EXPANSION_VISIBLE = 0f;
@@ -143,6 +148,14 @@ public class KeyguardBouncer {
}
/**
+ * Get the KeyguardBouncer expansion
+ * @return 1=HIDDEN, 0=SHOWING, in between 0 and 1 means the bouncer is in transition.
+ */
+ public float getExpansion() {
+ return mExpansion;
+ }
+
+ /**
* Enable/disable only the back button
*/
public void setBackButtonEnabled(boolean enabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 318907da89f8..a00e75642f55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -86,8 +86,10 @@ import com.android.systemui.unfold.SysUIUnfoldComponent;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Inject;
@@ -166,13 +168,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Override
public void onExpansionChanged(float expansion) {
- if (mAlternateAuthInterceptor != null) {
- mAlternateAuthInterceptor.setBouncerExpansionChanged(expansion);
- }
if (mBouncerAnimating) {
mCentralSurfaces.setBouncerHiddenFraction(expansion);
}
- updateStates();
}
@Override
@@ -184,9 +182,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (!isVisible) {
mCentralSurfaces.setBouncerHiddenFraction(KeyguardBouncer.EXPANSION_HIDDEN);
}
- if (mAlternateAuthInterceptor != null) {
- mAlternateAuthInterceptor.onBouncerVisibilityChanged();
- }
/* Register predictive back callback when keyguard becomes visible, and unregister
when it's hidden. */
@@ -252,6 +247,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private int mLastBiometricMode;
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
+ final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsModernBouncerEnabled;
private OnDismissAction mAfterKeyguardGoneAction;
@@ -465,7 +461,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else {
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
} else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
// Don't expand to the bouncer. Instead transition back to the lock screen (see
@@ -475,7 +471,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
} else {
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+ mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
}
} else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
@@ -485,7 +481,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (mBouncer != null) {
mBouncer.setExpansion(fraction);
} else {
- mBouncerInteractor.setExpansion(fraction);
+ mBouncerInteractor.setPanelExpansion(fraction);
}
}
if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
@@ -504,7 +500,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else {
- mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+ mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
} else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
// Panel expanded while pulsing but didn't translate the bouncer (because we are
@@ -1356,7 +1352,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
}
- if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
+ if (mAlternateAuthInterceptor != null && isShowingAlternateAuth()) {
resetAlternateAuth(false);
executeAfterKeyguardGoneAction();
}
@@ -1442,6 +1438,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
pw.println(" mPendingWakeupAction: " + mPendingWakeupAction);
pw.println(" isBouncerShowing(): " + isBouncerShowing());
pw.println(" bouncerIsOrWillBeShowing(): " + bouncerIsOrWillBeShowing());
+ pw.println(" Registered KeyguardViewManagerCallbacks:");
+ for (KeyguardViewManagerCallback callback : mCallbacks) {
+ pw.println(" " + callback);
+ }
if (mBouncer != null) {
mBouncer.dump(pw);
@@ -1466,6 +1466,20 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
/**
+ * Add a callback to listen for changes
+ */
+ public void addCallback(KeyguardViewManagerCallback callback) {
+ mCallbacks.add(callback);
+ }
+
+ /**
+ * Removes callback to stop receiving updates
+ */
+ public void removeCallback(KeyguardViewManagerCallback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ /**
* Whether qs is currently expanded.
*/
public float getQsExpansion() {
@@ -1477,8 +1491,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
*/
public void setQsExpansion(float qsExpansion) {
mQsExpansion = qsExpansion;
- if (mAlternateAuthInterceptor != null) {
- mAlternateAuthInterceptor.setQsExpansion(qsExpansion);
+ for (KeyguardViewManagerCallback callback : mCallbacks) {
+ callback.onQSExpansionChanged(mQsExpansion);
}
}
@@ -1492,21 +1506,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
&& mAlternateAuthInterceptor.isShowingAlternateAuthBouncer();
}
- public boolean isShowingAlternateAuthOrAnimating() {
- return mAlternateAuthInterceptor != null
- && (mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()
- || mAlternateAuthInterceptor.isAnimating());
- }
-
/**
- * Forward touches to any alternate authentication affordances.
+ * Forward touches to callbacks.
*/
- public boolean onTouch(MotionEvent event) {
- if (mAlternateAuthInterceptor == null) {
- return false;
+ public void onTouch(MotionEvent event) {
+ for (KeyguardViewManagerCallback callback: mCallbacks) {
+ callback.onTouch(event);
}
-
- return mAlternateAuthInterceptor.onTouch(event);
}
/** Update keyguard position based on a tapped X coordinate. */
@@ -1640,45 +1646,33 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
boolean isShowingAlternateAuthBouncer();
/**
- * print information for the alternate auth interceptor registered
- */
- void dump(PrintWriter pw);
-
- /**
- * @return true if the new auth method bouncer is currently animating in or out.
- */
- boolean isAnimating();
-
- /**
- * How much QS is fully expanded where 0f is not showing and 1f is fully expanded.
- */
- void setQsExpansion(float qsExpansion);
-
- /**
- * Forward potential touches to authentication interceptor
- * @return true if event was handled
+ * Use when an app occluding the keyguard would like to give the user ability to
+ * unlock the device using udfps.
+ *
+ * @param color of the udfps icon. should have proper contrast with its background. only
+ * used if requestUdfps = true
*/
- boolean onTouch(MotionEvent event);
+ void requestUdfps(boolean requestUdfps, int color);
/**
- * Update pin/pattern/password bouncer expansion amount where 0 is visible and 1 is fully
- * hidden
+ * print information for the alternate auth interceptor registered
*/
- void setBouncerExpansionChanged(float expansion);
+ void dump(PrintWriter pw);
+ }
+ /**
+ * Callback for KeyguardViewManager state changes.
+ */
+ public interface KeyguardViewManagerCallback {
/**
- * called when the bouncer view visibility has changed.
+ * Set the amount qs is expanded. For example, swipe down from the top of the
+ * lock screen to start the full QS expansion.
*/
- void onBouncerVisibilityChanged();
+ default void onQSExpansionChanged(float qsExpansion) { }
/**
- * Use when an app occluding the keyguard would like to give the user ability to
- * unlock the device using udfps.
- *
- * @param color of the udfps icon. should have proper contrast with its background. only
- * used if requestUdfps = true
+ * Forward touch events to callbacks
*/
- void requestUdfps(boolean requestUdfps, int color);
-
+ default void onTouch(MotionEvent event) { }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
new file mode 100644
index 000000000000..e61890523ebb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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.statusbar.pipeline.mobile.data.model
+
+import android.net.NetworkCapabilities
+
+/** Provides information about a mobile network connection */
+data class MobileConnectivityModel(
+ /** Whether mobile is the connected transport see [NetworkCapabilities.TRANSPORT_CELLULAR] */
+ val isConnected: Boolean = false,
+ /** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
+ val isValidated: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 06e8f467ee0b..581842bc2f57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -16,11 +16,15 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings.Global
import android.telephony.CellSignalStrength
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyDisplayInfo
import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
@@ -34,6 +38,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetwork
import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import com.android.systemui.util.settings.GlobalSettings
import java.lang.IllegalStateException
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -42,9 +47,12 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/**
@@ -65,14 +73,23 @@ interface MobileConnectionRepository {
*/
val subscriptionModelFlow: Flow<MobileSubscriptionModel>
/** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
- val dataEnabled: Flow<Boolean>
+ val dataEnabled: StateFlow<Boolean>
+ /**
+ * True if this connection represents the default subscription per
+ * [SubscriptionManager.getDefaultDataSubscriptionId]
+ */
+ val isDefaultDataSubscription: StateFlow<Boolean>
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
class MobileConnectionRepositoryImpl(
+ private val context: Context,
private val subId: Int,
private val telephonyManager: TelephonyManager,
+ private val globalSettings: GlobalSettings,
+ defaultDataSubId: StateFlow<Int>,
+ globalMobileDataSettingChangedEvent: Flow<Unit>,
bgDispatcher: CoroutineDispatcher,
logger: ConnectivityPipelineLogger,
scope: CoroutineScope,
@@ -86,6 +103,8 @@ class MobileConnectionRepositoryImpl(
}
}
+ private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
var state = MobileSubscriptionModel()
conflatedCallbackFlow {
@@ -165,33 +184,75 @@ class MobileConnectionRepositoryImpl(
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
+ .onEach { telephonyCallbackEvent.tryEmit(Unit) }
.logOutputChange(logger, "MobileSubscriptionModel")
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
}
+ /** Produces whenever the mobile data setting changes for this subId */
+ private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ globalSettings.registerContentObserver(
+ globalSettings.getUriFor("${Global.MOBILE_DATA}$subId"),
+ /* notifyForDescendants */ true,
+ observer
+ )
+
+ awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+ }
+
/**
* There are a few cases where we will need to poll [TelephonyManager] so we can update some
* internal state where callbacks aren't provided. Any of those events should be merged into
* this flow, which can be used to trigger the polling.
*/
- private val telephonyPollingEvent: Flow<Unit> = subscriptionModelFlow.map {}
+ private val telephonyPollingEvent: Flow<Unit> =
+ merge(
+ telephonyCallbackEvent,
+ localMobileDataSettingChangedEvent,
+ globalMobileDataSettingChangedEvent,
+ )
- override val dataEnabled: Flow<Boolean> = telephonyPollingEvent.map { dataConnectionAllowed() }
+ override val dataEnabled: StateFlow<Boolean> =
+ telephonyPollingEvent
+ .mapLatest { dataConnectionAllowed() }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed())
private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
+ override val isDefaultDataSubscription: StateFlow<Boolean> =
+ defaultDataSubId
+ .mapLatest { it == subId }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId)
+
class Factory
@Inject
constructor(
+ private val context: Context,
private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger,
+ private val globalSettings: GlobalSettings,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
) {
- fun build(subId: Int): MobileConnectionRepository {
+ fun build(
+ subId: Int,
+ defaultDataSubId: StateFlow<Int>,
+ globalMobileDataSettingChangedEvent: Flow<Unit>,
+ ): MobileConnectionRepository {
return MobileConnectionRepositoryImpl(
+ context,
subId,
telephonyManager.createForSubscriptionId(subId),
+ globalSettings,
+ defaultDataSubId,
+ globalMobileDataSettingChangedEvent,
bgDispatcher,
logger,
scope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 0e2428ae393a..c3c1f1403c60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -16,15 +16,27 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.annotation.SuppressLint
import android.content.Context
import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.provider.Settings
+import android.provider.Settings.Global.MOBILE_DATA
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
import androidx.annotation.VisibleForTesting
+import com.android.internal.telephony.PhoneConstants
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.MobileMappings.Config
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -32,7 +44,9 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -40,10 +54,12 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -57,13 +73,22 @@ interface MobileConnectionsRepository {
val subscriptionsFlow: Flow<List<SubscriptionInfo>>
/** Observable for the subscriptionId of the current mobile data connection */
- val activeMobileDataSubscriptionId: Flow<Int>
+ val activeMobileDataSubscriptionId: StateFlow<Int>
/** Observable for [MobileMappings.Config] tracking the defaults */
val defaultDataSubRatConfig: StateFlow<Config>
+ /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
+ val defaultDataSubId: StateFlow<Int>
+
+ /** The current connectivity status for the default mobile network connection */
+ val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>
+
/** Get or create a repository for the line of service for the given subscription ID */
fun getRepoForSubId(subId: Int): MobileConnectionRepository
+
+ /** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
+ val globalMobileDataSettingChangedEvent: Flow<Unit>
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -72,10 +97,12 @@ interface MobileConnectionsRepository {
class MobileConnectionsRepositoryImpl
@Inject
constructor(
+ private val connectivityManager: ConnectivityManager,
private val subscriptionManager: SubscriptionManager,
private val telephonyManager: TelephonyManager,
private val logger: ConnectivityPipelineLogger,
broadcastDispatcher: BroadcastDispatcher,
+ private val globalSettings: GlobalSettings,
private val context: Context,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
@@ -121,17 +148,26 @@ constructor(
telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
+
+ private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
+ MutableSharedFlow(extraBufferCapacity = 1)
+
+ override val defaultDataSubId: StateFlow<Int> =
+ broadcastDispatcher
+ .broadcastFlow(
+ IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ ) { intent, _ ->
+ intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+ }
+ .distinctUntilChanged()
+ .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
.stateIn(
scope,
- started = SharingStarted.WhileSubscribed(),
- SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ SharingStarted.WhileSubscribed(),
+ SubscriptionManager.getDefaultDataSubscriptionId()
)
- private val defaultDataSubChangedEvent =
- broadcastDispatcher.broadcastFlow(
- IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
- )
-
private val carrierConfigChangedEvent =
broadcastDispatcher.broadcastFlow(
IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
@@ -148,9 +184,8 @@ constructor(
* This flow will produce whenever the default data subscription or the carrier config changes.
*/
override val defaultDataSubRatConfig: StateFlow<Config> =
- combine(defaultDataSubChangedEvent, carrierConfigChangedEvent) { _, _ ->
- Config.readConfig(context)
- }
+ merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
+ .mapLatest { Config.readConfig(context) }
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
@@ -168,6 +203,57 @@ constructor(
?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
}
+ /**
+ * In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual
+ * connection repositories also observe the URI for [MOBILE_DATA] + subId.
+ */
+ override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ globalSettings.registerContentObserver(
+ globalSettings.getUriFor(MOBILE_DATA),
+ true,
+ observer
+ )
+
+ awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+ }
+
+ @SuppressLint("MissingPermission")
+ override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
+ conflatedCallbackFlow {
+ val callback =
+ object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+ override fun onLost(network: Network) {
+ // Send a disconnected model when lost. Maybe should create a sealed
+ // type or null here?
+ trySend(MobileConnectivityModel())
+ }
+
+ override fun onCapabilitiesChanged(
+ network: Network,
+ caps: NetworkCapabilities
+ ) {
+ trySend(
+ MobileConnectivityModel(
+ isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
+ isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED),
+ )
+ )
+ }
+ }
+
+ connectivityManager.registerDefaultNetworkCallback(callback)
+
+ awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
+
private fun isValidSubId(subId: Int): Boolean {
subscriptionsFlow.value.forEach {
if (it.subscriptionId == subId) {
@@ -181,7 +267,11 @@ constructor(
@VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
- return mobileConnectionRepositoryFactory.build(subId)
+ return mobileConnectionRepositoryFactory.build(
+ subId,
+ defaultDataSubId,
+ globalMobileDataSettingChangedEvent,
+ )
}
private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
index 77de849691db..91886bb121d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
@@ -26,7 +26,6 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.mapLatest
@@ -40,7 +39,7 @@ import kotlinx.coroutines.withContext
*/
interface UserSetupRepository {
/** Observable tracking [DeviceProvisionedController.isUserSetup] */
- val isUserSetupFlow: Flow<Boolean>
+ val isUserSetupFlow: StateFlow<Boolean>
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index f99d278c3903..0da84f0bec9c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -18,81 +18,109 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CarrierConfigManager
import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.util.CarrierConfigTracker
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
interface MobileIconInteractor {
+ /** Only true if mobile is the default transport but is not validated, otherwise false */
+ val isDefaultConnectionFailed: StateFlow<Boolean>
+
+ /** True when telephony tells us that the data state is CONNECTED */
+ val isDataConnected: StateFlow<Boolean>
+
+ // TODO(b/256839546): clarify naming of default vs active
+ /** True if we want to consider the data connection enabled */
+ val isDefaultDataEnabled: StateFlow<Boolean>
+
/** Observable for the data enabled state of this connection */
- val isDataEnabled: Flow<Boolean>
+ val isDataEnabled: StateFlow<Boolean>
/** Observable for RAT type (network type) indicator */
- val networkTypeIconGroup: Flow<MobileIconGroup>
+ val networkTypeIconGroup: StateFlow<MobileIconGroup>
/** True if this line of service is emergency-only */
- val isEmergencyOnly: Flow<Boolean>
+ val isEmergencyOnly: StateFlow<Boolean>
/** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
- val level: Flow<Int>
+ val level: StateFlow<Int>
/** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
- val numberOfLevels: Flow<Int>
-
- /** True when we want to draw an icon that makes room for the exclamation mark */
- val cutOut: Flow<Boolean>
+ val numberOfLevels: StateFlow<Int>
}
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
class MobileIconInteractorImpl(
- defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>,
- defaultMobileIconGroup: Flow<MobileIconGroup>,
+ @Application scope: CoroutineScope,
+ defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
+ defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
+ defaultMobileIconGroup: StateFlow<MobileIconGroup>,
+ override val isDefaultConnectionFailed: StateFlow<Boolean>,
mobileMappingsProxy: MobileMappingsProxy,
connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
- override val isDataEnabled: Flow<Boolean> = connectionRepository.dataEnabled
+ override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
+
+ override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */
- override val networkTypeIconGroup: Flow<MobileIconGroup> =
+ override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
combine(
- mobileStatusInfo,
- defaultMobileIconMapping,
- defaultMobileIconGroup,
- ) { info, mapping, defaultGroup ->
- val lookupKey =
- when (val resolved = info.resolvedNetworkType) {
- is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
- is OverrideNetworkType -> mobileMappingsProxy.toIconKeyOverride(resolved.type)
+ mobileStatusInfo,
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ ) { info, mapping, defaultGroup ->
+ val lookupKey =
+ when (val resolved = info.resolvedNetworkType) {
+ is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
+ is OverrideNetworkType ->
+ mobileMappingsProxy.toIconKeyOverride(resolved.type)
+ }
+ mapping[lookupKey] ?: defaultGroup
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
+
+ override val isEmergencyOnly: StateFlow<Boolean> =
+ mobileStatusInfo
+ .mapLatest { it.isEmergencyOnly }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val level: StateFlow<Int> =
+ mobileStatusInfo
+ .mapLatest { mobileModel ->
+ // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
+ if (mobileModel.isGsm) {
+ mobileModel.primaryLevel
+ } else {
+ mobileModel.cdmaLevel
}
- mapping[lookupKey] ?: defaultGroup
- }
-
- override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
-
- override val level: Flow<Int> =
- mobileStatusInfo.map { mobileModel ->
- // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
- if (mobileModel.isGsm) {
- mobileModel.primaryLevel
- } else {
- mobileModel.cdmaLevel
}
- }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
/**
* This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
* once it's wired up inside of [CarrierConfigTracker]
*/
- override val numberOfLevels: Flow<Int> = flowOf(4)
+ override val numberOfLevels: StateFlow<Int> = MutableStateFlow(4)
- /** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */
- // TODO: find a better name for this?
- override val cutOut: Flow<Boolean> = flowOf(false)
+ override val isDataConnected: StateFlow<Boolean> =
+ mobileStatusInfo
+ .mapLatest { subscriptionModel -> subscriptionModel.dataConnectionState == Connected }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 614d583c3c48..a4175c3a6ab1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
@@ -35,7 +36,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
/**
@@ -51,12 +54,16 @@ import kotlinx.coroutines.flow.stateIn
interface MobileIconsInteractor {
/** List of subscriptions, potentially filtered for CBRS */
val filteredSubscriptions: Flow<List<SubscriptionInfo>>
+ /** True if the active mobile data subscription has data enabled */
+ val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
/** The icon mapping from network type to [MobileIconGroup] for the default subscription */
- val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
+ val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
/** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
- val defaultMobileIconGroup: Flow<MobileIconGroup>
+ val defaultMobileIconGroup: StateFlow<MobileIconGroup>
+ /** True only if the default network is mobile, and validation also failed */
+ val isDefaultConnectionFailed: StateFlow<Boolean>
/** True once the user has been set up */
- val isUserSetup: Flow<Boolean>
+ val isUserSetup: StateFlow<Boolean>
/**
* Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
* subId. Will throw if the ID is invalid
@@ -79,6 +86,22 @@ constructor(
private val activeMobileDataSubscriptionId =
mobileConnectionsRepo.activeMobileDataSubscriptionId
+ private val activeMobileDataConnectionRepo: StateFlow<MobileConnectionRepository?> =
+ activeMobileDataSubscriptionId
+ .mapLatest { activeId ->
+ if (activeId == INVALID_SUBSCRIPTION_ID) {
+ null
+ } else {
+ mobileConnectionsRepo.getRepoForSubId(activeId)
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
+ activeMobileDataConnectionRepo
+ .flatMapLatest { it?.dataEnabled ?: flowOf(false) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
mobileConnectionsRepo.subscriptionsFlow
@@ -132,22 +155,40 @@ constructor(
*/
override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
mobileConnectionsRepo.defaultDataSubRatConfig
- .map { mobileMappingsProxy.mapIconSets(it) }
+ .mapLatest { mobileMappingsProxy.mapIconSets(it) }
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
/** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
mobileConnectionsRepo.defaultDataSubRatConfig
- .map { mobileMappingsProxy.getDefaultIcons(it) }
+ .mapLatest { mobileMappingsProxy.getDefaultIcons(it) }
.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
- override val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+ /**
+ * We want to show an error state when cellular has actually failed to validate, but not if some
+ * other transport type is active, because then we expect there not to be validation.
+ */
+ override val isDefaultConnectionFailed: StateFlow<Boolean> =
+ mobileConnectionsRepo.defaultMobileNetworkConnectivity
+ .mapLatest { connectivityModel ->
+ if (!connectivityModel.isConnected) {
+ false
+ } else {
+ !connectivityModel.isValidated
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow
/** Vends out new [MobileIconInteractor] for a particular subId */
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
MobileIconInteractorImpl(
+ scope,
+ activeDataConnectionHasDataEnabled,
defaultMobileIconMapping,
defaultMobileIconGroup,
+ isDefaultConnectionFailed,
mobileMappingsProxy,
mobileConnectionsRepo.getRepoForSubId(subId),
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 81317398f086..7869021c0501 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -24,10 +24,12 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIc
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
/**
* View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
@@ -39,29 +41,38 @@ import kotlinx.coroutines.flow.flowOf
*
* TODO: figure out where carrier merged and VCN models go (probably here?)
*/
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
class MobileIconViewModel
constructor(
val subscriptionId: Int,
iconInteractor: MobileIconInteractor,
logger: ConnectivityPipelineLogger,
) {
+ /** Whether or not to show the error state of [SignalDrawable] */
+ private val showExclamationMark: Flow<Boolean> =
+ iconInteractor.isDefaultDataEnabled.mapLatest { !it }
+
/** An int consumable by [SignalDrawable] for display */
- var iconId: Flow<Int> =
- combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) {
+ val iconId: Flow<Int> =
+ combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) {
level,
numberOfLevels,
- cutOut ->
- SignalDrawable.getState(level, numberOfLevels, cutOut)
+ showExclamationMark ->
+ SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
}
.distinctUntilChanged()
.logOutputChange(logger, "iconId($subscriptionId)")
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
- var networkTypeIcon: Flow<Icon?> =
- combine(iconInteractor.networkTypeIconGroup, iconInteractor.isDataEnabled) {
- networkTypeIconGroup,
- isDataEnabled ->
- if (!isDataEnabled) {
+ val networkTypeIcon: Flow<Icon?> =
+ combine(
+ iconInteractor.networkTypeIconGroup,
+ iconInteractor.isDataConnected,
+ iconInteractor.isDataEnabled,
+ iconInteractor.isDefaultConnectionFailed,
+ ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection ->
+ if (!dataConnected || !dataEnabled || failedConnection) {
null
} else {
val desc =
@@ -72,5 +83,5 @@ constructor(
}
}
- var tint: Flow<Int> = flowOf(Color.CYAN)
+ val tint: Flow<Int> = flowOf(Color.CYAN)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthCredentialViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthCredentialViewTest.kt
new file mode 100644
index 000000000000..fbda08fab799
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthCredentialViewTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
+import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.PromptInfo
+import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.os.Handler
+import android.os.IBinder
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.ViewUtils
+import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternView
+import com.android.internal.widget.VerifyCredentialResponse
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthContainerView.BiometricCallback
+import com.android.systemui.biometrics.AuthCredentialView.ErrorTimer
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class AuthCredentialViewTest : SysuiTestCase() {
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ @Mock lateinit var callback: AuthDialogCallback
+ @Mock lateinit var lockPatternUtils: LockPatternUtils
+ @Mock lateinit var userManager: UserManager
+ @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock lateinit var windowToken: IBinder
+ @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
+
+ private var authContainer: TestAuthContainerView? = null
+ private var authCredentialView: AuthCredentialPatternView? = null
+ private var lockPatternView: LockPatternView? = null
+ private var biometricCallback: BiometricCallback? = null
+ private var errorTimer: ErrorTimer? = null
+
+ @After
+ fun tearDown() {
+ if (authContainer?.isAttachedToWindow == true) {
+ ViewUtils.detachView(authContainer)
+ }
+ }
+
+ @Test
+ fun testAuthCredentialPatternView_onErrorTimeoutFinish_setPatternEnabled() {
+ `when`(lockPatternUtils.getCredentialTypeForUser(anyInt()))
+ .thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PATTERN)
+ `when`(lockPatternUtils.getKeyguardStoredPasswordQuality(anyInt()))
+ .thenReturn(PASSWORD_QUALITY_SOMETHING)
+ val errorResponse: VerifyCredentialResponse = VerifyCredentialResponse.fromError()
+
+ assertThat(initializeFingerprintContainer()).isNotNull()
+ authContainer?.animateToCredentialUI()
+ waitForIdleSync()
+
+ authCredentialView = spy(authContainer?.mCredentialView as AuthCredentialPatternView)
+ authCredentialView?.onCredentialVerified(errorResponse, 5000)
+ errorTimer = authCredentialView?.mErrorTimer
+ errorTimer?.onFinish()
+ waitForIdleSync()
+
+ verify(authCredentialView)?.onErrorTimeoutFinish()
+
+ lockPatternView = authCredentialView?.mLockPatternView
+ assertThat(lockPatternView?.isEnabled).isTrue()
+ }
+
+ private fun initializeFingerprintContainer(
+ authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
+ addToView: Boolean = true
+ ) =
+ initializeContainer(
+ TestAuthContainerView(
+ authenticators = authenticators,
+ fingerprintProps = fingerprintSensorPropertiesInternal()
+ ),
+ addToView
+ )
+
+ private fun initializeContainer(
+ view: TestAuthContainerView,
+ addToView: Boolean
+ ): TestAuthContainerView {
+ authContainer = view
+ if (addToView) {
+ authContainer!!.addToView()
+ biometricCallback = authContainer?.mBiometricCallback
+ }
+ return authContainer!!
+ }
+
+ private inner class TestAuthContainerView(
+ authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
+ fingerprintProps: List<FingerprintSensorPropertiesInternal> = listOf(),
+ faceProps: List<FaceSensorPropertiesInternal> = listOf()
+ ) :
+ AuthContainerView(
+ Config().apply {
+ mContext = context
+ mCallback = callback
+ mSensorIds =
+ (fingerprintProps.map { it.sensorId } + faceProps.map { it.sensorId })
+ .toIntArray()
+ mSkipAnimation = true
+ mPromptInfo = PromptInfo().apply { this.authenticators = authenticators }
+ },
+ fingerprintProps,
+ faceProps,
+ wakefulnessLifecycle,
+ userManager,
+ lockPatternUtils,
+ interactionJankMonitor,
+ Handler(TestableLooper.get(this).looper),
+ FakeExecutor(FakeSystemClock())
+ ) {
+ override fun postOnAnimation(runnable: Runnable) {
+ runnable.run()
+ }
+ }
+
+ override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages()
+
+ private fun AuthContainerView.addToView() {
+ ViewUtils.attachView(this)
+ waitForIdleSync()
+ assertThat(isAttachedToWindow).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index c85334db9499..90948ff3b769 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -42,6 +42,8 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -103,6 +105,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
@Mock private lateinit var udfpsView: UdfpsView
@Mock private lateinit var udfpsEnrollView: UdfpsEnrollView
@Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var bouncerInteractor: BouncerInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -136,7 +140,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
configurationController, systemClock, keyguardStateController,
unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
- controllerCallback, onTouch, activityLaunchAnimator, isDebuggable
+ controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
+ bouncerInteractor, isDebuggable
)
block()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 28e13b8e81ab..be39c0de22a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -69,7 +69,9 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -171,6 +173,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
private FakeExecutor mFgExecutor;
@Mock
private UdfpsDisplayMode mUdfpsDisplayMode;
+ @Mock
+ private FeatureFlags mFeatureFlags;
// Stuff for configuring mocks
@Mock
@@ -191,6 +195,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
private ActivityLaunchAnimator mActivityLaunchAnimator;
@Mock
private AlternateUdfpsTouchProvider mAlternateTouchProvider;
+ @Mock
+ private BouncerInteractor mBouncerInteractor;
// Capture listeners so that they can be used to send events
@Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
@@ -252,6 +258,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
mStatusBarKeyguardViewManager,
mDumpManager,
mKeyguardUpdateMonitor,
+ mFeatureFlags,
mFalsingManager,
mPowerManager,
mAccessibilityManager,
@@ -270,7 +277,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
mLatencyTracker,
mActivityLaunchAnimator,
Optional.of(mAlternateTouchProvider),
- mBiometricsExecutor);
+ mBiometricsExecutor,
+ mBouncerInteractor);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
new file mode 100644
index 000000000000..e5c7a42c06a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.biometrics;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase {
+ // Dependencies
+ protected @Mock UdfpsKeyguardView mView;
+ protected @Mock Context mResourceContext;
+ protected @Mock StatusBarStateController mStatusBarStateController;
+ protected @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
+ protected @Mock StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ protected @Mock LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ protected @Mock DumpManager mDumpManager;
+ protected @Mock DelayableExecutor mExecutor;
+ protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ protected @Mock KeyguardStateController mKeyguardStateController;
+ protected @Mock KeyguardViewMediator mKeyguardViewMediator;
+ protected @Mock ConfigurationController mConfigurationController;
+ protected @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ protected @Mock SystemUIDialogManager mDialogManager;
+ protected @Mock UdfpsController mUdfpsController;
+ protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+ protected @Mock KeyguardBouncer mBouncer;
+ protected @Mock BouncerInteractor mBouncerInteractor;
+
+ protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+ protected FakeSystemClock mSystemClock = new FakeSystemClock();
+
+ protected UdfpsKeyguardViewController mController;
+
+ // Capture listeners so that they can be used to send events
+ private @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
+ protected StatusBarStateController.StateListener mStatusBarStateListener;
+
+ private @Captor ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
+ protected List<ShadeExpansionListener> mExpansionListeners;
+
+ private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
+ mAltAuthInterceptorCaptor;
+ protected StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
+
+ private @Captor ArgumentCaptor<KeyguardStateController.Callback>
+ mKeyguardStateControllerCallbackCaptor;
+ protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mView.getContext()).thenReturn(mResourceContext);
+ when(mResourceContext.getString(anyInt())).thenReturn("test string");
+ when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
+ when(mView.getUnpausedAlpha()).thenReturn(255);
+ mController = createUdfpsKeyguardViewController();
+ }
+
+ protected void sendStatusBarStateChanged(int statusBarState) {
+ mStatusBarStateListener.onStateChanged(statusBarState);
+ }
+
+ protected void captureStatusBarStateListeners() {
+ verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
+ mStatusBarStateListener = mStateListenerCaptor.getValue();
+ }
+
+ protected void captureStatusBarExpansionListeners() {
+ verify(mShadeExpansionStateManager, times(2))
+ .addExpansionListener(mExpansionListenerCaptor.capture());
+ // first (index=0) is from super class, UdfpsAnimationViewController.
+ // second (index=1) is from UdfpsKeyguardViewController
+ mExpansionListeners = mExpansionListenerCaptor.getAllValues();
+ }
+
+ protected void updateStatusBarExpansion(float fraction, boolean expanded) {
+ ShadeExpansionChangeEvent event =
+ new ShadeExpansionChangeEvent(
+ fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
+ for (ShadeExpansionListener listener : mExpansionListeners) {
+ listener.onPanelExpansionChanged(event);
+ }
+ }
+
+ protected void captureAltAuthInterceptor() {
+ verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
+ mAltAuthInterceptorCaptor.capture());
+ mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
+ }
+
+ protected void captureKeyguardStateControllerCallback() {
+ verify(mKeyguardStateController).addCallback(
+ mKeyguardStateControllerCallbackCaptor.capture());
+ mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+ }
+
+ public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
+ return createUdfpsKeyguardViewController(false);
+ }
+
+ protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
+ boolean useModernBouncer) {
+ mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
+ when(mStatusBarKeyguardViewManager.getBouncer()).thenReturn(
+ useModernBouncer ? null : mBouncer);
+ return new UdfpsKeyguardViewController(
+ mView,
+ mStatusBarStateController,
+ mShadeExpansionStateManager,
+ mStatusBarKeyguardViewManager,
+ mKeyguardUpdateMonitor,
+ mDumpManager,
+ mLockscreenShadeTransitionController,
+ mConfigurationController,
+ mSystemClock,
+ mKeyguardStateController,
+ mUnlockedScreenOffAnimationController,
+ mDialogManager,
+ mUdfpsController,
+ mActivityLaunchAnimator,
+ mFeatureFlags,
+ mBouncerInteractor);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index c0f9c82fb131..55b61948ee45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -25,125 +25,52 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
-public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
- // Dependencies
- @Mock
- private UdfpsKeyguardView mView;
- @Mock
- private Context mResourceContext;
- @Mock
- private StatusBarStateController mStatusBarStateController;
- @Mock
- private ShadeExpansionStateManager mShadeExpansionStateManager;
- @Mock
- private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- @Mock
- private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
- @Mock
- private DumpManager mDumpManager;
- @Mock
- private DelayableExecutor mExecutor;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private KeyguardViewMediator mKeyguardViewMediator;
- @Mock
- private ConfigurationController mConfigurationController;
- @Mock
- private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
- @Mock
- private SystemUIDialogManager mDialogManager;
- @Mock
- private UdfpsController mUdfpsController;
- @Mock
- private ActivityLaunchAnimator mActivityLaunchAnimator;
- private FakeSystemClock mSystemClock = new FakeSystemClock();
-
- private UdfpsKeyguardViewController mController;
-
- // Capture listeners so that they can be used to send events
- @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
- private StatusBarStateController.StateListener mStatusBarStateListener;
-
- @Captor private ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
- private List<ShadeExpansionListener> mExpansionListeners;
-
- @Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
- mAltAuthInterceptorCaptor;
- private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
-
- @Captor private ArgumentCaptor<KeyguardStateController.Callback>
- mKeyguardStateControllerCallbackCaptor;
- private KeyguardStateController.Callback mKeyguardStateControllerCallback;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mView.getContext()).thenReturn(mResourceContext);
- when(mResourceContext.getString(anyInt())).thenReturn("test string");
- when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
- when(mView.getUnpausedAlpha()).thenReturn(255);
- mController = new UdfpsKeyguardViewController(
- mView,
- mStatusBarStateController,
- mShadeExpansionStateManager,
- mStatusBarKeyguardViewManager,
- mKeyguardUpdateMonitor,
- mDumpManager,
- mLockscreenShadeTransitionController,
- mConfigurationController,
- mSystemClock,
- mKeyguardStateController,
- mUnlockedScreenOffAnimationController,
- mDialogManager,
- mUdfpsController,
- mActivityLaunchAnimator);
+public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest {
+ private @Captor ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback>
+ mBouncerExpansionCallbackCaptor;
+ private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
+
+ @Override
+ public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
+ return createUdfpsKeyguardViewController(/* useModernBouncer */ false);
+ }
+
+ @Test
+ public void testShouldPauseAuth_bouncerShowing() {
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+ captureBouncerExpansionCallback();
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+
+ assertTrue(mController.shouldPauseAuth());
}
+
+
@Test
public void testRegistersExpansionChangedListenerOnAttached() {
mController.onViewAttached();
@@ -202,20 +129,6 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
}
@Test
- public void testShouldPauseAuthBouncerShowing() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
- sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-
- captureAltAuthInterceptor();
- when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
- when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
- mAltAuthInterceptor.onBouncerVisibilityChanged();
-
- assertTrue(mController.shouldPauseAuth());
- }
-
- @Test
public void testShouldPauseAuthUnpausedAlpha0() {
mController.onViewAttached();
captureStatusBarStateListeners();
@@ -503,41 +416,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
verify(mView, atLeastOnce()).setPauseAuth(false);
}
- private void sendStatusBarStateChanged(int statusBarState) {
- mStatusBarStateListener.onStateChanged(statusBarState);
- }
-
- private void captureStatusBarStateListeners() {
- verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
- mStatusBarStateListener = mStateListenerCaptor.getValue();
- }
-
- private void captureStatusBarExpansionListeners() {
- verify(mShadeExpansionStateManager, times(2))
- .addExpansionListener(mExpansionListenerCaptor.capture());
- // first (index=0) is from super class, UdfpsAnimationViewController.
- // second (index=1) is from UdfpsKeyguardViewController
- mExpansionListeners = mExpansionListenerCaptor.getAllValues();
- }
-
- private void updateStatusBarExpansion(float fraction, boolean expanded) {
- ShadeExpansionChangeEvent event =
- new ShadeExpansionChangeEvent(
- fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
- for (ShadeExpansionListener listener : mExpansionListeners) {
- listener.onPanelExpansionChanged(event);
- }
- }
-
- private void captureAltAuthInterceptor() {
- verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
- mAltAuthInterceptorCaptor.capture());
- mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
- }
-
- private void captureKeyguardStateControllerCallback() {
- verify(mKeyguardStateController).addCallback(
- mKeyguardStateControllerCallbackCaptor.capture());
- mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+ private void captureBouncerExpansionCallback() {
+ verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
+ mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
new file mode 100644
index 000000000000..7b1976811868
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControllerBaseTest() {
+ lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+
+ @Before
+ override fun setUp() {
+ allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
+ MockitoAnnotations.initMocks(this)
+ keyguardBouncerRepository =
+ KeyguardBouncerRepository(
+ mock(com.android.keyguard.ViewMediatorCallback::class.java),
+ mKeyguardUpdateMonitor
+ )
+ super.setUp()
+ }
+
+ override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewController? {
+ mBouncerInteractor =
+ BouncerInteractor(
+ keyguardBouncerRepository,
+ mock(BouncerView::class.java),
+ mock(Handler::class.java),
+ mKeyguardStateController,
+ mock(KeyguardSecurityModel::class.java),
+ mock(BouncerCallbackInteractor::class.java),
+ mock(FalsingCollector::class.java),
+ mock(DismissCallbackRegistry::class.java),
+ mock(KeyguardBypassController::class.java),
+ mKeyguardUpdateMonitor
+ )
+ return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
+ }
+
+ /** After migration, replaces LockIconViewControllerTest version */
+ @Test
+ fun testShouldPauseAuthBouncerShowing() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN view attached and we're on the keyguard
+ mController.onViewAttached()
+ captureStatusBarStateListeners()
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+
+ // WHEN the bouncer expansion is VISIBLE
+ val job = mController.listenForBouncerExpansion(this)
+ keyguardBouncerRepository.setVisible(true)
+ keyguardBouncerRepository.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+ yield()
+
+ // THEN UDFPS shouldPauseAuth == true
+ assertTrue(mController.shouldPauseAuth())
+
+ job.cancel()
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 25bc91f413de..eb6e5174d078 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -40,6 +40,7 @@ import java.util.concurrent.Executor
import junit.framework.Assert.assertSame
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
index e6c8dd87d982..5743b2f03d3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
@@ -27,8 +27,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
@@ -57,6 +57,7 @@ class BouncerInteractorTest : SysuiTestCase() {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var repository: KeyguardBouncerRepository
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
+ @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
@Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor
@@ -86,6 +87,7 @@ class BouncerInteractorTest : SysuiTestCase() {
)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
`when`(repository.show.value).thenReturn(null)
+ `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
}
@Test
@@ -97,7 +99,7 @@ class BouncerInteractorTest : SysuiTestCase() {
verify(repository).setHide(false)
verify(repository).setStartingToHide(false)
verify(repository).setScrimmed(true)
- verify(repository).setExpansion(EXPANSION_VISIBLE)
+ verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
verify(repository).setShowingSoon(true)
verify(keyguardStateController).notifyBouncerShowing(true)
verify(bouncerCallbackInteractor).dispatchStartingToShow()
@@ -108,7 +110,7 @@ class BouncerInteractorTest : SysuiTestCase() {
@Test
fun testShow_isNotScrimmed() {
- verify(repository, never()).setExpansion(EXPANSION_VISIBLE)
+ verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
}
@Test
@@ -124,7 +126,6 @@ class BouncerInteractorTest : SysuiTestCase() {
verify(falsingCollector).onBouncerHidden()
verify(keyguardStateController).notifyBouncerShowing(false)
verify(repository).setShowingSoon(false)
- verify(repository).setOnDismissAction(null)
verify(repository).setVisible(false)
verify(repository).setHide(true)
verify(repository).setShow(null)
@@ -132,26 +133,26 @@ class BouncerInteractorTest : SysuiTestCase() {
@Test
fun testExpansion() {
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
- bouncerInteractor.setExpansion(0.6f)
- verify(repository).setExpansion(0.6f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+ bouncerInteractor.setPanelExpansion(0.6f)
+ verify(repository).setPanelExpansion(0.6f)
verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
}
@Test
fun testExpansion_fullyShown() {
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
- bouncerInteractor.setExpansion(EXPANSION_VISIBLE)
+ bouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
verify(falsingCollector).onBouncerShown()
verify(bouncerCallbackInteractor).dispatchFullyShown()
}
@Test
fun testExpansion_fullyHidden() {
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
- bouncerInteractor.setExpansion(EXPANSION_HIDDEN)
+ bouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
verify(repository).setVisible(false)
verify(repository).setShow(null)
verify(falsingCollector).onBouncerHidden()
@@ -161,8 +162,8 @@ class BouncerInteractorTest : SysuiTestCase() {
@Test
fun testExpansion_startingToHide() {
- `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
- bouncerInteractor.setExpansion(0.1f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ bouncerInteractor.setPanelExpansion(0.1f)
verify(repository).setStartingToHide(true)
verify(bouncerCallbackInteractor).dispatchStartingToHide()
}
@@ -178,8 +179,7 @@ class BouncerInteractorTest : SysuiTestCase() {
val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
val cancelAction = mock(Runnable::class.java)
bouncerInteractor.setDismissAction(onDismissAction, cancelAction)
- verify(repository)
- .setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+ verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
}
@Test
@@ -234,7 +234,7 @@ class BouncerInteractorTest : SysuiTestCase() {
@Test
fun testIsFullShowing() {
`when`(repository.isVisible.value).thenReturn(true)
- `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+ `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
assertThat(bouncerInteractor.isFullyShowing()).isTrue()
`when`(repository.isVisible.value).thenReturn(false)
@@ -255,7 +255,7 @@ class BouncerInteractorTest : SysuiTestCase() {
assertThat(bouncerInteractor.isInTransit()).isTrue()
`when`(repository.showingSoon.value).thenReturn(false)
assertThat(bouncerInteractor.isInTransit()).isFalse()
- `when`(repository.expansionAmount.value).thenReturn(0.5f)
+ `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
assertThat(bouncerInteractor.isInTransit()).isTrue()
}
@@ -269,10 +269,9 @@ class BouncerInteractorTest : SysuiTestCase() {
@Test
fun testWillDismissWithAction() {
- `when`(repository.onDismissAction.value?.onDismissAction)
- .thenReturn(mock(ActivityStarter.OnDismissAction::class.java))
+ `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
assertThat(bouncerInteractor.willDismissWithAction()).isTrue()
- `when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null)
+ `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
assertThat(bouncerInteractor.willDismissWithAction()).isFalse()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 6adce7a827b6..c1fa9b39f50b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -61,6 +61,7 @@ import android.view.Display;
import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -201,6 +202,8 @@ public class NavigationBarTest extends SysuiTestCase {
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
private Resources mResources;
+ @Mock
+ private ViewRootImpl mViewRootImpl;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
@@ -227,6 +230,7 @@ public class NavigationBarTest extends SysuiTestCase {
when(mUserContextProvider.createCurrentUserContext(any(Context.class)))
.thenReturn(mContext);
when(mNavigationBarView.getResources()).thenReturn(mResources);
+ when(mNavigationBarView.getViewRootImpl()).thenReturn(mViewRootImpl);
setupSysuiDependency();
// This class inflates views that call Dependency.get, thus these injections are still
// necessary.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
index 99a17a613041..9115ab3bacca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
@@ -24,6 +24,7 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.TextView;
import androidx.test.filters.SmallTest;
@@ -48,6 +49,7 @@ public class QSCarrierTest extends SysuiTestCase {
public void setUp() throws Exception {
mTestableLooper = TestableLooper.get(this);
LayoutInflater inflater = LayoutInflater.from(mContext);
+ mContext.ensureTestableResources();
mTestableLooper.runWithLooper(() ->
mQSCarrier = (QSCarrier) inflater.inflate(R.layout.qs_carrier, null));
@@ -119,4 +121,30 @@ public class QSCarrierTest extends SysuiTestCase {
mQSCarrier.updateState(c, true);
assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility());
}
+
+ @Test
+ public void testCarrierNameMaxWidth_smallScreen_fromResource() {
+ int maxEms = 10;
+ mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_large_screen_shade_header, false);
+ TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+
+ mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+
+ assertEquals(maxEms, carrierText.getMaxEms());
+ }
+
+ @Test
+ public void testCarrierNameMaxWidth_largeScreen_maxInt() {
+ int maxEms = 10;
+ mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+ mContext.getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_large_screen_shade_header, true);
+ TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+
+ mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+
+ assertEquals(Integer.MAX_VALUE, carrierText.getMaxEms());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 760bb9bec559..081a2181cfe5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -47,7 +47,11 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -59,6 +63,7 @@ import org.mockito.Mockito.`when` as whenever
@RunWithLooper
class FooterActionsViewModelTest : SysuiTestCase() {
private lateinit var utils: FooterActionsTestUtils
+ private val testDispatcher = UnconfinedTestDispatcher(TestCoroutineScheduler())
@Before
fun setUp() {
@@ -130,6 +135,7 @@ class FooterActionsViewModelTest : SysuiTestCase() {
showPowerButton = false,
footerActionsInteractor =
utils.footerActionsInteractor(
+ bgDispatcher = testDispatcher,
userSwitcherRepository =
utils.userSwitcherRepository(
userTracker = userTracker,
@@ -137,6 +143,7 @@ class FooterActionsViewModelTest : SysuiTestCase() {
userManager = userManager,
userInfoController = userInfoController,
userSwitcherController = userSwitcherControllerWrapper.controller,
+ bgDispatcher = testDispatcher,
),
)
)
@@ -217,9 +224,11 @@ class FooterActionsViewModelTest : SysuiTestCase() {
footerActionsInteractor =
utils.footerActionsInteractor(
qsSecurityFooterUtils = qsSecurityFooterUtils,
+ bgDispatcher = testDispatcher,
securityRepository =
utils.securityRepository(
securityController = securityController,
+ bgDispatcher = testDispatcher,
),
),
)
@@ -288,9 +297,14 @@ class FooterActionsViewModelTest : SysuiTestCase() {
footerActionsInteractor =
utils.footerActionsInteractor(
qsSecurityFooterUtils = qsSecurityFooterUtils,
- securityRepository = utils.securityRepository(securityController),
+ securityRepository =
+ utils.securityRepository(
+ securityController,
+ bgDispatcher = testDispatcher,
+ ),
foregroundServicesRepository =
utils.foregroundServicesRepository(fgsManagerController),
+ bgDispatcher = testDispatcher,
),
)
@@ -376,6 +390,7 @@ class FooterActionsViewModelTest : SysuiTestCase() {
utils.footerActionsInteractor(
qsSecurityFooterUtils = qsSecurityFooterUtils,
broadcastDispatcher = broadcastDispatcher,
+ bgDispatcher = testDispatcher,
),
)
@@ -400,4 +415,7 @@ class FooterActionsViewModelTest : SysuiTestCase() {
underTest.onVisibilityChangeRequested(visible = true)
assertThat(underTest.isVisible.value).isTrue()
}
+
+ private fun runBlockingTest(block: suspend TestScope.() -> Unit) =
+ runTest(testDispatcher) { block() }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 2c76be64aa7c..b067ee76f3b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
@@ -136,6 +137,20 @@ public class QSIconViewImplTest extends SysuiTestCase {
assertEquals(mIconView.getColor(s1), mIconView.getColor(s2));
}
+ @Test
+ public void testIconNotAnimatedWhenAllowAnimationsFalse() {
+ ImageView iv = new ImageView(mContext);
+ AnimatedVectorDrawable d = mock(AnimatedVectorDrawable.class);
+ State s = new State();
+ s.icon = mock(Icon.class);
+ when(s.icon.getDrawable(any())).thenReturn(d);
+ when(s.icon.getInvisibleDrawable(any())).thenReturn(d);
+
+ mIconView.updateIcon(iv, s, false);
+
+ verify(d, never()).start();
+ }
+
private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
return new Drawable.ConstantState() {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 26a0770a7bba..a4a7995ae3c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -152,7 +152,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we should intercept touch
@@ -165,7 +165,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
// WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(false);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we shouldn't intercept touch
@@ -178,7 +178,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
// WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
// THEN we should handle the touch
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 421f918a135e..7478e4c1f878 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -22,6 +22,7 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -60,6 +61,7 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -194,6 +196,25 @@ public class NotificationTestHelper {
}
/**
+ * Creates a generic row with rounded border.
+ *
+ * @return a generic row with the set roundness.
+ * @throws Exception
+ */
+ public ExpandableNotificationRow createRowWithRoundness(
+ float topRoundness,
+ float bottomRoundness,
+ SourceType sourceType
+ ) throws Exception {
+ ExpandableNotificationRow row = createRow();
+ row.requestTopRoundness(topRoundness, false, sourceType);
+ row.requestBottomRoundness(bottomRoundness, /*animate = */ false, sourceType);
+ assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f);
+ assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f);
+ return row;
+ }
+
+ /**
* Creates a generic row.
*
* @return a generic row with no special properties.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 7c41abba6176..438b528944be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -25,6 +25,7 @@ import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -151,4 +152,37 @@ public class NotificationChildrenContainerTest extends SysuiTestCase {
Assert.assertNotNull("Children container must have a header after recreation",
mChildrenContainer.getCurrentHeaderView());
}
+
+ @Test
+ public void addNotification_shouldResetOnScrollRoundness() throws Exception {
+ ExpandableNotificationRow row = mNotificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.OnScroll);
+
+ mChildrenContainer.addNotification(row, 0);
+
+ Assert.assertEquals(0f, row.getTopRoundness(), /* delta = */ 0f);
+ Assert.assertEquals(0f, row.getBottomRoundness(), /* delta = */ 0f);
+ }
+
+ @Test
+ public void addNotification_shouldNotResetOtherRoundness() throws Exception {
+ ExpandableNotificationRow row1 = mNotificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.DefaultValue);
+ ExpandableNotificationRow row2 = mNotificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.OnDismissAnimation);
+
+ mChildrenContainer.addNotification(row1, 0);
+ mChildrenContainer.addNotification(row2, 0);
+
+ Assert.assertEquals(1f, row1.getTopRoundness(), /* delta = */ 0f);
+ Assert.assertEquals(1f, row1.getBottomRoundness(), /* delta = */ 0f);
+ Assert.assertEquals(1f, row2.getTopRoundness(), /* delta = */ 0f);
+ Assert.assertEquals(1f, row2.getBottomRoundness(), /* delta = */ 0f);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 77418138158b..bda233611158 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,6 +1,7 @@
package com.android.systemui.statusbar.notification.stack
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
@@ -8,8 +9,10 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.SourceType
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
@@ -37,6 +40,13 @@ class NotificationShelfTest : SysuiTestCase() {
private val shelfState = shelf.viewState as NotificationShelf.ShelfState
private val ambientState = mock(AmbientState::class.java)
private val hostLayoutController: NotificationStackScrollLayoutController = mock()
+ private val notificationTestHelper by lazy {
+ allowTestableLooperAsMainThread()
+ NotificationTestHelper(
+ mContext,
+ mDependency,
+ TestableLooper.get(this))
+ }
@Before
fun setUp() {
@@ -299,6 +309,39 @@ class NotificationShelfTest : SysuiTestCase() {
)
}
+ @Test
+ fun resetOnScrollRoundness_shouldSetOnScrollTo0() {
+ val row: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.OnScroll)
+
+ NotificationShelf.resetOnScrollRoundness(row)
+
+ assertEquals(0f, row.topRoundness)
+ assertEquals(0f, row.bottomRoundness)
+ }
+
+ @Test
+ fun resetOnScrollRoundness_shouldNotResetOtherRoundness() {
+ val row1: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.DefaultValue)
+ val row2: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+ /* topRoundness = */ 1f,
+ /* bottomRoundness = */ 1f,
+ /* sourceType = */ SourceType.OnDismissAnimation)
+
+ NotificationShelf.resetOnScrollRoundness(row1)
+ NotificationShelf.resetOnScrollRoundness(row2)
+
+ assertEquals(1f, row1.topRoundness)
+ assertEquals(1f, row1.bottomRoundness)
+ assertEquals(1f, row2.topRoundness)
+ assertEquals(1f, row2.bottomRoundness)
+ }
+
private fun setFractionToShade(fraction: Float) {
whenever(ambientState.fractionToShade).thenReturn(fraction)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index de1fec85360b..288f54c7d03c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -17,16 +17,18 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileConnectionRepository : MobileConnectionRepository {
private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
- override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow
+ override val subscriptionModelFlow = _subscriptionsModelFlow
private val _dataEnabled = MutableStateFlow(true)
override val dataEnabled = _dataEnabled
+ private val _isDefaultDataSubscription = MutableStateFlow(true)
+ override val isDefaultDataSubscription = _isDefaultDataSubscription
+
fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
_subscriptionsModelFlow.value = model
}
@@ -34,4 +36,8 @@ class FakeMobileConnectionRepository : MobileConnectionRepository {
fun setDataEnabled(enabled: Boolean) {
_dataEnabled.value = enabled
}
+
+ fun setIsDefaultDataSubscription(isDefault: Boolean) {
+ _isDefaultDataSubscription.value = isDefault
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 813e750684a0..533d5d9d5b4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -17,8 +17,9 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -26,18 +27,26 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository {
private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
- private val _activeMobileDataSubscriptionId =
- MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
private val _defaultDataSubRatConfig = MutableStateFlow(Config())
override val defaultDataSubRatConfig = _defaultDataSubRatConfig
+ private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
+ override val defaultDataSubId = _defaultDataSubId
+
+ private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel())
+ override val defaultMobileNetworkConnectivity = _mobileConnectivity
+
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
}
+ private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
+ override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent
+
fun setSubscriptions(subs: List<SubscriptionInfo>) {
_subscriptionsFlow.value = subs
}
@@ -46,6 +55,18 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository {
_defaultDataSubRatConfig.value = config
}
+ fun setDefaultDataSubId(id: Int) {
+ _defaultDataSubId.value = id
+ }
+
+ fun setMobileConnectivity(model: MobileConnectivityModel) {
+ _mobileConnectivity.value = model
+ }
+
+ suspend fun triggerGlobalMobileDataSettingChangedEvent() {
+ _globalMobileDataSettingChangedEvent.emit(Unit)
+ }
+
fun setActiveMobileDataSubscriptionId(subId: Int) {
_activeMobileDataSubscriptionId.value = subId
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
index 6c495c5c705a..141b50c017e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
@@ -16,13 +16,12 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
/** Defaults to `true` */
class FakeUserSetupRepository : UserSetupRepository {
private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
- override val isUserSetupFlow: Flow<Boolean> = _isUserSetup
+ override val isUserSetupFlow = _isUserSetup
fun setUserSetup(setup: Boolean) {
_isUserSetup.value = setup
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
index 093936444789..5ce51bb62c78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.os.UserHandle
+import android.provider.Settings
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
@@ -42,6 +44,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -67,16 +70,23 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Mock private lateinit var logger: ConnectivityPipelineLogger
private val scope = CoroutineScope(IMMEDIATE)
+ private val globalSettings = FakeSettings()
+ private val connectionsRepo = FakeMobileConnectionsRepository()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ globalSettings.userId = UserHandle.USER_ALL
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
underTest =
MobileConnectionRepositoryImpl(
+ context,
SUB_1_ID,
telephonyManager,
+ globalSettings,
+ connectionsRepo.defaultDataSubId,
+ connectionsRepo.globalMobileDataSettingChangedEvent,
IMMEDIATE,
logger,
scope,
@@ -290,14 +300,20 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
}
@Test
- fun dataEnabled_isEnabled() =
+ fun dataEnabled_initial_false() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
- var latest: Boolean? = null
- val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+ assertThat(underTest.dataEnabled.value).isFalse()
+ }
- assertThat(latest).isTrue()
+ @Test
+ fun dataEnabled_isEnabled_true() =
+ runBlocking(IMMEDIATE) {
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
+ val job = underTest.dataEnabled.launchIn(this)
+
+ assertThat(underTest.dataEnabled.value).isTrue()
job.cancel()
}
@@ -306,10 +322,59 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
fun dataEnabled_isDisabled() =
runBlocking(IMMEDIATE) {
whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+ val job = underTest.dataEnabled.launchIn(this)
+
+ assertThat(underTest.dataEnabled.value).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isDefaultDataSubscription_isDefault() =
+ runBlocking(IMMEDIATE) {
+ connectionsRepo.setDefaultDataSubId(SUB_1_ID)
+
+ var latest: Boolean? = null
+ val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isDefaultDataSubscription_isNotDefault() =
+ runBlocking(IMMEDIATE) {
+ // Our subId is SUB_1_ID
+ connectionsRepo.setDefaultDataSubId(123)
+
+ var latest: Boolean? = null
+ val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isDataConnectionAllowed_subIdSettingUpdate_valueUpdated() =
+ runBlocking(IMMEDIATE) {
+ val subIdSettingName = "${Settings.Global.MOBILE_DATA}$SUB_1_ID"
var latest: Boolean? = null
val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+ // We don't read the setting directly, we query telephony when changes happen
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+ globalSettings.putInt(subIdSettingName, 0)
+ assertThat(latest).isFalse()
+
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
+ globalSettings.putInt(subIdSettingName, 1)
+ assertThat(latest).isTrue()
+
+ whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+ globalSettings.putInt(subIdSettingName, 0)
assertThat(latest).isFalse()
job.cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
index 326e0d28166f..a953a3d802e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
@@ -16,26 +16,33 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.content.Intent
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.provider.Settings
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
+import com.android.internal.telephony.PhoneConstants
import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
@@ -43,7 +50,6 @@ import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
-import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -54,32 +60,26 @@ import org.mockito.MockitoAnnotations
class MobileConnectionsRepositoryTest : SysuiTestCase() {
private lateinit var underTest: MobileConnectionsRepositoryImpl
+ @Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var logger: ConnectivityPipelineLogger
- @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
private val scope = CoroutineScope(IMMEDIATE)
+ private val globalSettings = FakeSettings()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(
- broadcastDispatcher.broadcastFlow(
- any(),
- nullable(),
- ArgumentMatchers.anyInt(),
- nullable(),
- )
- )
- .thenReturn(flowOf(Unit))
underTest =
MobileConnectionsRepositoryImpl(
+ connectivityManager,
subscriptionManager,
telephonyManager,
logger,
- broadcastDispatcher,
+ fakeBroadcastDispatcher,
+ globalSettings,
context,
IMMEDIATE,
scope,
@@ -214,6 +214,139 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun testDefaultDataSubId_updatesOnBroadcast() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+ val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(
+ context,
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
+ )
+ }
+
+ assertThat(latest).isEqualTo(SUB_2_ID)
+
+ fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+ receiver.onReceive(
+ context,
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+ )
+ }
+
+ assertThat(latest).isEqualTo(SUB_1_ID)
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileConnectivity_default() {
+ assertThat(underTest.defaultMobileNetworkConnectivity.value)
+ .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
+ }
+
+ @Test
+ fun mobileConnectivity_isConnected_isValidated() =
+ runBlocking(IMMEDIATE) {
+ val caps = createCapabilities(connected = true, validated = true)
+
+ var latest: MobileConnectivityModel? = null
+ val job =
+ underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest)
+ .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = true))
+
+ job.cancel()
+ }
+
+ @Test
+ fun globalMobileDataSettingsChangedEvent_producesOnSettingChange() =
+ runBlocking(IMMEDIATE) {
+ var produced = false
+ val job =
+ underTest.globalMobileDataSettingChangedEvent
+ .onEach { produced = true }
+ .launchIn(this)
+
+ assertThat(produced).isFalse()
+
+ globalSettings.putInt(Settings.Global.MOBILE_DATA, 0)
+
+ assertThat(produced).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileConnectivity_isConnected_isNotValidated() =
+ runBlocking(IMMEDIATE) {
+ val caps = createCapabilities(connected = true, validated = false)
+
+ var latest: MobileConnectivityModel? = null
+ val job =
+ underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest)
+ .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = false))
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileConnectivity_isNotConnected_isNotValidated() =
+ runBlocking(IMMEDIATE) {
+ val caps = createCapabilities(connected = false, validated = false)
+
+ var latest: MobileConnectivityModel? = null
+ val job =
+ underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest)
+ .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
+
+ job.cancel()
+ }
+
+ /** In practice, I don't think this state can ever happen (!connected, validated) */
+ @Test
+ fun mobileConnectivity_isNotConnected_isValidated() =
+ runBlocking(IMMEDIATE) {
+ val caps = createCapabilities(connected = false, validated = true)
+
+ var latest: MobileConnectivityModel? = null
+ val job =
+ underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isEqualTo(MobileConnectivityModel(false, true))
+
+ job.cancel()
+ }
+
+ private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected)
+ whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(validated)
+ }
+
+ private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+ val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
+ verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
verify(subscriptionManager)
@@ -242,5 +375,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
private const val SUB_2_ID = 2
private val SUB_2 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+
+ private const val NET_ID = 123
+ private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 5611c448c550..3ae7d3ca1c19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -28,18 +28,23 @@ class FakeMobileIconInteractor : MobileIconInteractor {
private val _isEmergencyOnly = MutableStateFlow(false)
override val isEmergencyOnly = _isEmergencyOnly
+ private val _isFailedConnection = MutableStateFlow(false)
+ override val isDefaultConnectionFailed = _isFailedConnection
+
+ override val isDataConnected = MutableStateFlow(true)
+
private val _isDataEnabled = MutableStateFlow(true)
override val isDataEnabled = _isDataEnabled
+ private val _isDefaultDataEnabled = MutableStateFlow(true)
+ override val isDefaultDataEnabled = _isDefaultDataEnabled
+
private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
override val level = _level
private val _numberOfLevels = MutableStateFlow(4)
override val numberOfLevels = _numberOfLevels
- private val _cutOut = MutableStateFlow(false)
- override val cutOut = _cutOut
-
fun setIconGroup(group: SignalIcon.MobileIconGroup) {
_iconGroup.value = group
}
@@ -52,6 +57,14 @@ class FakeMobileIconInteractor : MobileIconInteractor {
_isDataEnabled.value = enabled
}
+ fun setIsDefaultDataEnabled(disabled: Boolean) {
+ _isDefaultDataEnabled.value = disabled
+ }
+
+ fun setIsFailedConnection(failed: Boolean) {
+ _isFailedConnection.value = failed
+ }
+
fun setLevel(level: Int) {
_level.value = level
}
@@ -59,8 +72,4 @@ class FakeMobileIconInteractor : MobileIconInteractor {
fun setNumberOfLevels(num: Int) {
_numberOfLevels.value = num
}
-
- fun setCutOut(cutOut: Boolean) {
- _cutOut.value = cutOut
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 2bd228603cb0..061c3b54650e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -26,8 +26,7 @@ import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy) :
- MobileIconsInteractor {
+class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor {
val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
val LTE_KEY = mobileMappings.toIconKey(LTE)
val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
@@ -46,9 +45,14 @@ class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy)
FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
)
+ override val isDefaultConnectionFailed = MutableStateFlow(false)
+
private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
override val filteredSubscriptions = _filteredSubscriptions
+ private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
+ override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
+
private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
override val defaultMobileIconMapping = _defaultMobileIconMapping
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index ff44af4c9204..7fc1c0f6272c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
@@ -34,6 +35,7 @@ import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsPro
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -49,12 +51,17 @@ class MobileIconInteractorTest : SysuiTestCase() {
private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
private val connectionRepository = FakeMobileConnectionRepository()
+ private val scope = CoroutineScope(IMMEDIATE)
+
@Before
fun setUp() {
underTest =
MobileIconInteractorImpl(
+ scope,
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled,
mobileIconsInteractor.defaultMobileIconMapping,
mobileIconsInteractor.defaultMobileIconGroup,
+ mobileIconsInteractor.isDefaultConnectionFailed,
mobileMappingsProxy,
connectionRepository,
)
@@ -196,6 +203,66 @@ class MobileIconInteractorTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun test_isDefaultDataEnabled_matchesParent() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDefaultDataEnabled.onEach { latest = it }.launchIn(this)
+
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
+ assertThat(latest).isTrue()
+
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun test_isDefaultConnectionFailed_matchedParent() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.isDefaultConnectionFailed.launchIn(this)
+
+ mobileIconsInteractor.isDefaultConnectionFailed.value = false
+ assertThat(underTest.isDefaultConnectionFailed.value).isFalse()
+
+ mobileIconsInteractor.isDefaultConnectionFailed.value = true
+ assertThat(underTest.isDefaultConnectionFailed.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun dataState_connected() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(dataConnectionState = DataConnectionState.Connected)
+ )
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun dataState_notConnected() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(dataConnectionState = DataConnectionState.Disconnected)
+ )
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 877ce0e6b351..b56dcd752557 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -17,8 +17,10 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
@@ -32,6 +34,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -168,6 +171,92 @@ class MobileIconsInteractorTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun activeDataConnection_turnedOn() =
+ runBlocking(IMMEDIATE) {
+ CONNECTION_1.setDataEnabled(true)
+ var latest: Boolean? = null
+ val job =
+ underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activeDataConnection_turnedOff() =
+ runBlocking(IMMEDIATE) {
+ CONNECTION_1.setDataEnabled(true)
+ var latest: Boolean? = null
+ val job =
+ underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+ CONNECTION_1.setDataEnabled(false)
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activeDataConnection_invalidSubId() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job =
+ underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+ connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
+ yield()
+
+ // An invalid active subId should tell us that data is off
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun failedConnection_connected_validated_notFailed() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+ connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, true))
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun failedConnection_notConnected_notValidated_notFailed() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+
+ connectionsRepository.setMobileConnectivity(MobileConnectivityModel(false, false))
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun failedConnection_connected_notValidated_failed() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+
+ connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, false))
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index ce0f33f400ab..d4c2c3f6cc2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -46,10 +46,12 @@ class MobileIconViewModelTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
interactor.apply {
setLevel(1)
- setCutOut(false)
+ setIsDefaultDataEnabled(true)
+ setIsFailedConnection(false)
setIconGroup(THREE_G)
setIsEmergencyOnly(false)
setNumberOfLevels(4)
+ isDataConnected.value = true
}
underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
}
@@ -59,8 +61,23 @@ class MobileIconViewModelTest : SysuiTestCase() {
runBlocking(IMMEDIATE) {
var latest: Int? = null
val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+ val expected = defaultSignal()
- assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false))
+ assertThat(latest).isEqualTo(expected)
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconId_cutout_whenDefaultDataDisabled() =
+ runBlocking(IMMEDIATE) {
+ interactor.setIsDefaultDataEnabled(false)
+
+ var latest: Int? = null
+ val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+ val expected = defaultSignal(level = 1, connected = false)
+
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@@ -97,6 +114,44 @@ class MobileIconViewModelTest : SysuiTestCase() {
}
@Test
+ fun networkType_nullWhenFailedConnection() =
+ runBlocking(IMMEDIATE) {
+ interactor.setIconGroup(THREE_G)
+ interactor.setIsDataEnabled(true)
+ interactor.setIsFailedConnection(true)
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_nullWhenDataDisconnects() =
+ runBlocking(IMMEDIATE) {
+ val initial =
+ Icon.Resource(
+ THREE_G.dataType,
+ ContentDescription.Resource(THREE_G.dataContentDescription)
+ )
+
+ interactor.setIconGroup(THREE_G)
+ var latest: Icon? = null
+ val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.setIconGroup(THREE_G)
+ assertThat(latest).isEqualTo(initial)
+
+ interactor.isDataConnected.value = false
+ yield()
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
fun networkType_null_changeToDisabled() =
runBlocking(IMMEDIATE) {
val expected =
@@ -119,6 +174,14 @@ class MobileIconViewModelTest : SysuiTestCase() {
job.cancel()
}
+ /** Convenience constructor for these tests */
+ private fun defaultSignal(
+ level: Int = 1,
+ connected: Boolean = true,
+ ): Int {
+ return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
private const val SUB_1_ID = 1
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
index 97571b23be56..f682e31c0547 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt
@@ -44,6 +44,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.advanceUntilIdle
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index e7ab63eab202..13a1cb6daf38 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -216,14 +216,10 @@ class Dimmer {
return;
}
- if (container != null) {
- // The dim method is called from WindowState.prepareSurfaces(), which is always called
- // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
- // relative to the highest Z layer with a dim.
- t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
- } else {
- t.setLayer(d.mDimLayer, Integer.MAX_VALUE);
- }
+ // The dim method is called from WindowState.prepareSurfaces(), which is always called
+ // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
+ // relative to the highest Z layer with a dim.
+ t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
t.setAlpha(d.mDimLayer, alpha);
t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
@@ -231,32 +227,6 @@ class Dimmer {
}
/**
- * Finish a dim started by dimAbove in the case there was no call to dimAbove.
- *
- * @param t A Transaction in which to finish the dim.
- */
- void stopDim(SurfaceControl.Transaction t) {
- if (mDimState != null) {
- t.hide(mDimState.mDimLayer);
- mDimState.isVisible = false;
- mDimState.mDontReset = false;
- }
- }
-
- /**
- * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
- * remove this effect. If the Dim can be assosciated with a particular child of the host
- * consider using the other variant of dimAbove which ties the Dim lifetime to the child
- * lifetime more explicitly.
- *
- * @param t A transaction in which to apply the Dim.
- * @param alpha The alpha at which to Dim.
- */
- void dimAbove(SurfaceControl.Transaction t, float alpha) {
- dim(t, null, 1, alpha, 0);
- }
-
- /**
* Place a dim above the given container, which should be a child of the host container.
* for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
* and the child should call dimAbove again to request the Dim to continue.
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 42a3ec6abbc5..2688ff757f64 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -81,11 +81,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_HIDE;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_SHOW;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -1439,90 +1435,6 @@ public class DisplayPolicy {
*/
int selectAnimation(WindowState win, int transit) {
ProtoLog.i(WM_DEBUG_ANIM, "selectAnimation in %s: transit=%d", win, transit);
- if (win == mStatusBar) {
- if (transit == TRANSIT_EXIT
- || transit == TRANSIT_HIDE) {
- return R.anim.dock_top_exit;
- } else if (transit == TRANSIT_ENTER
- || transit == TRANSIT_SHOW) {
- return R.anim.dock_top_enter;
- }
- } else if (win == mNavigationBar) {
- if (win.getAttrs().windowAnimations != 0) {
- return ANIMATION_STYLEABLE;
- }
- // This can be on either the bottom or the right or the left.
- if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
- if (transit == TRANSIT_EXIT
- || transit == TRANSIT_HIDE) {
- if (mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
- return R.anim.dock_bottom_exit_keyguard;
- } else {
- return R.anim.dock_bottom_exit;
- }
- } else if (transit == TRANSIT_ENTER
- || transit == TRANSIT_SHOW) {
- return R.anim.dock_bottom_enter;
- }
- } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
- if (transit == TRANSIT_EXIT
- || transit == TRANSIT_HIDE) {
- return R.anim.dock_right_exit;
- } else if (transit == TRANSIT_ENTER
- || transit == TRANSIT_SHOW) {
- return R.anim.dock_right_enter;
- }
- } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
- if (transit == TRANSIT_EXIT
- || transit == TRANSIT_HIDE) {
- return R.anim.dock_left_exit;
- } else if (transit == TRANSIT_ENTER
- || transit == TRANSIT_SHOW) {
- return R.anim.dock_left_enter;
- }
- }
- } else if (win == mStatusBarAlt || win == mNavigationBarAlt || win == mClimateBarAlt
- || win == mExtraNavBarAlt) {
- if (win.getAttrs().windowAnimations != 0) {
- return ANIMATION_STYLEABLE;
- }
-
- int pos = (win == mStatusBarAlt) ? mStatusBarAltPosition : mNavigationBarAltPosition;
-
- boolean isExitOrHide = transit == TRANSIT_EXIT || transit == TRANSIT_HIDE;
- boolean isEnterOrShow = transit == TRANSIT_ENTER || transit == TRANSIT_SHOW;
-
- switch (pos) {
- case ALT_BAR_LEFT:
- if (isExitOrHide) {
- return R.anim.dock_left_exit;
- } else if (isEnterOrShow) {
- return R.anim.dock_left_enter;
- }
- break;
- case ALT_BAR_RIGHT:
- if (isExitOrHide) {
- return R.anim.dock_right_exit;
- } else if (isEnterOrShow) {
- return R.anim.dock_right_enter;
- }
- break;
- case ALT_BAR_BOTTOM:
- if (isExitOrHide) {
- return R.anim.dock_bottom_exit;
- } else if (isEnterOrShow) {
- return R.anim.dock_bottom_enter;
- }
- break;
- case ALT_BAR_TOP:
- if (isExitOrHide) {
- return R.anim.dock_top_exit;
- } else if (isEnterOrShow) {
- return R.anim.dock_top_enter;
- }
- break;
- }
- }
if (transit == TRANSIT_PREVIEW_DONE) {
if (win.hasAppShownWindows()) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index a8d13c57ffa2..eaa08fd5eb0b 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1578,7 +1578,9 @@ public class DisplayRotation {
false /* forceRelayout */);
} else {
// Revert the rotation to our saved value if we transition from HALF_FOLDED.
- mRotation = mHalfFoldSavedRotation;
+ if (mHalfFoldSavedRotation != -1) {
+ mRotation = mHalfFoldSavedRotation;
+ }
// Tell the device to update its orientation (mFoldState is still HALF_FOLDED here
// so we will override USER_ROTATION_LOCKED and allow a rotation).
mService.updateRotation(false /* alwaysSendConfiguration */,
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index f11c2a7da840..dcb7fe3fbc8b 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -604,7 +604,10 @@ public class LockTaskController {
getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
}
if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) {
- getStatusBarService().showPinningEnterExitToast(false /* entering */);
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEnterExitToast(false /* entering */);
+ }
}
mWindowManager.onLockTaskStateChanged(mLockTaskModeState);
} catch (RemoteException ex) {
@@ -619,7 +622,10 @@ public class LockTaskController {
void showLockTaskToast() {
if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
try {
- getStatusBarService().showPinningEscapeToast();
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEscapeToast();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Failed to send pinning escape toast", e);
}
@@ -727,7 +733,10 @@ public class LockTaskController {
// When lock task starts, we disable the status bars.
try {
if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
- getStatusBarService().showPinningEnterExitToast(true /* entering */);
+ final IStatusBarService statusBarService = getStatusBarService();
+ if (statusBarService != null) {
+ statusBarService.showPinningEnterExitToast(true /* entering */);
+ }
}
mWindowManager.onLockTaskStateChanged(lockTaskModeState);
mLockTaskModeState = lockTaskModeState;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 91a755526264..4d29c4d0a134 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -879,8 +879,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
void abort() {
// This calls back into itself via controller.abort, so just early return here.
if (mState == STATE_ABORT) return;
- if (mState != STATE_COLLECTING) {
- throw new IllegalStateException("Too late to abort.");
+ if (mState != STATE_COLLECTING && mState != STATE_STARTED) {
+ throw new IllegalStateException("Too late to abort. state=" + mState);
}
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
mState = STATE_ABORT;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ac720be90563..9c9d751244e6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8684,11 +8684,12 @@ public class WindowManagerService extends IWindowManager.Stub
h.ownerPid = callingPid;
if (region == null) {
- h.replaceTouchableRegionWithCrop = true;
+ h.replaceTouchableRegionWithCrop(null);
} else {
h.touchableRegion.set(region);
+ h.replaceTouchableRegionWithCrop = false;
+ h.setTouchableRegionCrop(surface);
}
- h.setTouchableRegionCrop(null /* use the input surface's bounds */);
final SurfaceControl.Transaction t = mTransactionFactory.get();
t.setInputWindowInfo(surface, h);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 5c0557f2d1f4..6e16b5de1d85 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -602,11 +602,17 @@ class WindowStateAnimator {
return true;
}
- final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD;
- if (isEntrance && isImeWindow) {
+ if (mWin.mAttrs.type == TYPE_INPUT_METHOD) {
mWin.getDisplayContent().adjustForImeIfNeeded();
- mWin.setDisplayLayoutNeeded();
- mService.mWindowPlacerLocked.requestTraversal();
+ if (isEntrance) {
+ mWin.setDisplayLayoutNeeded();
+ mService.mWindowPlacerLocked.requestTraversal();
+ }
+ }
+
+ if (mWin.mControllableInsetProvider != null) {
+ // All our animations should be driven by the insets control target.
+ return false;
}
// Only apply an animation if the display isn't frozen. If it is
@@ -654,14 +660,10 @@ class WindowStateAnimator {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
mAnimationIsEntrance = isEntrance;
}
- } else if (!isImeWindow) {
+ } else {
mWin.cancelAnimation();
}
- if (!isEntrance && isImeWindow) {
- mWin.getDisplayContent().adjustForImeIfNeeded();
- }
-
return mWin.isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 55a7c1ba0bca..befe4e85a7de 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -24,7 +24,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -139,34 +138,12 @@ public class DimmerTests extends WindowTestsBase {
}
@Test
- public void testDimAboveNoChildCreatesSurface() {
- final float alpha = 0.8f;
- mDimmer.dimAbove(mTransaction, alpha);
-
- SurfaceControl dimLayer = getDimLayer();
-
- assertNotNull("Dimmer should have created a surface", dimLayer);
-
- verify(mTransaction).setAlpha(dimLayer, alpha);
- verify(mTransaction).setLayer(dimLayer, Integer.MAX_VALUE);
- }
-
- @Test
- public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() {
- float alpha = 0.8f;
- mDimmer.dimAbove(mTransaction, alpha);
- final SurfaceControl firstSurface = getDimLayer();
-
- alpha = 0.9f;
- mDimmer.dimAbove(mTransaction, alpha);
-
- assertEquals(firstSurface, getDimLayer());
- verify(mTransaction).setAlpha(firstSurface, 0.9f);
- }
-
- @Test
public void testUpdateDimsAppliesCrop() {
- mDimmer.dimAbove(mTransaction, 0.8f);
+ TestWindowContainer child = new TestWindowContainer(mWm);
+ mHost.addChild(child, 0);
+
+ final float alpha = 0.8f;
+ mDimmer.dimAbove(mTransaction, child, alpha);
int width = 100;
int height = 300;
@@ -178,17 +155,6 @@ public class DimmerTests extends WindowTestsBase {
}
@Test
- public void testDimAboveNoChildNotReset() {
- mDimmer.dimAbove(mTransaction, 0.8f);
- SurfaceControl dimLayer = getDimLayer();
- mDimmer.resetDimStates();
-
- mDimmer.updateDims(mTransaction, new Rect());
- verify(mTransaction).show(getDimLayer());
- verify(mTransaction, never()).remove(dimLayer);
- }
-
- @Test
public void testDimAboveWithChildCreatesSurfaceAboveChild() {
TestWindowContainer child = new TestWindowContainer(mWm);
mHost.addChild(child, 0);