From f763c25c1e863b6bdaef60e24dc36c4221c78a89 Mon Sep 17 00:00:00 2001 From: "yw.bae" Date: Fri, 6 Jan 2023 22:49:14 +0900 Subject: [PATCH] Implement Flash Notifications UI for Settings app. Bug: 237628564 Test: make RunSettingsRoboTests ROBOTEST_FILTER=CameraFlashNotificationPreferenceControllerTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=ColorSelectorLayoutTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=FlashNotificationsPreferenceControllerTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=FlashNotificationsPreferenceFragmentTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=FlashNotificationsPreviewPreferenceControllerTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=FlashNotificationsPreviewPreferenceTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=FlashNotificationsUtilTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=ScreenFlashNotificationColorDialogFragmentTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=ScreenFlashNotificationColorTest Change-Id: I0987590ddfcfd0873ec419db263f6a7eade81844 Signed-off-by: yw.bae Signed-off-by: Angela Wang --- res/drawable/ic_flash_notification.xml | 35 +++ .../screen_flash_color_01_checked_layer.xml | 44 ++++ res/drawable/screen_flash_color_01_layer.xml | 33 +++ .../screen_flash_color_01_selector.xml | 20 ++ .../screen_flash_color_02_checked_layer.xml | 44 ++++ res/drawable/screen_flash_color_02_layer.xml | 33 +++ .../screen_flash_color_02_selector.xml | 20 ++ .../screen_flash_color_03_checked_layer.xml | 44 ++++ res/drawable/screen_flash_color_03_layer.xml | 33 +++ .../screen_flash_color_03_selector.xml | 20 ++ .../screen_flash_color_04_checked_layer.xml | 44 ++++ res/drawable/screen_flash_color_04_layer.xml | 33 +++ .../screen_flash_color_04_selector.xml | 20 ++ .../screen_flash_color_05_checked_layer.xml | 44 ++++ res/drawable/screen_flash_color_05_layer.xml | 33 +++ .../screen_flash_color_05_selector.xml | 20 ++ .../screen_flash_color_06_checked_layer.xml | 44 ++++ res/drawable/screen_flash_color_06_layer.xml | 33 +++ .../screen_flash_color_06_selector.xml | 20 ++ .../screen_flash_color_07_checked_layer.xml | 44 ++++ res/drawable/screen_flash_color_07_layer.xml | 33 +++ .../screen_flash_color_07_selector.xml | 20 ++ .../screen_flash_color_08_checked_layer.xml | 44 ++++ res/drawable/screen_flash_color_08_layer.xml | 33 +++ .../screen_flash_color_08_selector.xml | 20 ++ .../screen_flash_color_09_checked_layer.xml | 44 ++++ res/drawable/screen_flash_color_09_layer.xml | 33 +++ .../screen_flash_color_09_selector.xml | 20 ++ .../screen_flash_color_10_checked_layer.xml | 44 ++++ res/drawable/screen_flash_color_10_layer.xml | 33 +++ .../screen_flash_color_10_selector.xml | 20 ++ .../screen_flash_color_11_checked_layer.xml | 44 ++++ res/drawable/screen_flash_color_11_layer.xml | 33 +++ .../screen_flash_color_11_selector.xml | 20 ++ .../screen_flash_color_12_checked_layer.xml | 44 ++++ res/drawable/screen_flash_color_12_layer.xml | 33 +++ .../screen_flash_color_12_selector.xml | 20 ++ .../flash_notification_preview_preference.xml | 45 ++++ res/layout/layout_color_selector.xml | 174 ++++++++++++++ res/layout/layout_color_selector_dialog.xml | 32 +++ res/values/arrays.xml | 15 ++ res/values/colors.xml | 33 +++ res/values/dimens.xml | 8 + res/values/strings.xml | 54 +++++ res/xml/accessibility_settings.xml | 8 + res/xml/configure_notification_settings.xml | 10 + res/xml/flash_notifications_settings.xml | 55 +++++ ...FlashNotificationPreferenceController.java | 61 +++++ .../accessibility/ColorSelectorLayout.java | 163 +++++++++++++ ...lashNotificationsPreferenceController.java | 60 +++++ .../FlashNotificationsPreferenceFragment.java | 61 +++++ .../FlashNotificationsPreviewPreference.java | 56 +++++ ...ificationsPreviewPreferenceController.java | 114 +++++++++ .../accessibility/FlashNotificationsUtil.java | 141 +++++++++++ .../ScreenFlashNotificationColor.java | 49 ++++ ...nFlashNotificationColorDialogFragment.java | 179 ++++++++++++++ ...FlashNotificationPreferenceController.java | 128 ++++++++++ ...hNotificationPreferenceControllerTest.java | 114 +++++++++ .../ColorSelectorLayoutTest.java | 77 ++++++ ...NotificationsPreferenceControllerTest.java | 101 ++++++++ ...shNotificationsPreferenceFragmentTest.java | 102 ++++++++ ...ationsPreviewPreferenceControllerTest.java | 185 +++++++++++++++ ...ashNotificationsPreviewPreferenceTest.java | 77 ++++++ .../FlashNotificationsUtilTest.java | 220 ++++++++++++++++++ ...shNotificationColorDialogFragmentTest.java | 196 ++++++++++++++++ .../ScreenFlashNotificationColorTest.java | 62 +++++ ...hNotificationPreferenceControllerTest.java | 207 ++++++++++++++++ .../ShadowFlashNotificationsUtils.java | 72 ++++++ 68 files changed, 4058 insertions(+) create mode 100644 res/drawable/ic_flash_notification.xml create mode 100644 res/drawable/screen_flash_color_01_checked_layer.xml create mode 100644 res/drawable/screen_flash_color_01_layer.xml create mode 100644 res/drawable/screen_flash_color_01_selector.xml create mode 100644 res/drawable/screen_flash_color_02_checked_layer.xml create mode 100644 res/drawable/screen_flash_color_02_layer.xml create mode 100644 res/drawable/screen_flash_color_02_selector.xml create mode 100644 res/drawable/screen_flash_color_03_checked_layer.xml create mode 100644 res/drawable/screen_flash_color_03_layer.xml create mode 100644 res/drawable/screen_flash_color_03_selector.xml create mode 100644 res/drawable/screen_flash_color_04_checked_layer.xml create mode 100644 res/drawable/screen_flash_color_04_layer.xml create mode 100644 res/drawable/screen_flash_color_04_selector.xml create mode 100644 res/drawable/screen_flash_color_05_checked_layer.xml create mode 100644 res/drawable/screen_flash_color_05_layer.xml create mode 100644 res/drawable/screen_flash_color_05_selector.xml create mode 100644 res/drawable/screen_flash_color_06_checked_layer.xml create mode 100644 res/drawable/screen_flash_color_06_layer.xml create mode 100644 res/drawable/screen_flash_color_06_selector.xml create mode 100644 res/drawable/screen_flash_color_07_checked_layer.xml create mode 100644 res/drawable/screen_flash_color_07_layer.xml create mode 100644 res/drawable/screen_flash_color_07_selector.xml create mode 100644 res/drawable/screen_flash_color_08_checked_layer.xml create mode 100644 res/drawable/screen_flash_color_08_layer.xml create mode 100644 res/drawable/screen_flash_color_08_selector.xml create mode 100644 res/drawable/screen_flash_color_09_checked_layer.xml create mode 100644 res/drawable/screen_flash_color_09_layer.xml create mode 100644 res/drawable/screen_flash_color_09_selector.xml create mode 100644 res/drawable/screen_flash_color_10_checked_layer.xml create mode 100644 res/drawable/screen_flash_color_10_layer.xml create mode 100644 res/drawable/screen_flash_color_10_selector.xml create mode 100644 res/drawable/screen_flash_color_11_checked_layer.xml create mode 100644 res/drawable/screen_flash_color_11_layer.xml create mode 100644 res/drawable/screen_flash_color_11_selector.xml create mode 100644 res/drawable/screen_flash_color_12_checked_layer.xml create mode 100644 res/drawable/screen_flash_color_12_layer.xml create mode 100644 res/drawable/screen_flash_color_12_selector.xml create mode 100644 res/layout/flash_notification_preview_preference.xml create mode 100644 res/layout/layout_color_selector.xml create mode 100644 res/layout/layout_color_selector_dialog.xml create mode 100644 res/xml/flash_notifications_settings.xml create mode 100644 src/com/android/settings/accessibility/CameraFlashNotificationPreferenceController.java create mode 100644 src/com/android/settings/accessibility/ColorSelectorLayout.java create mode 100644 src/com/android/settings/accessibility/FlashNotificationsPreferenceController.java create mode 100644 src/com/android/settings/accessibility/FlashNotificationsPreferenceFragment.java create mode 100644 src/com/android/settings/accessibility/FlashNotificationsPreviewPreference.java create mode 100644 src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceController.java create mode 100644 src/com/android/settings/accessibility/FlashNotificationsUtil.java create mode 100644 src/com/android/settings/accessibility/ScreenFlashNotificationColor.java create mode 100644 src/com/android/settings/accessibility/ScreenFlashNotificationColorDialogFragment.java create mode 100644 src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/ColorSelectorLayoutTest.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreferenceFragmentTest.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceTest.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/FlashNotificationsUtilTest.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationColorDialogFragmentTest.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationColorTest.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/ShadowFlashNotificationsUtils.java diff --git a/res/drawable/ic_flash_notification.xml b/res/drawable/ic_flash_notification.xml new file mode 100644 index 00000000000..53ebab0559b --- /dev/null +++ b/res/drawable/ic_flash_notification.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_01_checked_layer.xml b/res/drawable/screen_flash_color_01_checked_layer.xml new file mode 100644 index 00000000000..ec9a074631e --- /dev/null +++ b/res/drawable/screen_flash_color_01_checked_layer.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_01_layer.xml b/res/drawable/screen_flash_color_01_layer.xml new file mode 100644 index 00000000000..d1d9d3b781c --- /dev/null +++ b/res/drawable/screen_flash_color_01_layer.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_01_selector.xml b/res/drawable/screen_flash_color_01_selector.xml new file mode 100644 index 00000000000..383fc75ed4f --- /dev/null +++ b/res/drawable/screen_flash_color_01_selector.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_02_checked_layer.xml b/res/drawable/screen_flash_color_02_checked_layer.xml new file mode 100644 index 00000000000..a212456be8c --- /dev/null +++ b/res/drawable/screen_flash_color_02_checked_layer.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_02_layer.xml b/res/drawable/screen_flash_color_02_layer.xml new file mode 100644 index 00000000000..6663bf466bd --- /dev/null +++ b/res/drawable/screen_flash_color_02_layer.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_02_selector.xml b/res/drawable/screen_flash_color_02_selector.xml new file mode 100644 index 00000000000..97f5eef6320 --- /dev/null +++ b/res/drawable/screen_flash_color_02_selector.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_03_checked_layer.xml b/res/drawable/screen_flash_color_03_checked_layer.xml new file mode 100644 index 00000000000..28711af9a91 --- /dev/null +++ b/res/drawable/screen_flash_color_03_checked_layer.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_03_layer.xml b/res/drawable/screen_flash_color_03_layer.xml new file mode 100644 index 00000000000..e006e01a382 --- /dev/null +++ b/res/drawable/screen_flash_color_03_layer.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_03_selector.xml b/res/drawable/screen_flash_color_03_selector.xml new file mode 100644 index 00000000000..7c9880fc778 --- /dev/null +++ b/res/drawable/screen_flash_color_03_selector.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_04_checked_layer.xml b/res/drawable/screen_flash_color_04_checked_layer.xml new file mode 100644 index 00000000000..67ca058dd11 --- /dev/null +++ b/res/drawable/screen_flash_color_04_checked_layer.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_04_layer.xml b/res/drawable/screen_flash_color_04_layer.xml new file mode 100644 index 00000000000..32d4b4ea03c --- /dev/null +++ b/res/drawable/screen_flash_color_04_layer.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_04_selector.xml b/res/drawable/screen_flash_color_04_selector.xml new file mode 100644 index 00000000000..50f00e9f7f4 --- /dev/null +++ b/res/drawable/screen_flash_color_04_selector.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_05_checked_layer.xml b/res/drawable/screen_flash_color_05_checked_layer.xml new file mode 100644 index 00000000000..74771c85d2f --- /dev/null +++ b/res/drawable/screen_flash_color_05_checked_layer.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_05_layer.xml b/res/drawable/screen_flash_color_05_layer.xml new file mode 100644 index 00000000000..c4977452c08 --- /dev/null +++ b/res/drawable/screen_flash_color_05_layer.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_05_selector.xml b/res/drawable/screen_flash_color_05_selector.xml new file mode 100644 index 00000000000..08e5d9da988 --- /dev/null +++ b/res/drawable/screen_flash_color_05_selector.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_06_checked_layer.xml b/res/drawable/screen_flash_color_06_checked_layer.xml new file mode 100644 index 00000000000..a47d0acd104 --- /dev/null +++ b/res/drawable/screen_flash_color_06_checked_layer.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_06_layer.xml b/res/drawable/screen_flash_color_06_layer.xml new file mode 100644 index 00000000000..9610a69929e --- /dev/null +++ b/res/drawable/screen_flash_color_06_layer.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_06_selector.xml b/res/drawable/screen_flash_color_06_selector.xml new file mode 100644 index 00000000000..b71f89129b7 --- /dev/null +++ b/res/drawable/screen_flash_color_06_selector.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_07_checked_layer.xml b/res/drawable/screen_flash_color_07_checked_layer.xml new file mode 100644 index 00000000000..a693eff4d81 --- /dev/null +++ b/res/drawable/screen_flash_color_07_checked_layer.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_07_layer.xml b/res/drawable/screen_flash_color_07_layer.xml new file mode 100644 index 00000000000..cb48fe6deb6 --- /dev/null +++ b/res/drawable/screen_flash_color_07_layer.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_07_selector.xml b/res/drawable/screen_flash_color_07_selector.xml new file mode 100644 index 00000000000..34a4c6999dd --- /dev/null +++ b/res/drawable/screen_flash_color_07_selector.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_08_checked_layer.xml b/res/drawable/screen_flash_color_08_checked_layer.xml new file mode 100644 index 00000000000..c8e0a4f2cf3 --- /dev/null +++ b/res/drawable/screen_flash_color_08_checked_layer.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_08_layer.xml b/res/drawable/screen_flash_color_08_layer.xml new file mode 100644 index 00000000000..f1b2adbdc65 --- /dev/null +++ b/res/drawable/screen_flash_color_08_layer.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_08_selector.xml b/res/drawable/screen_flash_color_08_selector.xml new file mode 100644 index 00000000000..82a52b59d66 --- /dev/null +++ b/res/drawable/screen_flash_color_08_selector.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_09_checked_layer.xml b/res/drawable/screen_flash_color_09_checked_layer.xml new file mode 100644 index 00000000000..437e41e54d7 --- /dev/null +++ b/res/drawable/screen_flash_color_09_checked_layer.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_09_layer.xml b/res/drawable/screen_flash_color_09_layer.xml new file mode 100644 index 00000000000..fb1680a34e1 --- /dev/null +++ b/res/drawable/screen_flash_color_09_layer.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_09_selector.xml b/res/drawable/screen_flash_color_09_selector.xml new file mode 100644 index 00000000000..c2c1889dd97 --- /dev/null +++ b/res/drawable/screen_flash_color_09_selector.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_10_checked_layer.xml b/res/drawable/screen_flash_color_10_checked_layer.xml new file mode 100644 index 00000000000..6e519e85e03 --- /dev/null +++ b/res/drawable/screen_flash_color_10_checked_layer.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_10_layer.xml b/res/drawable/screen_flash_color_10_layer.xml new file mode 100644 index 00000000000..8727abf4ff4 --- /dev/null +++ b/res/drawable/screen_flash_color_10_layer.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_10_selector.xml b/res/drawable/screen_flash_color_10_selector.xml new file mode 100644 index 00000000000..27761665f41 --- /dev/null +++ b/res/drawable/screen_flash_color_10_selector.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_11_checked_layer.xml b/res/drawable/screen_flash_color_11_checked_layer.xml new file mode 100644 index 00000000000..d7c781d48cf --- /dev/null +++ b/res/drawable/screen_flash_color_11_checked_layer.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_11_layer.xml b/res/drawable/screen_flash_color_11_layer.xml new file mode 100644 index 00000000000..ebf2601156d --- /dev/null +++ b/res/drawable/screen_flash_color_11_layer.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_11_selector.xml b/res/drawable/screen_flash_color_11_selector.xml new file mode 100644 index 00000000000..6d1d3389490 --- /dev/null +++ b/res/drawable/screen_flash_color_11_selector.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_12_checked_layer.xml b/res/drawable/screen_flash_color_12_checked_layer.xml new file mode 100644 index 00000000000..b2769235f9e --- /dev/null +++ b/res/drawable/screen_flash_color_12_checked_layer.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_12_layer.xml b/res/drawable/screen_flash_color_12_layer.xml new file mode 100644 index 00000000000..b03c54cb652 --- /dev/null +++ b/res/drawable/screen_flash_color_12_layer.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/drawable/screen_flash_color_12_selector.xml b/res/drawable/screen_flash_color_12_selector.xml new file mode 100644 index 00000000000..0c78c955a7a --- /dev/null +++ b/res/drawable/screen_flash_color_12_selector.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/res/layout/flash_notification_preview_preference.xml b/res/layout/flash_notification_preview_preference.xml new file mode 100644 index 00000000000..ecc6f4f01e4 --- /dev/null +++ b/res/layout/flash_notification_preview_preference.xml @@ -0,0 +1,45 @@ + + + + + + + + + diff --git a/res/layout/layout_color_selector.xml b/res/layout/layout_color_selector.xml new file mode 100644 index 00000000000..c366add00ec --- /dev/null +++ b/res/layout/layout_color_selector.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/layout_color_selector_dialog.xml b/res/layout/layout_color_selector_dialog.xml new file mode 100644 index 00000000000..70d4509f07e --- /dev/null +++ b/res/layout/layout_color_selector_dialog.xml @@ -0,0 +1,32 @@ + + + + + + + \ No newline at end of file diff --git a/res/values/arrays.xml b/res/values/arrays.xml index f0de6d05d9c..690fe3f9d0a 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1415,4 +1415,19 @@ persian + + + @color/screen_flash_preset_opacity_color_01 + @color/screen_flash_preset_opacity_color_02 + @color/screen_flash_preset_opacity_color_03 + @color/screen_flash_preset_opacity_color_04 + @color/screen_flash_preset_opacity_color_05 + @color/screen_flash_preset_opacity_color_06 + @color/screen_flash_preset_opacity_color_07 + @color/screen_flash_preset_opacity_color_08 + @color/screen_flash_preset_opacity_color_09 + @color/screen_flash_preset_opacity_color_10 + @color/screen_flash_preset_opacity_color_11 + @color/screen_flash_preset_opacity_color_12 + diff --git a/res/values/colors.xml b/res/values/colors.xml index 5d98e94f083..d0deaf2645a 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -181,4 +181,37 @@ #699FF3 #70699FF3 #FFEE675C + + + + #D4D4D4 + #000000 + + + #0000FE + #0080FF + #00FFFF + #00FF7F + #00FF01 + #80FF00 + #FFFF00 + #FF7F00 + #FE0000 + #FF017E + #FF00FE + #7F00FF + + + #4D0000FE + #660080FF + #4D00FFFF + #6600FF7F + #6600FF01 + #6680FF00 + #66FFFF00 + #66FF7F00 + #66FE0000 + #4DFF017E + #4DFF00FE + #667F00FF diff --git a/res/values/dimens.xml b/res/values/dimens.xml index ad6ee3dc625..1bd09438e8d 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -416,4 +416,12 @@ 0dp + + + 38dp + 38dp + 2dp + 32dp + 3dp + 1dp diff --git a/res/values/strings.xml b/res/values/strings.xml index 31dd0b8ccd3..8be46b8b32d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -6476,6 +6476,8 @@ + + @@ -11739,4 +11741,56 @@ Fingerprint sensor + + + + Flash notifications + + Off + + On / camera flash + + On / screen flash + + On / camera and screen flash + + Flash the camera light or the screen when you receive notifications or when alarms sound. + + Use flash notifications with caution if your are light sensitive + + Preview + + Camera flash notification + + Screen flash notification + + Screen flash color + + Blue + + Azure + + Cyan + + Spring green + + Green + + Chartreuse green + + Yellow + + Orange + + Red + + Rose + + Magenta + + Violet + + Done + + Cancel diff --git a/res/xml/accessibility_settings.xml b/res/xml/accessibility_settings.xml index f8f32cbe208..e0759769fec 100644 --- a/res/xml/accessibility_settings.xml +++ b/res/xml/accessibility_settings.xml @@ -150,6 +150,14 @@ settings:searchable="true" settings:controller="com.android.settings.accessibility.AudioDescriptionPreferenceController"/> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceController.java b/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceController.java new file mode 100644 index 00000000000..c630267e10c --- /dev/null +++ b/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceController.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; +import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; + +import android.content.Context; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.core.TogglePreferenceController; + + +/** + * Controller for Camera flash notification. + */ +public class CameraFlashNotificationPreferenceController extends TogglePreferenceController { + + public CameraFlashNotificationPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return FlashNotificationsUtil.isTorchAvailable(mContext) + ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public boolean isChecked() { + return Settings.System.getInt(mContext.getContentResolver(), + SETTING_KEY_CAMERA_FLASH_NOTIFICATION, OFF) != OFF; + } + + @Override + public boolean setChecked(boolean isChecked) { + return Settings.System.putInt(mContext.getContentResolver(), + SETTING_KEY_CAMERA_FLASH_NOTIFICATION, (isChecked ? ON : OFF)); + } + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_accessibility; + } +} diff --git a/src/com/android/settings/accessibility/ColorSelectorLayout.java b/src/com/android/settings/accessibility/ColorSelectorLayout.java new file mode 100644 index 00000000000..02ef16d306d --- /dev/null +++ b/src/com/android/settings/accessibility/ColorSelectorLayout.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.RadioButton; + +import androidx.annotation.ColorInt; +import androidx.annotation.Nullable; + +import com.android.settings.R; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Layout for Screen flash notification color picker. + */ +public class ColorSelectorLayout extends LinearLayout { + // Holds the checked id. The selection is empty by default + private int mCheckedId = -1; + // Tracks children radio buttons checked state + private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener; + + private ColorSelectorLayout.OnCheckedChangeListener mOnCheckedChangeListener; + + private final List mRadioButtonResourceIds = Arrays.asList( + R.id.color_radio_button_00, + R.id.color_radio_button_01, + R.id.color_radio_button_02, + R.id.color_radio_button_03, + R.id.color_radio_button_04, + R.id.color_radio_button_05, + R.id.color_radio_button_06, + R.id.color_radio_button_07, + R.id.color_radio_button_08, + R.id.color_radio_button_09, + R.id.color_radio_button_10, + R.id.color_radio_button_11 + ); + + private List mColorList; + + public ColorSelectorLayout(Context context) { + super(context); + mChildOnCheckedChangeListener = new CheckedStateTracker(); + inflate(mContext, R.layout.layout_color_selector, this); + init(); + mColorList = Arrays.stream(mContext.getResources() + .getIntArray(R.array.screen_flash_notification_preset_opacity_colors)) + .boxed() + .collect(Collectors.toList()); + } + + public ColorSelectorLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + mChildOnCheckedChangeListener = new CheckedStateTracker(); + inflate(mContext, R.layout.layout_color_selector, this); + init(); + mColorList = Arrays.stream(mContext.getResources() + .getIntArray(R.array.screen_flash_notification_preset_opacity_colors)) + .boxed() + .collect(Collectors.toList()); + } + + private void init() { + for (int resId : mRadioButtonResourceIds) { + RadioButton radioButton = findViewById(resId); + if (radioButton != null) { + radioButton.setOnCheckedChangeListener(mChildOnCheckedChangeListener); + } + } + } + + void setOnCheckedChangeListener(ColorSelectorLayout.OnCheckedChangeListener listener) { + mOnCheckedChangeListener = listener; + } + + void setCheckedColor(@ColorInt int color) { + int resId = getResId(mColorList.indexOf(color)); + if (resId != NO_ID && resId == mCheckedId) return; + + if (mCheckedId != NO_ID) { + setCheckedStateForView(mCheckedId, false); + } + + if (resId != NO_ID) { + setCheckedStateForView(resId, true); + } + + setCheckedId(resId); + } + + int getCheckedColor(int defaultColor) { + int checkedItemIndex = mRadioButtonResourceIds.indexOf(mCheckedId); + if (checkedItemIndex < 0 || checkedItemIndex >= mColorList.size()) { + return defaultColor; + } else { + return mColorList.get(checkedItemIndex); + } + } + + private int getResId(int index) { + if (index < 0 || index >= mRadioButtonResourceIds.size()) { + return NO_ID; + } else { + return mRadioButtonResourceIds.get(index); + } + } + + private void setCheckedId(int resId) { + mCheckedId = resId; + if (mOnCheckedChangeListener != null) { + mOnCheckedChangeListener.onCheckedChanged(this); + } + } + + private void setCheckedStateForView(int viewId, boolean checked) { + final View checkedView = findViewById(viewId); + if (checkedView instanceof RadioButton) { + ((RadioButton) checkedView).setChecked(checked); + } + } + + interface OnCheckedChangeListener { + void onCheckedChanged(ColorSelectorLayout group); + } + + private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (!isChecked) { + return; + } + + if (mCheckedId != NO_ID) { + setCheckedStateForView(mCheckedId, false); + } + + int id = buttonView.getId(); + setCheckedId(id); + } + } +} diff --git a/src/com/android/settings/accessibility/FlashNotificationsPreferenceController.java b/src/com/android/settings/accessibility/FlashNotificationsPreferenceController.java new file mode 100644 index 00000000000..77e78d193ce --- /dev/null +++ b/src/com/android/settings/accessibility/FlashNotificationsPreferenceController.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import android.content.Context; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +/** + * Controller for flash notifications. + */ +public class FlashNotificationsPreferenceController extends BasePreferenceController { + + public FlashNotificationsPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + final int res; + + switch (FlashNotificationsUtil.getFlashNotificationsState(mContext)) { + case FlashNotificationsUtil.State.CAMERA: + res = R.string.flash_notifications_summary_on_camera; + break; + case FlashNotificationsUtil.State.SCREEN: + res = R.string.flash_notifications_summary_on_screen; + break; + case FlashNotificationsUtil.State.CAMERA_SCREEN: + res = R.string.flash_notifications_summary_on_camera_and_screen; + break; + case FlashNotificationsUtil.State.OFF: + default: + res = R.string.flash_notifications_summary_off; + break; + } + + return mContext.getString(res); + } +} diff --git a/src/com/android/settings/accessibility/FlashNotificationsPreferenceFragment.java b/src/com/android/settings/accessibility/FlashNotificationsPreferenceFragment.java new file mode 100644 index 00000000000..f35d1a1b1a6 --- /dev/null +++ b/src/com/android/settings/accessibility/FlashNotificationsPreferenceFragment.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import android.content.Context; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +/** + * Fragment for flash notifications. + */ +@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) +public class FlashNotificationsPreferenceFragment extends DashboardFragment { + + @Override + protected int getPreferenceScreenResId() { + return R.xml.flash_notifications_settings; + } + + @Override + protected String getLogTag() { + return "FlashNotificationsPreferenceFragment"; + } + + @Override + public int getMetricsCategory() { + // TODO: Flash notifications have to add SettingsEnums. + return 0; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + use(ScreenFlashNotificationPreferenceController.class).setParentFragment(this); + } + + @Override + public int getHelpResource() { + return R.string.help_url_flash_notifications; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.flash_notifications_settings); +} diff --git a/src/com/android/settings/accessibility/FlashNotificationsPreviewPreference.java b/src/com/android/settings/accessibility/FlashNotificationsPreviewPreference.java new file mode 100644 index 00000000000..9028bf182be --- /dev/null +++ b/src/com/android/settings/accessibility/FlashNotificationsPreviewPreference.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.preference.Preference; + +import com.android.settings.R; + +/** + * Preference for Flash notifications preview. + */ +public class FlashNotificationsPreviewPreference extends Preference { + + public FlashNotificationsPreviewPreference(Context context) { + super(context); + init(); + } + + public FlashNotificationsPreviewPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public FlashNotificationsPreviewPreference(Context context, AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + public FlashNotificationsPreviewPreference(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + setLayoutResource(R.layout.flash_notification_preview_preference); + } +} diff --git a/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceController.java b/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceController.java new file mode 100644 index 00000000000..8774043c8d3 --- /dev/null +++ b/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceController.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + + +import static com.android.settings.accessibility.FlashNotificationsUtil.ACTION_FLASH_NOTIFICATION_START_PREVIEW; +import static com.android.settings.accessibility.FlashNotificationsUtil.EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE; +import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; +import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; +import static com.android.settings.accessibility.FlashNotificationsUtil.TYPE_SHORT_PREVIEW; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleEventObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; + +/** + * Controller for flash notifications preview. + */ +public class FlashNotificationsPreviewPreferenceController extends + BasePreferenceController implements LifecycleEventObserver { + + private Preference mPreference; + private final ContentResolver mContentResolver; + + @VisibleForTesting + final ContentObserver mContentObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange, @Nullable Uri uri) { + onSettingChanged(); + } + }; + + public FlashNotificationsPreviewPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mContentResolver = context.getContentResolver(); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + onSettingChanged(); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (getPreferenceKey().equals(preference.getKey())) { + Intent intent = new Intent(ACTION_FLASH_NOTIFICATION_START_PREVIEW); + intent.putExtra(EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE, TYPE_SHORT_PREVIEW); + mContext.sendBroadcast(intent); + return true; + } + + return super.handlePreferenceTreeClick(preference); + } + + @Override + public void onStateChanged(@NonNull LifecycleOwner lifecycleOwner, + @NonNull Lifecycle.Event event) { + if (event == Lifecycle.Event.ON_RESUME) { + mContentResolver.registerContentObserver( + Settings.System.getUriFor(SETTING_KEY_CAMERA_FLASH_NOTIFICATION), + /* notifyForDescendants= */ false, mContentObserver); + mContentResolver.registerContentObserver( + Settings.System.getUriFor(SETTING_KEY_SCREEN_FLASH_NOTIFICATION), + /* notifyForDescendants= */ false, mContentObserver); + } else if (event == Lifecycle.Event.ON_PAUSE) { + mContentResolver.unregisterContentObserver(mContentObserver); + } + } + + private void onSettingChanged() { + if (mPreference == null) return; + + mPreference.setEnabled(FlashNotificationsUtil.getFlashNotificationsState(mContext) + != FlashNotificationsUtil.State.OFF); + } +} diff --git a/src/com/android/settings/accessibility/FlashNotificationsUtil.java b/src/com/android/settings/accessibility/FlashNotificationsUtil.java new file mode 100644 index 00000000000..429936e41ba --- /dev/null +++ b/src/com/android/settings/accessibility/FlashNotificationsUtil.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; + +import android.content.Context; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.provider.Settings; +import android.util.Log; + +import androidx.annotation.ColorInt; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +class FlashNotificationsUtil { + static final String LOG_TAG = "FlashNotificationsUtil"; + static final String ACTION_FLASH_NOTIFICATION_START_PREVIEW = + "com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW"; + static final String ACTION_FLASH_NOTIFICATION_STOP_PREVIEW = + "com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW"; + static final String EXTRA_FLASH_NOTIFICATION_PREVIEW_COLOR = + "com.android.internal.intent.extra.FLASH_NOTIFICATION_PREVIEW_COLOR"; + static final String EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE = + "com.android.internal.intent.extra.FLASH_NOTIFICATION_PREVIEW_TYPE"; + + static final String SETTING_KEY_CAMERA_FLASH_NOTIFICATION = + "camera_flash_notification"; + static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION = + "screen_flash_notification"; + static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR = + "screen_flash_notification_color_global"; + + static final int TYPE_SHORT_PREVIEW = 0; + static final int TYPE_LONG_PREVIEW = 1; + + static final int DEFAULT_SCREEN_FLASH_COLOR = + ScreenFlashNotificationColor.YELLOW.mColorInt; + + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FlashNotificationsUtil.State.OFF, + FlashNotificationsUtil.State.CAMERA, + FlashNotificationsUtil.State.SCREEN, + FlashNotificationsUtil.State.CAMERA_SCREEN, + }) + @interface State { + int OFF = 0; + int CAMERA = 1; + int SCREEN = 2; + int CAMERA_SCREEN = 3; + } + + static boolean isTorchAvailable(@NonNull Context context) { + // TODO This is duplicated logic of FlashNotificationsController.getCameraId. + final CameraManager cameraManager = context.getSystemService(CameraManager.class); + if (cameraManager == null) return false; + + try { + final String[] ids = cameraManager.getCameraIdList(); + + for (String id : ids) { + final CameraCharacteristics c = cameraManager.getCameraCharacteristics(id); + + final Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); + if (flashAvailable == null) continue; + + final Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING); + if (lensFacing == null) continue; + + if (flashAvailable && lensFacing == CameraCharacteristics.LENS_FACING_BACK) { + return true; + } + } + } catch (CameraAccessException ignored) { + Log.w(LOG_TAG, "Failed to get valid camera for camera flash notification."); + } + return false; + } + + static ScreenFlashNotificationColor getScreenColor(@ColorInt int colorInt) + throws ScreenColorNotFoundException { + + colorInt |= ScreenFlashNotificationColor.ALPHA_MASK; + for (ScreenFlashNotificationColor color : ScreenFlashNotificationColor.values()) { + if (colorInt == color.mOpaqueColorInt) { + return color; + } + } + + throw new ScreenColorNotFoundException(); + } + + @NonNull + static String getColorDescriptionText(@NonNull Context context, @ColorInt int color) { + try { + return context.getString(getScreenColor(color).mStringRes); + } catch (ScreenColorNotFoundException e) { + return ""; + } + } + + @State + static int getFlashNotificationsState(Context context) { + if (context == null) { + return State.OFF; + } + + final boolean isTorchAvailable = FlashNotificationsUtil.isTorchAvailable(context); + final boolean isCameraFlashEnabled = Settings.System.getInt(context.getContentResolver(), + SETTING_KEY_CAMERA_FLASH_NOTIFICATION, State.OFF) != State.OFF; + final boolean isScreenFlashEnabled = Settings.System.getInt(context.getContentResolver(), + SETTING_KEY_SCREEN_FLASH_NOTIFICATION, State.OFF) != State.OFF; + + return ((isTorchAvailable && isCameraFlashEnabled) ? State.CAMERA : State.OFF) + | (isScreenFlashEnabled ? State.SCREEN : State.OFF); + } + + static class ScreenColorNotFoundException extends Exception { + } +} diff --git a/src/com/android/settings/accessibility/ScreenFlashNotificationColor.java b/src/com/android/settings/accessibility/ScreenFlashNotificationColor.java new file mode 100644 index 00000000000..ae6de3e4eeb --- /dev/null +++ b/src/com/android/settings/accessibility/ScreenFlashNotificationColor.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import androidx.annotation.ColorInt; +import androidx.annotation.StringRes; + +import com.android.settings.R; + +enum ScreenFlashNotificationColor { + BLUE(0x4D0000FE, R.string.screen_flash_color_blue), + AZURE(0x660080FF, R.string.screen_flash_color_azure), + CYAN(0x4D00FFFF, R.string.screen_flash_color_cyan), + SPRING_GREEN(0x6600FF7F, R.string.screen_flash_color_spring_green), + GREEN(0x6600FF01, R.string.screen_flash_color_green), + CHARTREUSE_GREEN(0x6680FF00, R.string.screen_flash_color_chartreuse_green), + YELLOW(0x66FFFF00, R.string.screen_flash_color_yellow), + ORANGE(0x66FF7F00, R.string.screen_flash_color_orange), + RED(0x66FE0000, R.string.screen_flash_color_red), + ROSE(0x4DFF017E, R.string.screen_flash_color_rose), + MAGENTA(0x4DFF00FE, R.string.screen_flash_color_magenta), + VIOLET(0x667F00FF, R.string.screen_flash_color_violet); + + static final int ALPHA_MASK = 0xFF000000; + + final int mColorInt; + final int mOpaqueColorInt; + final int mStringRes; + + ScreenFlashNotificationColor(@ColorInt int colorInt, @StringRes int stringRes) { + this.mColorInt = colorInt; + this.mStringRes = stringRes; + this.mOpaqueColorInt = colorInt | ALPHA_MASK; + } +} diff --git a/src/com/android/settings/accessibility/ScreenFlashNotificationColorDialogFragment.java b/src/com/android/settings/accessibility/ScreenFlashNotificationColorDialogFragment.java new file mode 100644 index 00000000000..8a440b65c38 --- /dev/null +++ b/src/com/android/settings/accessibility/ScreenFlashNotificationColorDialogFragment.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.FlashNotificationsUtil.ACTION_FLASH_NOTIFICATION_START_PREVIEW; +import static com.android.settings.accessibility.FlashNotificationsUtil.ACTION_FLASH_NOTIFICATION_STOP_PREVIEW; +import static com.android.settings.accessibility.FlashNotificationsUtil.DEFAULT_SCREEN_FLASH_COLOR; +import static com.android.settings.accessibility.FlashNotificationsUtil.EXTRA_FLASH_NOTIFICATION_PREVIEW_COLOR; +import static com.android.settings.accessibility.FlashNotificationsUtil.EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE; +import static com.android.settings.accessibility.FlashNotificationsUtil.TYPE_LONG_PREVIEW; + +import android.app.Dialog; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; + +import com.android.settings.R; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.function.Consumer; + +/** + * DialogFragment for Screen flash notification color picker. + */ +public class ScreenFlashNotificationColorDialogFragment extends DialogFragment implements + ColorSelectorLayout.OnCheckedChangeListener { + + private static final int PREVIEW_LONG_TIME_MS = 5000; + private static final int BETWEEN_STOP_AND_START_DELAY_MS = 250; + private static final int MARGIN_FOR_STOP_DELAY_MS = 100; + + @ColorInt + private int mCurrentColor = Color.TRANSPARENT; + private Consumer mConsumer; + + private Timer mTimer = null; + private Boolean mIsPreview = false; + + static ScreenFlashNotificationColorDialogFragment getInstance(int initialColor, + Consumer colorConsumer) { + final ScreenFlashNotificationColorDialogFragment result = + new ScreenFlashNotificationColorDialogFragment(); + result.mCurrentColor = initialColor; + result.mConsumer = colorConsumer != null ? colorConsumer : i -> { + }; + return result; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + final View dialogView = getLayoutInflater().inflate(R.layout.layout_color_selector_dialog, + null); + + final ColorSelectorLayout colorSelectorLayout = dialogView.findViewById( + R.id.color_selector_preference); + if (colorSelectorLayout != null) { + colorSelectorLayout.setOnCheckedChangeListener(this); + colorSelectorLayout.setCheckedColor(mCurrentColor); + } + + final AlertDialog createdDialog = new AlertDialog.Builder(getContext()) + .setView(dialogView) + .setTitle(R.string.screen_flash_notification_color_title) + .setNeutralButton(R.string.flash_notifications_preview, null) + .setNegativeButton(R.string.color_selector_dialog_cancel, (dialog, which) -> { + }) + .setPositiveButton(R.string.color_selector_dialog_done, (dialog, which) -> { + mCurrentColor = colorSelectorLayout.getCheckedColor(DEFAULT_SCREEN_FLASH_COLOR); + mConsumer.accept(mCurrentColor); + }) + .create(); + createdDialog.setOnShowListener( + dialogInterface -> createdDialog.getButton(AlertDialog.BUTTON_NEUTRAL) + .setOnClickListener(v -> showColor())); + + return createdDialog; + } + + @Override + public void onPause() { + super.onPause(); + cancelPreviewTask(); + } + + @Override + public void onCheckedChanged(ColorSelectorLayout group) { + mCurrentColor = group.getCheckedColor(DEFAULT_SCREEN_FLASH_COLOR); + if (mIsPreview) { + showColor(); + } + } + + private void showColor() { + int startDelay = 0; + + synchronized (this) { + if (mTimer != null) mTimer.cancel(); + + mTimer = new Timer(); + if (mIsPreview) { + mTimer.schedule(getStopTask(), 0); + startDelay = BETWEEN_STOP_AND_START_DELAY_MS; + } + mTimer.schedule(getStartTask(), startDelay); + mTimer.schedule(getStopTask(), + startDelay + PREVIEW_LONG_TIME_MS + MARGIN_FOR_STOP_DELAY_MS); + } + } + + private TimerTask getStartTask() { + return new TimerTask() { + @Override + public void run() { + synchronized (this) { + startPreviewLocked(); + } + } + }; + } + + private TimerTask getStopTask() { + return new TimerTask() { + @Override + public void run() { + synchronized (this) { + stopPreviewLocked(); + } + } + }; + } + + private void cancelPreviewTask() { + synchronized (this) { + if (mTimer != null) mTimer.cancel(); + stopPreviewLocked(); + } + } + + private void startPreviewLocked() { + if (getContext() == null) return; + + mIsPreview = true; + Intent intent = new Intent(ACTION_FLASH_NOTIFICATION_START_PREVIEW); + intent.putExtra(EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE, TYPE_LONG_PREVIEW); + intent.putExtra(EXTRA_FLASH_NOTIFICATION_PREVIEW_COLOR, mCurrentColor); + getContext().sendBroadcast(intent); + } + + private void stopPreviewLocked() { + if (getContext() == null) return; + + Intent stopIntent = new Intent(ACTION_FLASH_NOTIFICATION_STOP_PREVIEW); + getContext().sendBroadcast(stopIntent); + mIsPreview = false; + } +} diff --git a/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceController.java b/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceController.java new file mode 100644 index 00000000000..546ae19eb88 --- /dev/null +++ b/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceController.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.AccessibilityUtil.State.OFF; +import static com.android.settings.accessibility.AccessibilityUtil.State.ON; +import static com.android.settings.accessibility.FlashNotificationsUtil.DEFAULT_SCREEN_FLASH_COLOR; +import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; +import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR; + +import android.content.Context; +import android.graphics.Color; +import android.provider.Settings; + +import androidx.fragment.app.Fragment; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.TogglePreferenceController; + +import java.util.function.Consumer; + +/** + * Controller for Screen flash notification. + */ +public class ScreenFlashNotificationPreferenceController extends TogglePreferenceController { + + private Fragment mParentFragment; + private Preference mPreference; + + public ScreenFlashNotificationPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + public void setParentFragment(Fragment parentFragment) { + this.mParentFragment = parentFragment; + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean isChecked() { + return Settings.System.getInt(mContext.getContentResolver(), + SETTING_KEY_SCREEN_FLASH_NOTIFICATION, OFF) != OFF; + } + + @Override + public boolean setChecked(boolean isChecked) { + if (isChecked) checkAndSetInitialColor(); + + return Settings.System.putInt(mContext.getContentResolver(), + SETTING_KEY_SCREEN_FLASH_NOTIFICATION, (isChecked ? ON : OFF)); + } + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_accessibility; + } + + @Override + public CharSequence getSummary() { + return FlashNotificationsUtil.getColorDescriptionText(mContext, + Settings.System.getInt(mContext.getContentResolver(), + SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, DEFAULT_SCREEN_FLASH_COLOR)); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + refreshColorSummary(); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (getPreferenceKey().equals(preference.getKey()) && mParentFragment != null) { + + final int initialColor = Settings.System.getInt(mContext.getContentResolver(), + SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, + DEFAULT_SCREEN_FLASH_COLOR); + + final Consumer consumer = color -> { + Settings.System.putInt(mContext.getContentResolver(), + SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, color); + refreshColorSummary(); + }; + + ScreenFlashNotificationColorDialogFragment + .getInstance(initialColor, consumer) + .show(mParentFragment.getParentFragmentManager(), + ScreenFlashNotificationColorDialogFragment.class.getSimpleName()); + return true; + } + + return super.handlePreferenceTreeClick(preference); + } + + private void checkAndSetInitialColor() { + if (Settings.System.getInt(mContext.getContentResolver(), + SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, Color.TRANSPARENT) + == Color.TRANSPARENT) { + Settings.System.putInt(mContext.getContentResolver(), + SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, DEFAULT_SCREEN_FLASH_COLOR); + } + } + + private void refreshColorSummary() { + if (mPreference != null) mPreference.setSummary(getSummary()); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceControllerTest.java new file mode 100644 index 00000000000..db4e5210315 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/CameraFlashNotificationPreferenceControllerTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowFlashNotificationsUtils.class) +public class CameraFlashNotificationPreferenceControllerTest { + private static final String PREFERENCE_KEY = "preference_key"; + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + + private CameraFlashNotificationPreferenceController mController; + private ContentResolver mContentResolver; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContentResolver = mContext.getContentResolver(); + mController = new CameraFlashNotificationPreferenceController(mContext, PREFERENCE_KEY); + } + + @After + public void tearDown() { + ShadowFlashNotificationsUtils.reset(); + } + + @Test + public void getAvailabilityStatus_torchAvailable_assertAvailable() { + ShadowFlashNotificationsUtils.setIsTorchAvailable(true); + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getAvailabilityStatus_torchUnavailable_assertUnsupportedOnDevice() { + ShadowFlashNotificationsUtils.setIsTorchAvailable(false); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + @Test + public void isChecked_setOff_assertFalse() { + Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 0); + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void isChecked_setOn_assertTrue() { + Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 1); + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void setChecked_setTrue_assertNotZero() { + mController.setChecked(true); + assertThat(Settings.System.getInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, + 0)).isNotEqualTo(0); + } + + @Test + public void setChecked_setFalse_assertNotOne() { + mController.setChecked(false); + assertThat(Settings.System.getInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, + 1)).isNotEqualTo(1); + } + + @Test + public void getSliceHighlightMenuRes() { + mController.getSliceHighlightMenuRes(); + assertThat(mController.getSliceHighlightMenuRes()) + .isEqualTo(R.string.menu_key_accessibility); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/ColorSelectorLayoutTest.java b/tests/robotests/src/com/android/settings/accessibility/ColorSelectorLayoutTest.java new file mode 100644 index 00000000000..8ce53e0ca6d --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/ColorSelectorLayoutTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.graphics.Color; + +import androidx.annotation.ColorInt; +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class ColorSelectorLayoutTest { + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + + ColorSelectorLayout mColorSelectorLayout; + + @ColorInt + int mCheckedColor = Color.TRANSPARENT; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mColorSelectorLayout = new ColorSelectorLayout(mContext); + mColorSelectorLayout.setOnCheckedChangeListener( + layout -> mCheckedColor = layout.getCheckedColor(Color.TRANSPARENT)); + } + + @Test + public void setColor_checkColorChanged() { + mColorSelectorLayout.setCheckedColor(ScreenFlashNotificationColor.AZURE.mColorInt); + assertThat(mCheckedColor) + .isEqualTo(ScreenFlashNotificationColor.AZURE.mColorInt); + } + + @Test + public void getCheckedColor_defaultValue() { + assertThat(mColorSelectorLayout.getCheckedColor(0xFF000000)) + .isEqualTo(0xFF000000); + } + + @Test + public void setSelectedColor_checkColorChanged() { + mColorSelectorLayout.setCheckedColor(ScreenFlashNotificationColor.AZURE.mColorInt); + assertThat(mColorSelectorLayout.getCheckedColor(0xFF000000)) + .isEqualTo(ScreenFlashNotificationColor.AZURE.mColorInt); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreferenceControllerTest.java new file mode 100644 index 00000000000..d4251ac801e --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreferenceControllerTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.ShadowFlashNotificationsUtils.setFlashNotificationsState; +import static com.android.settings.core.BasePreferenceController.AVAILABLE; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowFlashNotificationsUtils.class) +public class FlashNotificationsPreferenceControllerTest { + private static final String PREFERENCE_KEY = "preference_key"; + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + + private FlashNotificationsPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mController = new FlashNotificationsPreferenceController(mContext, PREFERENCE_KEY); + } + + @After + public void tearDown() { + ShadowFlashNotificationsUtils.reset(); + } + + @Test + public void getAvailabilityStatus() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void getSummary_stateOff_assertOff() { + setFlashNotificationsState(FlashNotificationsUtil.State.OFF); + + assertThat(mController.getSummary().toString()).isEqualTo(mContext.getString( + R.string.flash_notifications_summary_off)); + } + + @Test + public void getSummary_stateCamera_assertCamera() { + setFlashNotificationsState(FlashNotificationsUtil.State.CAMERA); + + assertThat(mController.getSummary().toString()).isEqualTo(mContext.getString( + R.string.flash_notifications_summary_on_camera)); + } + + @Test + public void getSummary_stateScreen_assertScreen() { + setFlashNotificationsState(FlashNotificationsUtil.State.SCREEN); + + assertThat(mController.getSummary().toString()).isEqualTo(mContext.getString( + R.string.flash_notifications_summary_on_screen)); + } + + @Test + public void getSummary_stateCameraScreen_assertCameraScreen() { + setFlashNotificationsState(FlashNotificationsUtil.State.CAMERA_SCREEN); + + assertThat(mController.getSummary().toString()).isEqualTo(mContext.getString( + R.string.flash_notifications_summary_on_camera_and_screen)); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreferenceFragmentTest.java new file mode 100644 index 00000000000..a063a9f45c2 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreferenceFragmentTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; +import com.android.settingslib.core.AbstractPreferenceController; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class FlashNotificationsPreferenceFragmentTest { + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + private FlashNotificationsPreferenceFragment mFragment; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mFragment = new FlashNotificationsPreferenceFragment(); + } + + @Test + public void getPreferenceScreenResId_isFlashNotificationsSettings() { + assertThat(mFragment.getPreferenceScreenResId()) + .isEqualTo(R.xml.flash_notifications_settings); + } + + @Test + public void getLogTag_isSpecifyTag() { + assertThat(mFragment.getLogTag()).isEqualTo("FlashNotificationsPreferenceFragment"); + } + + @Test + public void getMetricsCategory_isZero() { + assertThat(mFragment.getMetricsCategory()).isEqualTo(0); // TODO + } + + @Test + public void onAttach_attachedTheTestFragment() { + ScreenFlashNotificationPreferenceController controller = mock( + ScreenFlashNotificationPreferenceController.class); + TestFlashNotificationsPreferenceFragment testFragment = + new TestFlashNotificationsPreferenceFragment(controller); + testFragment.onAttach(mContext); + verify(controller).setParentFragment(eq(testFragment)); + } + + @Test + public void getHelpResource_isExpectedResource() { + assertThat(mFragment.getHelpResource()) + .isEqualTo(R.string.help_url_flash_notifications); + } + + static class TestFlashNotificationsPreferenceFragment extends + FlashNotificationsPreferenceFragment { + final AbstractPreferenceController mController; + + TestFlashNotificationsPreferenceFragment(AbstractPreferenceController controller) { + mController = controller; + } + + @Override + protected T use(Class clazz) { + return (T) mController; + } + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceControllerTest.java new file mode 100644 index 00000000000..5f1dfbb1e63 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceControllerTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.FlashNotificationsUtil.ACTION_FLASH_NOTIFICATION_START_PREVIEW; +import static com.android.settings.accessibility.FlashNotificationsUtil.EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE; +import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; +import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; +import static com.android.settings.accessibility.FlashNotificationsUtil.TYPE_LONG_PREVIEW; +import static com.android.settings.accessibility.FlashNotificationsUtil.TYPE_SHORT_PREVIEW; +import static com.android.settings.accessibility.ShadowFlashNotificationsUtils.setFlashNotificationsState; +import static com.android.settings.core.BasePreferenceController.AVAILABLE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; + +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowFlashNotificationsUtils.class) +public class FlashNotificationsPreviewPreferenceControllerTest { + private static final String PREFERENCE_KEY = "preference_key"; + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private Preference mPreference; + @Spy + private ContentResolver mContentResolver = mContext.getContentResolver(); + + private FlashNotificationsPreviewPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mPreferenceScreen.findPreference(PREFERENCE_KEY)).thenReturn(mPreference); + when(mPreference.getKey()).thenReturn(PREFERENCE_KEY); + + when(mContext.getContentResolver()).thenReturn(mContentResolver); + mController = new FlashNotificationsPreviewPreferenceController(mContext, PREFERENCE_KEY); + } + + @After + public void tearDown() { + ShadowFlashNotificationsUtils.reset(); + } + + @Test + public void testGetAvailabilityStatus() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void testDisplayPreference_torchPresent_cameraOff_screenOff_verifyDisabled() { + setFlashNotificationsState(FlashNotificationsUtil.State.OFF); + + mController.displayPreference(mPreferenceScreen); + verify(mPreference).setEnabled(eq(false)); + } + + @Test + public void testDisplayPreference_torchPresent_cameraOn_screenOff_verifyEnabled() { + setFlashNotificationsState(FlashNotificationsUtil.State.CAMERA); + + mController.displayPreference(mPreferenceScreen); + verify(mPreference).setEnabled(eq(true)); + } + + @Test + public void testDisplayPreference_torchPresent_cameraOff_screenOn_verifyEnabled() { + setFlashNotificationsState(FlashNotificationsUtil.State.SCREEN); + + mController.displayPreference(mPreferenceScreen); + verify(mPreference).setEnabled(eq(true)); + } + + @Test + public void testDisplayPreference_torchPresent_cameraOn_screenOn_verifyEnabled() { + setFlashNotificationsState(FlashNotificationsUtil.State.CAMERA_SCREEN); + + mController.displayPreference(mPreferenceScreen); + verify(mPreference).setEnabled(eq(true)); + } + + @Ignore + @Test + public void testHandlePreferenceTreeClick_invalidPreference() { + mController.handlePreferenceTreeClick(mock(Preference.class)); + verify(mContext, never()).sendBroadcast(any()); + } + + @Test + public void handlePreferenceTreeClick_assertAction() { + mController.handlePreferenceTreeClick(mPreference); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext).sendBroadcast(captor.capture()); + Intent captured = captor.getValue(); + + assertThat(captured.getAction()).isEqualTo(ACTION_FLASH_NOTIFICATION_START_PREVIEW); + } + + @Test + public void handlePreferenceTreeClick_assertExtra() { + mController.handlePreferenceTreeClick(mPreference); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + verify(mContext).sendBroadcast(captor.capture()); + Intent captured = captor.getValue(); + + assertThat(captured.getIntExtra(EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE, TYPE_LONG_PREVIEW)) + .isEqualTo(TYPE_SHORT_PREVIEW); + } + + @Test + public void onStateChanged_onResume_cameraUri_verifyRegister() { + mController.onStateChanged(mock(LifecycleOwner.class), Lifecycle.Event.ON_RESUME); + verify(mContentResolver).registerContentObserver( + eq(Settings.System.getUriFor(SETTING_KEY_CAMERA_FLASH_NOTIFICATION)), anyBoolean(), + eq(mController.mContentObserver)); + } + + @Test + public void onStateChanged_onResume_screenUri_verifyRegister() { + mController.onStateChanged(mock(LifecycleOwner.class), Lifecycle.Event.ON_RESUME); + verify(mContentResolver).registerContentObserver( + eq(Settings.System.getUriFor(SETTING_KEY_SCREEN_FLASH_NOTIFICATION)), anyBoolean(), + eq(mController.mContentObserver)); + } + + @Test + public void onStateChanged_onPause_verifyUnregister() { + mController.onStateChanged(mock(LifecycleOwner.class), Lifecycle.Event.ON_PAUSE); + verify(mContentResolver).unregisterContentObserver(eq(mController.mContentObserver)); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceTest.java b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceTest.java new file mode 100644 index 00000000000..ab38c1a734a --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsPreviewPreferenceTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class FlashNotificationsPreviewPreferenceTest { + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Spy + private final Context mContext = ApplicationProvider.getApplicationContext(); + private final AttributeSet mAttributeSet = Robolectric.buildAttributeSet().build(); + + @Test + public void constructor_assertLayoutResource_P00() { + FlashNotificationsPreviewPreference preference = new FlashNotificationsPreviewPreference( + mContext); + assertThat(preference.getLayoutResource()) + .isEqualTo(R.layout.flash_notification_preview_preference); + } + + @Test + public void constructor_assertLayoutResource_P01() { + FlashNotificationsPreviewPreference preference = new FlashNotificationsPreviewPreference( + mContext, mAttributeSet); + assertThat(preference.getLayoutResource()) + .isEqualTo(R.layout.flash_notification_preview_preference); + } + + @Test + public void constructor_assertLayoutResource_P02() { + FlashNotificationsPreviewPreference preference = new FlashNotificationsPreviewPreference( + mContext, mAttributeSet, 0); + assertThat(preference.getLayoutResource()) + .isEqualTo(R.layout.flash_notification_preview_preference); + } + + @Test + public void constructor_assertLayoutResource_P03() { + FlashNotificationsPreviewPreference preference = new FlashNotificationsPreviewPreference( + mContext, mAttributeSet, 0, 0); + assertThat(preference.getLayoutResource()) + .isEqualTo(R.layout.flash_notification_preview_preference); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsUtilTest.java b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsUtilTest.java new file mode 100644 index 00000000000..65f1c44b7dd --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/FlashNotificationsUtilTest.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static android.hardware.camera2.CameraCharacteristics.FLASH_INFO_AVAILABLE; +import static android.hardware.camera2.CameraCharacteristics.LENS_FACING; +import static android.hardware.camera2.CameraCharacteristics.LENS_FACING_BACK; +import static android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT; + +import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_CAMERA_FLASH_NOTIFICATION; +import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; +import static com.android.settings.accessibility.FlashNotificationsUtil.getColorDescriptionText; +import static com.android.settings.accessibility.FlashNotificationsUtil.getFlashNotificationsState; +import static com.android.settings.accessibility.FlashNotificationsUtil.getScreenColor; +import static com.android.settings.accessibility.FlashNotificationsUtil.isTorchAvailable; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.when; +import static org.robolectric.shadow.api.Shadow.extract; +import static org.robolectric.shadows.ShadowCameraCharacteristics.newCameraCharacteristics; + +import android.content.ContentResolver; +import android.content.Context; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.provider.Settings; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowCameraCharacteristics; +import org.robolectric.shadows.ShadowCameraManager; + +@RunWith(RobolectricTestRunner.class) +public class FlashNotificationsUtilTest { + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Spy + private Context mContext = ApplicationProvider.getApplicationContext(); + @Spy + private CameraManager mCameraManager = mContext.getSystemService(CameraManager.class); + + private ShadowCameraManager mShadowCameraManager; + private ContentResolver mContentResolver; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mShadowCameraManager = Shadows.shadowOf(mCameraManager); + mContentResolver = mContext.getContentResolver(); + } + + @Test + public void isTorchAvailable_nullCameraManager_assertFalse() { + when(mContext.getSystemService(CameraManager.class)).thenReturn(null); + assertThat(isTorchAvailable(mContext)).isFalse(); + } + + @Test + public void isTorchAvailable_noCamera_assertFalse() { + assertThat(isTorchAvailable(mContext)).isFalse(); + } + + @Test + public void isTorchAvailable_getCameraIdListThrowException_assertFalse() + throws CameraAccessException { + when(mCameraManager.getCameraIdList()).thenThrow(CameraAccessException.class); + assertThat(isTorchAvailable(mContext)).isFalse(); + } + + @Test + public void isTorchAvailable_getCameraCharacteristicsThrowException_assertFalse() + throws CameraAccessException { + CameraCharacteristics cameraCharacteristics = newCameraCharacteristics(); + mShadowCameraManager.addCamera("0", cameraCharacteristics); + + when(mCameraManager.getCameraCharacteristics("0")).thenThrow(CameraAccessException.class); + + assertThat(isTorchAvailable(mContext)).isFalse(); + } + + @Test + public void isTorchAvailable_torchNotPresent_assertFalse() { + setTorchNotPresent(); + + assertThat(isTorchAvailable(mContext)).isFalse(); + } + + @Test + public void isTorchAvailable_torchPresent_assertTrue() { + setTorchPresent(); + + assertThat(isTorchAvailable(mContext)).isTrue(); + } + + @Test + public void isTorchAvailable_lensFacingFront_assertFalse() { + CameraCharacteristics cameraCharacteristics = newCameraCharacteristics(); + ShadowCameraCharacteristics shadowCameraCharacteristics = extract(cameraCharacteristics); + shadowCameraCharacteristics.set(FLASH_INFO_AVAILABLE, true); + shadowCameraCharacteristics.set(LENS_FACING, LENS_FACING_FRONT); + mShadowCameraManager.addCamera("0", cameraCharacteristics); + + assertThat(isTorchAvailable(mContext)).isFalse(); + } + + @Test + public void getScreenColor_undefinedColor_throwException() { + assertThrows(FlashNotificationsUtil.ScreenColorNotFoundException.class, () -> + getScreenColor(0x4D0000FF)); + } + + @Test + public void getScreenColor_azureColor_returnAzure() throws Exception { + assertThat(getScreenColor(0x4D0000FF)).isEqualTo(ScreenFlashNotificationColor.AZURE); + } + + @Test + public void getColorDescriptionText_undefinedColor_returnEmpty() { + assertThat(getColorDescriptionText(mContext, 0x4D0000FF)).isEqualTo(""); + } + + @Test + public void getColorDescriptionText_azureColor_returnAzureName() { + assertThat(getColorDescriptionText(mContext, ScreenFlashNotificationColor.AZURE.mColorInt)) + .isEqualTo(mContext.getString(ScreenFlashNotificationColor.AZURE.mStringRes)); + } + + @Test + public void getFlashNotificationsState_torchPresent_cameraOff_screenOff_assertOff() { + setTorchPresent(); + Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 0); + Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0); + + assertThat(getFlashNotificationsState(mContext)) + .isEqualTo(FlashNotificationsUtil.State.OFF); + } + + @Test + public void getFlashNotificationsState_torchNotPresent_cameraOn_screenOff_assertOff() { + setTorchNotPresent(); + Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 1); + Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0); + + assertThat(getFlashNotificationsState(mContext)) + .isEqualTo(FlashNotificationsUtil.State.OFF); + } + + @Test + public void getFlashNotificationsState_torchPresent_cameraOn_screenOff_assertCamera() { + setTorchPresent(); + Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 1); + Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0); + + assertThat(getFlashNotificationsState(mContext)) + .isEqualTo(FlashNotificationsUtil.State.CAMERA); + } + + @Test + public void getFlashNotificationsState_torchPresent_cameraOff_screenOn_assertScreen() { + setTorchPresent(); + Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 0); + Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 1); + + assertThat(getFlashNotificationsState(mContext)) + .isEqualTo(FlashNotificationsUtil.State.SCREEN); + } + + @Test + public void testGetFlashNotificationsState_torchPresent_cameraOn_screenOn_assertCameraScreen() { + setTorchPresent(); + Settings.System.putInt(mContentResolver, SETTING_KEY_CAMERA_FLASH_NOTIFICATION, 1); + Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 1); + + assertThat(getFlashNotificationsState(mContext)) + .isEqualTo(FlashNotificationsUtil.State.CAMERA_SCREEN); + } + + private void setTorchPresent() { + CameraCharacteristics cameraCharacteristics = newCameraCharacteristics(); + ShadowCameraCharacteristics shadowCameraCharacteristics = extract(cameraCharacteristics); + shadowCameraCharacteristics.set(FLASH_INFO_AVAILABLE, true); + shadowCameraCharacteristics.set(LENS_FACING, LENS_FACING_BACK); + mShadowCameraManager.addCamera("0", cameraCharacteristics); + } + + private void setTorchNotPresent() { + CameraCharacteristics cameraCharacteristics = newCameraCharacteristics(); + ShadowCameraCharacteristics shadowCameraCharacteristics = extract(cameraCharacteristics); + shadowCameraCharacteristics.set(FLASH_INFO_AVAILABLE, false); + mShadowCameraManager.addCamera("0", cameraCharacteristics); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationColorDialogFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationColorDialogFragmentTest.java new file mode 100644 index 00000000000..dab13a0e886 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationColorDialogFragmentTest.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static android.content.DialogInterface.BUTTON_NEGATIVE; +import static android.content.DialogInterface.BUTTON_NEUTRAL; +import static android.content.DialogInterface.BUTTON_POSITIVE; + +import static com.android.settings.accessibility.FlashNotificationsUtil.ACTION_FLASH_NOTIFICATION_START_PREVIEW; +import static com.android.settings.accessibility.FlashNotificationsUtil.ACTION_FLASH_NOTIFICATION_STOP_PREVIEW; +import static com.android.settings.accessibility.FlashNotificationsUtil.EXTRA_FLASH_NOTIFICATION_PREVIEW_COLOR; +import static com.android.settings.accessibility.FlashNotificationsUtil.EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE; +import static com.android.settings.accessibility.FlashNotificationsUtil.TYPE_LONG_PREVIEW; +import static com.android.settings.accessibility.FlashNotificationsUtil.TYPE_SHORT_PREVIEW; +import static com.android.settings.accessibility.ScreenFlashNotificationColor.AZURE; +import static com.android.settings.accessibility.ScreenFlashNotificationColor.BLUE; +import static com.android.settings.accessibility.ScreenFlashNotificationColor.CYAN; +import static com.android.settings.accessibility.ScreenFlashNotificationColor.ROSE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.robolectric.Shadows.shadowOf; + +import android.content.Intent; +import android.graphics.Color; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowContextWrapper; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class ScreenFlashNotificationColorDialogFragmentTest { + + private ShadowContextWrapper mShadowContextWrapper; + private ScreenFlashNotificationColorDialogFragment mDialogFragment; + private AlertDialog mAlertDialog; + private ColorSelectorLayout mColorSelectorLayout; + private int mCurrentColor; + + @Before + public void setUp() { + FragmentActivity fragmentActivity = Robolectric.setupActivity(FragmentActivity.class); + mShadowContextWrapper = shadowOf(fragmentActivity); + + mCurrentColor = ROSE.mColorInt; + mDialogFragment = ScreenFlashNotificationColorDialogFragment.getInstance( + mCurrentColor, selectedColor -> mCurrentColor = selectedColor + ); + mDialogFragment.show(fragmentActivity.getSupportFragmentManager(), "test"); + + mAlertDialog = (AlertDialog) mDialogFragment.getDialog(); + if (mAlertDialog != null) { + mColorSelectorLayout = mAlertDialog.findViewById(R.id.color_selector_preference); + } + } + + @Test + public void test_assertShow() { + assertThat(mAlertDialog.isShowing()).isTrue(); + } + + @Test + public void clickNeutral_assertShow() { + performClickOnDialog(BUTTON_NEUTRAL); + assertThat(mAlertDialog.isShowing()).isTrue(); + } + + @Test + public void clickNeutral_assertStartPreview() throws InterruptedException { + performClickOnDialog(BUTTON_NEUTRAL); + Thread.sleep(100); + + Intent captured = getLastCapturedIntent(); + assertThat(captured.getAction()).isEqualTo(ACTION_FLASH_NOTIFICATION_START_PREVIEW); + assertThat(captured.getIntExtra(EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE, TYPE_SHORT_PREVIEW)) + .isEqualTo(TYPE_LONG_PREVIEW); + assertThat(captured.getIntExtra(EXTRA_FLASH_NOTIFICATION_PREVIEW_COLOR, Color.TRANSPARENT)) + .isEqualTo(ROSE.mColorInt); + } + + @Test + public void clickNegative_assertNotShow() { + performClickOnDialog(BUTTON_NEGATIVE); + assertThat(mAlertDialog.isShowing()).isFalse(); + } + + @Test + public void clickPositive_assertNotShow() { + performClickOnDialog(BUTTON_POSITIVE); + assertThat(mAlertDialog.isShowing()).isFalse(); + } + + @Test + public void clickNeutralAndPause_assertStopPreview() throws InterruptedException { + performClickOnDialog(BUTTON_NEUTRAL); + Thread.sleep(100); + mDialogFragment.onPause(); + Thread.sleep(100); + + assertThat(getLastCapturedIntent().getAction()) + .isEqualTo(ACTION_FLASH_NOTIFICATION_STOP_PREVIEW); + } + + @Test + public void clickNeutralAndClickNegative_assertStopPreview() throws InterruptedException { + performClickOnDialog(BUTTON_NEUTRAL); + Thread.sleep(100); + performClickOnDialog(BUTTON_NEGATIVE); + Thread.sleep(100); + + assertThat(getLastCapturedIntent().getAction()) + .isEqualTo(ACTION_FLASH_NOTIFICATION_STOP_PREVIEW); + } + + @Test + public void clickNeutralAndClickPositive_assertStopPreview() throws InterruptedException { + performClickOnDialog(BUTTON_NEUTRAL); + Thread.sleep(100); + performClickOnDialog(BUTTON_POSITIVE); + Thread.sleep(100); + + assertThat(getLastCapturedIntent().getAction()) + .isEqualTo(ACTION_FLASH_NOTIFICATION_STOP_PREVIEW); + } + + @Test + public void clickNeutralAndClickColor_assertStartPreview() throws InterruptedException { + performClickOnDialog(BUTTON_NEUTRAL); + Thread.sleep(100); + checkColorButton(CYAN); + Thread.sleep(500); + + Intent captured = getLastCapturedIntent(); + assertThat(captured.getAction()).isEqualTo(ACTION_FLASH_NOTIFICATION_START_PREVIEW); + assertThat(captured.getIntExtra(EXTRA_FLASH_NOTIFICATION_PREVIEW_TYPE, TYPE_SHORT_PREVIEW)) + .isEqualTo(TYPE_LONG_PREVIEW); + assertThat(captured.getIntExtra(EXTRA_FLASH_NOTIFICATION_PREVIEW_COLOR, Color.TRANSPARENT)) + .isEqualTo(CYAN.mColorInt); + } + + @Test + public void clickColorAndClickNegative_assertColor() { + checkColorButton(AZURE); + performClickOnDialog(BUTTON_NEGATIVE); + + assertThat(mCurrentColor).isEqualTo(ROSE.mColorInt); + } + + @Test + public void clickColorAndClickPositive_assertColor() { + checkColorButton(BLUE); + performClickOnDialog(BUTTON_POSITIVE); + + assertThat(mCurrentColor).isEqualTo(BLUE.mColorInt); + } + + private void checkColorButton(ScreenFlashNotificationColor color) { + mColorSelectorLayout.setCheckedColor(color.mColorInt); + } + + private void performClickOnDialog(int whichButton) { + mAlertDialog.getButton(whichButton).performClick(); + } + + private Intent getLastCapturedIntent() { + final List capturedIntents = new ArrayList<>( + mShadowContextWrapper.getBroadcastIntents()); + final int size = capturedIntents.size(); + return capturedIntents.get(size - 1); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationColorTest.java b/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationColorTest.java new file mode 100644 index 00000000000..51e53f37b54 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationColorTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.ParameterizedRobolectricTestRunner; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(ParameterizedRobolectricTestRunner.class) +public class ScreenFlashNotificationColorTest { + + private static final int OPAQUE_COLOR_MASK = 0xFF000000; + + @ParameterizedRobolectricTestRunner.Parameters(name = "Color: {0}") + public static List params() { + final List list = new ArrayList<>(); + for (ScreenFlashNotificationColor color : ScreenFlashNotificationColor.values()) { + list.add(new Object[]{color}); + } + return list; + } + + final ScreenFlashNotificationColor mColor; + + public ScreenFlashNotificationColorTest(ScreenFlashNotificationColor color) { + mColor = color; + } + + @Test + public void colorInt_assertNotTranslucent() { + assertThat(mColor.mColorInt & OPAQUE_COLOR_MASK).isNotEqualTo(0); + } + + @Test + public void opaqueColorMask() { + assertThat(mColor.mOpaqueColorInt & OPAQUE_COLOR_MASK).isEqualTo(OPAQUE_COLOR_MASK); + } + + @Test + public void stringRes_assertValid() { + assertThat(mColor.mStringRes).isNotEqualTo(0); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceControllerTest.java new file mode 100644 index 00000000000..0662daa6fbb --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/ScreenFlashNotificationPreferenceControllerTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.FlashNotificationsUtil.DEFAULT_SCREEN_FLASH_COLOR; +import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION; +import static com.android.settings.accessibility.FlashNotificationsUtil.SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR; +import static com.android.settings.core.BasePreferenceController.AVAILABLE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.graphics.Color; +import android.provider.Settings; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; + +import java.util.function.Consumer; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = { + ScreenFlashNotificationPreferenceControllerTest + .ShadowScreenFlashNotificationColorDialogFragment.class, + ShadowFlashNotificationsUtils.class, +}) +public class ScreenFlashNotificationPreferenceControllerTest { + private static final String PREFERENCE_KEY = "preference_key"; + private static final String COLOR_DESCRIPTION_TEXT = "Colorful"; + + @Rule + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private Preference mPreference; + @Mock + private Fragment mParentFragment; + @Mock + private FragmentManager mFragmentManager; + @Mock + private ScreenFlashNotificationColorDialogFragment mDialogFragment; + + private ScreenFlashNotificationPreferenceController mController; + private ContentResolver mContentResolver; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + FragmentActivity fragmentActivity = Robolectric.setupActivity(FragmentActivity.class); + Context context = fragmentActivity.getApplicationContext(); + ShadowScreenFlashNotificationColorDialogFragment.setInstance(mDialogFragment); + ShadowFlashNotificationsUtils.setColorDescriptionText(COLOR_DESCRIPTION_TEXT); + + mContentResolver = context.getContentResolver(); + mController = new ScreenFlashNotificationPreferenceController(context, PREFERENCE_KEY); + when(mPreferenceScreen.findPreference(PREFERENCE_KEY)).thenReturn(mPreference); + when(mPreference.getKey()).thenReturn(PREFERENCE_KEY); + mController.setParentFragment(mParentFragment); + when(mParentFragment.getParentFragmentManager()).thenReturn(mFragmentManager); + } + + @After + public void tearDown() { + ShadowScreenFlashNotificationColorDialogFragment.reset(); + } + + @Test + public void getAvailabilityStatus() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void isChecked_setOff_assertFalse() { + Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 0); + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void isChecked_setOn_assertTrue() { + Settings.System.putInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, 1); + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void setChecked_whenTransparentColor_setTrue_assertNotTransparentColor() { + Settings.System.putInt(mContentResolver, + SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, Color.TRANSPARENT); + mController.setChecked(true); + assertThat(Settings.System.getInt(mContentResolver, + SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, 0)) + .isEqualTo(DEFAULT_SCREEN_FLASH_COLOR); + } + + @Test + public void setChecked_whenNotTransparent_setTrue_assertSameColor() { + Settings.System.putInt(mContentResolver, + SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, 0x4D0000FF); + mController.setChecked(true); + assertThat(Settings.System.getInt(mContentResolver, + SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR, 0)) + .isEqualTo(0x4D0000FF); + } + + @Test + public void setChecked_setTrue_assertOn() { + mController.setChecked(true); + assertThat(Settings.System.getInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, + 0)).isEqualTo(1); + } + + @Test + public void setChecked_setFalse_assertOff() { + mController.setChecked(false); + assertThat(Settings.System.getInt(mContentResolver, SETTING_KEY_SCREEN_FLASH_NOTIFICATION, + 1)).isEqualTo(0); + } + + @Test + public void getSliceHighlightMenuRes() { + assertThat(mController.getSliceHighlightMenuRes()) + .isEqualTo(R.string.menu_key_accessibility); + } + + @Test + public void getSummary() { + assertThat(mController.getSummary()).isEqualTo(COLOR_DESCRIPTION_TEXT); + } + + @Test + public void displayPreference() { + mController.displayPreference(mPreferenceScreen); + verify(mPreference).setSummary(COLOR_DESCRIPTION_TEXT); + } + + @Test + public void handlePreferenceTreeClick() { + mController.handlePreferenceTreeClick(mPreference); + verify(mDialogFragment).show(any(FragmentManager.class), anyString()); + } + + /** + * Note: Actually, shadow of ScreenFlashNotificationColorDialogFragment will not be used. + * Instance that returned with {@link #getInstance} should be set with {@link #setInstance} + */ + @Implements(ScreenFlashNotificationColorDialogFragment.class) + public static class ShadowScreenFlashNotificationColorDialogFragment { + static ScreenFlashNotificationColorDialogFragment sInstance = null; + + @Implementation + protected static ScreenFlashNotificationColorDialogFragment getInstance( + int initialColor, Consumer colorConsumer) { + return sInstance; + } + + public static void setInstance(ScreenFlashNotificationColorDialogFragment instance) { + sInstance = instance; + } + + @Resetter + public static void reset() { + sInstance = null; + } + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/ShadowFlashNotificationsUtils.java b/tests/robotests/src/com/android/settings/accessibility/ShadowFlashNotificationsUtils.java new file mode 100644 index 00000000000..41f03ad2201 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/ShadowFlashNotificationsUtils.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.FlashNotificationsUtil.State; + +import android.content.Context; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; + +@Implements(FlashNotificationsUtil.class) +public class ShadowFlashNotificationsUtils { + + private static boolean sIsTorchAvailable; + private static int sState; + private static String sColorDescriptionText = ""; + + public static void setIsTorchAvailable(boolean isTorchAvailable) { + sIsTorchAvailable = isTorchAvailable; + } + + @Implementation + protected static boolean isTorchAvailable(Context context) { + return sIsTorchAvailable; + } + + public static void setFlashNotificationsState(@State int state) { + sState = state; + } + + @State + @Implementation + protected static int getFlashNotificationsState(Context context) { + return sState; + } + + public static void setColorDescriptionText(@NonNull String text) { + sColorDescriptionText = text; + } + + @Implementation + @NonNull + protected static String getColorDescriptionText(@NonNull Context context, @ColorInt int color) { + return sColorDescriptionText; + } + + @Resetter + public static void reset() { + sIsTorchAvailable = false; + sState = 0; + sColorDescriptionText = ""; + } +}