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