diff --git a/Android.bp b/Android.bp index cb898bef834..b9de8b9e03f 100644 --- a/Android.bp +++ b/Android.bp @@ -64,6 +64,8 @@ android_library { "res", "res-export", // for external usage "res-product", + "res-derp", + "TouchGestures/res", ], static_libs: [ // External dependencies @@ -74,6 +76,7 @@ android_library { // Android internal dependencies "BiometricsSharedLib", + "SystemUISharedLib", "SystemUIUnfoldLib", "WifiTrackerLib", "android.hardware.dumpstate-V1-java", @@ -110,6 +113,9 @@ android_library { "device_policy_aconfig_flags_lib", "kotlinx-coroutines-core", "kotlinx-coroutines-android", + "vendor.lineage.fastcharge-V1.0-java", + "VendorSupport-preference", + "glide", ], plugins: ["androidx.room_room-compiler-plugin"], @@ -166,3 +172,8 @@ filegroup { name: "Settings_proguard_flags", srcs: ["proguard.flags"], } + +android_library_import { + name: "glide", + aars: ["libs/glide-4.16.0.aar"], +} diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 69d000a9e89..c5523905740 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -149,6 +149,12 @@ + + + + + + @@ -808,7 +815,8 @@ android:label="@string/device_picker" android:configChanges="orientation|keyboardHidden|screenSize" android:exported="true" - android:clearTaskOnLaunch="true"> + android:clearTaskOnLaunch="true" + android:theme="@style/Theme.SubSettingsBase"> @@ -1542,6 +1550,26 @@ android:value="true" /> + + + + + + + + + + + + + + @@ -2916,6 +2945,10 @@ android:enableOnBackInvokedCallback="false" android:excludeFromRecents="true" /> + + @@ -3985,6 +4018,13 @@ + + + + + + @@ -4657,6 +4697,20 @@ android:value="true"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TouchGestures b/TouchGestures new file mode 120000 index 00000000000..78925b743e8 --- /dev/null +++ b/TouchGestures @@ -0,0 +1 @@ +../TouchGestures \ No newline at end of file diff --git a/libs/glide-4.16.0.aar b/libs/glide-4.16.0.aar new file mode 100644 index 00000000000..91444c3e9e3 Binary files /dev/null and b/libs/glide-4.16.0.aar differ diff --git a/proguard.flags b/proguard.flags index 492404ce7be..a178a0d9654 100644 --- a/proguard.flags +++ b/proguard.flags @@ -65,3 +65,6 @@ -keep class androidx.window.extensions.** { *; } -dontwarn androidx.window.extensions.** -keep class androidx.window.** { *; } + +# Keep our extensions +-keep class org.derpfest.settings.** diff --git a/res-derp/drawable/derp_preference_background_bottom.xml b/res-derp/drawable/derp_preference_background_bottom.xml new file mode 100644 index 00000000000..e44e6cb4ab3 --- /dev/null +++ b/res-derp/drawable/derp_preference_background_bottom.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res-derp/drawable/derp_preference_background_middle.xml b/res-derp/drawable/derp_preference_background_middle.xml new file mode 100644 index 00000000000..2dae11a1e5a --- /dev/null +++ b/res-derp/drawable/derp_preference_background_middle.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res-derp/drawable/derp_preference_background_top.xml b/res-derp/drawable/derp_preference_background_top.xml new file mode 100644 index 00000000000..d476e86e70f --- /dev/null +++ b/res-derp/drawable/derp_preference_background_top.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res-derp/layout/derp_dashboard_pref.xml b/res-derp/layout/derp_dashboard_pref.xml new file mode 100644 index 00000000000..fa74fcfbefe --- /dev/null +++ b/res-derp/layout/derp_dashboard_pref.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + diff --git a/res-derp/layout/derp_dashboard_preference_bottom.xml b/res-derp/layout/derp_dashboard_preference_bottom.xml new file mode 100644 index 00000000000..23e8539b2c8 --- /dev/null +++ b/res-derp/layout/derp_dashboard_preference_bottom.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/res-derp/layout/derp_dashboard_preference_middle.xml b/res-derp/layout/derp_dashboard_preference_middle.xml new file mode 100644 index 00000000000..0c5edd370d2 --- /dev/null +++ b/res-derp/layout/derp_dashboard_preference_middle.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/res-derp/layout/derp_dashboard_preference_top.xml b/res-derp/layout/derp_dashboard_preference_top.xml new file mode 100644 index 00000000000..d68db45d128 --- /dev/null +++ b/res-derp/layout/derp_dashboard_preference_top.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/res-derp/layout/derp_first_header_pref.xml b/res-derp/layout/derp_first_header_pref.xml new file mode 100644 index 00000000000..123dc9a6f40 --- /dev/null +++ b/res-derp/layout/derp_first_header_pref.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/res-derp/values-night/colors.xml b/res-derp/values-night/colors.xml new file mode 100644 index 00000000000..a910f52fd39 --- /dev/null +++ b/res-derp/values-night/colors.xml @@ -0,0 +1,22 @@ + + + + @android:color/system_neutral1_800 + @android:color/system_neutral1_900 + @android:color/system_neutral1_800 + @android:color/system_accent1_200 + \ No newline at end of file diff --git a/res-derp/values/colors.xml b/res-derp/values/colors.xml new file mode 100644 index 00000000000..a3d1672dfb4 --- /dev/null +++ b/res-derp/values/colors.xml @@ -0,0 +1,26 @@ + + + + + #1f000000 + @android:color/system_neutral1_10 + #33FFFFFF + @android:color/system_neutral1_50 + @android:color/system_neutral1_100 + @android:color/system_accent1_400 + diff --git a/res-derp/values/dimens.xml b/res-derp/values/dimens.xml new file mode 100644 index 00000000000..22d8f637d48 --- /dev/null +++ b/res-derp/values/dimens.xml @@ -0,0 +1,25 @@ + + + + + + + 28dp + 5dp + 16dp + -16dp + diff --git a/res-derp/values/strings.xml b/res-derp/values/strings.xml new file mode 100644 index 00000000000..3d9e35627b4 --- /dev/null +++ b/res-derp/values/strings.xml @@ -0,0 +1,25 @@ + + + + + + Connectivity + Personalize + @string/security_settings_title + System + diff --git a/res-derp/values/styles.xml b/res-derp/values/styles.xml new file mode 100644 index 00000000000..eb453a5cf24 --- /dev/null +++ b/res-derp/values/styles.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res-derp/xml/derp_top_level_settings.xml b/res-derp/xml/derp_top_level_settings.xml new file mode 100644 index 00000000000..73e58f29dd5 --- /dev/null +++ b/res-derp/xml/derp_top_level_settings.xml @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/color/audio_icon.xml b/res/color/audio_icon.xml new file mode 100644 index 00000000000..ca93eca8563 --- /dev/null +++ b/res/color/audio_icon.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/res/color/option_border_color.xml b/res/color/option_border_color.xml new file mode 100644 index 00000000000..880724c3594 --- /dev/null +++ b/res/color/option_border_color.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/res/drawable-nodpi/fingerprint_sensor_location_front_overlay.png b/res/drawable-nodpi/fingerprint_sensor_location_front_overlay.png new file mode 100644 index 00000000000..f1e5bde0edd Binary files /dev/null and b/res/drawable-nodpi/fingerprint_sensor_location_front_overlay.png differ diff --git a/res/drawable-nodpi/swipe_to_screenshot.png b/res/drawable-nodpi/swipe_to_screenshot.png new file mode 100644 index 00000000000..853c09e7afc Binary files /dev/null and b/res/drawable-nodpi/swipe_to_screenshot.png differ diff --git a/res/drawable/color_temperature_preview.xml b/res/drawable/color_temperature_preview.xml new file mode 100644 index 00000000000..0a358f6bf2c --- /dev/null +++ b/res/drawable/color_temperature_preview.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/res/drawable/color_tuning_preview.xml b/res/drawable/color_tuning_preview.xml new file mode 100644 index 00000000000..589237aae66 --- /dev/null +++ b/res/drawable/color_tuning_preview.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/res/drawable/ic_account_circle_24dp.xml b/res/drawable/ic_account_circle_24dp.xml index f41f57929c3..faad3bf0226 100644 --- a/res/drawable/ic_account_circle_24dp.xml +++ b/res/drawable/ic_account_circle_24dp.xml @@ -21,8 +21,5 @@ android:tint="?android:attr/colorAccent"> - + android:pathData="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M7.07,18.28C7.5,17.38 10.12,16.5 12,16.5C13.88,16.5 16.5,17.38 16.93,18.28C15.57,19.36 13.86,20 12,20C10.14,20 8.43,19.36 7.07,18.28M18.36,16.83C16.93,15.09 13.46,14.5 12,14.5C10.54,14.5 7.07,15.09 5.64,16.83C4.62,15.5 4,13.82 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,13.82 19.38,15.5 18.36,16.83M12,6C10.06,6 8.5,7.56 8.5,9.5C8.5,11.44 10.06,13 12,13C13.94,13 15.5,11.44 15.5,9.5C15.5,7.56 13.94,6 12,6M12,11A1.5,1.5 0 0,1 10.5,9.5A1.5,1.5 0 0,1 12,8A1.5,1.5 0 0,1 13.5,9.5A1.5,1.5 0 0,1 12,11Z" /> diff --git a/res/drawable/ic_adaptive_connectivity.xml b/res/drawable/ic_adaptive_connectivity.xml new file mode 100644 index 00000000000..f7a61f0bbb1 --- /dev/null +++ b/res/drawable/ic_adaptive_connectivity.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/res/drawable/ic_brightness_thumb.xml b/res/drawable/ic_brightness_thumb.xml new file mode 100644 index 00000000000..e85acd8416c --- /dev/null +++ b/res/drawable/ic_brightness_thumb.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/res/drawable/ic_calls_sms.xml b/res/drawable/ic_calls_sms.xml index 2033e8fc0dc..953eea45529 100644 --- a/res/drawable/ic_calls_sms.xml +++ b/res/drawable/ic_calls_sms.xml @@ -1,29 +1,10 @@ - - - - + android:tint="?android:attr/colorControlNormal"> + diff --git a/res/drawable/ic_derp_logo.xml b/res/drawable/ic_derp_logo.xml new file mode 100644 index 00000000000..f9bec0a9474 --- /dev/null +++ b/res/drawable/ic_derp_logo.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/drawable/ic_hand_gesture.xml b/res/drawable/ic_hand_gesture.xml new file mode 100644 index 00000000000..2fddba17375 --- /dev/null +++ b/res/drawable/ic_hand_gesture.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/drawable/ic_homepage_search.xml b/res/drawable/ic_homepage_search.xml index 3da1cc75a73..3895b6b2e85 100644 --- a/res/drawable/ic_homepage_search.xml +++ b/res/drawable/ic_homepage_search.xml @@ -20,7 +20,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?android:attr/textColorSecondary"> + android:tint="?android:attr/colorAccent"> diff --git a/res/drawable/ic_media_output.xml b/res/drawable/ic_media_output.xml new file mode 100644 index 00000000000..8f1351f8517 --- /dev/null +++ b/res/drawable/ic_media_output.xml @@ -0,0 +1,32 @@ + + + + + + + diff --git a/res/drawable/ic_menu_add.xml b/res/drawable/ic_menu_add.xml new file mode 100644 index 00000000000..926be7b770a --- /dev/null +++ b/res/drawable/ic_menu_add.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/res/drawable/ic_network_cell.xml b/res/drawable/ic_network_cell.xml index 1f24717a151..c997cc7ae13 100644 --- a/res/drawable/ic_network_cell.xml +++ b/res/drawable/ic_network_cell.xml @@ -21,6 +21,6 @@ android:viewportHeight="24.0" android:tint="?android:attr/colorControlNormal"> diff --git a/res/drawable/ic_security_pattern_3x3.xml b/res/drawable/ic_security_pattern_3x3.xml new file mode 100644 index 00000000000..b8e4df44a0f --- /dev/null +++ b/res/drawable/ic_security_pattern_3x3.xml @@ -0,0 +1,26 @@ + + + + diff --git a/res/drawable/ic_security_pattern_4x4.xml b/res/drawable/ic_security_pattern_4x4.xml new file mode 100644 index 00000000000..92c580f5e2c --- /dev/null +++ b/res/drawable/ic_security_pattern_4x4.xml @@ -0,0 +1,26 @@ + + + + diff --git a/res/drawable/ic_security_pattern_5x5.xml b/res/drawable/ic_security_pattern_5x5.xml new file mode 100644 index 00000000000..7b4dabaad79 --- /dev/null +++ b/res/drawable/ic_security_pattern_5x5.xml @@ -0,0 +1,26 @@ + + + + diff --git a/res/drawable/ic_security_pattern_6x6.xml b/res/drawable/ic_security_pattern_6x6.xml new file mode 100644 index 00000000000..1861284dbbf --- /dev/null +++ b/res/drawable/ic_security_pattern_6x6.xml @@ -0,0 +1,26 @@ + + + + diff --git a/res/drawable/ic_settings_backup_restore.xml b/res/drawable/ic_settings_backup_restore.xml new file mode 100644 index 00000000000..245b0fcc855 --- /dev/null +++ b/res/drawable/ic_settings_backup_restore.xml @@ -0,0 +1,25 @@ + + + + diff --git a/res/drawable/ic_settings_buttons.xml b/res/drawable/ic_settings_buttons.xml new file mode 100644 index 00000000000..b5e1d79ebdc --- /dev/null +++ b/res/drawable/ic_settings_buttons.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/res/drawable/ic_settings_print.xml b/res/drawable/ic_settings_print.xml index 41f3a24fd53..26a7008cbaf 100644 --- a/res/drawable/ic_settings_print.xml +++ b/res/drawable/ic_settings_print.xml @@ -24,5 +24,8 @@ android:tint="?android:attr/colorControlNormal"> + android:pathData="M19 8h-1V3H6v5H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zM8 5h8v3H8V5zm8 12v2H8v-4h8v2zm2-2v-2H6v2H4v-4c0-0.55 0.45 -1 1-1h14c0.55 0 1 0.45 1 1v4h-2z" /> + diff --git a/res/drawable/ic_settings_statusbar.xml b/res/drawable/ic_settings_statusbar.xml new file mode 100644 index 00000000000..26a16d4757b --- /dev/null +++ b/res/drawable/ic_settings_statusbar.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/res/drawable/ic_usb_24.xml b/res/drawable/ic_usb_24.xml new file mode 100644 index 00000000000..0d1e45013d8 --- /dev/null +++ b/res/drawable/ic_usb_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/res/drawable/ic_wifi_off.xml b/res/drawable/ic_wifi_off.xml new file mode 100644 index 00000000000..1480a59c3a7 --- /dev/null +++ b/res/drawable/ic_wifi_off.xml @@ -0,0 +1,25 @@ + + + + \ No newline at end of file diff --git a/res/drawable/icon_background.xml b/res/drawable/icon_background.xml new file mode 100644 index 00000000000..45e3ac5b0f2 --- /dev/null +++ b/res/drawable/icon_background.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/res/drawable/option_border_custom.xml b/res/drawable/option_border_custom.xml new file mode 100644 index 00000000000..6ac78ca7190 --- /dev/null +++ b/res/drawable/option_border_custom.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/res/drawable/option_border_edge_custom.xml b/res/drawable/option_border_edge_custom.xml new file mode 100644 index 00000000000..197b4453838 --- /dev/null +++ b/res/drawable/option_border_edge_custom.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + diff --git a/res/drawable/quickly_open_camera.xml b/res/drawable/quickly_open_camera.xml deleted file mode 100644 index dcbf9f4dc68..00000000000 --- a/res/drawable/quickly_open_camera.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/res/drawable/ring_notif_increasing.xml b/res/drawable/ring_notif_increasing.xml new file mode 100644 index 00000000000..c534e302348 --- /dev/null +++ b/res/drawable/ring_notif_increasing.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/res/drawable/settings_panel_rounded_top_corner_background.xml b/res/drawable/settings_panel_rounded_top_corner_background.xml index 1c234cca9b3..5e44b119bf0 100644 --- a/res/drawable/settings_panel_rounded_top_corner_background.xml +++ b/res/drawable/settings_panel_rounded_top_corner_background.xml @@ -21,6 +21,6 @@ android:topRightRadius="@dimen/settings_panel_corner_radius" android:bottomLeftRadius="0dp" android:bottomRightRadius="0dp"/> - + - \ No newline at end of file + diff --git a/res/drawable/tile_icon_refresh_rate.xml b/res/drawable/tile_icon_refresh_rate.xml new file mode 100644 index 00000000000..4cfbc6c5941 --- /dev/null +++ b/res/drawable/tile_icon_refresh_rate.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/res/layout/button_backlight.xml b/res/layout/button_backlight.xml new file mode 100644 index 00000000000..d1dfb9453d2 --- /dev/null +++ b/res/layout/button_backlight.xml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/changelog.xml b/res/layout/changelog.xml new file mode 100644 index 00000000000..42a298a60df --- /dev/null +++ b/res/layout/changelog.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + diff --git a/res/layout/derp_logo.xml b/res/layout/derp_logo.xml new file mode 100644 index 00000000000..194450db42e --- /dev/null +++ b/res/layout/derp_logo.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/res/layout/dialog_brightness.xml b/res/layout/dialog_brightness.xml new file mode 100644 index 00000000000..3c48d761786 --- /dev/null +++ b/res/layout/dialog_brightness.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/res/layout/dialog_light_settings.xml b/res/layout/dialog_light_settings.xml new file mode 100644 index 00000000000..18f69c2075c --- /dev/null +++ b/res/layout/dialog_light_settings.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/dialog_time.xml b/res/layout/dialog_time.xml new file mode 100644 index 00000000000..35723178414 --- /dev/null +++ b/res/layout/dialog_time.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/res/layout/display_color_calibration.xml b/res/layout/display_color_calibration.xml new file mode 100644 index 00000000000..caa741d3d72 --- /dev/null +++ b/res/layout/display_color_calibration.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/display_picture_adjustment.xml b/res/layout/display_picture_adjustment.xml new file mode 100644 index 00000000000..467ca184e22 --- /dev/null +++ b/res/layout/display_picture_adjustment.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/display_temperature.xml b/res/layout/display_temperature.xml new file mode 100644 index 00000000000..c10f7c4c72c --- /dev/null +++ b/res/layout/display_temperature.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + diff --git a/res/layout/fingerprint_enroll_find_sensor_base.xml b/res/layout/fingerprint_enroll_find_sensor_base.xml index 62203f71c81..6c3f5d6297b 100644 --- a/res/layout/fingerprint_enroll_find_sensor_base.xml +++ b/res/layout/fingerprint_enroll_find_sensor_base.xml @@ -29,11 +29,6 @@ android:clipToPadding="false" android:clipChildren="false"> - - + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingTop="@dimen/fingerprint_find_sensor_padding_top"> + + diff --git a/res/layout/item_option.xml b/res/layout/item_option.xml new file mode 100644 index 00000000000..113324ac8b7 --- /dev/null +++ b/res/layout/item_option.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + diff --git a/res/layout/item_view.xml b/res/layout/item_view.xml new file mode 100644 index 00000000000..be8ded4f34b --- /dev/null +++ b/res/layout/item_view.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/res/layout/lock_clock_fonts_option.xml b/res/layout/lock_clock_fonts_option.xml new file mode 100644 index 00000000000..822ec8e48aa --- /dev/null +++ b/res/layout/lock_clock_fonts_option.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + diff --git a/res/layout/preference_application_light.xml b/res/layout/preference_application_light.xml new file mode 100644 index 00000000000..bf9976a7bee --- /dev/null +++ b/res/layout/preference_application_light.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/preference_brightness.xml b/res/layout/preference_brightness.xml new file mode 100644 index 00000000000..47c6839838f --- /dev/null +++ b/res/layout/preference_brightness.xml @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/res/layout/preference_charging_limit.xml b/res/layout/preference_charging_limit.xml new file mode 100644 index 00000000000..9c2b4fe5eef --- /dev/null +++ b/res/layout/preference_charging_limit.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + diff --git a/res/layout/preference_icon.xml b/res/layout/preference_icon.xml new file mode 100644 index 00000000000..ef31cf85c37 --- /dev/null +++ b/res/layout/preference_icon.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + diff --git a/res/layout/preference_increasing_ring.xml b/res/layout/preference_increasing_ring.xml new file mode 100644 index 00000000000..11795a2eaee --- /dev/null +++ b/res/layout/preference_increasing_ring.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/private_dns_mode_dialog.xml b/res/layout/private_dns_mode_dialog.xml index 96ebd2c3955..e7e14992211 100644 --- a/res/layout/private_dns_mode_dialog.xml +++ b/res/layout/private_dns_mode_dialog.xml @@ -35,6 +35,10 @@ android:id="@+id/private_dns_mode_off" layout="@layout/preference_widget_dialog_radiobutton"/> + + diff --git a/res/layout/pulse_time_item.xml b/res/layout/pulse_time_item.xml new file mode 100644 index 00000000000..5267eeaaf07 --- /dev/null +++ b/res/layout/pulse_time_item.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/res/layout/set_backup_pw.xml b/res/layout/set_backup_pw.xml index ce9a39880f8..9d7a5f47dcc 100644 --- a/res/layout/set_backup_pw.xml +++ b/res/layout/set_backup_pw.xml @@ -69,6 +69,8 @@ android:id="@+id/backup_pw_cancel_button" android:layout_below="@id/confirm_new_backup_pw" android:text="@string/backup_pw_cancel_button_text" + android:backgroundTint="?android:attr/colorAccent" + android:textColor="?android:textColorPrimaryInverse" android:layout_height="wrap_content" android:layout_width="wrap_content" /> @@ -76,6 +78,8 @@ android:id="@+id/backup_pw_set_button" android:layout_below="@id/confirm_new_backup_pw" android:layout_toEndOf="@id/backup_pw_cancel_button" + android:backgroundTint="?android:attr/colorAccent" + android:textColor="?android:textColorPrimaryInverse" android:text="@string/backup_pw_set_button_text" android:layout_height="wrap_content" android:layout_width="wrap_content" /> diff --git a/res/layout/settings_homepage_app_bar_regular_phone_layout.xml b/res/layout/settings_homepage_app_bar_regular_phone_layout.xml index f817dd49f40..b5b2a85aa0e 100644 --- a/res/layout/settings_homepage_app_bar_regular_phone_layout.xml +++ b/res/layout/settings_homepage_app_bar_regular_phone_layout.xml @@ -22,21 +22,28 @@ android:background="?android:attr/colorBackground" android:orientation="vertical"> - - - + + + + + + - \ No newline at end of file + diff --git a/res/layout/settings_homepage_container.xml b/res/layout/settings_homepage_container.xml index 89d40d08b44..a082854e22d 100644 --- a/res/layout/settings_homepage_container.xml +++ b/res/layout/settings_homepage_container.xml @@ -66,6 +66,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + android:gravity="bottom" app:layout_scrollFlags="scroll|exitUntilCollapsed"> + - diff --git a/res/layout/wifi_network_config.xml b/res/layout/wifi_network_config.xml index dbd3e673913..c9b909cd217 100644 --- a/res/layout/wifi_network_config.xml +++ b/res/layout/wifi_network_config.xml @@ -237,6 +237,18 @@ android:entries="@array/eap_ocsp_type"/> + + + + "Basisbandweergawe" "Kernweergawe" "Bounommer" - "Google Play-stelselopdatering" + "Mainline-stelselopdatering" "Batteryinligting" "Nie beskikbaar nie" "Berging" @@ -1602,7 +1602,7 @@ "Regulasie- en veiligheidsgids" "Kopiereg" "Lisensie" - "Google Play-stelselopdateringlisensies" + "Mainline-stelselopdateringlisensies" "Bepalings en voorwaardes" "Stelsel se WebView-lisensie" "Muurpapierkrediete" diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml index f83f6666d31..ba00df9f590 100644 --- a/res/values-am/strings.xml +++ b/res/values-am/strings.xml @@ -1331,7 +1331,7 @@ "የቤዝባንድ ሥሪት" "የከርነል ሥሪት" "የግንባታ ቁጥር" - "Google Play ሥርዓት ዝማኔ" + "Mainline ሥርዓት ዝማኔ" "የባትሪ መረጃ" "አይገኝም" "ማከማቻ" @@ -1602,7 +1602,7 @@ "የደህንነት እና የቁጥጥር መመሪያ ጽሁፍ" "የቅጂ መብት" "ፍቃድ" - "የGoogle Play ስርዓት ዝማኔ ፈቃዶች" + "የMainline ስርዓት ዝማኔ ፈቃዶች" "ውሎች እና ደንቦች" "የስርዓት WebView ፍቃድ" "የልጣፍ ምስጋናዎች" diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index e5140e8520a..a5a348e12ae 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -1331,7 +1331,7 @@ "إصدار النطاق الأساسي" "إصدار النواة" "رقم الإصدار" - "‏تحديث نظام Google Play" + "‏تحديث نظام Mainline" "معلومات البطارية" "غير متاحة" "التخزين" @@ -1602,7 +1602,7 @@ "دليل السلامة والمعلومات التنظيمية" "حقوق الطبع والنشر" "الترخيص" - "‏تراخيص تحديث نظام Google Play" + "‏تراخيص تحديث نظام Mainline" "الأحكام والشروط" "‏ترخيص WebView للنظام" "المساهمون في الخلفية" diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml index d7228bd0580..eb8fa7f6984 100644 --- a/res/values-as/strings.xml +++ b/res/values-as/strings.xml @@ -1331,7 +1331,7 @@ "বেইছবেণ্ডৰ সংস্কৰণ" "কাৰ্ণেলৰ সংস্কৰণ" "বিল্ড নম্বৰ" - "Google Play ছিষ্টেম আপডে’ট" + "Mainline ছিষ্টেম আপডে’ট" "বেটাৰীৰ তথ্য" "উপলব্ধ নহয়" "ষ্ট’ৰেজ" @@ -1602,7 +1602,7 @@ "সুৰক্ষা আৰু ৰেগুলেটৰী হাতপুথি" "স্ৱত্ৱাধিকাৰ" "লাইচেঞ্চ" - "Google Play ছিষ্টেম আপডে’টৰ অনুজ্ঞাপত্ৰসমূহ" + "Mainline ছিষ্টেম আপডে’টৰ অনুজ্ঞাপত্ৰসমূহ" "চৰ্তসমূহ" "ছিষ্টেম ৱেবভিউ লাইচেঞ্চ" "ৱালপেপাৰ ক্ৰেডিট" diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml index 00d885b1208..36b3c36e33c 100644 --- a/res/values-az/strings.xml +++ b/res/values-az/strings.xml @@ -1331,7 +1331,7 @@ "Baseband versiyası" "Kernel versiyası" "Montaj nömrəsi" - "Google Play sistem güncəllənməsi" + "Mainline sistem güncəllənməsi" "Batareya məlumatı" "Əlçatımlı deyil" "Yaddaş" @@ -1602,7 +1602,7 @@ "Təhlükəsizlik və tənzimləyici təlimatı" "Müəlliflik hüququ" "Lisenziya" - "Google Play sistem güncəllənməsi lisenziyaları" + "Mainline sistem güncəllənməsi lisenziyaları" "Şərtlər" "Sistem WebView Lisenziyası" "Divar kağızı kreditləri" diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml index cf5cf613320..1336f4b32a4 100644 --- a/res/values-b+sr+Latn/strings.xml +++ b/res/values-b+sr+Latn/strings.xml @@ -1331,7 +1331,7 @@ "Verzija osnovnog propusnog opsega" "Verzija jezgra" "Broj verzije" - "Google Play ažuriranje sistema" + "Mainline ažuriranje sistema" "Informacije o bateriji" "Nije dostupno" "Memorijski prostor" @@ -1602,7 +1602,7 @@ "Priručnik sa bezbednosnim i regulatornim podacima" "Autorska prava" "Licenca" - "Licence za Google Play ažuriranje sistema" + "Licence za Mainline ažuriranje sistema" "Uslovi i odredbe" "Licenca za sistemski WebView" "Impresum pozadine" diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index 7e444201ae1..0b9445f0485 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -1331,7 +1331,7 @@ "Версія модуля сувязі" "Версія ядра" "Нумар зборкі" - "Абнаўленне сістэмы Google Play" + "Абнаўленне сістэмы Mainline" "Інфармацыя пра акумулятар" "Недаступна" "Сховішча" @@ -1602,7 +1602,7 @@ "Інфармацыя па эксплуатацыі і бяспецы" "Аўтарскія правы" "Ліцэнзія" - "Ліцэнзіі абнаўлення сістэмы Google Play" + "Ліцэнзіі абнаўлення сістэмы Mainline" "Умовы выкарыстання" "Сістэмная ліцэнзія WebView" "Звесткі пра шпалеры" diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index 9d9a0a644ca..bdfc198aaa4 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -1331,7 +1331,7 @@ "Версия на базовия диапазон" "Версия на ядрото" "Номер на версията" - "Системна актуализация на Google Play" + "Системна актуализация на Mainline" "Информация за батерията" "Не е налично" "Хранилище" @@ -1602,7 +1602,7 @@ "Наръчник за безопасността и норм. изисквания" "Авторски права" "Лиценз" - "Лицензи за системните актуализации на Google Play" + "Лицензи за системните актуализации на Mainline" "Общи условия" "Системен лиценз за WebView" "Признания за тапета" @@ -4362,7 +4362,7 @@ "Най-скорошният регистрационен файл за сигурността" "Нищо" "Инсталирани са приложения" - "Броят на приложенията е приблизителен и може да не включва тези, които не са инсталирани от Google Play Магазин." + "Броят на приложенията е приблизителен и може да не включва тези, които не са инсталирани от Mainline Магазин." "{count,plural, =1{Поне # приложение}other{Поне # приложения}}" "Разрешения за местоположението" "Разрешения за микрофона" @@ -4394,7 +4394,7 @@ "Информация за финансираното устройство" "Кредитодателят ви може да променя настройките на това устройство и да инсталира софтуер на него по време на настройването.\n\nАко пропуснете плащане, кредитодателят може да заключи устройството ви и да промени настройките му.\n\nЗа да научите повече, свържете се с кредитодателя си." "Ако устройството ви е финансирано, не можете да правите следното:" - "Инсталиране на приложения извън Google Play Магазин" + "Инсталиране на приложения извън Mainline Магазин" "Рестартиране на устройството ви в безопасен режим" "Добавяне на няколко потребители на устройството ви" "Промяна на датата, часа и часовите зони" diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml index da74cc646b0..ff73f1e9120 100644 --- a/res/values-bn/strings.xml +++ b/res/values-bn/strings.xml @@ -1331,7 +1331,7 @@ "বেসব্যান্ড ভার্সন" "কার্নেল ভার্সন" "বিল্ড নম্বর" - "Google Play সিস্টেম আপডেট" + "Mainline সিস্টেম আপডেট" "ব্যাটারি সংক্রান্ত তথ্য" "উপলভ্য নয়" "স্টোরেজ" @@ -1602,7 +1602,7 @@ "নিরাপত্তা ও নিয়ন্ত্রণ বিধির নির্দেশিকা" "কপিরাইট" "লাইসেন্স" - "Google Play সিস্টেম আপডেট লাইসেন্স" + "Mainline সিস্টেম আপডেট লাইসেন্স" "চুক্তি এবং শর্তাবলী" "সিস্টেম ওয়েবভিউ লাইসেন্স" "ওয়ালপেপার ক্রেডিট" diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml index f2c8c2465f2..43cb216fcfe 100644 --- a/res/values-bs/strings.xml +++ b/res/values-bs/strings.xml @@ -1331,7 +1331,7 @@ "Verzija nemoduliranog signala" "Osnovna verzija" "Broj verzije" - "Ažuriranje sistema Google Playa" + "Ažuriranje sistema Mainlinea" "Informacije o bateriji" "Nije dostupno" "Pohrana" @@ -1602,7 +1602,7 @@ "Priručnik za sigurnost i propise" "Autorska prava" "Licenca" - "Licence za ažuriranje sistema Google Playa" + "Licence za ažuriranje sistema Mainlinea" "Uslovi i odredbe" "Sistemske WebView licence" "Zahvale za pozadinske slike" diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index 6005e23ea38..a03d2e7c2df 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -1331,7 +1331,7 @@ "Verze základního pásma" "Verze jádra" "Číslo sestavení" - "Aktualizace systému Google Play" + "Aktualizace systému Mainline" "Informace o baterii" "Nedostupné" "Úložiště" @@ -1602,7 +1602,7 @@ "Příručka k bezpečnosti a předpisům" "Autorská práva" "Licence" - "Licence aktualizace systému Google Play" + "Licence aktualizace systému Mainline" "Smluvní podmínky" "Systémová licence WebView" "Autorství tapet" diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index 18f871c3332..e3b03553b6f 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -1331,7 +1331,7 @@ "Basebandversion" "Kernesystem" "Buildnummer" - "System­opdatering til Google Play" + "System­opdatering til Mainline" "Batterioplysninger" "Ikke tilgængelig" "Lagerplads" @@ -1602,7 +1602,7 @@ "Brugervejledning i sikkerhed og regler" "Ophavsret" "Licens" - "Licenser til Google Play-systemopdateringer" + "Licenser til Mainline-systemopdateringer" "Vilkår og betingelser" "System WebView-licens" "Kreditering til baggrund" diff --git a/res/values-de/derp_strings.xml b/res/values-de/derp_strings.xml new file mode 100644 index 00000000000..929ca46a61b --- /dev/null +++ b/res/values-de/derp_strings.xml @@ -0,0 +1,862 @@ + + + + + Root-Debugging + Android-Debugging als Root erlauben + + + Extras + Erweiterte Einstellungen + + + DerpFest-Version + Unbekannt + + + Plattform-Version + + + Entwickler + + + Bildschirmausrichtung + Automatische Bildschirmausrichtung aktiviert + Automatische Bildschirmausrichtung deaktiviert + Sperrbildschirm + Grad + Bildschirmausrichtung bei + 0 Grad + 90 Grad + 180 Grad + 270 Grad + + + DerpFest-Updater + Auf neue DerpFest-Updates überprüfen + + + Sprachen, Gesten, Updater, Zeit und Sicherung + + + Größe des Musters auswählen + + + Sperrmuster-Fehler anzeigen + + Sperrmuster-Punkte anzeigen + + + Tippen für Standby + Doppeltippe auf Statusleiste oder Sperrbildschirm, um das Display auszuschalten + + + Game Space + Verbessere Dein Spielerlebnis + + + Maximale Aktualisierungsrate + + + Minimale Aktualisierungsrate + + + Suchen Sie den Fingerabdrucksensor auf der Vorderseite Ihres Tablets. + Suchen Sie den Fingerabdrucksensor auf der Vorderseite Ihres Geräts. + Suchen Sie den Fingerabdrucksensor auf der Vorderseite Ihres Telefons. + Suchen Sie den Fingerabdrucksensor auf der Rückseite Ihres Tablets. + Suchen Sie den Fingerabdrucksensor auf der Rückseite Ihres Geräts. + Suchen Sie den Fingerabdrucksensor auf der Rückseite Ihres Telefons. + Suchen Sie den Fingerabdrucksensor auf der Seite Ihres Tablets. + Suchen Sie den Fingerabdrucksensor auf der Seite Ihres Geräts. + Suchen Sie den Fingerabdrucksensor auf der Seite Ihres Telefons. + + + Berühren Sie den Sensor auf der Vorderseite Ihres Tablets. + Berühren Sie den Sensor an der Vorderseite Ihres Geräts. + Berühren Sie den Sensor an der Vorderseite des Telefons. + Berühren Sie den Sensor auf der Rückseite Ihres Tablets. + Berühren Sie den Sensor auf der Rückseite Ihres Geräts. + Berühren Sie den Sensor auf der Rückseite Ihres Telefons. + Berühren Sie den Sensor an der Seite Ihres Tablets. + Berühren Sie den Sensor an der Seite Ihres Geräts. + Berühren Sie den Sensor an der Seite Ihres Telefons. + + + Navigationsleiste + Gestenleiste ausblenden + Blendet die Gestenleiste am unteren Bildschirmrand aus + IME-Schaltflächenbereich ausblenden + IME-Schaltflächenbereich unter der Tastatur ausblenden + + + Zufällige Anordnung + Bei jedem Entsperrversuch die Ziffernanordnung zufällig neu wählen + + + Einmalige automatische Helligkeitseinstellung + Helligkeitsanpassung erfolgt nur beim Einschalten des Bildschirms + + + Hohe Touchscreen-Abtastrate + Touchscreen-Abtastrate erhöhen + + + Hohe Touch-Empfindlichkeit + Touch-Empfindlichkeit erhöhen, um den Touchscreen auch mit Handschuhen bedienen zu können + + + Minimale Zeit zwischen Benachrichtigungstönen + Töne und Vibrationen nur alle %1$s zulassen + Keine Beschränkung + 10 Sekunden + 30 Sekunden + 1 Minute + 5 Minuten + 15 Minuten + 30 Minuten + + + Unbeabsichtigtes Aufwecken verhindern + Vor dem Aufwecken des Gerätes Näherungssensor überprüfen + + + Bildschirmgesten + Führe durch verschiedene Bildschirmgesten einige schnelle Aktionen aus + + + Clients erlauben VPN zu verwenden + Erlaubt Hotspot-Clients die VPN-Verbindungen dieses Gerätes für die Upstream-Konnektivität zu verwenden + + + Touchscreen als Maus verwenden + Erlaubt es, den über dem Touchscreen schwebenden Finger im Browser, Remote Desktop usw. als Mauszeiger zu verwenden. + + + Beim Einstecken aufwecken + Beim Anschließen oder Trennen einer Stromquelle den Bildschirm einschalten + + + Schnelles Laden + Deaktivieren, um die vom Gerät beim Laden erzeugte Wärme zu reduzieren oder die Lebensdauer des Akkus zu verlängern + + + Fingerabdrucksensor lange drücken, um das Telefon zu entsperren + + Fingerabdrucksensor lange drücken, um das Tablet zu entsperren + + Fingerabdrucksensor lange drücken, um das Gerät zu entsperren + + Zum Anschalten und Entsperren des Geräts den Fingerabdrucksensor im Display gedrückt halten. + + + Reines Schwarz + Rein schwarzer Hintergrund für dunkles Design + + + Optimiert Ihren Bildschirm basierend auf der Tageszeit und den Umgebungsbedingungen, um die Lesbarkeit zu verbessern und die Ermüdung der Augen zu mildern + Bildschirmmodus + Farbtemperatur + Tag: %1$d K Nacht: %2$d K + %1$d K + Tag + Nacht + Automatischer Außen-Modus + Bei grellem Sonnenlicht Helligkeit und Sättigung automatisch erhöhen + Anti-Flimmern + Verhindert starkes Flimmern und reduziert die Belastung der Augen bei schlechten Lichtverhältnissen. Kann die Farbgenauigkeit verschlechtern und die Art und Weise, wie einige Elemente angezeigt werden, beeinträchtigen. + Akkuverbrauch reduzieren + Anpassen der Anzeige für den geringsten Akkuverbrauch ohne Beeinträchtigungen + Farben verbessern + Verbessert die Farben von Hauttönen, Landschaften und von anderen Bildern + Farbprofil + Standard + Genaue Farbdarstellung und helle Weißtöne + Natürlich + Realistische Farben und Hauttöne + Dynamisch + Verbesserte Farben und helleres Weiß + Kino + Perfekte Farbwiedergabe für Videos + Astronomie + Tiefes Rot für den Erhalt der Nachtsicht + Fotografie + Perfekte Farbwiedergabe für Fotos + Einfach + Display unkalibriert verwenden + Adaptiv + Farben an Umgebungsbedingungen anpassen + Lesen + Wärmere Farben für weniger Augenbelastung + sRGB + Farben passend zum sRGB-Farbraum + DCI-P3 + Farben passend zum DCI-P3-Farbraum + Lesemodus + Graustufen-Modus zum Langzeit-Lesen + Farbkalibrierung + Displayfarben kalibrieren + Rot + Grün + Blau + + + Bildanpassung + Farbton, Sättigung, Leuchtdichte und Kontrast anpassen + Farbton + Sättigung + Leuchtdichte + Kontrast + + + Vorschau der LED-Einstellungen + + + LED-Einstellungen ändern + Pulsdauer und Geschwindigkeit + Standard + Angepasst + Helligkeitsstufe + App auswählen + + + Immer an + Sehr kurz + Kurz + Standard + Lang + Sehr lang + Sehr schnell + Schnell + Standard + Langsam + Sehr langsam + + + Helligkeitsstufen + Standard + Nicht stören + + + Ladekontrolle + Anpassen von Ladeplan und -grenzen + Ladekontrolle aktivieren + Lademodus + Automatischer Zeitplan + Startzeitpunkt für den Ladevorgang anhand der eingestellten Alarme automatisch bestimmen + Benutzerdefinierter Zeitplan + Zielzeit bis zur vollständigen Aufladung festlegen + Ladung begrenzen + Aufladung auf einen bestimmten Prozentsatz begrenzen + Startzeit + Ladekontrolle wird aktiviert, wenn du nach %s mit dem Laden beginnst + Zielzeit bis zur vollständigen Aufladung + Akku wird bis %s vollständig geladen sein + Begrenzung + + + Akku-LED + Anpassen des Verhaltens der Akkuladeleuchte + Akkulicht aktivieren + Bei schwachem Akku pulsieren + Ausschalten, wenn vollständig geladen + Farben + Akku schwach + Laden + Aufgeladen + + + Benachrichtigungs-LED + Anpassen des Verhaltens der Benachrichtigungs-LED + Benachrichtigungslicht aktivieren + Allgemein + Erweitert + Apps + Telefon + App-Anpassungen + Standard + Verpasster Anruf + Sprachnachricht + LED-Anzeige bei aktivem Display + LED-Anzeige im \"Nicht stören\"-Modus + LED-Helligkeit reduzieren + Farben automatisch wählen + Farben automatisch wählen + + + LED-Einstellungen + Benachrichtigungs-LED durch Einstellungen aktiviert + Um App-spezifische Einstellungen hinzuzufügen, „%1$s“ aktivieren und „Hinzufügen“ antippen + + + Pop-up + Wichtige Benachrichtigungen in einem kleinen, schwebenden Fenster anzeigen + Pop-ups sind global deaktiviert + + + Warnung! Diese Option funktioniert möglicherweise nicht richtig oder führt zu Datenverlusten und wird daher nicht empfohlen! + + + Unterstütztes GPS verwenden + + Satellitenhilfsdaten aus dem Internet herunterladen, die den Startvorgang von GPS erheblich verbessern können. Für Notrufe ist das unterstützte GPS immer erlaubt. + + + Zurücksetzen + + Tasten + Ein-/Aus-Taste + Home-Taste + Zurück-Taste + Menü-Taste + Suche-Taste + Anwendungsverlauf-Taste + Kamera-Taste + Lautstärketasten + Kurzes Drücken + Langes Drücken + Doppeltes Tippen + Keine Aktion + Menü öffnen/schließen + Anwendungsverlauf + Suchassistent + Sprachsuche + In-App-Suche + Kamera starten + Bildschirm ausschalten + Vorherige App + Geteilter Bildschirm + App im Vordergrund beenden + Kurzer Blick + Der Bildschirm bleibt aktiviert, solange die Kamera-Taste halb durchgedrückt gehalten wird. + Kamera starten + Langes Drücken und Loslassen startet die Kamera + Wiedergabe steuern + Bei abgeschaltetem Bildschirm durch langes Drücken der Lautstärketasten zum nächsten/vorherigen Titel springen + Cursorsteuerung + Deaktiviert + Lauter-/Leiser-Taste bewegt den Cursor nach links/rechts + Lauter-/Leiser-Taste bewegt den Cursor nach rechts/links + Anruf beenden + Aktuellen Anruf durch Drücken der Ein-/Aus-Taste beenden + Umkehren + Belegung der Lautstärketasten umkehren, wenn das Gerät gedreht wird + Regler auf der linken Seite anzeigen + Lautstärkeregler auf der linken Seite des Bildschirms anzeigen + Gerät aufwecken + Anruf annehmen + Eingehende Anrufe durch Drücken der Lautstärketaste annehmen + Anruf beantworten + Eingehende Anrufe durch Drücken der Home-Taste annehmen + Extras + Für zugeschnittenen Screenshot klicken + Lautstärketaste nach unten und Powertaste kurz drücken, um einen partiellen Screenshot zu erstellen + + + Navigationsleiste + Taskleiste aktivieren + Layout umkehren + Kehrt das Layout der Navigationsleiste und anderer Elemente, wie dem IME-Switcher um + Navigationsleiste im Querformat auf der linken Seite anzeigen + Pfeiltasten beim Tippen anzeigen + Beim Tippen Links/Rechts-Cursortasten anzeigen (deaktiviert die Eingabemethodenauswahl). + Zurück (langes Drücken) + Home (langes Drücken) + Home (doppeltes Tippen) + Aktion für langes Drücken auf Anwendungsverlauf + Aktion für langes Wischen am Rand + + + Ein-/Aus-Menü + Ändere welche Elemente im Ein-/Aus-Menü angezeigt werden sollen + Elemente des Ein-/Aus-Menü + Screenshot + Für zugeschnittenen Screenshot gedrückt halten + Flugmodus + Benutzer wechseln + Fehlerbericht + Fehlerberichte sind deaktiviert, da die Entwickleroptionen nicht aktiviert sind + Fehlerberichte sind für nicht-primäre Benutzer deaktiviert. + Notfall + Gerätesteuerung + Erweiterter Neustart + Bei entsperrtem Bildschirm dem Ein-/Aus-Menü Optionen für Neustart in Recovery oder in Bootloader hinzufügen + Notfallelement ist aktiviert + + + Doppelt drücken für Kamera + Kamera ohne Entsperren deines Bildschirms schnell öffnen + Lange drücken für Taschenlampe + Taschenlampe bei ausgeschaltetem Bildschirm durch langes Drücken der Ein-/Aus-Taste anschalten + Taschenlampe automatisch ausschalten + Nie + 1 Minute + 2 Minuten + 5 Minuten + 10 Minuten + + + Hintergrundbeleuchtung + Tasten beleuchten + Tasten nur beleuchten, wenn sie gedrückt werden + Tastatur beleuchten + Tasten-Helligkeit + Tastaturhelligkeit + Automatisch abschalten nach + Nie ausschalten + Deaktiviert + Für %s aktiviert + Aktiviert + + + Navigationsleiste aktivieren + Navigationstasten deaktivieren und stattdessen eine Navigationsleiste auf dem Bildschirm anzeigen + + + Sensortasten tauschen + Anordnung der Tasten für Anwendungsverlauf und Zurück tauschen + + + Weitere Einstellungen + Weitere Tasten + + + Ansteigende Klingeltonlautstärke + Anfangslautstärke + Anstiegszeit + + + Statusleiste + Schnelles Öffnen + Herunterziehen der Benachrichtigungsleiste am %1$s Rand öffnet die Schnelleinstellungen + linken + rechten + Aus + Links + Rechts + + + Statusleistensymbole + Systemsymbole + Festlegen, welche Statusleistensymbole angezeigt werden + + + Uhr + Sekunden anzeigen + Stunden, Minuten und Sekunden anzeigen + Automatisch ausblenden + Uhr auf dem Startbildschirm ausblenden + Position der Uhr + Rechts + Links + Zentriert + AM/PM-Format + 24-Stunden-Format ist aktiviert + Standard + Klein + Ausgeblendet + + + Akkuanzeige + Akkuanzeige + Verhalten und Stil des Batteriesymbols in der Statusleiste anpassen + Akkudarstellung + Hochformat + Kreis + Querformat + Querformat (umgedreht) + Text + Akkuanzeige mit Prozent + Immer den Akkuprozentsatz anzeigen + Innerhalb des Symbols + Akkuprozentsatz innerhalb des Symbols anzeigen + Akkuanzeige in Prozent beim Aufladen + Beim Aufladen immer den Akkuprozentsatz anzeigen + + + Helligkeit + Helligkeitsregler + Helligkeit in den Schnelleinstellungen anpassen + Automatische Helligkeit + Umschalter für adaptive Helligkeit neben dem Schieberegler anzeigen + Niemals anzeigen + Anzeigen wenn erweitert + Immer anzeigen + Helligkeitssteuerung + Helligkeit durch Wischen über die Statusleiste anpassen + + + Datenverkehr-Anzeige + Datenverkehr-Anzeige einblenden + Aktuellen Datenverkehr in der Statusleiste anzeigen + Automatisch ausblenden + Datenverkehr-Anzeige bei Inaktivität ausblenden + Einheiten der Datenübertragungsraten + Datenverkehr-Anzeige aufgrund der Uhrzeitposition deaktiviert + + + Schnelleinstellungen + + + USB beschränken + Unterstützung für USB-Anschlüsse wie Kopfhörer, Speichergeräte, Eingabegeräte (Mäuse, Tastaturen, Joysticks) und ADB regeln + USB-Verbindungen ablehnen + USB-Verbindungen zulassen, wenn entsperrt + USB-Verbindungen immer zulassen + + + Wischen für Bildschirmfoto + Mit drei Fingern wischen, um ein Bildschirmfoto aufzunehmen + + + SELinux-Status + Deaktiviert + Permissive + Enforcing + + + USB-Konfiguration + Standardverhalten bei Verbindung mit einem Gerät über USB + + + Panikauslöser + + Aktiviert einen Notfall-Panikauslöser + + + System-UI-Neustart + + + Weniger unwichtige + Popup-Benachrichtigungen nur für Anrufe, Nachrichten, Alarme, Erinnerungen und wichtige Gespräche anzeigen + + + Radio-Info + + + Verfügbarkeit der Batterieoptimierung + + + In die Zwischenablage kopiert + Gedrückthalten zum Kopieren + + + Einst. für Vibration während Telefonaten + Bei hergestellter Verbindung vibrieren + Bei Anklopfen vibrieren + Beim Trennen der Verbindung vibrieren + + + Nie + 15 Sekunden + 30 Sekunden + 1 Minute + 2 Minuten + 5 Minuten + 10 Minuten + 30 Minuten + 1 Stunde + 2 Stunden + 4 Stunden + 8 Stunden + 12 Stunden + 24 Stunden + 36 Stunden + 48 Stunden + 72 Stunden + + + WLAN automatisch ausschalten + + + WLAN schaltet sich nach %1$s aus, wenn kein Netzwerk verbunden ist + Deaktiviert + + + Bluetooth-Zeitabschaltung + + Bluetooth schaltet sich nach %1$s aus, wenn keine Geräte verbunden sind + Bluetooth nicht automatisch ausschalten + + + Ein-/Aus-Menü auf dem Sperrbildschirm + Zugriff auf Ein-/Aus-Menü vom gesperrten Sperrbildschirm aus erlauben + + + Weichzeichnen aktivieren + Aktiviert einen Weichzeichnen-Effekt in einigen Elementen der Benutzeroberfläche + + + Millisekunden + + + Klingelton-Vibrationsmuster + Benachrichtigungs-Vibrationsmuster + Benutzerdefiniert + Benutzerdefiniertes Vibrationsmuster + Ein benutzerdefiniertes Vibrationsmuster festlegen + + + Detaillierte Batterieinformationen anzeigen + + Technologie + + Zustand + Gut + Überhitzt + Unbrauchbar + Überspannung + Unbestimmter Fehler + Kalt + Unbekannt + + Temperatur + + Spannung + + %1$d mAh + + Nennkapazität + %1$d mAh + + Maximale Kapazität + %1$d mAh (%2$d%%) + + + Weniger als eine Minute + + %1$s heute + + + Verlaufsprotokoll für Benachrichtigungen anzeigen + + + Nicht validieren + + Kein Zertifikat angegeben. Ihre Verbindung wird nicht privat sein. + + + Klingelton - SIM 1 + + Klingelton - SIM 2 + + + Erkennungsbereich der Zurück-Geste + Überall + Unten + Options + Erkennungsbereich + Wo die Zurück-Geste überall erkannt werden soll + + + Gesten-Navigationsgriff + Haptisches Feedback bei Zurück-Geste + Bei Zurück-Geste vibrieren + + + Zwischenablage-Overlay anzeigen + Ein Overlay in der linken unteren Ecke des Bildschirms anzeigen, wenn etwas in die Zwischenablage kopiert wird + + + Adaptive Wiedergabe + Adaptive Wiedergabe verwenden + Aus + Die adaptive Wiedergabe hält das Medium automatisch an, wenn die Lautstärke stummgeschaltet ist, und wird automatisch fortgesetzt, wenn die Lautstärke innerhalb der eingestellten Zeit wiederhergestellt wird. + Verwenden Sie ein Timeout von + Kein Timeout + Ein (kein Timeout) + 30 Sekunden + Ein (30 Sekunden) + 1 Minute + Ein (1 Minute) + 2 Minuten + Ein (2 Minuten) + 5 Minuten + Ein (5 Minuten) + 10 Minuten + Ein (10 Minuten) + + + Sensoren blockieren + Zugriff auf einige Bewegungssensoren für stromintensive Apps blockieren + Sensoren für diese Apps blockieren + Fügen Sie Apps für den Sensorblock hinzu + Signifikante Bewegungs-, Beschleunigungsmesser- und Linearbeschleunigungssensoren werden für diese Apps blockiert + App auswählen + Ausgewähltes Element entfernen? + Entfernen + + + Geste zum Durchlaufen der Klingeltonmodi + Ein (Modi durchlaufen) + Standardverhalten + + + SoC-Modell + RAM insgesamt + + + Ton beim Laden + Vibration beim Laden + + + Weitere Einstellungen + Weitere Einstellungen für Haptisches Feedback + Anpassen der haptischen Rückmeldung für System-Widgets + Helligkeitsregelung + Haptisches Feedback bei Helligkeitsregelung mithilfe des Schiebereglers + + + Welleneffekt + Welleneffekt zeigen bei Entsperrung mit Fingerabdruck + + + Flag für sichere Fenster ignorieren + Entfernt Screenshot- und Screenrecord-Limits für alle Apps. Dies kann in einigen Fällen bequem sein, kann aber zu Datenschutzlecks führen. + + + Smart-Cover-Modus + Verhalten des Geräts beim Öffnen/Schließen der Abdeckung + Nichts tun + Sperren/Entsperren + Schlafen/Aufwecken + + + Änderungshistorie + Seit dem letzten Update + Changelog kann nicht geladen werden + + + Anzeigegröße, Schriftart und Text individuell anpassen + + + Eine schwebende Schaltfläche zum Drehen einblenden + + + Ladeanimation + Animation anzeigen, wenn das Gerät angeschlossen wird + + + Fußzeile + Datennutzung + In der Fußzeile der Schnelleinstellungen die Datennutzung anzeigen + + + Globales VPN + Gesamten Datenverkehr auf dem Gerät durch dieses VPN leiten, einschließlich des Arbeitsprofils und anderer Benutzer. + Gesamten Datenverkehr auf dem Gerät durch dieses VPN leiten, einschließlich des Arbeitsprofils und anderer Benutzer. Hinweis: Wenn diese Option aktiviert ist, kannst du kein separates VPN in einem Arbeitsprofil oder für andere Benutzer verwenden + Du musst zuerst alle aktiven VPN-Verbindungen deaktivieren, um dies zu aktivieren + + + Animation der Zurück-Geste + Einen animierten Pfeil für die Zurück-Geste anzeigen + + + Akkustandbalken + Akkustandbalken auf dem Ambient Display anzeigen + Akkustandbalken beim Laden + Während des Ladevorgangs einen Akkustandbalken auf dem Ambient Display anzeigen + Akkustandbalken immer anzeigen + Akkustandbalken immer auf dem Ambient Display anzeigen + + + Inaktivitätsdisplay beim Laden + Zeit und Informationen werden ausschließlich beim Laden angezeigt + + + Hintergrundprozesse + + + Andere Töne und Vibrationen + + + App-Lautstärke + Lautstärkeregelung per App + Ermöglicht die Einstellung unterschiedlicher Lautstärken für jede App.\nHINWEIS: Dadurch wird der Multi-Audio-Fokus aktiviert + + + Zeitplan für Inaktivitätsdisplay + Sonnenuntergang + Sonnenaufgang + Schaltet sich von Sonnenuntergang bis zu einer bestimmten Uhrzeit ein + Schaltet sich ab einer bestimmten Uhrzeit bis zum Sonnenaufgang ein + + + Taschenerkennung + Bildschirm- und Tasteneingaben blockieren, wenn das Gerät in der Tasche steckt + + + Über Pulse + Pulse ist eine brillante Audiovisualisierung, wenn Musik auf dem Gerät abgespielt wird + Audiovisualisierung + Pulse + Audiovisualisierung für Navigationsleiste, Sperrbildschirm und Ambient Screen + Pulse für Navigationsleiste + Visualisiert Audio in der Navigationsleiste + Pulse auf Sperrbildschirm + Visualisiert Audio auf dem Sperrbildschirm + Pulse für Ambient Screen + Visualisiert Audio auf dem Ambient Screen (Umgebungspuls) + Darstellungsmodus + Verblassende Blöcke + Durchgehende Balken + Einstellungen für verblassende Blöcke + Empfindlichkeit + Geschwindigkeit des Lavalampeneffekts + Anzahl durchgezogener Balken + Deckkraft durchgezogener Balken + Einstellungen für durchgezogene Balken + Farbe + Akzentfarbe + Benutzerdefiniert + Lavalampe + Albumcover + Farbe auswählen + Glättung aktivieren + Alle Balken werden flüssiger animiert + Balkenbreite + Balkenabstand + Blockgröße + Blockabstand + Abgerundete Ecken + Durchgezogene Linien mit abgerundeter Spitze + + + Gravitation + Unten + Oben + Mittig + Mittig gespiegelt + Den mittig gespiegelten Modus aktivieren + Vertikal gespiegelt + Erzeugt einen weiteren Pulse, der vertikal gedreht ist + + + Island-Benachrichtigungen + Pop-up-Benachrichtigungen im „Dynamic Island“-Stil anzeigen + Mit der von iOS’ Dynamic Island inspirierten Funktion der Island-Benachrichtigungen werden Androids Pop-up-Benachrichtigungen zu einem Schnellzugriff und einem kompakten Benachrichtigungstool umgestaltet.\n\nDie Schnellaktionen der Island haben u. a. die folgenden Funktionen:\n1. Tippen auf die Island-Benachrichtigung: Anruf entgegennehmen.\n2. Gedrückthalten der Island-Benachrichtigung: Anruf abweisen.\n3. Gedrückthalten der Island-Benachrichtigung: Aufklappen der Island-Benachrichtigungs-Vorschau. + + Aktuelle Wiedergabe + Aktuell gespieltes Lied im Dynamic-Island-Stil anzeigen + + + Statusleisten-Lyric + Lyrik in der Statusleiste anzeigen (App-Unterstützung erforderlich) + Statusleisten-Lyric aktivieren + + + Schnelleinstellungen + Umschalten der Schnelleinstellungen auf dem sicheren Sperrbildschirm zulassen + + + Doppelt Tippen + Doppelt tippen, um einen schnellen Blick auf das Smartphone-Display zu werfen + Benachrichtigungen durch doppeltes Tippen anzeigen + + + Schriftstil der Uhr + Schrift der Sperrbildschirmuhr festlegen + diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 2a147fa3856..15a1d1416d1 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -1331,7 +1331,7 @@ "Baseband-Version" "Kernel-Version" "Build-Nummer" - "Google Play-Systemupdate" + "Mainline-Systemupdate" "Akkuinformationen" "Nicht verfügbar" "Speicher" @@ -1602,7 +1602,7 @@ "Sicherheitsinformationen & rechtliche Hinweise" "Urheberrecht" "Lizenz" - "Lizenzen für Google Play-Systemupdates" + "Lizenzen für Mainline-Systemupdates" "Nutzungsbedingungen" "System-WebView-Lizenz" "Quellenangaben für Hintergründe" diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index f8c58ef613c..272961b22c8 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -1331,7 +1331,7 @@ "Έκδοση βασικού φάσματος" "Έκδοση Kernel" "Αριθμός έκδοσης" - "Ενημέρωση συστήματος Google Play" + "Ενημέρωση συστήματος Mainline" "Πληροφορίες μπαταρίας" "Δεν είναι διαθέσιμο" "Αποθηκευτικός χώρος" @@ -1602,7 +1602,7 @@ "Εγχειρίδιο ασφάλειας και κανονισμών" "Πνευματικά δικαιώματα" "Άδεια" - "Άδειες ενημέρωσης συστήματος Google Play" + "Άδειες ενημέρωσης συστήματος Mainline" "Όροι και προϋποθέσεις" "Άδεια συστήματος WebView" "Πληροφορίες συντελεστών ταπετσαρίας" diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml index ecea4aeab43..47cb7b5cb50 100644 --- a/res/values-en-rAU/strings.xml +++ b/res/values-en-rAU/strings.xml @@ -1331,7 +1331,7 @@ "Baseband version" "Kernel version" "Build number" - "Google Play system update" + "Mainline system update" "Battery information" "Not available" "Storage" @@ -1602,7 +1602,7 @@ "Safety and regulatory manual" "Copyright" "Licence" - "Google Play system update licences" + "Mainline system update licences" "Terms and conditions" "System WebView Licence" "Wallpaper credits" @@ -3985,7 +3985,7 @@ "Memory usage" "App usage" "Details" - "%1$s avg memory used in last 3 hours" + "%1$s average memory used in last 3 hours" "No memory used in last 3 hours" "Sort by avg. use" "Sort by max. use" diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml index 8c4154b1cae..6741ce54306 100644 --- a/res/values-en-rCA/strings.xml +++ b/res/values-en-rCA/strings.xml @@ -1331,7 +1331,7 @@ "Baseband version" "Kernel version" "Build number" - "Google Play system update" + "Mainline system update" "Battery information" "Not available" "Storage" @@ -1602,7 +1602,7 @@ "Safety & regulatory manual" "Copyright" "License" - "Google Play system update licenses" + "Mainline system update licenses" "Terms and conditions" "System WebView License" "Wallpaper credits" @@ -3985,7 +3985,7 @@ "Memory usage" "App usage" "Details" - "%1$s avg memory used in last 3 hours" + "%1$s average memory used in last 3 hours" "No memory used in last 3 hours" "Sort by avg use" "Sort by max use" diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml index 18a664961db..98cc4aa513c 100644 --- a/res/values-en-rGB/strings.xml +++ b/res/values-en-rGB/strings.xml @@ -1331,7 +1331,7 @@ "Baseband version" "Kernel version" "Build number" - "Google Play system update" + "Mainline system update" "Battery information" "Not available" "Storage" @@ -1602,7 +1602,7 @@ "Safety and regulatory manual" "Copyright" "Licence" - "Google Play system update licences" + "Mainline system update licences" "Terms and conditions" "System WebView Licence" "Wallpaper credits" @@ -3985,7 +3985,7 @@ "Memory usage" "App usage" "Details" - "%1$s avg memory used in last 3 hours" + "%1$s average memory used in last 3 hours" "No memory used in last 3 hours" "Sort by avg. use" "Sort by max. use" diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml index 0c74ac73efc..a4bba3115f5 100644 --- a/res/values-en-rIN/strings.xml +++ b/res/values-en-rIN/strings.xml @@ -1331,7 +1331,7 @@ "Baseband version" "Kernel version" "Build number" - "Google Play system update" + "Mainline system update" "Battery information" "Not available" "Storage" @@ -1602,7 +1602,7 @@ "Safety and regulatory manual" "Copyright" "Licence" - "Google Play system update licences" + "Mainline system update licences" "Terms and conditions" "System WebView Licence" "Wallpaper credits" @@ -3985,7 +3985,7 @@ "Memory usage" "App usage" "Details" - "%1$s avg memory used in last 3 hours" + "%1$s average memory used in last 3 hours" "No memory used in last 3 hours" "Sort by avg. use" "Sort by max. use" diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml index dc01e6217a8..647c24890b2 100644 --- a/res/values-en-rXC/strings.xml +++ b/res/values-en-rXC/strings.xml @@ -1331,7 +1331,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎‏‎Baseband version‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‎‎‏‏‎‎‎‎‏‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‏‏‏‎‎‏‎‏‎‏‎‏‎‎‎‏‏‎‎‎‎‏‎Kernel version‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‏‎‏‎‎‏‎‏‏‏‏‎‏‎‎‏‎‏‏‏‎‎‏‎‎‏‏‎‎‏‎‎‎‎‏‏‎‎‏‎‎Build number‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‏‎‏‎‏‏‎‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎Google Play system update‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‏‎‏‎‏‏‎‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎Mainline system update‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‎‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‎‎‏‎‏‎‏‎‎‏‏‏‏‎‏‎‎‎‎Battery information‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‏‏‎‏‏‎‎‎‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‎‏‏‎‎Not available‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‎‎‏‏‎‎‏‎‏‎Storage‎‏‎‎‏‎" @@ -1602,7 +1602,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‏‎‏‎‎‎‏‎‏‏‎‎‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‎‎‎‎Safety & regulatory manual‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‎‏‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‏‏‏‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‎‎‏‎Copyright‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‏‎‎‎‏‏‏‏‎‎‎‎‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎License‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‎Google Play system update licenses‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‏‏‎‏‏‎‎‎Mainline system update licenses‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‏‎‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‎‎‎‎‎‏‎‏‏‏‎‏‎‎‏‏‎‏‎‎‎‎‎‏‏‏‎‏‏‏‎‎‏‎Terms and conditions‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‏‎‎‎‏‎‏‎‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‏‎‏‏‏‏‎‎‏‏‎‏‏‎‎‏‎‎‏‎‏‎System WebView License‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‏‏‎‎‏‏‏‎‎‎‎‎Wallpaper credits‎‏‎‎‏‎" @@ -3985,7 +3985,7 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‏‎‏‎‏‏‎‎‎‏‎‎‎‏‏‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‎‎‎‎‏‎‏‎‏‎‎‏‎Memory usage‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‎‎‎‏‎‎‎‎‎‎‎‏‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‎‏‎App usage‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‎‏‏‎‎‏‏‎‎‏‎‏‎‏‎‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‎‏‏‎‎‎‏‏‎Details‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‏‏‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‎‏‎‏‎‏‎‎‏‏‏‎‎‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ avg memory used in last 3 hours‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‏‏‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‎‏‎‏‎‏‎‎‏‏‏‎‎‎‏‎‎‏‏‎%1$s‎‏‎‎‏‏‏‎ average memory used in last 3 hours‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‏‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‏‏‎‎‎‏‎‎‎No memory used in last 3 hours‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‎‏‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‏‎‏‏‏‎Sort by avg use‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‎‏‏‏‎‎‏‏‏‎‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎Sort by max use‎‏‎‎‏‎" diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 84cc2766624..359339edd11 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -1331,7 +1331,7 @@ "Versión de la banda base" "Versión del kernel" "Número de compilación" - "Actualización del sistema de Google Play" + "Actualización del sistema de Mainline" "Información de la batería" "No disponible" "Almacenamiento" @@ -1602,7 +1602,7 @@ "Manual de seguridad y normativas" "Derechos de autor" "Licencia" - "Licencias de la actualización del sistema de Google Play" + "Licencias de la actualización del sistema de Mainline" "Condiciones de uso" "Licencia de WebView del sistema" "Créditos de fondos de pantalla" diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml index 3bcd28c87ea..45f90bc7b8b 100644 --- a/res/values-et/strings.xml +++ b/res/values-et/strings.xml @@ -1331,7 +1331,7 @@ "Põhiribaversioon" "Tuuma versioon" "Järgunumber" - "Google Play süsteemivärskendus" + "Mainline süsteemivärskendus" "Akuteave" "Pole saadaval" "Salvestusruum" @@ -1602,7 +1602,7 @@ "Ohutus- ja regulatiivjuhend" "Autoriõigus" "Litsents" - "Google Play süsteemivärskenduste litsentsid" + "Mainline süsteemivärskenduste litsentsid" "Nõuded ja tingimused" "Süsteemi WebView\' litsents" "Taustapildi autor" diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index 2b8b0100faa..43e2185c3cc 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -1331,7 +1331,7 @@ "Oinarri-bandaren bertsioa" "Kernel bertsioa" "Konpilazio-zenbakia" - "Google Play-ren sistemaren eguneratzea" + "Mainline-ren sistemaren eguneratzea" "Bateriari buruzko informazioa" "Ez dago erabilgarri" "Biltegiratzeko tokia" @@ -1602,7 +1602,7 @@ "Segurtasunaren eta arauen eskuliburua" "Copyrighta" "Lizentzia" - "Google Play-ko sistemaren eguneratzeen lizentziak" + "Mainline-ko sistemaren eguneratzeen lizentziak" "Zehaztapenak eta baldintzak" "Sistemaren WebView lizentzia" "Horma-paperen kredituak" diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index da607544504..dc984f4cad9 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -1331,7 +1331,7 @@ "نسخهٔ باند پایه" "نسخهٔ اصلی" "شمارهٔ ساخت" - "‏به‌روزرسانی سیستم Google Play" + "‏به‌روزرسانی سیستم Mainline" "اطلاعات باتری" "موجود نیست" "حافظه" @@ -1602,7 +1602,7 @@ "راهنمای ایمنی و مقررات نظارتی" "حق نشر" "مجوز" - "‏مجوزهای به‌روزرسانی سیستم Google Play" + "‏مجوزهای به‌روزرسانی سیستم Mainline" "شرایط و مقررات" "مجوز وب‌نمای سیستم" "دست‌اندرکاران کاغذدیواری" diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index 88d422a15de..cc020d6a505 100644 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -1331,7 +1331,7 @@ "Baseband-versio" "Kernel-versio" "Ohjelmistoversion numero" - "Google Play ‑järjestelmäpäivitys" + "Mainline ‑järjestelmäpäivitys" "Akun tiedot" "Ei käytettävissä" "Tallennustila" @@ -1602,7 +1602,7 @@ "Turvallisuus- ja säädöstenmukaisuusopas" "Tekijänoikeudet" "Käyttölupa" - "Google Play ‑järjestelmäpäivitysten lisenssit" + "Mainline ‑järjestelmäpäivitysten lisenssit" "Käyttöehdot" "WebView-järjestelmälisenssi" "Taustakuvan tekijä" diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml index 1648447e7f3..863699fc21f 100644 --- a/res/values-fr-rCA/strings.xml +++ b/res/values-fr-rCA/strings.xml @@ -1331,7 +1331,7 @@ "Version de bande de base" "Version du noyau" "Numéro de version" - "Mise à jour du système Google Play" + "Mise à jour du système Mainline" "Informations sur la pile" "Non disponible" "Stockage" @@ -1602,7 +1602,7 @@ "Guide relatif à la sécurité et à la réglementation" "Droits d\'auteur" "Licence" - "Licences des mises à jour système pour Google Play" + "Licences des mises à jour système pour Mainline" "Conditions d\'utilisation" "Licence WebView du système" "À propos du fond d\'écran" @@ -4362,7 +4362,7 @@ "Journal de sécurité le plus récent" "Aucune" "Applis installées" - "Le nombre d\'applis est estimé. Il peut exclure les applis installées à partir d\'une source autre que la boutique Google Play Store." + "Le nombre d\'applis est estimé. Il peut exclure les applis installées à partir d\'une source autre que la boutique Mainline Store." "{count,plural, =1{Minimum de # appli}one{Minimum de # appli}other{Minimum de # applis}}" "Autorisations de localisation" "Autorisations du microphone" diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml index 89d8bfe98ea..eab43a535ca 100644 --- a/res/values-gl/strings.xml +++ b/res/values-gl/strings.xml @@ -1331,7 +1331,7 @@ "Versión de banda base" "Versión de kernel" "Número de compilación" - "Actualización do sistema de Google Play" + "Actualización do sistema de Mainline" "Información sobre a batería" "Non dispoñible" "Almacenamento" diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml index 6362340085d..43dfeb17fa0 100644 --- a/res/values-gu/strings.xml +++ b/res/values-gu/strings.xml @@ -1331,7 +1331,7 @@ "બેઝબૅન્ડ વર્ઝન" "કર્નલ વર્ઝન" "બિલ્ડ નંબર" - "Google Play સિસ્ટમ અપડેટ" + "Mainline સિસ્ટમ અપડેટ" "બૅટરી માહિતી" "ઉપલબ્ધ નથી" "સ્ટોરેજ" @@ -1602,7 +1602,7 @@ "સુરક્ષા અને નિયમન મેન્યુઅલ" "કોપિરાઇટ" "લાઇસન્સ" - "Google Play સિસ્ટમ અપડેટના લાઇસન્સ" + "Mainline સિસ્ટમ અપડેટના લાઇસન્સ" "નિયમો અને શરતો" "સિસ્ટમ વેબવ્યૂ લાઇસન્સ" "વૉલપેપર ક્રેડિટ" diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index 96984bbae4f..c13bc737bdf 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -1331,7 +1331,7 @@ "मोबाइल रेडियो (बेसबैंड वर्शन)" "Kernel वर्शन" "बिल्‍ड नंबर" - "Google Play का सिस्टम अपडेट" + "Mainline का सिस्टम अपडेट" "बैटरी की जानकारी" "उपलब्ध नहीं" "स्टोरेज" @@ -1602,7 +1602,7 @@ "सुरक्षा और नियमों से जुड़ी गाइड" "कॉपीराइट" "लाइसेंस" - "Google Play के सिस्टम अपडेट के लाइसेंस" + "Mainline के सिस्टम अपडेट के लाइसेंस" "नियम और शर्तें" "सिस्टम वेबव्यू लाइसेंस" "वॉलपेपर क्रेडिट" diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index d16312122d6..4fa7bcd4076 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -1331,7 +1331,7 @@ "Osnovna verzija" "Verzija jezgre" "Broj međuverzije" - "Ažuriranje sustava s Google Playa" + "Ažuriranje sustava s Mainlinea" "Informacije o bateriji" "Nije dostupno" "Pohrana" @@ -1602,7 +1602,7 @@ "Priručnik o sigurnosti i zakonskim propisima" "Autorska prava" "Licenca" - "Licence za ažuriranje sustava s Google Playa" + "Licence za ažuriranje sustava s Mainlinea" "Uvjeti i odredbe" "WebView licenca sustava" "Zasluge za pozadine" diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index 11012878f4d..8f7b1d6bcaa 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -1331,7 +1331,7 @@ "Alapsáv verziója" "Kernel verziója" "Buildszám" - "Google Play-rendszerfrissítés" + "Mainline-rendszerfrissítés" "Akkumulátoradatok" "Nem érhető el" "Tárhely" @@ -1602,7 +1602,7 @@ "Biztonsági és az előírásokkal kapcsolatos útmutató" "Szerzői jog" "Licenc" - "Google Play rendszerfrissítési licencek" + "Mainline rendszerfrissítési licencek" "Általános Szerződési Feltételek" "WebView-rendszerlicenc" "Háttérképek készítői" diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml index ad6c011c3de..4dc61cbfc74 100644 --- a/res/values-hy/strings.xml +++ b/res/values-hy/strings.xml @@ -1331,7 +1331,7 @@ "Baseband-ի տարբերակը" "Միջուկի տարբերակը" "Կառուցման համարը" - "Google Play-ի համակարգային թարմացում" + "Mainline-ի համակարգային թարմացում" "Տեղեկություններ մարտկոցի մասին" "Անհասանելի է" "Տարածք" @@ -1602,7 +1602,7 @@ "Անվտանգության և ստանդարտների ձեռնարկ" "Հեղինակային իրավունք" "Արտոնագիր" - "Google Play-ի համակարգային թարմացման լիցենզիաներ" + "Mainline-ի համակարգային թարմացման լիցենզիաներ" "Կանոններն ու պայմանները" "Համակարգի WebView-ի լիցենզիա" "Պաստառների հեղինակներ" @@ -4394,7 +4394,7 @@ "Ֆինանսավորվող սարքի մասին" "Կարգավորման ժամանակ ձեր բանկը կարող է փոխել կարգավորումները և այս սարքում ծրագրեր տեղադրել։\n\nԵթե դուք բաց թողնեք վճարումը, բանկը կարող է կողպել ձեր սարքը և փոխել սարքի կարգավորումները։\n\nԼրացուցիչ տեղեկությունների համար դիմեք ձեր բանկին։" "Եթե ձեր սարքը ապառիկ է գնված, դուք կարող եք՝" - "Տեղադրել կողմնակի հավելվածներ (Google Play-ից դուրս)" + "Տեղադրել կողմնակի հավելվածներ (Mainline-ից դուրս)" "Վերագործարկել սարքը անվտանգ ռեժիմում" "Ավելացնել սարքին մի քանի օգտատերեր" "Փոխել սարքի ամսաթիվը, ժամը և ժամային գոտին" diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index e6d2c3787a4..c6b0821208c 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -1331,7 +1331,7 @@ "Versi pita basis" "Versi kernel" "Nomor build" - "Update sistem Google Play" + "Update sistem Mainline" "Informasi baterai" "Tidak tersedia" "Penyimpanan" @@ -1602,7 +1602,7 @@ "Manual keselamatan & peraturan" "Hak cipta" "Lisensi" - "Lisensi update sistem Google Play" + "Lisensi update sistem Mainline" "Persyaratan dan ketentuan" "Lisensi WebView Sistem" "Kredit wallpaper" diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml index b29105bfd69..d7e97c69767 100644 --- a/res/values-is/strings.xml +++ b/res/values-is/strings.xml @@ -1331,7 +1331,7 @@ "Grunnbandsútgáfa" "Kjarnaútgáfa" "Útgáfunúmer smíðar" - "Kerfisuppfærsla Google Play" + "Kerfisuppfærsla Mainline" "Upplýsingar um rafhlöðu" "Ekki tiltækt" "Geymsla" @@ -1602,7 +1602,7 @@ "Handbók um öryggi og reglur" "Höfundarréttur" "Leyfi" - "Kerfisuppfærsluleyfi Google Play" + "Kerfisuppfærsluleyfi Mainline" "Skilmálar" "Leyfi vefglugga í kerfi" "Höfundur veggfóðurs" diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 750cc43845b..afd39fe6e5a 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -1331,7 +1331,7 @@ "Versione banda di base" "Versione kernel" "Numero build" - "Aggiornamento di sistema Google Play" + "Aggiornamento di sistema Mainline" "Informazioni sulla batteria" "Non disponibile" "Spazio di archiviazione" @@ -1602,7 +1602,7 @@ "Manuale su normative e sicurezza" "Copyright" "Licenza" - "Licenze aggiornamenti di sistema Google Play" + "Licenze aggiornamenti di sistema Mainline" "Termini e condizioni" "Licenza di sistema per WebView" "Attribuzioni sfondi" diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index ed67aec8c16..c07dfdfb494 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -1331,7 +1331,7 @@ "גרסת פס בסיס" "גרסת ליבה" "‏מספר Build" - "‏עדכון מערכת של Google Play" + "‏עדכון מערכת של Mainline" "מידע על הסוללה" "לא זמין" "אחסון" @@ -1602,7 +1602,7 @@ "מדריך בטיחות ותקנות" "זכויות יוצרים" "רישיון" - "‏רישיונות לעדכון מערכת של Google Play" + "‏רישיונות לעדכון מערכת של Mainline" "תנאים והגבלות" "‏רישיון WebView במערכת" "קרדיטים של טפטים" diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 99fc0342d6e..344bcb97bfc 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -1331,7 +1331,7 @@ "ベースバンド バージョン" "カーネル バージョン" "ビルド番号" - "Google Play システム アップデート" + "Mainline システム アップデート" "バッテリー情報" "該当なし" "ストレージ" @@ -1602,7 +1602,7 @@ "安全と規制に関する情報" "著作権" "ライセンス" - "Google Play システム アップデートのライセンス" + "Mainline システム アップデートのライセンス" "利用規約" "システムのWebViewライセンス" "壁紙のクレジット" diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml index ef484759551..ca3a5644976 100644 --- a/res/values-ka/strings.xml +++ b/res/values-ka/strings.xml @@ -1331,7 +1331,7 @@ "Baseband-ის ვერსია" "kernel-ის ვერსია" "ანაწყობის ნომერი" - "Google Play სისტემის განახლება" + "Mainline სისტემის განახლება" "ინფორმაცია ბატარეის შესახებ" "მიუწვდომელი" "მეხსიერება" @@ -1602,7 +1602,7 @@ "უსაფრთხოება და ნორმებთან შესაბამისობა" "საავტორო უფლებები" "ლიცენზია" - "Google Play სისტემის განახლების ლიცენზიები" + "Mainline სისტემის განახლების ლიცენზიები" "წესები და პირობები" "სისტემის ვებ მიმოხილვის ლიცენზია" "ფონის შემქმნელები" diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml index fcc1206e08b..a555307de02 100644 --- a/res/values-kk/strings.xml +++ b/res/values-kk/strings.xml @@ -1331,7 +1331,7 @@ "Радиомодуль нұсқасы" "Ядро нұсқасы" "Құрама нөмірі" - "Google Play арқылы жүйені жаңарту" + "Mainline арқылы жүйені жаңарту" "Батарея ақпараты" "Қолжетімсіз" "Жад" @@ -1602,7 +1602,7 @@ "Қауіпсіздік және нормативтік ережелер" "Авторлық құқықтар" "Лицензия" - "Google Play арқылы жүйені жаңарту лицензиялары" + "Mainline арқылы жүйені жаңарту лицензиялары" "Шарттары" "Жүйенің WebView лицензиясы" "Тұсқағаз авторлары" diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml index 9c7210411dd..145b95f493f 100644 --- a/res/values-km/strings.xml +++ b/res/values-km/strings.xml @@ -1331,7 +1331,7 @@ "កំណែបេសប៊េន" "កំណែ​ខឺណែល" "លេខ​កំណែបង្កើត" - "បច្ចុប្បន្នភាព​ប្រព័ន្ធ Google Play" + "បច្ចុប្បន្នភាព​ប្រព័ន្ធ Mainline" "ព័ត៌មាន​ថ្ម" "មិនមាន" "ទំហំ​ផ្ទុក" @@ -1602,7 +1602,7 @@ "សៀវភៅណែនាំសុវត្ថិភាព និងបទបញ្ជា" "រក្សាសិទ្ធិ" "អាជ្ញាប័ណ្ណ" - "អាជ្ញាបណ្ណបច្ចុប្បន្នភាព​ប្រព័ន្ធ Google Play" + "អាជ្ញាបណ្ណបច្ចុប្បន្នភាព​ប្រព័ន្ធ Mainline" "លក្ខខណ្ឌ" "អាជ្ញាប័ណ្ណ System WebView" "ក្រេឌីត​ផ្ទាំងរូបភាព" diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml index 28bafcffaa7..76c6dbd8691 100644 --- a/res/values-kn/strings.xml +++ b/res/values-kn/strings.xml @@ -1331,7 +1331,7 @@ "ಬೇಸ್‌ಬ್ಯಾಂಡ್ ಆವೃತ್ತಿ" "ಕೆರ್ನಲ್ ಆವೃತ್ತಿ" "ಬಿಲ್ಡ್ ಸಂಖ್ಯೆ" - "Google Play ಸಿಸ್ಟಂ ಅಪ್‌ಡೇಟ್" + "Mainline ಸಿಸ್ಟಂ ಅಪ್‌ಡೇಟ್" "ಬ್ಯಾಟರಿ ಮಾಹಿತಿ" "ಲಭ್ಯವಿಲ್ಲ" "ಸಂಗ್ರಹಣೆ" @@ -1602,7 +1602,7 @@ "ಸುರಕ್ಷತೆ ಮತ್ತು ನಿಯಂತ್ರಣಾ ಕೈಪಿಡಿ" "ಹಕ್ಕುಸ್ವಾಮ್ಯ" "ಪರವಾನಗಿ" - "Google Play ಸಿಸ್ಟಂ ಅಪ್‌ಡೇಟ್‌ ಪರವಾನಗಿಗಳು" + "Mainline ಸಿಸ್ಟಂ ಅಪ್‌ಡೇಟ್‌ ಪರವಾನಗಿಗಳು" "ನಿಯಮಗಳು ಮತ್ತು ಷರತ್ತುಗಳು" "ಸಿಸ್ಟಂ ವೆಬ್‌ವೀಕ್ಷಣೆ ಪರವಾನಗಿ" "ವಾಲ್‌ಪೇಪರ್ ಕ್ರೆಡಿಟ್‌ಗಳು" diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index 6bcd8ca7c8c..7b6cef8bd6f 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -1331,7 +1331,7 @@ "베이스밴드 버전" "커널 버전" "빌드 번호" - "Google Play 시스템 업데이트" + "Mainline 시스템 업데이트" "배터리 정보" "표시할 수 없음" "저장용량" @@ -1602,7 +1602,7 @@ "안전 및 규제 설명서" "저작권" "라이선스" - "Google Play 시스템 업데이트 라이선스" + "Mainline 시스템 업데이트 라이선스" "약관" "시스템 WebView 라이선스" "배경화면 출처 표시" diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml index cc80db87556..88fc6aeb600 100644 --- a/res/values-ky/strings.xml +++ b/res/values-ky/strings.xml @@ -1331,7 +1331,7 @@ "Радиомодуль версиясы" "Өзөктүн версиясы" "Курама номери" - "Google Play системасын жаңыртуу" + "Mainline системасын жаңыртуу" "Батарея жөнүндө маалымат" "Жеткиликтүү эмес" "Сактагыч" @@ -1602,7 +1602,7 @@ "Коопсуздук жана стандарттарга жооп берүү" "Автордук укук" "Уруксаттама" - "Google Play системасын жаңыртуу уруксаттамалары" + "Mainline системасын жаңыртуу уруксаттамалары" "Пайдалануу шарттары" "System WebView уруксаттамасы" "Тушкагаздын авторлору" diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml index 16e04a2788c..d223a720034 100644 --- a/res/values-lo/strings.xml +++ b/res/values-lo/strings.xml @@ -1331,7 +1331,7 @@ "ເວີຊັນເບສແບນ" "ເວີຊັນຂອງເຄີນເນວ" "ໝາຍເລກ Build" - "ການອັບເດດລະບົບ Google Play" + "ການອັບເດດລະບົບ Mainline" "ຂໍ້ມູນແບັດເຕີຣີ" "ຍັງບໍ່ສາມາດໃຊ້ໄດ້" "ພື້ນທີ່ຈັດເກັບຂໍ້ມູນ" @@ -1602,7 +1602,7 @@ "ຄູ່ມືຄວາມປອດໄພ ແລະ ການຄວບຄຸມ" "ສະຫງວນລິຂະສິດ" "ລິຂະສິດ" - "ໃບອະນຸຍາດອັບເດດລະບົບ Google Play" + "ໃບອະນຸຍາດອັບເດດລະບົບ Mainline" "ຂໍ້ກຳນົດ ແລະເງື່ອນໄຂ" "ໃບ​ອະ​ນຸ​ຍາດ WebView ຂອງ​ລະ​ບົບ" "ເຄຣດິດຮູບພື້ນຫຼັງ" diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index 6da4a558d4a..b9a1df58556 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -1331,7 +1331,7 @@ "Nemoduliuojamo perdavimo versija" "„Kernel“ versija" "Versijos numeris" - "„Google Play“ sistemos naujinys" + "„Mainline“ sistemos naujinys" "Akumuliatoriaus informacija" "Negalima" "Saugykla" @@ -1602,7 +1602,7 @@ "Saugos ir reguliavimo vadovas" "Autorių teisės" "Licencija" - "„Google Play“ sistemos naujinių licencijos" + "„Mainline“ sistemos naujinių licencijos" "Taisyklės ir nuostatos" "Sistemos „WebView“ licencija" "Ekrano fono kreditai" diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml index f956299a37f..bc6d101fd17 100644 --- a/res/values-mk/strings.xml +++ b/res/values-mk/strings.xml @@ -1331,7 +1331,7 @@ "Верзија на немодулиран опсег" "Верзија на кернел" "Број на верзија" - "Системско ажурирање од Google Play" + "Системско ажурирање од Mainline" "Информации за батеријата" "Не е достапна" "Простор" @@ -1602,7 +1602,7 @@ "Упатство за безбедност и регулатива" "Авторски права" "Лиценца" - "Лиценци за системско ажурирање од Google Play" + "Лиценци за системско ажурирање од Mainline" "Одредби и услови" "Системска лиценца за WebView" "Заслуги за тапети" diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml index 6ac501d60cc..1c1bb83899d 100644 --- a/res/values-ml/strings.xml +++ b/res/values-ml/strings.xml @@ -1331,7 +1331,7 @@ "ബെയിസ്ബാൻഡ് പതിപ്പ്" "പ്രധാന പതിപ്പ്" "ബിൽഡ് നമ്പർ" - "Google Play സിസ്‌റ്റം അപ്‌ഡേറ്റ്" + "Mainline സിസ്‌റ്റം അപ്‌ഡേറ്റ്" "ബാറ്ററി വിവരം" "ലഭ്യമല്ല" "സ്റ്റോറേജ്" @@ -1602,7 +1602,7 @@ "സുരക്ഷയുടെയും നിയന്ത്രണങ്ങളുടെയും മാനുവൽ" "പകർപ്പവകാശം" "ലൈസന്‍സ്" - "Google Play സിസ്‌റ്റം അപ്‌ഡേറ്റ് ‌ലൈസൻസുകൾ" + "Mainline സിസ്‌റ്റം അപ്‌ഡേറ്റ് ‌ലൈസൻസുകൾ" "നിബന്ധനകളും വ്യവസ്ഥകളും" "സിസ്‌റ്റം WebView ലൈസൻസ്" "വാൾപേപ്പർ ക്രെഡിറ്റുകൾ" diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml index 2cabffdd4a4..0223e84301f 100644 --- a/res/values-mn/strings.xml +++ b/res/values-mn/strings.xml @@ -1331,7 +1331,7 @@ "Долгион баригчийн хувилбар" "Кернел хувилбар" "Хийцийн дугаар" - "Google Play-н систем шинэчлэлт" + "Mainline-н систем шинэчлэлт" "Батарейн мэдээлэл" "Боломжгүй" "Хадгалах сан" @@ -1602,7 +1602,7 @@ "Аюулгүй байдал, зохицуулалтын гарын авлага" "Зохиогчийн эрх" "Лиценз" - "Google Play-н систем шинэчлэлтийн лиценз" + "Mainline-н систем шинэчлэлтийн лиценз" "Ерөнхий нөхцлүүд" "Системийн WebView-ийн лиценз" "Дэлгэцийн зураг дээрх оролцогчид" diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml index 2e3da3a94e6..7720588cad1 100644 --- a/res/values-mr/strings.xml +++ b/res/values-mr/strings.xml @@ -1331,7 +1331,7 @@ "बेसबँड आवृत्ती" "कर्नेल आवृत्ती" "बिल्ड नंबर" - "Google Play सिस्टीम अपडेट" + "Mainline सिस्टीम अपडेट" "बॅटरी संबंधित माहिती" "उपलब्ध नाही" "स्टोरेज" @@ -1602,7 +1602,7 @@ "सुरक्षितता आणि नियमन विषयक पुस्तिका" "कॉपीराइट" "परवाना" - "Google Play सिस्टीम अपडेट परवाने" + "Mainline सिस्टीम अपडेट परवाने" "अटी आणि नियम" "सिस्टम WebView परवाना" "वॉलपेपर श्रेय" diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml index d693b904799..ebe1d2dad7f 100644 --- a/res/values-ms/strings.xml +++ b/res/values-ms/strings.xml @@ -1331,7 +1331,7 @@ "Versi jalur asas" "Versi inti" "Nombor binaan" - "Kemaskinian sistem Google Play" + "Kemaskinian sistem Mainline" "Maklumat bateri" "Tidak tersedia" "Storan" @@ -1602,7 +1602,7 @@ "Manual keselamatan & kawal selia" "Hak cipta" "Lesen" - "Lesen kemaskinian sistem Google Play" + "Lesen kemaskinian sistem Mainline" "Terma dan Syarat" "Lesen WebView Sistem" "Penghargaan kertas dinding" diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index a09d3018747..8d2e760f915 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -1331,7 +1331,7 @@ "Baseband ဗားရှင်း" "Kernel ဗားရှင်း" "တည်ဆောက်ပုံအမှတ်" - "Google Play စနစ် အပ်ဒိတ်" + "Mainline စနစ် အပ်ဒိတ်" "ဘက်ထရီအချက်အလက်" "မရရှိနိုင်ပါ" "သိုလှောင်ခန်း" @@ -1602,7 +1602,7 @@ "လုံခြုံမှုနှင့်စည်းမျဉ်း လမ်းညွှန်" "မူပိုင်ခွင့်" "လိုင်စင်" - "Google Play စနစ် အပ်ဒိတ်လုပ်ခြင်း လိုင်စင်များ" + "Mainline စနစ် အပ်ဒိတ်လုပ်ခြင်း လိုင်စင်များ" "သတ်မှတ်ချက်များနှင့် အခြေအနေများ" "စနစ် WebView လိုင်စင်" "နောက်ခံ ဖန်တီးသူများ" diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index 79524722b23..f2024b7f569 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -1331,7 +1331,7 @@ "Basisbåndversjon" "Kjerneversjon" "Delversjonsnummer" - "Google Play-systemoppdatering" + "Mainline-systemoppdatering" "Batteriinformasjon" "Ikke tilgjengelig" "Lagring" @@ -1602,7 +1602,7 @@ "Håndbok for sikkerhet og regelverk" "Opphavsrett" "Lisens" - "Google Play-system: oppdateringslisenser" + "Mainline-system: oppdateringslisenser" "Betingelser" "System WebView License" "Bakgrunner" diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml index 674943a2f5c..b45a3dc9df5 100644 --- a/res/values-ne/strings.xml +++ b/res/values-ne/strings.xml @@ -1331,7 +1331,7 @@ "बेसब्यान्डको संस्करण" "कर्नेलको संस्करण" "बिल्ड नम्बर" - "Google Play को सिस्टम अपडेट" + "Mainline को सिस्टम अपडेट" "ब्याट्रीसम्बन्धी जानकारी" "अनुपलब्ध" "भण्डारण" @@ -1602,7 +1602,7 @@ "सुरक्षा र नियामक मार्गदर्शन" "प्रतिलिपि अधिकार" "लाइसेन्स" - "Google Play सिस्टम अपडेट गर्ने इजाजतपत्र" + "Mainline सिस्टम अपडेट गर्ने इजाजतपत्र" "सेवाका सर्तहरू" "सिस्टम वेबभ्यु लाइसेन्स" "वालपेपरका श्रेय" diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml index fb73e6ecc44..38aaff8507e 100644 --- a/res/values-night/colors.xml +++ b/res/values-night/colors.xml @@ -18,7 +18,8 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> #783BE5 #3F5FBD - @*android:color/material_grey_900 + @*android:color/background_device_default_dark + @*android:color/background_device_default_dark ?androidprv:attr/materialColorSurfaceBright #5F6368 diff --git a/res/values-night/themes.xml b/res/values-night/themes.xml index 141badbc9c6..9f6ea555039 100644 --- a/res/values-night/themes.xml +++ b/res/values-night/themes.xml @@ -22,8 +22,8 @@ @*android:color/primary_dark_device_default_settings @android:color/system_surface_container_dark - @android:color/black - ?attr/colorPrimaryDark + @android:color/transparent + @android:color/system_surface_container_dark + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index d972e138eec..f5122d47911 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -90,12 +90,14 @@ 48dp - 56dp + 104dp 24dp 20dp 40dp + + 104dp 8dp 24dp 24dp diff --git a/res/values/strings.xml b/res/values/strings.xml index f92fd2aca84..a227bfc8fc9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3056,7 +3056,7 @@ Android version - Android security update + Security update Model @@ -3071,8 +3071,8 @@ Kernel version Build number - - Google Play system update + + Mainline system update Battery information @@ -3500,11 +3500,11 @@ Erase all data (factory reset) - "
  • Music
  • \n
  • Photos
  • \n
  • Other user data
  • "
    +
  • Music
  • \n
  • Photos
  • \n
  • Other user data
  • - "
  • eSIMs
  • "
    +
  • eSIMs
  • - "\n\nThis will not cancel your mobile service plan. + \n\nThis will not cancel your mobile service plan. All of your personal information and downloaded apps will be deleted. You can\u2019t undo this action. @@ -3728,8 +3728,8 @@ License - - Google Play system update licenses + + Mainline system update licenses Terms and conditions @@ -5747,7 +5747,7 @@ Background time - %1$s%2$s + %1$s%2$s%3$s Low battery @@ -9871,7 +9871,7 @@ Details - %1$s avg memory used in last 3 hours + %1$s average memory used in last 3 hours No memory used in last 3 hours @@ -11428,7 +11428,7 @@ Depends on another setting - + com.google @@ -11439,7 +11439,7 @@ - Account + Google Account %d accounts @@ -11499,7 +11499,7 @@ Prevent ringing - Press Power & Volume Up together to + Press Power & Volume Up together to cycle between Shortcut to prevent ringing @@ -12134,7 +12134,7 @@ %1$s copied to clipboard. - + Profile picture, double tap to open Google Account Accessibility usage diff --git a/res/values/styles.xml b/res/values/styles.xml index 0bd04061d35..caca9e2a9f9 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -221,8 +221,6 @@ parent="@*android:style/TextAppearance.DeviceDefault.Body2"> true 11sp - - 0.072727273 @@ -736,7 +736,7 @@ 36dp 16sp ?android:attr/textColorPrimary - sans-serif + @*android:string/config_bodyFontFamily start @@ -752,7 +752,7 @@ wrap_content 16sp ?android:attr/textColorPrimary - sans-serif + @*android:string/config_bodyFontFamily start @@ -761,7 +761,7 @@ wrap_content 14sp ?android:attr/textColorSecondary - sans-serif + @*android:string/config_bodyFontFamily start @@ -791,14 +791,14 @@ diff --git a/res/values/themes.xml b/res/values/themes.xml index e91ca491213..fe37a9da0bc 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -203,8 +203,8 @@ true @*android:color/ripple_material_light - @android:color/white - ?attr/colorPrimaryDark + @android:color/transparent + @android:color/system_surface_container_light @android:color/system_surface_container_light diff --git a/res/values/vrr_strings.xml b/res/values/vrr_strings.xml new file mode 100644 index 00000000000..765122a234f --- /dev/null +++ b/res/values/vrr_strings.xml @@ -0,0 +1,12 @@ + + + + Refresh rate + %d Hz + %d Hz (Adaptive) + Adaptive refresh rate + Automatically lower the refresh rate when the display is idle, to save power. May cause flickering or color shift on certain displays + A higher refresh rate makes the display smoother, but increases power consumption + refresh, rate, smooth, peak, 60hz, 90hz, 120hz, 144hz, 165hz + Automatically raises the refresh rate from 60 to %1$d Hz for some content. Increases battery usage. + diff --git a/res/xml/accessibility_vibration_intensity_settings.xml b/res/xml/accessibility_vibration_intensity_settings.xml index f9a5578c517..889fd7aaa10 100644 --- a/res/xml/accessibility_vibration_intensity_settings.xml +++ b/res/xml/accessibility_vibration_intensity_settings.xml @@ -43,6 +43,26 @@ + + + + + + + + + @@ -85,4 +105,19 @@ + + + + + + + diff --git a/res/xml/accessibility_vibration_settings.xml b/res/xml/accessibility_vibration_settings.xml index 3ce92a6e303..1f0fa4abde0 100644 --- a/res/xml/accessibility_vibration_settings.xml +++ b/res/xml/accessibility_vibration_settings.xml @@ -43,6 +43,26 @@ + + + + + + + + + @@ -85,4 +105,19 @@ + + + + + + + diff --git a/res/xml/adaptive_playback_sound_settings.xml b/res/xml/adaptive_playback_sound_settings.xml new file mode 100644 index 00000000000..1483c1b3df3 --- /dev/null +++ b/res/xml/adaptive_playback_sound_settings.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + diff --git a/res/xml/always_on_display_schedule.xml b/res/xml/always_on_display_schedule.xml new file mode 100644 index 00000000000..127099f71ab --- /dev/null +++ b/res/xml/always_on_display_schedule.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/ambient_battery_settings.xml b/res/xml/ambient_battery_settings.xml new file mode 100644 index 00000000000..0b995187350 --- /dev/null +++ b/res/xml/ambient_battery_settings.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/res/xml/app_notification_settings.xml b/res/xml/app_notification_settings.xml index f96a375224e..6fd4497299b 100644 --- a/res/xml/app_notification_settings.xml +++ b/res/xml/app_notification_settings.xml @@ -97,6 +97,14 @@ settings:allowDividerAbove="true" settings:allowDividerBelow="false" /> + + + + + + + diff --git a/res/xml/auto_brightness_detail.xml b/res/xml/auto_brightness_detail.xml index 0584d776f00..af4fdb45a4c 100644 --- a/res/xml/auto_brightness_detail.xml +++ b/res/xml/auto_brightness_detail.xml @@ -32,6 +32,23 @@ settings:userRestriction="no_config_brightness" settings:controller="com.android.settings.display.AutoBrightnessDetailPreferenceController"/> + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/battery_settings.xml b/res/xml/battery_settings.xml new file mode 100644 index 00000000000..810c2065b1f --- /dev/null +++ b/res/xml/battery_settings.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + diff --git a/res/xml/button_settings.xml b/res/xml/button_settings.xml new file mode 100644 index 00000000000..7533d2b18ce --- /dev/null +++ b/res/xml/button_settings.xml @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/channel_notification_settings.xml b/res/xml/channel_notification_settings.xml index a3895e38c5e..f4aea30f215 100644 --- a/res/xml/channel_notification_settings.xml +++ b/res/xml/channel_notification_settings.xml @@ -81,24 +81,69 @@ android:icon="@drawable/ic_volume_ringer_vibrate" android:title="@string/notification_vibrate_title" /> + + + + + + + + + + + + + + + + @@ -106,14 +151,14 @@ diff --git a/res/xml/charging_control_settings.xml b/res/xml/charging_control_settings.xml new file mode 100644 index 00000000000..90e2c904fae --- /dev/null +++ b/res/xml/charging_control_settings.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml index b673a0839b0..fce4ccf09eb 100644 --- a/res/xml/configure_notification_settings.xml +++ b/res/xml/configure_notification_settings.xml @@ -98,7 +98,7 @@ android:summary="@string/lock_screen_notifs_redact_work_summary" settings:controller="com.android.settings.notification.RedactNotificationPreferenceController" /> - + + + + - - + + android:title="@string/notification_light_title" + android:summary="@string/notification_light_summary" + android:fragment="com.android.settings.derp.notificationlight.NotificationLightSettings" + settings:controller="com.android.settings.derp.notificationlight.NotificationLightPreferenceController" /> + + + + + diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml index 95aa8773c68..d66915369e4 100644 --- a/res/xml/connected_devices.xml +++ b/res/xml/connected_devices.xml @@ -60,6 +60,14 @@ settings:useAdminDisabledSummary="true" settings:userRestriction="no_config_bluetooth" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/dark_mode_settings.xml b/res/xml/dark_mode_settings.xml index 7f136594807..6937a320567 100644 --- a/res/xml/dark_mode_settings.xml +++ b/res/xml/dark_mode_settings.xml @@ -20,6 +20,11 @@ android:title="@string/dark_ui_mode" settings:keywords="@string/keywords_dark_ui_mode"> + + - @@ -62,4 +66,10 @@ settings:searchable="false" settings:controller="com.android.settings.display.darkmode.DarkModeCustomBedtimePreferenceController" /> + diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index 866a529dd8b..fa42cf54528 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -141,10 +141,10 @@ android:title="@string/color_temperature" android:summary="@string/color_temperature_desc" /> - + android:summary="@string/ota_disable_automatic_update_summary" /--> @@ -189,6 +189,12 @@ android:summary="@string/enable_adb_wireless_summary" settings:keywords="@string/keywords_adb_wireless" /> + + - - - - - - - + + + + + + + + + + + + - - - - - + settings:controller="com.android.settings.display.SmartAutoRotatePreferenceController" + settings:keywords="@string/keywords_auto_rotate" /> - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/double_tap_power_settings.xml b/res/xml/double_tap_power_settings.xml index fb5dd52b913..7896046d1cd 100644 --- a/res/xml/double_tap_power_settings.xml +++ b/res/xml/double_tap_power_settings.xml @@ -24,7 +24,7 @@ + app:lottie_rawRes="@raw/lottie_quickly_open_camera"/> + + + + + + + + + + + + + + + + - + + + + diff --git a/res/xml/gesture_navigation_settings.xml b/res/xml/gesture_navigation_settings.xml index a4b5af9d848..0b07b871d2e 100644 --- a/res/xml/gesture_navigation_settings.xml +++ b/res/xml/gesture_navigation_settings.xml @@ -20,9 +20,27 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:key="gesture_navigation_settings_page" - android:title="@string/gesture_settings_activity_title" + android:title="@string/edge_to_edge_navigation_title" settings:keywords="@string/keywords_gesture_navigation_settings"> + + + + + + + + + + + + + + + + + + + + + - - + + - diff --git a/res/xml/gestures.xml b/res/xml/gestures.xml index 035c7f7c801..f15633c86ea 100644 --- a/res/xml/gestures.xml +++ b/res/xml/gestures.xml @@ -41,12 +41,12 @@ settings:searchable="false" settings:controller="com.android.settings.gestures.DoubleTwistPreferenceController" /> - + settings:keywords="@string/keywords_system_navigation" />--> + + + + + + + + + + + + + + diff --git a/res/xml/haptic_settings.xml b/res/xml/haptic_settings.xml new file mode 100644 index 00000000000..4ba2a4926df --- /dev/null +++ b/res/xml/haptic_settings.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/res/xml/hardware_info.xml b/res/xml/hardware_info.xml index e086a486ee0..fc4c7b547e8 100644 --- a/res/xml/hardware_info.xml +++ b/res/xml/hardware_info.xml @@ -30,6 +30,24 @@ settings:controller="com.android.settings.deviceinfo.hardwareinfo.DeviceModelPreferenceController" settings:enableCopying="true"/> + + + + + + + + + + + + + + + + diff --git a/res/xml/keyboard_settings.xml b/res/xml/keyboard_settings.xml index 21b2bb06f5b..feab9cc4472 100644 --- a/res/xml/keyboard_settings.xml +++ b/res/xml/keyboard_settings.xml @@ -61,6 +61,14 @@ android:key="pointer_speed" android:title="@string/pointer_speed" android:dialogTitle="@string/pointer_speed" /> + + + - \ No newline at end of file + diff --git a/res/xml/livedisplay.xml b/res/xml/livedisplay.xml new file mode 100644 index 00000000000..76933967d00 --- /dev/null +++ b/res/xml/livedisplay.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/location_settings.xml b/res/xml/location_settings.xml index 3f26d04287e..0ba5da8fc97 100644 --- a/res/xml/location_settings.xml +++ b/res/xml/location_settings.xml @@ -35,6 +35,12 @@ settings:controller="com.android.settings.location.RecentLocationAccessSeeAllButtonPreferenceController" settings:searchable="false"/> + + diff --git a/res/xml/monet_settings.xml b/res/xml/monet_settings.xml new file mode 100644 index 00000000000..4eeb89f6178 --- /dev/null +++ b/res/xml/monet_settings.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/more_security_privacy_settings.xml b/res/xml/more_security_privacy_settings.xml index 799ff1e815a..abae79fe0dc 100644 --- a/res/xml/more_security_privacy_settings.xml +++ b/res/xml/more_security_privacy_settings.xml @@ -141,6 +141,24 @@ settings:keywords="@string/content_protection_preference_title" settings:controller="com.android.settings.security.ContentProtectionPreferenceController" /> + + + + + - - - @@ -94,6 +86,16 @@ android:selectable="false" android:title="@string/my_device_info_device_details_category_title"> + + + + - - - - diff --git a/res/xml/network_traffic_settings.xml b/res/xml/network_traffic_settings.xml new file mode 100644 index 00000000000..a1720ef5d4c --- /dev/null +++ b/res/xml/network_traffic_settings.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + diff --git a/res/xml/night_display_settings.xml b/res/xml/night_display_settings.xml index 95d503415f5..5b560559f8a 100644 --- a/res/xml/night_display_settings.xml +++ b/res/xml/night_display_settings.xml @@ -35,7 +35,7 @@ android:title="@string/twilight_mode_location_off_dialog_message" settings:controller="com.android.settings.display.TwilightLocationPreferenceController"/> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/physical_keyboard_settings.xml b/res/xml/physical_keyboard_settings.xml index 5b545bbc2b1..ead96c20b18 100644 --- a/res/xml/physical_keyboard_settings.xml +++ b/res/xml/physical_keyboard_settings.xml @@ -15,6 +15,7 @@ --> @@ -60,4 +61,20 @@ android:defaultValue="false" /> + + + + + + + + + + diff --git a/res/xml/power_menu_actions_settings.xml b/res/xml/power_menu_actions_settings.xml new file mode 100644 index 00000000000..d99ebf96b50 --- /dev/null +++ b/res/xml/power_menu_actions_settings.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/power_usage_summary.xml b/res/xml/power_usage_summary.xml index 77c6b7322c2..a3cdcf8a632 100644 --- a/res/xml/power_usage_summary.xml +++ b/res/xml/power_usage_summary.xml @@ -51,12 +51,21 @@ settings:keywords="@string/keywords_battery_saver" settings:controller="com.android.settings.fuelgauge.BatterySaverController" /> + + android:key="battery_lights" + android:title="@string/battery_light_title" + android:summary="@string/battery_light_summary" + android:fragment="com.android.settings.derp.notificationlight.BatteryLightSettings" + settings:controller="com.android.settings.derp.notificationlight.BatteryLightPreferenceController" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/qs_panel_settings.xml b/res/xml/qs_panel_settings.xml new file mode 100644 index 00000000000..cb0119ed8e2 --- /dev/null +++ b/res/xml/qs_panel_settings.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/refresh_rate_settings.xml b/res/xml/refresh_rate_settings.xml new file mode 100644 index 00000000000..d3e87abe15f --- /dev/null +++ b/res/xml/refresh_rate_settings.xml @@ -0,0 +1,23 @@ + + + + diff --git a/res/xml/screen_lock_settings.xml b/res/xml/screen_lock_settings.xml index 19061d9b855..07cce577f00 100644 --- a/res/xml/screen_lock_settings.xml +++ b/res/xml/screen_lock_settings.xml @@ -27,6 +27,14 @@ android:key="visiblepattern" android:title="@string/lockpattern_settings_enable_visible_pattern_title" /> + + + + - - + + + + + + + diff --git a/res/xml/screen_off_udfps_settings.xml b/res/xml/screen_off_udfps_settings.xml new file mode 100644 index 00000000000..1e1bddbd6ff --- /dev/null +++ b/res/xml/screen_off_udfps_settings.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/res/xml/security_lockscreen_settings.xml b/res/xml/security_lockscreen_settings.xml index 15d530357d9..a4c560c4598 100644 --- a/res/xml/security_lockscreen_settings.xml +++ b/res/xml/security_lockscreen_settings.xml @@ -28,7 +28,7 @@ android:summary="@string/summary_placeholder" settings:keywords="@string/keywords_lock_screen_notif"/> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/sensor_block_settings.xml b/res/xml/sensor_block_settings.xml new file mode 100644 index 00000000000..bb204a1f6e3 --- /dev/null +++ b/res/xml/sensor_block_settings.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/res/xml/smart_battery_detail.xml b/res/xml/smart_battery_detail.xml index 40476743253..8fdd3a5995b 100644 --- a/res/xml/smart_battery_detail.xml +++ b/res/xml/smart_battery_detail.xml @@ -30,8 +30,7 @@ android:key="smart_battery" android:title="@string/smart_battery_title" android:summary="@string/smart_battery_summary" - settings:controller="com.android.settings.fuelgauge.SmartBatteryPreferenceController" - settings:allowDividerAbove="true"/> + settings:controller="com.android.settings.fuelgauge.SmartBatteryPreferenceController"/> + + + + + + + + + @@ -125,6 +151,14 @@ android:order="-107" settings:controller="com.android.settings.notification.SpatialAudioParentPreferenceController"/> + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/status_bar_lyric_settings.xml b/res/xml/status_bar_lyric_settings.xml new file mode 100644 index 00000000000..3504ab5afbe --- /dev/null +++ b/res/xml/status_bar_lyric_settings.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/res/xml/status_bar_settings.xml b/res/xml/status_bar_settings.xml new file mode 100644 index 00000000000..967ec9385b0 --- /dev/null +++ b/res/xml/status_bar_settings.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/xml/swipe_to_screenshot_gesture_settings.xml b/res/xml/swipe_to_screenshot_gesture_settings.xml new file mode 100644 index 00000000000..d1f11c8772b --- /dev/null +++ b/res/xml/swipe_to_screenshot_gesture_settings.xml @@ -0,0 +1,37 @@ + + + + + + + + + + diff --git a/res/xml/system_dashboard_fragment.xml b/res/xml/system_dashboard_fragment.xml index 6225f4f8382..9bce02667be 100644 --- a/res/xml/system_dashboard_fragment.xml +++ b/res/xml/system_dashboard_fragment.xml @@ -46,6 +46,14 @@ android:fragment="com.android.settings.inputmethod.TrackpadSettings" settings:controller="com.android.settings.inputmethod.TrackpadSettingsController"/> + + + + + + + + + + + + + + + + - - diff --git a/res/xml/vpn_app_management.xml b/res/xml/vpn_app_management.xml index dffbbbe3116..0305959595f 100644 --- a/res/xml/vpn_app_management.xml +++ b/res/xml/vpn_app_management.xml @@ -23,6 +23,12 @@ android:key="version" android:selectable="false"/> + + + + - - diff --git a/src/com/android/settings/DefaultRingtonePreference.java b/src/com/android/settings/DefaultRingtonePreference.java index 4c654887227..e6d5ced14b8 100644 --- a/src/com/android/settings/DefaultRingtonePreference.java +++ b/src/com/android/settings/DefaultRingtonePreference.java @@ -62,12 +62,14 @@ protected void onSaveRingtone(Uri ringtoneUri) { @VisibleForTesting void setActualDefaultRingtoneUri(Uri ringtoneUri) { - RingtoneManager.setActualDefaultRingtoneUri(mUserContext, getRingtoneType(), ringtoneUri); + RingtoneManager.setActualDefaultRingtoneUriBySlot(mUserContext, getRingtoneType(), + ringtoneUri, getSlotId()); } @Override protected Uri onRestoreRingtone() { - return RingtoneManager.getActualDefaultRingtoneUri(mUserContext, getRingtoneType()); + return RingtoneManager.getActualDefaultRingtoneUriBySlot(mUserContext, getRingtoneType(), + getSlotId()); } } diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java index 97b9aae0cd1..e09277e8abb 100644 --- a/src/com/android/settings/DisplaySettings.java +++ b/src/com/android/settings/DisplaySettings.java @@ -24,15 +24,19 @@ import com.android.settings.display.BrightnessLevelPreferenceController; import com.android.settings.display.CameraGesturePreferenceController; import com.android.settings.display.LiftToWakePreferenceController; +import com.android.settings.display.PocketJudgePreferenceController; import com.android.settings.display.ShowOperatorNamePreferenceController; import com.android.settings.display.TapToWakePreferenceController; import com.android.settings.display.ThemePreferenceController; +import com.android.settings.display.ForceDarkPreferenceController; import com.android.settings.display.VrDisplayPreferenceController; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.search.SearchIndexable; +import com.android.internal.derp.hardware.LineageHardwareManager; + import java.util.ArrayList; import java.util.List; @@ -40,6 +44,10 @@ public class DisplaySettings extends DashboardFragment { private static final String TAG = "DisplaySettings"; + private static final String KEY_HIGH_TOUCH_POLLING_RATE = "high_touch_polling_rate_enable"; + private static final String KEY_HIGH_TOUCH_SENSITIVITY = "high_touch_sensitivity_enable"; + private static final String KEY_PROXIMITY_ON_WAKE = "proximity_on_wake"; + @Override public int getMetricsCategory() { return SettingsEnums.DISPLAY; @@ -75,10 +83,12 @@ private static List buildPreferenceControllers( final List controllers = new ArrayList<>(); controllers.add(new CameraGesturePreferenceController(context)); controllers.add(new LiftToWakePreferenceController(context)); + controllers.add(new PocketJudgePreferenceController(context)); controllers.add(new TapToWakePreferenceController(context)); controllers.add(new VrDisplayPreferenceController(context)); controllers.add(new ShowOperatorNamePreferenceController(context)); controllers.add(new ThemePreferenceController(context)); + controllers.add(new ForceDarkPreferenceController(context)); controllers.add(new BrightnessLevelPreferenceController(context, lifecycle)); return controllers; } @@ -86,6 +96,25 @@ private static List buildPreferenceControllers( public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider(R.xml.display_settings) { + @Override + public List getNonIndexableKeys(Context context) { + List keys = super.getNonIndexableKeys(context); + LineageHardwareManager hardware = LineageHardwareManager.getInstance(context); + if (!hardware.isSupported( + LineageHardwareManager.FEATURE_HIGH_TOUCH_POLLING_RATE)) { + keys.add(KEY_HIGH_TOUCH_POLLING_RATE); + } + if (!hardware.isSupported( + LineageHardwareManager.FEATURE_HIGH_TOUCH_SENSITIVITY)) { + keys.add(KEY_HIGH_TOUCH_SENSITIVITY); + } + if (!context.getResources().getBoolean( + com.android.internal.R.bool.config_proximityCheckOnWake)) { + keys.add(KEY_PROXIMITY_ON_WAKE); + } + return keys; + } + @Override public List createPreferenceControllers( Context context) { diff --git a/src/com/android/settings/RingtonePreference.java b/src/com/android/settings/RingtonePreference.java index 6b9dad7ffbf..43600c28f6b 100644 --- a/src/com/android/settings/RingtonePreference.java +++ b/src/com/android/settings/RingtonePreference.java @@ -40,6 +40,7 @@ *

    * If the user chooses the "Default" item, the saved string will be one of * {@link System#DEFAULT_RINGTONE_URI}, + * {@link System#DEFAULT_RINGTONE2_URI}, * {@link System#DEFAULT_NOTIFICATION_URI}, or * {@link System#DEFAULT_ALARM_ALERT_URI}. If the user chooses the "Silent" * item, the saved string will be an empty string. @@ -55,6 +56,9 @@ public class RingtonePreference extends Preference { private static final String TAG = "RingtonePreference"; + // Default is slot0 + private int mSlotId = 0; + private int mRingtoneType; private boolean mShowDefault; private boolean mShowSilent; @@ -89,6 +93,25 @@ public int getUserId() { return mUserId; } + /** + * Sets the slot id that this preference belongs to. + * + * @param slotId The slot id that this preference belongs to. + */ + public void setSlotId(int slotId) { + mSlotId = slotId; + } + + /** + * Returns the slot id that this preference belongs to. + * + * @return The slot id that this preference belongs to. + * @see #setSlotId(int) + */ + public int getSlotId() { + return mSlotId; + } + /** * Returns the sound type(s) that are shown in the picker. * @@ -167,7 +190,7 @@ public void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) { ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, mShowDefault); if (mShowDefault) { ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, - RingtoneManager.getDefaultUri(getRingtoneType())); + RingtoneManager.getDefaultUriBySlot(getRingtoneType(), getSlotId())); } ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, mShowSilent); diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 3367bf18f9f..a128e22b86f 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -92,6 +92,7 @@ public static class DisplaySettingsActivity extends SettingsActivity { /* empty public static class NightDisplaySettingsActivity extends SettingsActivity { /* empty */ } public static class NightDisplaySuggestionActivity extends NightDisplaySettingsActivity { /* empty */ } public static class SmartAutoRotateSettingsActivity extends SettingsActivity { /* empty */ } + public static class RefreshRateSettingsActivity extends SettingsActivity { /* empty */ } public static class MyDeviceInfoActivity extends SettingsActivity { /* empty */ } public static class ModuleLicensesActivity extends SettingsActivity { /* empty */ } public static class ApplicationSettingsActivity extends SettingsActivity { /* empty */ } @@ -496,4 +497,6 @@ public static class ResetMobileNetworkSettingsActivity extends SettingsActivity public static class HearingDevicesActivity extends SettingsActivity { /* empty */ } public static class HearingDevicesPairingActivity extends SettingsActivity { /* empty */ } + + public static class DevRunningServicesActivity extends SettingsActivity { /* empty */ } } diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java index 66397c0e66f..85514f18fce 100644 --- a/src/com/android/settings/SettingsPreferenceFragment.java +++ b/src/com/android/settings/SettingsPreferenceFragment.java @@ -53,6 +53,9 @@ import com.android.settingslib.core.instrumentation.Instrumentable; import com.android.settingslib.search.Indexable; import com.android.settingslib.widget.LayoutPreference; +import com.android.settings.derp.notificationlight.ApplicationLightPreference; +import com.android.settings.derp.notificationlight.BrightnessPreference; +import com.android.settings.derp.buttons.preference.ButtonBacklightBrightness; import com.google.android.material.appbar.AppBarLayout; import com.google.android.setupcompat.util.WizardManagerHelper; @@ -265,7 +268,7 @@ public void highlightPreferenceIfNeeded() { * Only override this method if the initial expanded child must be determined at run time. */ public int getInitialExpandedChildCount() { - return 0; + return Integer.MAX_VALUE; } /** @@ -562,6 +565,15 @@ public void onDisplayPreferenceDialog(Preference preference) { } else if (preference instanceof CustomEditTextPreferenceCompat) { f = CustomEditTextPreferenceCompat.CustomPreferenceDialogFragment .newInstance(preference.getKey()); + } else if (preference instanceof ApplicationLightPreference) { + f = ApplicationLightPreference.CustomPreferenceDialogFragment + .newInstance(preference.getKey()); + } else if (preference instanceof BrightnessPreference) { + f = BrightnessPreference.CustomPreferenceDialogFragment + .newInstance(preference.getKey()); + } else if (preference instanceof ButtonBacklightBrightness) { + f = ButtonBacklightBrightness.CustomPreferenceDialogFragment + .newInstance(preference.getKey()); } else { super.onDisplayPreferenceDialog(preference); return; diff --git a/src/com/android/settings/accessibility/InCallConnectVibrationTogglePreferenceController.java b/src/com/android/settings/accessibility/InCallConnectVibrationTogglePreferenceController.java new file mode 100644 index 00000000000..7ed00ecc4ca --- /dev/null +++ b/src/com/android/settings/accessibility/InCallConnectVibrationTogglePreferenceController.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2022 The LibreMobileOS Foundation + * + * 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.os.VibrationAttributes; +import android.os.Vibrator; +import android.provider.Settings; + +/** Preference controller for incall vibration with only a toggle for on/off states. */ +public class InCallConnectVibrationTogglePreferenceController extends VibrationTogglePreferenceController { + + /** General configuration for incall vibration intensity settings. */ + public static final class InCallVibrationPreferenceConfig extends VibrationPreferenceConfig { + + public InCallVibrationPreferenceConfig(Context context) { + super(context, Settings.System.VIBRATE_ON_CONNECT, + VibrationAttributes.USAGE_UNKNOWN); + } + + /** Returns the default intensity to be displayed when the setting value is not set. */ + public int getDefaultValue() { + return Vibrator.VIBRATION_INTENSITY_OFF; + } + + /** Reads setting value for corresponding {@link VibrationPreferenceConfig} */ + @Override + public int readIntensity() { + return Settings.System.getInt(mContentResolver, getSettingKey(), getDefaultValue()); + } + } + + public InCallConnectVibrationTogglePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey, new InCallVibrationPreferenceConfig(context)); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } +} diff --git a/src/com/android/settings/accessibility/InCallDisconnectVibrationTogglePreferenceController.java b/src/com/android/settings/accessibility/InCallDisconnectVibrationTogglePreferenceController.java new file mode 100644 index 00000000000..9536d68f5c2 --- /dev/null +++ b/src/com/android/settings/accessibility/InCallDisconnectVibrationTogglePreferenceController.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2022 The LibreMobileOS Foundation + * + * 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.os.VibrationAttributes; +import android.os.Vibrator; +import android.provider.Settings; + +/** Preference controller for incall vibration with only a toggle for on/off states. */ +public class InCallDisconnectVibrationTogglePreferenceController extends VibrationTogglePreferenceController { + + /** General configuration for incall vibration intensity settings. */ + public static final class InCallVibrationPreferenceConfig extends VibrationPreferenceConfig { + + public InCallVibrationPreferenceConfig(Context context) { + super(context, Settings.System.VIBRATE_ON_DISCONNECT, + VibrationAttributes.USAGE_UNKNOWN); + } + + /** Returns the default intensity to be displayed when the setting value is not set. */ + public int getDefaultValue() { + return Vibrator.VIBRATION_INTENSITY_OFF; + } + + /** Reads setting value for corresponding {@link VibrationPreferenceConfig} */ + @Override + public int readIntensity() { + return Settings.System.getInt(mContentResolver, getSettingKey(), getDefaultValue()); + } + } + + public InCallDisconnectVibrationTogglePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey, new InCallVibrationPreferenceConfig(context)); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } +} diff --git a/src/com/android/settings/accessibility/InCallWaitingVibrationTogglePreferenceController.java b/src/com/android/settings/accessibility/InCallWaitingVibrationTogglePreferenceController.java new file mode 100644 index 00000000000..353fdf42934 --- /dev/null +++ b/src/com/android/settings/accessibility/InCallWaitingVibrationTogglePreferenceController.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2022 The LibreMobileOS Foundation + * + * 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.os.VibrationAttributes; +import android.os.Vibrator; +import android.provider.Settings; + +/** Preference controller for incall vibration with only a toggle for on/off states. */ +public class InCallWaitingVibrationTogglePreferenceController extends VibrationTogglePreferenceController { + + /** General configuration for incall vibration intensity settings. */ + public static final class InCallVibrationPreferenceConfig extends VibrationPreferenceConfig { + + public InCallVibrationPreferenceConfig(Context context) { + super(context, Settings.System.VIBRATE_ON_CALLWAITING, + VibrationAttributes.USAGE_UNKNOWN); + } + + /** Returns the default intensity to be displayed when the setting value is not set. */ + public int getDefaultValue() { + return Vibrator.VIBRATION_INTENSITY_OFF; + } + + /** Reads setting value for corresponding {@link VibrationPreferenceConfig} */ + @Override + public int readIntensity() { + return Settings.System.getInt(mContentResolver, getSettingKey(), getDefaultValue()); + } + } + + public InCallWaitingVibrationTogglePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey, new InCallVibrationPreferenceConfig(context)); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } +} diff --git a/src/com/android/settings/accounts/AccountFeatureProviderImpl.java b/src/com/android/settings/accounts/AccountFeatureProviderImpl.java index 90b581ba80e..716cdcba6d6 100644 --- a/src/com/android/settings/accounts/AccountFeatureProviderImpl.java +++ b/src/com/android/settings/accounts/AccountFeatureProviderImpl.java @@ -1,16 +1,20 @@ package com.android.settings.accounts; import android.accounts.Account; +import android.accounts.AccountManager; import android.content.Context; +import com.android.settings.R; +import com.android.settings.overlay.FeatureFactory; + public class AccountFeatureProviderImpl implements AccountFeatureProvider { @Override public String getAccountType() { - return null; + return FeatureFactory.getAppContext().getString(R.string.account_type); } @Override public Account[] getAccounts(Context context) { - return new Account[0]; + return AccountManager.get(context).getAccountsByType(getAccountType()); } } diff --git a/src/com/android/settings/applications/AppStorageSettings.java b/src/com/android/settings/applications/AppStorageSettings.java index e45657fc067..15283cb8947 100644 --- a/src/com/android/settings/applications/AppStorageSettings.java +++ b/src/com/android/settings/applications/AppStorageSettings.java @@ -210,13 +210,6 @@ void handleClearDataClick() { if (mAppsControlDisallowedAdmin != null && !mAppsControlDisallowedBySystem) { RestrictedLockUtils.sendShowAdminSupportDetailsIntent( getActivity(), mAppsControlDisallowedAdmin); - } else if (mAppEntry.info.manageSpaceActivityName != null) { - if (!Utils.isMonkeyRunning()) { - Intent intent = new Intent(Intent.ACTION_DEFAULT); - intent.setClassName(mAppEntry.info.packageName, - mAppEntry.info.manageSpaceActivityName); - startActivityForResult(intent, REQUEST_MANAGE_SPACE); - } } else { showDialogInner(DLG_CLEAR_DATA, 0); } @@ -300,11 +293,8 @@ private void initDataButtons() { if (appHasSpaceManagementUI) { intent.setClassName(mAppEntry.info.packageName, mAppEntry.info.manageSpaceActivityName); } - final boolean isManageSpaceActivityAvailable = - getPackageManager().resolveActivity(intent, 0) != null; - if ((!appHasSpaceManagementUI && appRestrictsClearingData) - || !isManageSpaceActivityAvailable) { + if (!appHasSpaceManagementUI && appRestrictsClearingData) { mButtonsPref .setButton1Text(R.string.clear_user_data_text) .setButton1Icon(R.drawable.ic_settings_delete) diff --git a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java index 851d763ea94..4e1ec775bf6 100644 --- a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java +++ b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java @@ -27,10 +27,16 @@ import android.content.pm.ComponentInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; +import android.icu.text.MeasureFormat; +import android.icu.text.MeasureFormat.FormatWidth; +import android.icu.util.Measure; +import android.icu.util.MeasureUnit; import android.location.LocationManager; +import android.os.Bundle; import android.os.RemoteException; import android.os.SystemConfigManager; import android.os.UserManager; @@ -48,10 +54,16 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Set; public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvider { private static final String TAG = "AppFeatureProviderImpl"; + private static final boolean DEBUG = false; + + private static final String WELLBEING_APP_PACKAGE = "com.google.android.apps.wellbeing.api"; + private static final String GET_APP_USAGE_MILLIS = "get_app_usage_millis"; + private static final String TOTAL_TIME_MILLIS = "total_time_millis"; protected final Context mContext; private final PackageManager mPm; @@ -368,4 +380,85 @@ public boolean isLongBackgroundTaskPermissionToggleSupported() { // converted to a special app-op permission, this should be updated. return false; } + + public CharSequence getTimeSpentInApp(String packageName) { + try { + if (!isPrivilegedApp(WELLBEING_APP_PACKAGE)) { + if (DEBUG) { + Log.d("ApplicationFeatureProviderImpl", "Not a privileged app."); + } + return ""; + } + + Bundle bundle = new Bundle(); + bundle.putString("packageName", packageName); + + Bundle providerCall = mContext.getContentResolver().call(WELLBEING_APP_PACKAGE, + GET_APP_USAGE_MILLIS, null, bundle); + if (providerCall != null) { + if (providerCall.getBoolean("success")) { + Bundle dataBundle = providerCall.getBundle("data"); + if (dataBundle == null) { + if (DEBUG) { + Log.d("ApplicationFeatureProviderImpl", "data bundle is null."); + } + return ""; + } + String readableDuration = getReadableDuration(dataBundle.getLong(TOTAL_TIME_MILLIS), + R.string.duration_less_than_one_minute); + return mContext.getString(R.string.screen_time_summary_usage_today, readableDuration); + } + } + if (DEBUG) { + Log.d("ApplicationFeatureProviderImpl", "Provider call unsuccessful"); + } + return ""; + } catch (Exception e) { + Log.w("ApplicationFeatureProviderImpl", + "Error getting time spent for app " + packageName, e); + return ""; + } + } + + private String getReadableDuration(Long totalTime, int defaultString) { + long hours, minutes; + + if (totalTime >= 3_600_000) { + hours = totalTime / 3_600_000; + totalTime -= 3_600_000 * hours; + } else { + hours = 0; + } + + if (totalTime >= 60_000) { + minutes = totalTime / 60_000; + totalTime -= 60_000 * minutes; + } else { + minutes = 0; + } + + Locale locale = Locale.getDefault(); + MeasureFormat measureFormat = MeasureFormat.getInstance(locale, FormatWidth.NARROW); + + if (hours > 0 && minutes > 0) { + return measureFormat.formatMeasures(new Measure(hours, MeasureUnit.HOUR), + new Measure(minutes, MeasureUnit.MINUTE)); + } else if (hours > 0) { + return measureFormat.formatMeasures(new Measure(hours, MeasureUnit.HOUR)); + } else if (minutes > 0) { + return measureFormat.formatMeasures(new Measure(minutes, MeasureUnit.MINUTE)); + } else if (totalTime <= 0) { + return measureFormat.formatMeasures(new Measure(0, MeasureUnit.MINUTE)); + } + + return mContext.getResources().getString(defaultString); + } + + private boolean isPrivilegedApp(String packageName) { + ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider(packageName, 0); + if (providerInfo != null) { + return providerInfo.applicationInfo.isPrivilegedApp(); + } + return false; + } } diff --git a/src/com/android/settings/applications/GameSpaceController.java b/src/com/android/settings/applications/GameSpaceController.java new file mode 100644 index 00000000000..ca442f4757f --- /dev/null +++ b/src/com/android/settings/applications/GameSpaceController.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 Bianca 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.applications; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.Intent; +import android.content.ComponentName; +import android.os.UserHandle; +import android.text.TextUtils; + +import androidx.preference.Preference; + +import com.android.settings.core.BasePreferenceController; + +public class GameSpaceController extends BasePreferenceController { + + private static final String GAME_PACKAGE = "io.chaldeaprjkt.gamespace"; + private static final String GAME_SETTINGS = "io.chaldeaprjkt.gamespace.settings.SettingsActivity"; + + private final PackageManager mPackageManager; + + public GameSpaceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mPackageManager = mContext.getPackageManager(); + } + + private Intent settingsIntent() { + Intent intent = new Intent(); + ComponentName component = new ComponentName(GAME_PACKAGE, GAME_SETTINGS); + intent.setComponent(component); + return intent; + } + + @Override + public int getAvailabilityStatus() { + return mContext.getPackageManager().resolveActivity(settingsIntent(), 0) != null + ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return false; + } + + Intent intent = settingsIntent(); + intent.putExtra("referer", this.getClass().getCanonicalName()); + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + return true; + } +} diff --git a/src/com/android/settings/applications/ProcStatsData.java b/src/com/android/settings/applications/ProcStatsData.java index aedb06640d0..fd178988d17 100644 --- a/src/com/android/settings/applications/ProcStatsData.java +++ b/src/com/android/settings/applications/ProcStatsData.java @@ -274,7 +274,7 @@ private ArrayList getProcs(ProcessDataCollection bgTotals, final ProcessState proc = mStats.mProcesses.get(pkgProc.getName(), pkgProc.getUid()); if (proc == null) { - Log.w(TAG, "No process found for pkg " + st.mPackageName + if (DEBUG) Log.w(TAG, "No process found for pkg " + st.mPackageName + "/" + st.mUid + " proc name " + pkgProc.getName()); continue; } diff --git a/src/com/android/settings/applications/RunningServices.java b/src/com/android/settings/applications/RunningServices.java index 4d1c166f904..1d1b80e21f3 100644 --- a/src/com/android/settings/applications/RunningServices.java +++ b/src/com/android/settings/applications/RunningServices.java @@ -112,6 +112,12 @@ private void updateOptionsMenu() { boolean showingBackground = mRunningProcessesView.mAdapter.getShowBackground(); mOptionsMenu.findItem(SHOW_RUNNING_SERVICES).setVisible(showingBackground); mOptionsMenu.findItem(SHOW_BACKGROUND_PROCESSES).setVisible(!showingBackground); + + if (!showingBackground) { + getActivity().setTitle(com.android.settingslib.R.string.runningservices_settings_title); + } else { + getActivity().setTitle(R.string.background_processes_settings_title); + } } @Override diff --git a/src/com/android/settings/applications/intentpicker/IntentPickerUtils.java b/src/com/android/settings/applications/intentpicker/IntentPickerUtils.java index 5b14bc7ce3e..8feef9c9fe8 100644 --- a/src/com/android/settings/applications/intentpicker/IntentPickerUtils.java +++ b/src/com/android/settings/applications/intentpicker/IntentPickerUtils.java @@ -30,7 +30,7 @@ /** The common APIs for intent picker */ public class IntentPickerUtils { private static final String TAG = "IntentPickerUtils"; - private static final boolean DEBUG = Build.IS_DEBUGGABLE; + private static final boolean DEBUG = Build.IS_ENG; private IntentPickerUtils() { } diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index 6c16d94a51d..0f9f49b0bd6 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -192,7 +192,7 @@ public class ManageApplications extends InstrumentedFragment MenuItem.OnActionExpandListener { static final String TAG = "ManageApplications"; - static final boolean DEBUG = Build.IS_DEBUGGABLE; + static final boolean DEBUG = Build.IS_ENG; // Intent extras. public static final String EXTRA_CLASSNAME = "classname"; diff --git a/src/com/android/settings/applications/sensors/SensorBlock.java b/src/com/android/settings/applications/sensors/SensorBlock.java new file mode 100644 index 00000000000..ad5df6d05fd --- /dev/null +++ b/src/com/android/settings/applications/sensors/SensorBlock.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2019-2020 crDroid Android 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.applications.sensors; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; + +import androidx.preference.SwitchPreference; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; + +import com.android.internal.logging.nano.MetricsProto; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settingslib.widget.FooterPreference; + +import org.derpfest.support.preferences.AppListPreference; +import org.derpfest.support.preferences.PackageListAdapter; +import org.derpfest.support.preferences.PackageListAdapter.PackageItem; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SensorBlock extends SettingsPreferenceFragment + implements Preference.OnPreferenceClickListener { + + private static final int DIALOG_BLOCKED_APPS = 1; + private static final String SENSOR_BLOCK = "sensor_block"; + + private PackageListAdapter mPackageAdapter; + private PackageManager mPackageManager; + private PreferenceGroup mSensorBlockPrefList; + private Preference mAddSensorBlockPref; + + private String mBlockedPackageList; + private Map mBlockedPackages; + private Context mContext; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Get launch-able applications + addPreferencesFromResource(R.xml.sensor_block_settings); + + getPreferenceScreen().addPreference(new FooterPreference.Builder(getActivity()).setTitle( + R.string.add_sensor_block_package_summary).build()); + + final PreferenceScreen prefScreen = getPreferenceScreen(); + + mPackageManager = getPackageManager(); + mPackageAdapter = new PackageListAdapter(getActivity()); + + mSensorBlockPrefList = (PreferenceGroup) findPreference("sensor_block_applications"); + mSensorBlockPrefList.setOrderingAsAdded(false); + + mBlockedPackages = new HashMap(); + + mAddSensorBlockPref = findPreference("add_sensor_block_packages"); + + mAddSensorBlockPref.setOnPreferenceClickListener(this); + + mContext = getActivity().getApplicationContext(); + } + + @Override + public void onResume() { + super.onResume(); + refreshCustomApplicationPrefs(); + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DERP; + } + + @Override + public int getDialogMetricsCategory(int dialogId) { + if (dialogId == DIALOG_BLOCKED_APPS) { + return MetricsProto.MetricsEvent.DERP; + } + return 0; + } + + /** + * Utility classes and supporting methods + */ + @Override + public Dialog onCreateDialog(int id) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + final Dialog dialog; + final ListView list = new ListView(getActivity()); + list.setAdapter(mPackageAdapter); + list.setDivider(null); + + builder.setTitle(R.string.profile_choose_app); + builder.setView(list); + dialog = builder.create(); + + switch (id) { + case DIALOG_BLOCKED_APPS: + list.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + // Add empty application definition, the user will be able to edit it later + PackageItem info = (PackageItem) parent.getItemAtPosition(position); + addCustomApplicationPref(info.packageName, mBlockedPackages); + dialog.cancel(); + } + }); + } + return dialog; + } + + public static void reset(Context mContext) { + ContentResolver resolver = mContext.getContentResolver(); + Settings.System.putIntForUser(resolver, + Settings.System.SENSOR_BLOCK, 0, UserHandle.USER_CURRENT); + Settings.System.putString(resolver, + Settings.System.SENSOR_BLOCKED_APP, null); + } + + /** + * Application class + */ + private static class Package { + public String name; + /** + * Stores all the application values in one call + * @param name + */ + public Package(String name) { + this.name = name; + } + + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(name); + return builder.toString(); + } + + public static Package fromString(String value) { + if (TextUtils.isEmpty(value)) { + return null; + } + + try { + Package item = new Package(value); + return item; + } catch (NumberFormatException e) { + return null; + } + } + + }; + + private void refreshCustomApplicationPrefs() { + if (!parsePackageList()) { + return; + } + + // Add the Application Preferences + if (mSensorBlockPrefList != null) { + mSensorBlockPrefList.removeAll(); + + for (Package pkg : mBlockedPackages.values()) { + try { + Preference pref = createPreferenceFromInfo(pkg); + mSensorBlockPrefList.addPreference(pref); + } catch (PackageManager.NameNotFoundException e) { + // Do nothing + } + } + + // Keep these at the top + mAddSensorBlockPref.setOrder(0); + // Add 'add' options + mSensorBlockPrefList.addPreference(mAddSensorBlockPref); + } + } + + @Override + public boolean onPreferenceClick(Preference preference) { + if (preference == mAddSensorBlockPref) { + showDialog(DIALOG_BLOCKED_APPS); + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.dialog_delete_title) + .setMessage(R.string.dialog_delete_message) + .setIconAttribute(android.R.attr.alertDialogIcon) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (preference == mSensorBlockPrefList.findPreference(preference.getKey())) { + removeApplicationPref(preference.getKey(), mBlockedPackages); + } + } + }) + .setNegativeButton(android.R.string.cancel, null); + + builder.show(); + } + return true; + } + + private void addCustomApplicationPref(String packageName, Map map) { + Package pkg = map.get(packageName); + if (pkg == null) { + pkg = new Package(packageName); + map.put(packageName, pkg); + savePackageList(false, map); + refreshCustomApplicationPrefs(); + } + } + + private Preference createPreferenceFromInfo(Package pkg) + throws PackageManager.NameNotFoundException { + PackageInfo info = mPackageManager.getPackageInfo(pkg.name, + PackageManager.GET_META_DATA); + Preference pref = + new AppListPreference(getActivity()); + + pref.setKey(pkg.name); + pref.setTitle(info.applicationInfo.loadLabel(mPackageManager)); + pref.setIcon(info.applicationInfo.loadIcon(mPackageManager)); + pref.setPersistent(false); + pref.setOnPreferenceClickListener(this); + return pref; + } + + private void removeApplicationPref(String packageName, Map map) { + if (map.remove(packageName) != null) { + savePackageList(false, map); + refreshCustomApplicationPrefs(); + } + } + + private boolean parsePackageList() { + boolean parsed = false; + + String sensorBlockString = Settings.System.getString(getContentResolver(), + Settings.System.SENSOR_BLOCKED_APP); + + if (sensorBlockString != null && + !TextUtils.equals(mBlockedPackageList, sensorBlockString)) { + mBlockedPackageList = sensorBlockString; + mBlockedPackages.clear(); + parseAndAddToMap(sensorBlockString, mBlockedPackages); + parsed = true; + } + + return parsed; + } + + private void parseAndAddToMap(String baseString, Map map) { + if (baseString == null) { + return; + } + + final String[] array = TextUtils.split(baseString, "\\|"); + for (String item : array) { + if (TextUtils.isEmpty(item)) { + continue; + } + Package pkg = Package.fromString(item); + map.put(pkg.name, pkg); + } + } + + + private void savePackageList(boolean preferencesUpdated, Map map) { + String setting = map == mBlockedPackages ? Settings.System.SENSOR_BLOCKED_APP : Settings.System.SENSOR_BLOCKED_APP_DUMMY; + + List settings = new ArrayList(); + for (Package app : map.values()) { + settings.add(app.toString()); + } + final String value = TextUtils.join("|", settings); + if (preferencesUpdated) { + if (TextUtils.equals(setting, Settings.System.SENSOR_BLOCKED_APP)) { + mBlockedPackageList = value; + } + } + Settings.System.putString(getContentResolver(), + setting, value); + } +} diff --git a/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java b/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java index 5be7c5331d3..88966782448 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollAnimationDrawable.java @@ -28,6 +28,7 @@ import android.graphics.Rect; import android.graphics.drawable.Drawable; +import com.android.settingslib.Utils; import com.android.settings.biometrics.BiometricEnrollSidecar; /** @@ -66,7 +67,7 @@ public FaceEnrollAnimationDrawable(Context context, ParticleCollection.Listener mListener = listener; mSquarePaint = new Paint(); - mSquarePaint.setColor(Color.WHITE); + mSquarePaint.setColor(Utils.getColorAttrDefaultColor(context, android.R.attr.colorBackground)); mSquarePaint.setAntiAlias(true); mCircleCutoutPaint = new Paint(); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 795f999a030..e00f3c5f3d5 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -1189,10 +1189,19 @@ public static class IconTouchDialog extends InstrumentedDialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { + final boolean isFrontFacingFps = getResources().getBoolean( + R.bool.config_is_front_facing_fps); + final boolean isSideMountedFps = getResources().getBoolean( + R.bool.config_is_side_fps); + final String fpsLocation = getString(isSideMountedFps + ? R.string.fingerprint_enroll_touch_dialog_message_side : isFrontFacingFps + ? R.string.fingerprint_enroll_touch_dialog_message_front + : R.string.fingerprint_enroll_touch_dialog_message_rear); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.Theme_AlertDialog); builder.setTitle(R.string.security_settings_fingerprint_enroll_touch_dialog_title) - .setMessage(R.string.security_settings_fingerprint_enroll_touch_dialog_message) + .setMessage(fpsLocation) .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok, new DialogInterface.OnClickListener() { @Override diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java index aeb0dac97c4..533cfea2af6 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java @@ -122,8 +122,23 @@ protected void onCreate(Bundle savedInstanceState) { mIsReverseDefaultRotation = getApplicationContext().getResources().getBoolean( com.android.internal.R.bool.config_reverseDefaultRotation); } else { + // Remaining usecases can be either front facing fps or dedicated + // side mounted fps (not embedded in the power button) + final boolean isFrontFacingFps = getResources().getBoolean( + R.bool.config_is_front_facing_fps); + final boolean isSideMountedFps = getResources().getBoolean( + R.bool.config_is_side_fps); + final String fpsLocation = getString(isSideMountedFps + ? R.string.fingerprint_enroll_find_sensor_message_side : isFrontFacingFps + ? R.string.fingerprint_enroll_find_sensor_message_front + : R.string.fingerprint_enroll_find_sensor_message_rear); + setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title); - setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message); + setDescriptionText(fpsLocation); + if (isFrontFacingFps) { + findViewById(R.id.fingerprint_sensor_location_front_overlay) + .setVisibility(View.VISIBLE); + } } if (savedInstanceState != null) { mNextClicked = savedInstanceState.getBoolean(SAVED_STATE_IS_NEXT_CLICKED, mNextClicked); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java index f6626b213bf..1d781a57ad4 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java @@ -28,6 +28,7 @@ import android.os.Bundle; import android.text.Html; import android.text.method.LinkMovementMethod; +import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.ImageView; @@ -161,6 +162,10 @@ protected void onCreate(Bundle savedInstanceState) { getNextButton().setEnabled(true); })); } + + if (TextUtils.isEmpty(footerLink.getText())) { + findViewById(R.id.layout_footer_learn_more).setVisibility(View.GONE); + } } @Override diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintLocationAnimationView.java b/src/com/android/settings/biometrics/fingerprint/FingerprintLocationAnimationView.java index 2916872e73f..08e86c3befa 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintLocationAnimationView.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintLocationAnimationView.java @@ -27,6 +27,7 @@ import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; +import android.widget.ImageView; import androidx.annotation.Nullable; @@ -54,6 +55,7 @@ public class FingerprintLocationAnimationView extends View implements private float mPulseRadius; private ValueAnimator mRadiusAnimator; private ValueAnimator mAlphaAnimator; + private ImageView mOverlayImage; public FingerprintLocationAnimationView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -93,7 +95,11 @@ private float getCenterX() { } private float getCenterY() { - return getHeight() * mFractionCenterY; + if (mOverlayImage == null) { + mOverlayImage = (ImageView) getRootView().findViewById( + R.id.fingerprint_sensor_location); + } + return mOverlayImage.getHeight() * mFractionCenterY; } @Override diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index 46461340c10..98e27ce536c 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -175,7 +175,17 @@ private static List createThePreferenceControllers if (manager == null || !manager.isHardwareDetected()) { return null; } - if (manager.isPowerbuttonFps()) { + List sensorProperties = + manager.getSensorPropertiesInternal(); + boolean isUdfps = false; + for (FingerprintSensorPropertiesInternal prop : sensorProperties) { + if (prop.isAnyUdfpsType()) { + isUdfps = true; + break; + } + } + if (!isUdfps && context.getResources().getBoolean( + com.android.internal.R.bool.config_fingerprintWakeAndUnlock)) { controllers.add( new FingerprintUnlockCategoryController( context, @@ -244,6 +254,8 @@ private static class FooterColumn { private PreferenceCategory mFingerprintsEnrolledCategory; private PreferenceCategory mFingerprintUnlockCategory; private PreferenceCategory mFingerprintUnlockFooter; + private boolean mFingerprintWakeAndUnlock; + private boolean mProximityCheckOnFingerprintUnlock; private FingerprintManager mFingerprintManager; private FingerprintUpdater mFingerprintUpdater; @@ -328,7 +340,7 @@ public void handleMessage(android.os.Message msg) { case MSG_REFRESH_FINGERPRINT_TEMPLATES: removeFingerprintPreference(msg.arg1); updateAddPreference(); - if (isSfps()) { + if (!isUdfps() && mFingerprintWakeAndUnlock) { updateFingerprintUnlockCategoryVisibility(); } updatePreferences(); @@ -418,6 +430,10 @@ public void onCreate(Bundle savedInstanceState) { mFingerprintManager = Utils.getFingerprintManagerOrNull(activity); mFingerprintUpdater = new FingerprintUpdater(activity, mFingerprintManager); mSensorProperties = mFingerprintManager.getSensorPropertiesInternal(); + mFingerprintWakeAndUnlock = getContext().getResources().getBoolean( + com.android.internal.R.bool.config_fingerprintWakeAndUnlock); + mProximityCheckOnFingerprintUnlock = getContext().getResources().getBoolean( + com.android.internal.R.bool.config_proximityCheckOnFpsUnlock); mToken = getIntent().getByteArrayExtra( ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN); @@ -486,10 +502,17 @@ public void onCreate(Bundle savedInstanceState) { private void updateFooterColumns(@NonNull Activity activity) { final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( activity, DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT, mUserId); - final Intent helpIntent = HelpUtils.getHelpIntent( - activity, getString(getHelpResource()), activity.getClass().getName()); - final View.OnClickListener learnMoreClickListener = (v) -> - activity.startActivityForResult(helpIntent, 0); + final Intent helpIntent; + final View.OnClickListener learnMoreClickListener; + if (getHelpResource() != 0) { + helpIntent = HelpUtils.getHelpIntent( + activity, getString(getHelpResource()), activity.getClass().getName()); + learnMoreClickListener = (v) -> + activity.startActivityForResult(helpIntent, 0); + } else { + helpIntent = null; + learnMoreClickListener = null; + } mFooterColumns.clear(); if (admin != null) { @@ -511,11 +534,13 @@ private void updateFooterColumns(@NonNull Activity activity) { column2.mTitle = getText( R.string.security_fingerprint_disclaimer_lockscreen_disabled_2 ); - if (isSfps()) { - column2.mLearnMoreOverrideText = getText( - R.string.security_settings_fingerprint_settings_footer_learn_more); + if (helpIntent != null) { + if (!isUdfps() && mFingerprintWakeAndUnlock) { + column2.mLearnMoreOverrideText = getText( + R.string.security_settings_fingerprint_settings_footer_learn_more); + } + column2.mLearnMoreClickListener = learnMoreClickListener; } - column2.mLearnMoreClickListener = learnMoreClickListener; mFooterColumns.add(column2); } else { final FooterColumn column = new FooterColumn(); @@ -523,17 +548,23 @@ private void updateFooterColumns(@NonNull Activity activity) { ? R.string.private_space_fingerprint_enroll_introduction_message : R.string.security_settings_fingerprint_enroll_introduction_v3_message, DeviceHelper.getDeviceName(getActivity())); - column.mLearnMoreClickListener = learnMoreClickListener; - column.mLearnMoreOverrideText = getText( - R.string.security_settings_fingerprint_settings_footer_learn_more); + if (helpIntent != null) { + column.mLearnMoreClickListener = learnMoreClickListener; + column.mLearnMoreOverrideText = getText( + R.string.security_settings_fingerprint_settings_footer_learn_more); + } mFooterColumns.add(column); } } private boolean isUdfps() { - for (FingerprintSensorPropertiesInternal prop : mSensorProperties) { - if (prop.isAnyUdfpsType()) { - return true; + mFingerprintManager = Utils.getFingerprintManagerOrNull(getActivity()); + if (mFingerprintManager != null) { + mSensorProperties = mFingerprintManager.getSensorPropertiesInternal(); + for (FingerprintSensorPropertiesInternal prop : mSensorProperties) { + if (prop.isAnyUdfpsType()) { + return true; + } } } return false; @@ -590,7 +621,7 @@ private void addFingerprintPreferences(PreferenceGroup root) { // This needs to be after setting ids, otherwise // |mRequireScreenOnToAuthPreferenceController.isChecked| is always checking the primary // user instead of the user with |mUserId|. - if (isSfps()) { + if (!isUdfps() && mFingerprintWakeAndUnlock) { scrollToPreference(fpPrefKey); addFingerprintUnlockCategory(); } @@ -640,7 +671,9 @@ private void setupAddFingerprintPreference() { private void addFingerprintUnlockCategory() { mFingerprintUnlockCategory = findPreference(KEY_FINGERPRINT_UNLOCK_CATEGORY); - setupFingerprintUnlockCategoryPreferences(); + if (mRequireScreenOnToAuthPreferenceController != null) { + setupFingerprintUnlockCategoryPreferences(); + } final Preference restToUnlockPreference = FeatureFactory.getFeatureFactory() .getFingerprintFeatureProvider() .getSfpsRestToUnlockFeature(getContext()) @@ -654,7 +687,9 @@ private void addFingerprintUnlockCategory() { mRequireScreenOnToAuthPreference.setOnPreferenceChangeListener( restToUnlockPreference.getOnPreferenceChangeListener()); } - updateFingerprintUnlockCategoryVisibility(); + if (mFingerprintUnlockCategoryPreferenceController != null) { + updateFingerprintUnlockCategoryVisibility(); + } } private void updateFingerprintUnlockCategoryVisibility() { @@ -676,6 +711,10 @@ private void setupFingerprintUnlockCategoryPreferences() { mRequireScreenOnToAuthPreferenceController.setChecked(!isChecked); return true; }); + if (mProximityCheckOnFingerprintUnlock) { + mRequireScreenOnToAuthPreference.setSummary(R.string. + security_settings_require_screen_on_to_auth_with_proximity_description); + } } private void updateAddPreference() { @@ -909,7 +948,8 @@ protected List createPreferenceControllers(Context private List buildPreferenceControllers(Context context) { final List controllers = createThePreferenceControllers(context); - if (isSfps()) { + if (!isUdfps() && context.getResources().getBoolean( + com.android.internal.R.bool.config_fingerprintWakeAndUnlock)) { for (AbstractPreferenceController controller : controllers) { if (controller.getPreferenceKey() == KEY_FINGERPRINT_UNLOCK_CATEGORY) { mFingerprintUnlockCategoryPreferenceController = diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java index 58bc7e34202..887dfe58f56 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java @@ -61,7 +61,7 @@ public boolean isChecked() { getUserHandle()); if (toReturn == -1) { toReturn = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_performantAuthDefault) ? 1 : 0; + com.android.internal.R.bool.config_fingerprintWakeAndUnlock) ? 1 : 0; Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED, toReturn, getUserHandle()); } @@ -96,8 +96,7 @@ public void updateState(Preference preference) { @Override public int getAvailabilityStatus() { if (mFingerprintManager != null - && mFingerprintManager.isHardwareDetected() - && mFingerprintManager.isPowerbuttonFps()) { + && mFingerprintManager.isHardwareDetected()) { return mFingerprintManager.hasEnrolledTemplates(getUserId()) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } else { diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java index 674a0dfa758..acca88fc0af 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java @@ -41,8 +41,7 @@ public FingerprintUnlockCategoryController(Context context, String key) { @Override public int getAvailabilityStatus() { if (mFingerprintManager != null - && mFingerprintManager.isHardwareDetected() - && mFingerprintManager.isPowerbuttonFps()) { + && mFingerprintManager.isHardwareDetected()) { return mFingerprintManager.hasEnrolledTemplates(getUserId()) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } else { diff --git a/src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java b/src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java new file mode 100644 index 00000000000..47e79e9c890 --- /dev/null +++ b/src/com/android/settings/bluetooth/BluetoothTimeoutPreferenceController.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020-2021 The Calyx Institute + * + * 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.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.os.UserManager; +import android.provider.Settings; +import android.util.Log; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.PreferenceControllerMixin; + +public class BluetoothTimeoutPreferenceController extends BasePreferenceController implements + PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + private static final String TAG = "BluetoothTimeoutPrefCtrl"; + + public static final int FALLBACK_BLUETOOTH_TIMEOUT_VALUE = 0; + + private final String mBluetoothTimeoutKey; + + protected BluetoothAdapter mBluetoothAdapter; + + public BluetoothTimeoutPreferenceController(Context context, String key) { + super(context, key); + mBluetoothTimeoutKey = key; + + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mBluetoothAdapter == null) { + Log.e(TAG, "Bluetooth is not supported on this device"); + return; + } + } + + @Override + public int getAvailabilityStatus() { + if (mBluetoothAdapter != null) { + return UserManager.get(mContext).isAdminUser() ? AVAILABLE : DISABLED_FOR_USER; + } + return UNSUPPORTED_ON_DEVICE; + } + + @Override + public String getPreferenceKey() { + return mBluetoothTimeoutKey; + } + + @Override + public void updateState(Preference preference) { + final ListPreference timeoutListPreference = (ListPreference) preference; + final long currentTimeout = Settings.Global.getLong(mContext.getContentResolver(), + Settings.Global.BLUETOOTH_OFF_TIMEOUT, FALLBACK_BLUETOOTH_TIMEOUT_VALUE); + timeoutListPreference.setValue(String.valueOf(currentTimeout)); + updateTimeoutPreferenceDescription(timeoutListPreference, + Long.parseLong(timeoutListPreference.getValue())); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + try { + long value = Long.parseLong((String) newValue); + Settings.Global.putLong(mContext.getContentResolver(), + Settings.Global.BLUETOOTH_OFF_TIMEOUT, value); + updateTimeoutPreferenceDescription((ListPreference) preference, value); + } catch (NumberFormatException e) { + Log.e(TAG, "could not persist bluetooth timeout setting", e); + } + return true; + } + + public static CharSequence getTimeoutDescription( + long currentTimeout, CharSequence[] entries, CharSequence[] values) { + if (currentTimeout < 0 || entries == null || values == null + || values.length != entries.length) { + return null; + } + + for (int i = 0; i < values.length; i++) { + long timeout = Long.parseLong(values[i].toString()); + if (currentTimeout == timeout) { + return entries[i]; + } + } + return null; + } + + private void updateTimeoutPreferenceDescription(ListPreference preference, + long currentTimeout) { + final CharSequence[] entries = preference.getEntries(); + final CharSequence[] values = preference.getEntryValues(); + final CharSequence timeoutDescription = getTimeoutDescription( + currentTimeout, entries, values); + String summary = ""; + if (timeoutDescription != null) { + if (currentTimeout != 0) + summary = mContext.getString(R.string.bluetooth_timeout_summary, + timeoutDescription); + else + summary = mContext.getString(R.string.bluetooth_timeout_summary2); + } + preference.setSummary(summary); + } +} diff --git a/src/com/android/settings/bluetooth/DevicePickerActivity.java b/src/com/android/settings/bluetooth/DevicePickerActivity.java index a4f025b267a..c58ddcff376 100644 --- a/src/com/android/settings/bluetooth/DevicePickerActivity.java +++ b/src/com/android/settings/bluetooth/DevicePickerActivity.java @@ -20,15 +20,14 @@ import android.os.Bundle; -import androidx.fragment.app.FragmentActivity; - import com.android.settings.R; +import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; /** * Activity for Bluetooth device picker dialog. The device picker logic * is implemented in the {@link BluetoothPairingDetail} fragment. */ -public final class DevicePickerActivity extends FragmentActivity { +public final class DevicePickerActivity extends CollapsingToolbarBaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { diff --git a/src/com/android/settings/connecteddevice/NfcAndPaymentFragmentController.java b/src/com/android/settings/connecteddevice/NfcAndPaymentFragmentController.java index ee0021ec951..f16dd378db5 100644 --- a/src/com/android/settings/connecteddevice/NfcAndPaymentFragmentController.java +++ b/src/com/android/settings/connecteddevice/NfcAndPaymentFragmentController.java @@ -16,21 +16,47 @@ package com.android.settings.connecteddevice; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.nfc.NfcAdapter; import android.os.UserManager; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + import com.android.settings.R; import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.core.lifecycle.events.OnStop; /** * Controller that used to show NFC and payment features */ -public class NfcAndPaymentFragmentController extends BasePreferenceController { +public class NfcAndPaymentFragmentController extends BasePreferenceController + implements LifecycleObserver, OnResume, OnStop { private final NfcAdapter mNfcAdapter; private final PackageManager mPackageManager; private final UserManager mUserManager; + private final IntentFilter mIntentFilter; + private Preference mPreference; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mPreference == null) { + return; + } + + final String action = intent.getAction(); + if (NfcAdapter.ACTION_ADAPTER_STATE_CHANGED.equals(action)) { + refreshSummary(mPreference); + } + } + }; public NfcAndPaymentFragmentController(Context context, String preferenceKey) { super(context, preferenceKey); @@ -38,6 +64,15 @@ public NfcAndPaymentFragmentController(Context context, String preferenceKey) { mPackageManager = context.getPackageManager(); mUserManager = context.getSystemService(UserManager.class); mNfcAdapter = NfcAdapter.getDefaultAdapter(context); + + mIntentFilter = isNfcAvailable() + ? new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED) : null; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); } @Override @@ -61,4 +96,26 @@ public CharSequence getSummary() { } return null; } + + @Override + public void onStop() { + if (!isNfcAvailable()) { + return; + } + + mContext.unregisterReceiver(mReceiver); + } + + @Override + public void onResume() { + if (!isNfcAvailable()) { + return; + } + + mContext.registerReceiver(mReceiver, mIntentFilter); + } + + private boolean isNfcAvailable() { + return mNfcAdapter != null; + } } diff --git a/src/com/android/settings/connecteddevice/usb/UsbBackend.java b/src/com/android/settings/connecteddevice/usb/UsbBackend.java index d194499c88c..6e643f42fea 100644 --- a/src/com/android/settings/connecteddevice/usb/UsbBackend.java +++ b/src/com/android/settings/connecteddevice/usb/UsbBackend.java @@ -165,6 +165,30 @@ public boolean areAllRolesSupported() { && mPortStatus.isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_HOST); } + public boolean isSingleDataRoleSupported() { + return mPort != null && mPortStatus != null + && ((!mPortStatus + .isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST) + && !mPortStatus + .isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_HOST)) + || (!mPortStatus + .isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE) + && !mPortStatus + .isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_DEVICE))); + } + + public boolean isSinglePowerRoleSupported() { + return mPort != null && mPortStatus != null + && ((!mPortStatus + .isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE) + && !mPortStatus + .isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST)) + || (!mPortStatus + .isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_DEVICE) + && !mPortStatus + .isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_HOST))); + } + public static String usbFunctionsToString(long functions) { // TODO replace with UsbManager.usbFunctionsToString once supported by Roboelectric return Long.toBinaryString(functions); diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsDataRoleController.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsDataRoleController.java index 8782c796140..8c291abd205 100644 --- a/src/com/android/settings/connecteddevice/usb/UsbDetailsDataRoleController.java +++ b/src/com/android/settings/connecteddevice/usb/UsbDetailsDataRoleController.java @@ -115,7 +115,8 @@ public void onRadioButtonClicked(SelectorWithWidgetPreference preference) { @Override public boolean isAvailable() { - return !Utils.isMonkeyRunning(); + return !Utils.isMonkeyRunning() + && !mUsbBackend.isSingleDataRoleSupported(); } @Override diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsFragment.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsFragment.java index f8cabbc08fb..31d5770d75f 100644 --- a/src/com/android/settings/connecteddevice/usb/UsbDetailsFragment.java +++ b/src/com/android/settings/connecteddevice/usb/UsbDetailsFragment.java @@ -106,7 +106,6 @@ protected List createPreferenceControllers(Context private static List createControllerList(Context context, UsbBackend usbBackend, UsbDetailsFragment fragment) { List ret = new ArrayList<>(); - ret.add(new UsbDetailsHeaderController(context, fragment, usbBackend)); ret.add(new UsbDetailsDataRoleController(context, fragment, usbBackend)); ret.add(new UsbDetailsFunctionsController(context, fragment, usbBackend)); ret.add(new UsbDetailsPowerRoleController(context, fragment, usbBackend)); diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderController.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderController.java deleted file mode 100644 index 39d7c751758..00000000000 --- a/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderController.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2018 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.connecteddevice.usb; - -import android.content.Context; - -import androidx.preference.PreferenceScreen; - -import com.android.settings.R; -import com.android.settings.widget.EntityHeaderController; -import com.android.settingslib.widget.LayoutPreference; - -/** - * This class adds a header with device name. - */ -public class UsbDetailsHeaderController extends UsbDetailsController { - private static final String KEY_DEVICE_HEADER = "usb_device_header"; - - private EntityHeaderController mHeaderController; - - public UsbDetailsHeaderController(Context context, UsbDetailsFragment fragment, - UsbBackend backend) { - super(context, fragment, backend); - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - final LayoutPreference headerPreference = screen.findPreference(KEY_DEVICE_HEADER); - mHeaderController = EntityHeaderController.newInstance(mFragment.getActivity(), mFragment, - headerPreference.findViewById(R.id.entity_header)); - } - - - @Override - protected void refresh(boolean connected, long functions, int powerRole, int dataRole) { - mHeaderController.setLabel(mContext.getString(R.string.usb_pref)); - mHeaderController.setIcon(mContext.getDrawable(R.drawable.ic_usb)); - mHeaderController.done(true /* rebindActions */); - } - - @Override - public String getPreferenceKey() { - return KEY_DEVICE_HEADER; - } -} diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsPowerRoleController.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsPowerRoleController.java index f00435a0cab..9a14f3cde86 100644 --- a/src/com/android/settings/connecteddevice/usb/UsbDetailsPowerRoleController.java +++ b/src/com/android/settings/connecteddevice/usb/UsbDetailsPowerRoleController.java @@ -126,7 +126,8 @@ public boolean onPreferenceClick(Preference preference) { @Override public boolean isAvailable() { - return !Utils.isMonkeyRunning(); + return !Utils.isMonkeyRunning() + && !mUsbBackend.isSinglePowerRoleSupported(); } @Override diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 1c14712df30..883aa0f15dc 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -46,6 +46,7 @@ import com.android.settings.applications.AppDashboardFragment; import com.android.settings.applications.ProcessStatsSummary; import com.android.settings.applications.ProcessStatsUi; +import com.android.settings.applications.RunningServices; import com.android.settings.applications.UsageAccessDetails; import com.android.settings.applications.appcompat.UserAspectRatioDetails; import com.android.settings.applications.appinfo.AlarmsAndRemindersDetails; @@ -109,6 +110,7 @@ import com.android.settings.display.ColorContrastFragment; import com.android.settings.display.NightDisplaySettings; import com.android.settings.display.ScreenTimeoutSettings; +import com.android.settings.display.RefreshRateSettings; import com.android.settings.display.SmartAutoRotatePreferenceFragment; import com.android.settings.display.darkmode.DarkModeSettingsFragment; import com.android.settings.dream.DreamSettings; @@ -343,6 +345,7 @@ public class SettingsGateway { MainClearConfirm.class.getName(), ResetDashboardFragment.class.getName(), NightDisplaySettings.class.getName(), + RefreshRateSettings.class.getName(), ManageDomainUrls.class.getName(), AutomaticStorageManagerSettings.class.getName(), StorageDashboardFragment.class.getName(), @@ -396,6 +399,7 @@ public class SettingsGateway { CellularSecuritySettingsFragment.class.getName(), AccessibilityHearingAidsFragment.class.getName(), HearingDevicePairingFragment.class.getName(), + RunningServices.class.getName(), }; public static final String[] SETTINGS_FOR_RESTRICTED = { @@ -442,5 +446,6 @@ public class SettingsGateway { UserBackupSettingsActivity.class.getName(), Settings.MemtagPageActivity.class.getName(), Settings.NavigationModeSettingsActivity.class.getName(), + Settings.DevRunningServicesActivity.class.getName(), }; } diff --git a/src/com/android/settings/custom/fragment/notifications/IslandSettings.java b/src/com/android/settings/custom/fragment/notifications/IslandSettings.java new file mode 100644 index 00000000000..51cbccc8ee1 --- /dev/null +++ b/src/com/android/settings/custom/fragment/notifications/IslandSettings.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 the risingOS Android 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.custom.fragment.notifications; + +import android.content.ContentResolver; +import android.content.Context; +import android.os.Bundle; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +import java.util.List; + +@SearchIndexable +public class IslandSettings extends SettingsPreferenceFragment { + + public static final String TAG = "IslandSettings"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.island_settings); + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DERP; + } + + /** + * For search + */ + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.island_settings) { + + @Override + public List getNonIndexableKeys(Context context) { + List keys = super.getNonIndexableKeys(context); + + return keys; + } + }; +} diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java index ffc97dc722c..3dddda24d5f 100644 --- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java @@ -42,6 +42,7 @@ import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.graphics.drawable.LayerDrawable; import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; @@ -90,6 +91,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { private static final String TAG = "DashboardFeatureImpl"; private static final String DASHBOARD_TILE_PREF_KEY_PREFIX = "dashboard_tile_pref_"; private static final String META_DATA_KEY_INTENT_ACTION = "com.android.settings.intent.action"; + private static final String WELLBEING_PACKAGE = "com.google.android.apps.wellbeing"; protected final Context mContext; @@ -443,6 +445,11 @@ private void setPreferenceIcon(Preference preference, Tile tile, boolean forceRo return; } if (TextUtils.equals(tile.getCategory(), CategoryKey.CATEGORY_HOMEPAGE)) { + if (iconPackage.equals(WELLBEING_PACKAGE) && iconDrawable instanceof LayerDrawable + && ((LayerDrawable) iconDrawable).getDrawable(1) != null) { + iconDrawable = ((LayerDrawable) iconDrawable).getDrawable(1); + iconDrawable.mutate(); + } iconDrawable.setTint(Utils.getHomepageIconColor(preference.getContext())); } diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index 9abc6c2a0fa..dca60f902be 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -556,12 +556,17 @@ private void refreshDashboardTiles(final String tag) { screen.addPreference(pref); } } else { + Preference group = null; if (tile.hasGroupKey() && mDashboardTilePrefKeys.containsKey(tile.getGroupKey())) { - Preference group = screen.findPreference(tile.getGroupKey()); - if (group instanceof PreferenceCategory) { - ((PreferenceCategory) group).addPreference(pref); - } + group = screen.findPreference(tile.getGroupKey()); + } else if ("top_level_google".equals(key)) { + group = screen.findPreference("top_level_account_category"); + } else if ("top_level_wellbeing".equals(key)) { + group = screen.findPreference("top_level_security_privacy_category"); + } + if (group instanceof PreferenceCategory) { + ((PreferenceCategory) group).addPreference(pref); } else { screen.addPreference(pref); } diff --git a/src/com/android/settings/deletionhelper/AutomaticStorageManagerSettings.java b/src/com/android/settings/deletionhelper/AutomaticStorageManagerSettings.java index a36d9edca55..133b291ea31 100644 --- a/src/com/android/settings/deletionhelper/AutomaticStorageManagerSettings.java +++ b/src/com/android/settings/deletionhelper/AutomaticStorageManagerSettings.java @@ -25,7 +25,7 @@ import android.view.View; import android.view.ViewGroup; -import androidx.preference.DropDownPreference; +import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceChangeListener; @@ -51,7 +51,7 @@ public class AutomaticStorageManagerSettings extends DashboardFragment private static final String KEY_DAYS = "days"; private AutomaticStorageManagerSwitchBarController mSwitchController; - private DropDownPreference mDaysToRetain; + private ListPreference mDaysToRetain; private SettingsMainSwitchBar mSwitchBar; @Override @@ -66,7 +66,7 @@ public View onCreateView( } private void initializeDaysToRetainPreference() { - mDaysToRetain = (DropDownPreference) findPreference(KEY_DAYS); + mDaysToRetain = (ListPreference) findPreference(KEY_DAYS); mDaysToRetain.setOnPreferenceChangeListener(this); ContentResolver cr = getContentResolver(); diff --git a/src/com/android/settings/derp/ChangelogActivity.java b/src/com/android/settings/derp/ChangelogActivity.java new file mode 100644 index 00000000000..9ad9227c461 --- /dev/null +++ b/src/com/android/settings/derp/ChangelogActivity.java @@ -0,0 +1,32 @@ +/* +* Copyright (C) 2016 The NitrogenOS Project +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +*/ + +package com.android.settings.derp; + +import android.os.Bundle; +import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity; + +public class ChangelogActivity extends CollapsingToolbarBaseActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getFragmentManager().beginTransaction().replace( + com.android.settingslib.collapsingtoolbar.R.id.content_frame, + new ChangelogFragment()).commit(); + } +} diff --git a/src/com/android/settings/derp/ChangelogFragment.java b/src/com/android/settings/derp/ChangelogFragment.java new file mode 100644 index 00000000000..48b1986b153 --- /dev/null +++ b/src/com/android/settings/derp/ChangelogFragment.java @@ -0,0 +1,130 @@ +/* + Copyright (C) 2016 Nitrogen 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.derp; + +import android.annotation.Nullable; +import android.app.Fragment; +import android.content.res.Resources; +import android.os.Bundle; +import android.graphics.Color; +import android.graphics.Typeface; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.util.Log; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.LinearLayout.LayoutParams; +import android.widget.TextView; + +import androidx.preference.PreferenceFragment; + +import com.android.settings.R; + +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +import com.android.internal.logging.nano.MetricsProto; + +public class ChangelogFragment extends PreferenceFragment { + + TextView textView; + + private static final String CHANGELOG_PATH = "/system/etc/Changelog.txt"; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.changelog, container, false); + } + + @Override + public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + textView = view.findViewById(R.id.changelog_text); + + InputStreamReader inputReader = null; + String text = null; + StringBuilder data = new StringBuilder(); + + Pattern date = Pattern.compile("(={20}|\\d{4}-\\d{2}-\\d{2})"); + Pattern commit = Pattern.compile("([a-f0-9]{7})"); + Pattern committer = Pattern.compile("\\[(\\D.*?)]"); + Pattern title = Pattern.compile("(\\R\\s+[\\*]\\s.*)"); + + try { + char tmp[] = new char[2048]; + int numRead; + + inputReader = new FileReader(CHANGELOG_PATH); + while ((numRead = inputReader.read(tmp)) >= 0) { + data.append(tmp, 0, numRead); + } +// text = data.toString(); + } catch (IOException e) { +// text = getString(R.string.changelog_error); + } finally { + try { + if (inputReader != null) { + inputReader.close(); + } + } catch (IOException e) { + } + } + + SpannableStringBuilder sb = new SpannableStringBuilder(data); + Resources.Theme theme = getContext().getTheme(); + TypedValue typedValue = new TypedValue(); + theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true); + final int color = getContext().getColor(typedValue.resourceId); + + Matcher m = date.matcher(data); + while (m.find()){ + sb.setSpan(new ForegroundColorSpan(color), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE); + sb.setSpan(new StyleSpan(Typeface.BOLD), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } + m = commit.matcher(data); + while (m.find()){ + sb.setSpan(new StyleSpan(Typeface.NORMAL), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } + m = committer.matcher(data); + while (m.find()){ + sb.setSpan(new ForegroundColorSpan(color), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE); + sb.setSpan(new StyleSpan(Typeface.NORMAL), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } + m = title.matcher(data); + while (m.find()){ + sb.setSpan(new ForegroundColorSpan(color), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE); + sb.setSpan(new StyleSpan(Typeface.BOLD), m.start(1), m.end(1), Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } + + textView.setText(sb); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + + } +} diff --git a/src/com/android/settings/derp/buttons/ButtonSettings.java b/src/com/android/settings/derp/buttons/ButtonSettings.java new file mode 100644 index 00000000000..4b91abe9a89 --- /dev/null +++ b/src/com/android/settings/derp/buttons/ButtonSettings.java @@ -0,0 +1,1013 @@ +/* + * SPDX-FileCopyrightText: 2016 The CyanogenMod project + * SPDX-FileCopyrightText: 2017-2023 The LineageOS project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.buttons; + +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.om.IOverlayManager; +import android.content.res.Resources; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.ArraySet; +import android.util.Log; +import android.view.Display; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreferenceCompat; + +import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.derp.buttons.preference.*; +import com.android.settings.derp.utils.DeviceUtils; +import com.android.settings.derp.utils.TelephonyUtils; +import com.android.settingslib.search.SearchIndexable; + +import static com.android.internal.util.derp.DeviceKeysConstants.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.android.internal.derp.hardware.LineageHardwareManager; + +@SearchIndexable +public class ButtonSettings extends SettingsPreferenceFragment + implements Preference.OnPreferenceChangeListener { + private static final String TAG = "SystemSettings"; + + private static final String KEY_BUTTON_BACKLIGHT = "button_backlight"; + private static final String KEY_BACK_WAKE_SCREEN = "back_wake_screen"; + private static final String KEY_CAMERA_LAUNCH = "camera_launch"; + private static final String KEY_CAMERA_SLEEP_ON_RELEASE = "camera_sleep_on_release"; + private static final String KEY_CAMERA_WAKE_SCREEN = "camera_wake_screen"; + private static final String KEY_BACK_LONG_PRESS = "hardware_keys_back_long_press"; + private static final String KEY_HOME_LONG_PRESS = "hardware_keys_home_long_press"; + private static final String KEY_HOME_DOUBLE_TAP = "hardware_keys_home_double_tap"; + private static final String KEY_HOME_WAKE_SCREEN = "home_wake_screen"; + private static final String KEY_MENU_PRESS = "hardware_keys_menu_press"; + private static final String KEY_MENU_LONG_PRESS = "hardware_keys_menu_long_press"; + private static final String KEY_MENU_WAKE_SCREEN = "menu_wake_screen"; + private static final String KEY_ASSIST_PRESS = "hardware_keys_assist_press"; + private static final String KEY_ASSIST_LONG_PRESS = "hardware_keys_assist_long_press"; + private static final String KEY_ASSIST_WAKE_SCREEN = "assist_wake_screen"; + private static final String KEY_APP_SWITCH_PRESS = "hardware_keys_app_switch_press"; + private static final String KEY_APP_SWITCH_LONG_PRESS = "hardware_keys_app_switch_long_press"; + private static final String KEY_APP_SWITCH_WAKE_SCREEN = "app_switch_wake_screen"; + private static final String KEY_VOLUME_KEY_CURSOR_CONTROL = "volume_key_cursor_control"; + private static final String KEY_SWAP_VOLUME_BUTTONS = "swap_volume_buttons"; + private static final String KEY_VOLUME_PANEL_ON_LEFT = "volume_panel_on_left"; + private static final String KEY_VOLUME_WAKE_SCREEN = "volume_wake_screen"; + private static final String KEY_VOLUME_ANSWER_CALL = "volume_answer_call"; + private static final String KEY_DISABLE_NAV_KEYS = "disable_nav_keys"; + private static final String KEY_NAVIGATION_ARROW_KEYS = "navigation_bar_menu_arrow_keys"; + private static final String KEY_NAVIGATION_BACK_LONG_PRESS = "navigation_back_long_press"; + private static final String KEY_NAVIGATION_HOME_LONG_PRESS = "navigation_home_long_press"; + private static final String KEY_NAVIGATION_HOME_DOUBLE_TAP = "navigation_home_double_tap"; + private static final String KEY_NAVIGATION_APP_SWITCH_LONG_PRESS = + "navigation_app_switch_long_press"; + private static final String KEY_EDGE_LONG_SWIPE = "navigation_bar_edge_long_swipe"; + private static final String KEY_POWER_END_CALL = "power_end_call"; + private static final String KEY_HOME_ANSWER_CALL = "home_answer_call"; + private static final String KEY_VOLUME_MUSIC_CONTROLS = "volbtn_music_controls"; + private static final String KEY_TORCH_LONG_PRESS_POWER_GESTURE = + "torch_long_press_power_gesture"; + private static final String KEY_TORCH_LONG_PRESS_POWER_TIMEOUT = + "torch_long_press_power_timeout"; + private static final String KEY_CLICK_PARTIAL_SCREENSHOT = + "click_partial_screenshot"; + private static final String KEY_SWAP_CAPACITIVE_KEYS = "swap_capacitive_keys"; + private static final String KEY_NAV_BAR_INVERSE = "sysui_nav_bar_inverse"; + private static final String KEY_ENABLE_TASKBAR = "enable_taskbar"; + + private static final String CATEGORY_POWER = "power_key"; + private static final String CATEGORY_HOME = "home_key"; + private static final String CATEGORY_BACK = "back_key"; + private static final String CATEGORY_MENU = "menu_key"; + private static final String CATEGORY_ASSIST = "assist_key"; + private static final String CATEGORY_APPSWITCH = "app_switch_key"; + private static final String CATEGORY_CAMERA = "camera_key"; + private static final String CATEGORY_VOLUME = "volume_keys"; + private static final String CATEGORY_NAVBAR = "navigation_bar_category"; + private static final String CATEGORY_EXTRAS = "extras_category"; + + private ListPreference mBackLongPressAction; + private ListPreference mHomeLongPressAction; + private ListPreference mHomeDoubleTapAction; + private ListPreference mMenuPressAction; + private ListPreference mMenuLongPressAction; + private ListPreference mAssistPressAction; + private ListPreference mAssistLongPressAction; + private ListPreference mAppSwitchPressAction; + private ListPreference mAppSwitchLongPressAction; + private SwitchPreferenceCompat mCameraWakeScreen; + private SwitchPreferenceCompat mCameraSleepOnRelease; + private ListPreference mVolumeKeyCursorControl; + private SwitchPreferenceCompat mSwapVolumeButtons; + private SwitchPreferenceCompat mVolumePanelOnLeft; + private SwitchPreferenceCompat mDisableNavigationKeys; + private SwitchPreferenceCompat mNavigationArrowKeys; + private ListPreference mNavigationBackLongPressAction; + private ListPreference mNavigationHomeLongPressAction; + private ListPreference mNavigationHomeDoubleTapAction; + private ListPreference mNavigationAppSwitchLongPressAction; + private ListPreference mEdgeLongSwipeAction; + private SwitchPreferenceCompat mPowerEndCall; + private SwitchPreferenceCompat mHomeAnswerCall; + private ListPreference mTorchLongPressPowerTimeout; + private SwitchPreferenceCompat mSwapCapacitiveKeys; + private SwitchPreferenceCompat mNavBarInverse; + private SwitchPreferenceCompat mEnableTaskbar; + + private PreferenceCategory mNavigationPreferencesCat; + + private Handler mHandler; + + private LineageHardwareManager mHardware; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mHardware = LineageHardwareManager.getInstance(getActivity()); + + addPreferencesFromResource(R.xml.button_settings); + + final Resources res = getResources(); + final ContentResolver resolver = requireActivity().getContentResolver(); + final PreferenceScreen prefScreen = getPreferenceScreen(); + + final boolean hasPowerKey = DeviceUtils.hasPowerKey(); + final boolean hasHomeKey = DeviceUtils.hasHomeKey(getActivity()); + final boolean hasBackKey = DeviceUtils.hasBackKey(getActivity()); + final boolean hasMenuKey = DeviceUtils.hasMenuKey(getActivity()); + final boolean hasAssistKey = DeviceUtils.hasAssistKey(getActivity()); + final boolean hasAppSwitchKey = DeviceUtils.hasAppSwitchKey(getActivity()); + final boolean hasCameraKey = DeviceUtils.hasCameraKey(getActivity()); + final boolean hasVolumeKeys = DeviceUtils.hasVolumeKeys(getActivity()); + + final boolean showHomeWake = DeviceUtils.canWakeUsingHomeKey(getActivity()); + final boolean showBackWake = DeviceUtils.canWakeUsingBackKey(getActivity()); + final boolean showMenuWake = DeviceUtils.canWakeUsingMenuKey(getActivity()); + final boolean showAssistWake = DeviceUtils.canWakeUsingAssistKey(getActivity()); + final boolean showAppSwitchWake = DeviceUtils.canWakeUsingAppSwitchKey(getActivity()); + final boolean showCameraWake = DeviceUtils.canWakeUsingCameraKey(getActivity()); + final boolean showVolumeWake = DeviceUtils.canWakeUsingVolumeKeys(getActivity()); + + final PreferenceCategory powerCategory = prefScreen.findPreference(CATEGORY_POWER); + final PreferenceCategory homeCategory = prefScreen.findPreference(CATEGORY_HOME); + final PreferenceCategory backCategory = prefScreen.findPreference(CATEGORY_BACK); + final PreferenceCategory menuCategory = prefScreen.findPreference(CATEGORY_MENU); + final PreferenceCategory assistCategory = prefScreen.findPreference(CATEGORY_ASSIST); + final PreferenceCategory appSwitchCategory = prefScreen.findPreference(CATEGORY_APPSWITCH); + final PreferenceCategory volumeCategory = prefScreen.findPreference(CATEGORY_VOLUME); + final PreferenceCategory cameraCategory = prefScreen.findPreference(CATEGORY_CAMERA); + final PreferenceCategory extrasCategory = prefScreen.findPreference(CATEGORY_EXTRAS); + + // Power button ends calls. + mPowerEndCall = findPreference(KEY_POWER_END_CALL); + + // Long press power while display is off to activate torchlight + SwitchPreferenceCompat torchLongPressPowerGesture = + findPreference(KEY_TORCH_LONG_PRESS_POWER_GESTURE); + final int torchLongPressPowerTimeout = Settings.System.getInt(resolver, + Settings.System.TORCH_LONG_PRESS_POWER_TIMEOUT, 0); + mTorchLongPressPowerTimeout = initList(KEY_TORCH_LONG_PRESS_POWER_TIMEOUT, + torchLongPressPowerTimeout); + + // Home button answers calls. + mHomeAnswerCall = findPreference(KEY_HOME_ANSWER_CALL); + + mHandler = new Handler(Looper.getMainLooper()); + + // Force Navigation bar related options + mDisableNavigationKeys = findPreference(KEY_DISABLE_NAV_KEYS); + + mNavigationPreferencesCat = findPreference(CATEGORY_NAVBAR); + + Action defaultBackLongPressAction = Action.fromIntSafe(res.getInteger( + com.android.internal.R.integer.config_longPressOnBackBehavior)); + Action defaultHomeLongPressAction = Action.fromIntSafe(res.getInteger( + com.android.internal.R.integer.config_longPressOnHomeBehavior)); + Action defaultHomeDoubleTapAction = Action.fromIntSafe(res.getInteger( + com.android.internal.R.integer.config_doubleTapOnHomeBehavior)); + Action defaultAppSwitchLongPressAction = Action.fromIntSafe(res.getInteger( + com.android.internal.R.integer.config_longPressOnAppSwitchBehavior)); + Action backLongPressAction = Action.fromSettings(resolver, + Settings.System.KEY_BACK_LONG_PRESS_ACTION, + defaultBackLongPressAction); + Action homeLongPressAction = Action.fromSettings(resolver, + Settings.System.KEY_HOME_LONG_PRESS_ACTION, + defaultHomeLongPressAction); + Action homeDoubleTapAction = Action.fromSettings(resolver, + Settings.System.KEY_HOME_DOUBLE_TAP_ACTION, + defaultHomeDoubleTapAction); + Action appSwitchLongPressAction = Action.fromSettings(resolver, + Settings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION, + defaultAppSwitchLongPressAction); + Action edgeLongSwipeAction = Action.fromSettings(resolver, + Settings.System.KEY_EDGE_LONG_SWIPE_ACTION, + Action.NOTHING); + + // Navigation bar arrow keys while typing + mNavigationArrowKeys = findPreference(KEY_NAVIGATION_ARROW_KEYS); + + // Navigation bar back long press + mNavigationBackLongPressAction = initList(KEY_NAVIGATION_BACK_LONG_PRESS, + backLongPressAction); + + // Navigation bar home long press + mNavigationHomeLongPressAction = initList(KEY_NAVIGATION_HOME_LONG_PRESS, + homeLongPressAction); + + // Navigation bar home double tap + mNavigationHomeDoubleTapAction = initList(KEY_NAVIGATION_HOME_DOUBLE_TAP, + homeDoubleTapAction); + + // Navigation bar app switch long press + mNavigationAppSwitchLongPressAction = initList(KEY_NAVIGATION_APP_SWITCH_LONG_PRESS, + appSwitchLongPressAction); + + // Edge long swipe gesture + mEdgeLongSwipeAction = initList(KEY_EDGE_LONG_SWIPE, edgeLongSwipeAction); + + // Hardware key disabler + if (isKeyDisablerSupported(getActivity())) { + // Remove keys that can be provided by the navbar + updateDisableNavkeysOption(); + mNavigationPreferencesCat.setEnabled(mDisableNavigationKeys.isChecked()); + mDisableNavigationKeys.setDisableDependentsState(true); + } else { + prefScreen.removePreference(mDisableNavigationKeys); + } + updateDisableNavkeysCategories(mDisableNavigationKeys.isChecked(), /* force */ true); + + if (hasPowerKey) { + if (!TelephonyUtils.isVoiceCapable(requireActivity())) { + powerCategory.removePreference(mPowerEndCall); + mPowerEndCall = null; + } + if (!DeviceUtils.deviceSupportsFlashLight(requireActivity())) { + powerCategory.removePreference(torchLongPressPowerGesture); + powerCategory.removePreference(mTorchLongPressPowerTimeout); + } + } + if (!hasPowerKey || powerCategory.getPreferenceCount() == 0) { + prefScreen.removePreference(powerCategory); + } + + if (hasHomeKey) { + if (!showHomeWake) { + homeCategory.removePreference(findPreference(KEY_HOME_WAKE_SCREEN)); + } + + if (!TelephonyUtils.isVoiceCapable(requireActivity())) { + homeCategory.removePreference(mHomeAnswerCall); + mHomeAnswerCall = null; + } + + mHomeLongPressAction = initList(KEY_HOME_LONG_PRESS, homeLongPressAction); + mHomeDoubleTapAction = initList(KEY_HOME_DOUBLE_TAP, homeDoubleTapAction); + if (mDisableNavigationKeys.isChecked()) { + mHomeLongPressAction.setEnabled(false); + mHomeDoubleTapAction.setEnabled(false); + } + + } + if (!hasHomeKey || homeCategory.getPreferenceCount() == 0) { + prefScreen.removePreference(homeCategory); + } + + if (hasBackKey) { + if (!showBackWake) { + backCategory.removePreference(findPreference(KEY_BACK_WAKE_SCREEN)); + } + + mBackLongPressAction = initList(KEY_BACK_LONG_PRESS, backLongPressAction); + if (mDisableNavigationKeys.isChecked()) { + mBackLongPressAction.setEnabled(false); + } + + } + if (!hasBackKey || backCategory.getPreferenceCount() == 0) { + prefScreen.removePreference(backCategory); + } + + if (hasMenuKey) { + if (!showMenuWake) { + menuCategory.removePreference(findPreference(KEY_MENU_WAKE_SCREEN)); + } + + Action pressAction = Action.fromSettings(resolver, + Settings.System.KEY_MENU_ACTION, Action.MENU); + mMenuPressAction = initList(KEY_MENU_PRESS, pressAction); + + Action longPressAction = Action.fromSettings(resolver, + Settings.System.KEY_MENU_LONG_PRESS_ACTION, + hasAssistKey ? Action.NOTHING : Action.APP_SWITCH); + mMenuLongPressAction = initList(KEY_MENU_LONG_PRESS, longPressAction); + + } + if (!hasMenuKey || menuCategory.getPreferenceCount() == 0) { + prefScreen.removePreference(menuCategory); + } + + if (hasAssistKey) { + if (!showAssistWake) { + assistCategory.removePreference(findPreference(KEY_ASSIST_WAKE_SCREEN)); + } + + Action pressAction = Action.fromSettings(resolver, + Settings.System.KEY_ASSIST_ACTION, Action.SEARCH); + mAssistPressAction = initList(KEY_ASSIST_PRESS, pressAction); + + Action longPressAction = Action.fromSettings(resolver, + Settings.System.KEY_ASSIST_LONG_PRESS_ACTION, Action.VOICE_SEARCH); + mAssistLongPressAction = initList(KEY_ASSIST_LONG_PRESS, longPressAction); + + } + if (!hasAssistKey || assistCategory.getPreferenceCount() == 0) { + prefScreen.removePreference(assistCategory); + } + + if (hasAppSwitchKey) { + if (!showAppSwitchWake) { + appSwitchCategory.removePreference(findPreference(KEY_APP_SWITCH_WAKE_SCREEN)); + } + + Action pressAction = Action.fromSettings(resolver, + Settings.System.KEY_APP_SWITCH_ACTION, Action.APP_SWITCH); + mAppSwitchPressAction = initList(KEY_APP_SWITCH_PRESS, pressAction); + + mAppSwitchLongPressAction = initList(KEY_APP_SWITCH_LONG_PRESS, + appSwitchLongPressAction); + + } + if (!hasAppSwitchKey || appSwitchCategory.getPreferenceCount() == 0) { + prefScreen.removePreference(appSwitchCategory); + } + + if (hasCameraKey) { + mCameraWakeScreen = findPreference(KEY_CAMERA_WAKE_SCREEN); + mCameraSleepOnRelease = findPreference(KEY_CAMERA_SLEEP_ON_RELEASE); + + if (!showCameraWake) { + prefScreen.removePreference(mCameraWakeScreen); + } + // Only show 'Camera sleep on release' if the device has a focus key + if (res.getBoolean( + com.android.internal.R.bool.config_singleStageCameraKey)) { + prefScreen.removePreference(mCameraSleepOnRelease); + } + } + if (!hasCameraKey || cameraCategory.getPreferenceCount() == 0) { + prefScreen.removePreference(cameraCategory); + } + + if (hasVolumeKeys) { + if (!showVolumeWake) { + volumeCategory.removePreference(findPreference(KEY_VOLUME_WAKE_SCREEN)); + } + + if (!TelephonyUtils.isVoiceCapable(requireActivity())) { + volumeCategory.removePreference(findPreference(KEY_VOLUME_ANSWER_CALL)); + } + + int cursorControlAction = Settings.System.getInt(resolver, + Settings.System.VOLUME_KEY_CURSOR_CONTROL, 0); + mVolumeKeyCursorControl = initList(KEY_VOLUME_KEY_CURSOR_CONTROL, + cursorControlAction); + + int swapVolumeKeys = Settings.System.getInt(resolver, + Settings.System.SWAP_VOLUME_KEYS_ON_ROTATION, 0); + mSwapVolumeButtons = prefScreen.findPreference(KEY_SWAP_VOLUME_BUTTONS); + if (mSwapVolumeButtons != null) { + mSwapVolumeButtons.setChecked(swapVolumeKeys > 0); + } + + final boolean volumePanelOnLeft = Settings.Secure.getIntForUser(resolver, + Settings.Secure.VOLUME_PANEL_ON_LEFT, 0, UserHandle.USER_CURRENT) != 0; + mVolumePanelOnLeft = prefScreen.findPreference(KEY_VOLUME_PANEL_ON_LEFT); + if (mVolumePanelOnLeft != null) { + mVolumePanelOnLeft.setChecked(volumePanelOnLeft); + } + } else { + extrasCategory.removePreference(findPreference(KEY_CLICK_PARTIAL_SCREENSHOT)); + } + if (!hasVolumeKeys || volumeCategory.getPreferenceCount() == 0) { + prefScreen.removePreference(volumeCategory); + } + + // Only show the navigation bar category on devices that have a navigation bar + // or support disabling the hardware keys + if (!hasNavigationBar() && !isKeyDisablerSupported(getActivity())) { + prefScreen.removePreference(mNavigationPreferencesCat); + } + + final ButtonBacklightBrightness backlight = findPreference(KEY_BUTTON_BACKLIGHT); + if (!DeviceUtils.hasButtonBacklightSupport(requireActivity()) + && !DeviceUtils.hasKeyboardBacklightSupport(getActivity())) { + prefScreen.removePreference(backlight); + } + + if (mCameraWakeScreen != null) { + if (mCameraSleepOnRelease != null && !res.getBoolean( + com.android.internal.R.bool.config_singleStageCameraKey)) { + mCameraSleepOnRelease.setDependency(KEY_CAMERA_WAKE_SCREEN); + } + } + + SwitchPreferenceCompat volumeWakeScreen = findPreference(KEY_VOLUME_WAKE_SCREEN); + SwitchPreferenceCompat volumeMusicControls = findPreference(KEY_VOLUME_MUSIC_CONTROLS); + + if (volumeWakeScreen != null) { + if (volumeMusicControls != null) { + volumeMusicControls.setDependency(KEY_VOLUME_WAKE_SCREEN); + volumeWakeScreen.setDisableDependentsState(true); + } + } + + mSwapCapacitiveKeys = findPreference(KEY_SWAP_CAPACITIVE_KEYS); + if (mSwapCapacitiveKeys != null && !isKeySwapperSupported(getActivity())) { + prefScreen.removePreference(mSwapCapacitiveKeys); + } else { + mSwapCapacitiveKeys.setOnPreferenceChangeListener(this); + mSwapCapacitiveKeys.setDependency(KEY_DISABLE_NAV_KEYS); + } + + mNavBarInverse = findPreference(KEY_NAV_BAR_INVERSE); + + mEnableTaskbar = findPreference(KEY_ENABLE_TASKBAR); + if (mEnableTaskbar != null) { + if (!hasNavigationBar()) { + mNavigationPreferencesCat.removePreference(mEnableTaskbar); + } else { + mEnableTaskbar.setOnPreferenceChangeListener(this); + mEnableTaskbar.setChecked(Settings.System.getInt(resolver, + Settings.System.ENABLE_TASKBAR, + isLargeScreen(requireContext()) ? 1 : 0) == 1); + toggleTaskBarDependencies(mEnableTaskbar.isChecked()); + } + } + + List unsupportedValues = new ArrayList<>(); + List entries = new ArrayList<>( + Arrays.asList(res.getStringArray(R.array.hardware_keys_action_entries))); + List values = new ArrayList<>( + Arrays.asList(res.getStringArray(R.array.hardware_keys_action_values))); + + // hide split screen option unconditionally - it doesn't work at the moment + // once someone gets it working again: hide it only for low-ram devices + // (check ActivityManager.isLowRamDeviceStatic()) + unsupportedValues.add(Action.SPLIT_SCREEN.ordinal()); + + for (int unsupportedValue: unsupportedValues) { + entries.remove(unsupportedValue); + values.remove(unsupportedValue); + } + + String[] actionEntries = entries.toArray(new String[0]); + String[] actionValues = values.toArray(new String[0]); + + if (hasBackKey) { + mBackLongPressAction.setEntries(actionEntries); + mBackLongPressAction.setEntryValues(actionValues); + } + + if (hasHomeKey) { + mHomeLongPressAction.setEntries(actionEntries); + mHomeLongPressAction.setEntryValues(actionValues); + + mHomeDoubleTapAction.setEntries(actionEntries); + mHomeDoubleTapAction.setEntryValues(actionValues); + } + + if (hasMenuKey) { + mMenuPressAction.setEntries(actionEntries); + mMenuPressAction.setEntryValues(actionValues); + + mMenuLongPressAction.setEntries(actionEntries); + mMenuLongPressAction.setEntryValues(actionValues); + } + + if (hasAssistKey) { + mAssistPressAction.setEntries(actionEntries); + mAssistPressAction.setEntryValues(actionValues); + + mAssistLongPressAction.setEntries(actionEntries); + mAssistLongPressAction.setEntryValues(actionValues); + } + + if (hasAppSwitchKey) { + mAppSwitchPressAction.setEntries(actionEntries); + mAppSwitchPressAction.setEntryValues(actionValues); + + mAppSwitchLongPressAction.setEntries(actionEntries); + mAppSwitchLongPressAction.setEntryValues(actionValues); + } + + mNavigationBackLongPressAction.setEntries(actionEntries); + mNavigationBackLongPressAction.setEntryValues(actionValues); + + mNavigationHomeLongPressAction.setEntries(actionEntries); + mNavigationHomeLongPressAction.setEntryValues(actionValues); + + mNavigationHomeDoubleTapAction.setEntries(actionEntries); + mNavigationHomeDoubleTapAction.setEntryValues(actionValues); + + mNavigationAppSwitchLongPressAction.setEntries(actionEntries); + mNavigationAppSwitchLongPressAction.setEntryValues(actionValues); + + mEdgeLongSwipeAction.setEntries(actionEntries); + mEdgeLongSwipeAction.setEntryValues(actionValues); + } + + @Override + public void onResume() { + super.onResume(); + + // Power button ends calls. + if (mPowerEndCall != null) { + final int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(), + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT); + final boolean powerButtonEndsCall = + (incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP); + mPowerEndCall.setChecked(powerButtonEndsCall); + } + + // Home button answers calls. + if (mHomeAnswerCall != null) { + final int incallHomeBehavior = Settings.Secure.getInt(getContentResolver(), + Settings.Secure.RING_HOME_BUTTON_BEHAVIOR, + Settings.Secure.RING_HOME_BUTTON_BEHAVIOR_DEFAULT); + final boolean homeButtonAnswersCall = + (incallHomeBehavior == Settings.Secure.RING_HOME_BUTTON_BEHAVIOR_ANSWER); + mHomeAnswerCall.setChecked(homeButtonAnswersCall); + } + } + + private ListPreference initList(String key, Action value) { + return initList(key, value.ordinal()); + } + + private ListPreference initList(String key, int value) { + ListPreference list = getPreferenceScreen().findPreference(key); + if (list == null) return null; + list.setValue(Integer.toString(value)); + list.setSummary(list.getEntry()); + list.setOnPreferenceChangeListener(this); + return list; + } + + private void handleListChange(ListPreference pref, Object newValue, String setting) { + String value = (String) newValue; + int index = pref.findIndexOfValue(value); + pref.setSummary(pref.getEntries()[index]); + Settings.System.putInt(getContentResolver(), setting, Integer.parseInt(value)); + } + + private void handleSystemListChange(ListPreference pref, Object newValue, String setting) { + String value = (String) newValue; + int index = pref.findIndexOfValue(value); + pref.setSummary(pref.getEntries()[index]); + Settings.System.putInt(getContentResolver(), setting, Integer.parseInt(value)); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mBackLongPressAction || + preference == mNavigationBackLongPressAction) { + handleListChange((ListPreference) preference, newValue, + Settings.System.KEY_BACK_LONG_PRESS_ACTION); + return true; + } else if (preference == mHomeLongPressAction || + preference == mNavigationHomeLongPressAction) { + handleListChange((ListPreference) preference, newValue, + Settings.System.KEY_HOME_LONG_PRESS_ACTION); + return true; + } else if (preference == mHomeDoubleTapAction || + preference == mNavigationHomeDoubleTapAction) { + handleListChange((ListPreference) preference, newValue, + Settings.System.KEY_HOME_DOUBLE_TAP_ACTION); + return true; + } else if (preference == mMenuPressAction) { + handleListChange(mMenuPressAction, newValue, + Settings.System.KEY_MENU_ACTION); + return true; + } else if (preference == mMenuLongPressAction) { + handleListChange(mMenuLongPressAction, newValue, + Settings.System.KEY_MENU_LONG_PRESS_ACTION); + return true; + } else if (preference == mAssistPressAction) { + handleListChange(mAssistPressAction, newValue, + Settings.System.KEY_ASSIST_ACTION); + return true; + } else if (preference == mAssistLongPressAction) { + handleListChange(mAssistLongPressAction, newValue, + Settings.System.KEY_ASSIST_LONG_PRESS_ACTION); + return true; + } else if (preference == mAppSwitchPressAction) { + handleListChange(mAppSwitchPressAction, newValue, + Settings.System.KEY_APP_SWITCH_ACTION); + return true; + } else if (preference == mAppSwitchLongPressAction || + preference == mNavigationAppSwitchLongPressAction) { + handleListChange((ListPreference) preference, newValue, + Settings.System.KEY_APP_SWITCH_LONG_PRESS_ACTION); + return true; + } else if (preference == mVolumeKeyCursorControl) { + handleSystemListChange(mVolumeKeyCursorControl, newValue, + Settings.System.VOLUME_KEY_CURSOR_CONTROL); + return true; + } else if (preference == mTorchLongPressPowerTimeout) { + handleListChange(mTorchLongPressPowerTimeout, newValue, + Settings.System.TORCH_LONG_PRESS_POWER_TIMEOUT); + return true; + } else if (preference == mEdgeLongSwipeAction) { + handleListChange(mEdgeLongSwipeAction, newValue, + Settings.System.KEY_EDGE_LONG_SWIPE_ACTION); + return true; + } else if (preference == mSwapCapacitiveKeys) { + mHardware.set(LineageHardwareManager.FEATURE_KEY_SWAP, (Boolean) newValue); + return true; + } else if (preference == mEnableTaskbar) { + toggleTaskBarDependencies((Boolean) newValue); + if ((Boolean) newValue && is2ButtonNavigationEnabled(requireContext())) { + // Let's switch to gestural mode if user previously had 2 buttons enabled. + setButtonNavigationMode(NAV_BAR_MODE_GESTURAL_OVERLAY); + } + Settings.System.putInt(getContentResolver(), + Settings.System.ENABLE_TASKBAR, ((Boolean) newValue) ? 1 : 0); + return true; + } + return false; + } + + private static boolean is2ButtonNavigationEnabled(Context context) { + return NAV_BAR_MODE_2BUTTON == context.getResources().getInteger( + com.android.internal.R.integer.config_navBarInteractionMode); + } + + private static void setButtonNavigationMode(String overlayPackage) { + IOverlayManager overlayManager = IOverlayManager.Stub.asInterface( + ServiceManager.getService(Context.OVERLAY_SERVICE)); + try { + overlayManager.setEnabledExclusiveInCategory(overlayPackage, UserHandle.USER_CURRENT); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private void toggleTaskBarDependencies(boolean enabled) { + enablePreference(mNavigationArrowKeys, !enabled); + enablePreference(mNavBarInverse, !enabled); + enablePreference(mNavigationBackLongPressAction, !enabled); + enablePreference(mNavigationHomeLongPressAction, !enabled); + enablePreference(mNavigationHomeDoubleTapAction, !enabled); + enablePreference(mNavigationAppSwitchLongPressAction, !enabled); + } + + private void enablePreference(Preference pref, boolean enabled) { + if (pref != null) { + pref.setEnabled(enabled); + } + } + + private static void writeDisableNavkeysOption(Context context, boolean enabled) { + Settings.System.putIntForUser(context.getContentResolver(), + Settings.System.FORCE_SHOW_NAVBAR, enabled ? 1 : 0, UserHandle.USER_CURRENT); + } + + private void updateDisableNavkeysOption() { + boolean enabled = Settings.System.getIntForUser( + requireActivity().getContentResolver(), + Settings.System.FORCE_SHOW_NAVBAR, 0, UserHandle.USER_CURRENT) != 0; + + mDisableNavigationKeys.setChecked(enabled); + } + + private void updateDisableNavkeysCategories(boolean navbarEnabled, boolean force) { + final PreferenceScreen prefScreen = getPreferenceScreen(); + + /* Disable hw-key options if they're disabled */ + final PreferenceCategory homeCategory = + prefScreen.findPreference(CATEGORY_HOME); + final PreferenceCategory backCategory = + prefScreen.findPreference(CATEGORY_BACK); + final PreferenceCategory menuCategory = + prefScreen.findPreference(CATEGORY_MENU); + final PreferenceCategory assistCategory = + prefScreen.findPreference(CATEGORY_ASSIST); + final PreferenceCategory appSwitchCategory = + prefScreen.findPreference(CATEGORY_APPSWITCH); + final ButtonBacklightBrightness backlight = + (ButtonBacklightBrightness) prefScreen.findPreference(KEY_BUTTON_BACKLIGHT); + + /* Toggle backlight control depending on navbar state, force it to + off if enabling */ + if (backlight != null) { + backlight.setEnabled(!navbarEnabled); + backlight.updateSummary(); + } + + /* Toggle hardkey control availability depending on navbar state */ + if (mNavigationPreferencesCat != null) { + if (force || navbarEnabled) { + if (DeviceUtils.isEdgeToEdgeEnabled(requireContext())) { + mNavigationPreferencesCat.addPreference(mEdgeLongSwipeAction); + + mNavigationPreferencesCat.removePreference(mNavigationArrowKeys); + mNavigationPreferencesCat.removePreference(mNavigationBackLongPressAction); + mNavigationPreferencesCat.removePreference(mNavigationHomeLongPressAction); + mNavigationPreferencesCat.removePreference(mNavigationHomeDoubleTapAction); + mNavigationPreferencesCat.removePreference(mNavigationAppSwitchLongPressAction); + } else if (DeviceUtils.isSwipeUpEnabled(getContext())) { + mNavigationPreferencesCat.addPreference(mNavigationBackLongPressAction); + mNavigationPreferencesCat.addPreference(mNavigationHomeLongPressAction); + mNavigationPreferencesCat.addPreference(mNavigationHomeDoubleTapAction); + + mNavigationPreferencesCat.removePreference(mNavigationAppSwitchLongPressAction); + mNavigationPreferencesCat.removePreference(mEdgeLongSwipeAction); + } else { + mNavigationPreferencesCat.addPreference(mNavigationBackLongPressAction); + mNavigationPreferencesCat.addPreference(mNavigationHomeLongPressAction); + mNavigationPreferencesCat.addPreference(mNavigationHomeDoubleTapAction); + mNavigationPreferencesCat.addPreference(mNavigationAppSwitchLongPressAction); + + mNavigationPreferencesCat.removePreference(mEdgeLongSwipeAction); + } + } + } + if (backCategory != null) { + enablePreference(mBackLongPressAction, !navbarEnabled); + } + if (homeCategory != null) { + enablePreference(mHomeAnswerCall, !navbarEnabled); + enablePreference(mHomeLongPressAction, !navbarEnabled); + enablePreference(mHomeDoubleTapAction, !navbarEnabled); + } + if (menuCategory != null) { + enablePreference(mMenuPressAction, !navbarEnabled); + enablePreference(mMenuLongPressAction, !navbarEnabled); + } + if (assistCategory != null) { + enablePreference(mAssistPressAction, !navbarEnabled); + enablePreference(mAssistLongPressAction, !navbarEnabled); + } + if (appSwitchCategory != null) { + enablePreference(mAppSwitchPressAction, !navbarEnabled); + enablePreference(mAppSwitchLongPressAction, !navbarEnabled); + } + } + + private static boolean hasNavigationBar() { + boolean hasNavigationBar = false; + try { + IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService(); + hasNavigationBar = windowManager.hasNavigationBar(Display.DEFAULT_DISPLAY); + } catch (RemoteException e) { + Log.e(TAG, "Error getting navigation bar status"); + } + return hasNavigationBar; + } + + private static boolean isKeyDisablerSupported(Context context) { + final LineageHardwareManager hardware = LineageHardwareManager.getInstance(context); + return hardware.isSupported(LineageHardwareManager.FEATURE_KEY_DISABLE); + } + + private static boolean isKeySwapperSupported(Context context) { + final LineageHardwareManager hardware = LineageHardwareManager.getInstance(context); + return hardware.isSupported(LineageHardwareManager.FEATURE_KEY_SWAP); + } + + public static void restoreKeyDisabler(Context context) { + if (!isKeyDisablerSupported(context)) { + return; + } + + boolean enabled = Settings.System.getIntForUser(context.getContentResolver(), + Settings.System.FORCE_SHOW_NAVBAR, 0, UserHandle.USER_CURRENT) != 0; + + writeDisableNavkeysOption(context, enabled); + } + + public static void restoreKeySwapper(Context context) { + if (!isKeySwapperSupported(context)) { + return; + } + + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); + final LineageHardwareManager hardware = LineageHardwareManager.getInstance(context); + hardware.set(LineageHardwareManager.FEATURE_KEY_SWAP, + preferences.getBoolean(KEY_SWAP_CAPACITIVE_KEYS, false)); + } + + @Override + public boolean onPreferenceTreeClick(Preference preference) { + if (preference == mSwapVolumeButtons) { + int value; + + if (mSwapVolumeButtons.isChecked()) { + /* The native inputflinger service uses the same logic of: + * 1 - the volume rocker is on one the sides, relative to the natural + * orientation of the display (true for all phones and most tablets) + * 2 - the volume rocker is on the top or bottom, relative to the + * natural orientation of the display (true for some tablets) + */ + value = getResources().getInteger( + R.integer.config_volumeRockerVsDisplayOrientation); + } else { + /* Disable the re-orient functionality */ + value = 0; + } + Settings.System.putInt(requireActivity().getContentResolver(), + Settings.System.SWAP_VOLUME_KEYS_ON_ROTATION, value); + } else if (preference == mVolumePanelOnLeft) { + Settings.Secure.putIntForUser(requireActivity().getContentResolver(), + Settings.Secure.VOLUME_PANEL_ON_LEFT, + mVolumePanelOnLeft.isChecked() ? 1 : 0, UserHandle.USER_CURRENT); + return true; + } else if (preference == mDisableNavigationKeys) { + mDisableNavigationKeys.setEnabled(false); + mNavigationPreferencesCat.setEnabled(false); + if (!mDisableNavigationKeys.isChecked()) { + setButtonNavigationMode(NAV_BAR_MODE_3BUTTON_OVERLAY); + } + writeDisableNavkeysOption(requireActivity(), mDisableNavigationKeys.isChecked()); + updateDisableNavkeysOption(); + updateDisableNavkeysCategories(true, false); + mHandler.postDelayed(() -> { + mDisableNavigationKeys.setEnabled(true); + mNavigationPreferencesCat.setEnabled(mDisableNavigationKeys.isChecked()); + updateDisableNavkeysCategories(mDisableNavigationKeys.isChecked(), false); + }, 1000); + } else if (preference == mPowerEndCall) { + handleTogglePowerButtonEndsCallPreferenceClick(); + return true; + } else if (preference == mHomeAnswerCall) { + handleToggleHomeButtonAnswersCallPreferenceClick(); + return true; + } + + return super.onPreferenceTreeClick(preference); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } + + private void handleTogglePowerButtonEndsCallPreferenceClick() { + Settings.Secure.putInt(getContentResolver(), + Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, (mPowerEndCall.isChecked() + ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP + : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF)); + } + + private void handleToggleHomeButtonAnswersCallPreferenceClick() { + Settings.Secure.putInt(getContentResolver(), + Settings.Secure.RING_HOME_BUTTON_BEHAVIOR, (mHomeAnswerCall.isChecked() + ? Settings.Secure.RING_HOME_BUTTON_BEHAVIOR_ANSWER + : Settings.Secure.RING_HOME_BUTTON_BEHAVIOR_DO_NOTHING)); + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.button_settings) { + + @Override + public List getNonIndexableKeys(Context context) { + final List result = new ArrayList<>(); + + if (!TelephonyUtils.isVoiceCapable(context)) { + result.add(KEY_POWER_END_CALL); + result.add(KEY_HOME_ANSWER_CALL); + result.add(KEY_VOLUME_ANSWER_CALL); + } + + if (!DeviceUtils.hasBackKey(context)) { + result.add(CATEGORY_BACK); + result.add(KEY_BACK_WAKE_SCREEN); + } else if (!DeviceUtils.canWakeUsingHomeKey(context)) { + result.add(KEY_BACK_WAKE_SCREEN); + } + + if (!DeviceUtils.hasHomeKey(context)) { + result.add(CATEGORY_HOME); + result.add(KEY_HOME_LONG_PRESS); + result.add(KEY_HOME_DOUBLE_TAP); + result.add(KEY_HOME_ANSWER_CALL); + result.add(KEY_HOME_WAKE_SCREEN); + } else if (!DeviceUtils.canWakeUsingHomeKey(context)) { + result.add(KEY_HOME_WAKE_SCREEN); + } + + if (!DeviceUtils.hasMenuKey(context)) { + result.add(CATEGORY_MENU); + result.add(KEY_MENU_PRESS); + result.add(KEY_MENU_LONG_PRESS); + result.add(KEY_MENU_WAKE_SCREEN); + } else if (!DeviceUtils.canWakeUsingMenuKey(context)) { + result.add(KEY_MENU_WAKE_SCREEN); + } + + if (!DeviceUtils.hasAssistKey(context)) { + result.add(CATEGORY_ASSIST); + result.add(KEY_ASSIST_PRESS); + result.add(KEY_ASSIST_LONG_PRESS); + result.add(KEY_ASSIST_WAKE_SCREEN); + } else if (!DeviceUtils.canWakeUsingAssistKey(context)) { + result.add(KEY_ASSIST_WAKE_SCREEN); + } + + if (!DeviceUtils.hasAppSwitchKey(context)) { + result.add(CATEGORY_APPSWITCH); + result.add(KEY_APP_SWITCH_PRESS); + result.add(KEY_APP_SWITCH_LONG_PRESS); + result.add(KEY_APP_SWITCH_WAKE_SCREEN); + } else if (!DeviceUtils.canWakeUsingAppSwitchKey(context)) { + result.add(KEY_APP_SWITCH_WAKE_SCREEN); + } + + if (!DeviceUtils.hasCameraKey(context)) { + result.add(CATEGORY_CAMERA); + result.add(KEY_CAMERA_LAUNCH); + result.add(KEY_CAMERA_SLEEP_ON_RELEASE); + result.add(KEY_CAMERA_WAKE_SCREEN); + } else if (!DeviceUtils.canWakeUsingCameraKey(context)) { + result.add(KEY_CAMERA_WAKE_SCREEN); + } + + if (!DeviceUtils.hasVolumeKeys(context)) { + result.add(CATEGORY_VOLUME); + result.add(KEY_SWAP_VOLUME_BUTTONS); + result.add(KEY_VOLUME_ANSWER_CALL); + result.add(KEY_VOLUME_KEY_CURSOR_CONTROL); + result.add(KEY_VOLUME_MUSIC_CONTROLS); + result.add(KEY_VOLUME_PANEL_ON_LEFT); + result.add(KEY_VOLUME_WAKE_SCREEN); + result.add(KEY_CLICK_PARTIAL_SCREENSHOT); + } else if (!DeviceUtils.canWakeUsingVolumeKeys(context)) { + result.add(KEY_VOLUME_WAKE_SCREEN); + } + + if (!DeviceUtils.deviceSupportsFlashLight(context)) { + result.add(KEY_TORCH_LONG_PRESS_POWER_GESTURE); + result.add(KEY_TORCH_LONG_PRESS_POWER_TIMEOUT); + } + + if (!isKeyDisablerSupported(context)) { + result.add(KEY_DISABLE_NAV_KEYS); + } + + if (!isKeySwapperSupported(context)) { + result.add(KEY_SWAP_CAPACITIVE_KEYS); + } + + if (!DeviceUtils.hasButtonBacklightSupport(context) + && !DeviceUtils.hasKeyboardBacklightSupport(context)) { + result.add(KEY_BUTTON_BACKLIGHT); + } + + if (hasNavigationBar()) { + if (DeviceUtils.isEdgeToEdgeEnabled(context)) { + result.add(KEY_NAVIGATION_ARROW_KEYS); + result.add(KEY_NAVIGATION_HOME_LONG_PRESS); + result.add(KEY_NAVIGATION_HOME_DOUBLE_TAP); + result.add(KEY_NAVIGATION_APP_SWITCH_LONG_PRESS); + } else if (DeviceUtils.isSwipeUpEnabled(context)) { + result.add(KEY_NAVIGATION_APP_SWITCH_LONG_PRESS); + result.add(KEY_EDGE_LONG_SWIPE); + } else { + result.add(KEY_EDGE_LONG_SWIPE); + } + } + return result; + } + }; +} diff --git a/src/com/android/settings/derp/buttons/PowerMenuActions.java b/src/com/android/settings/derp/buttons/PowerMenuActions.java new file mode 100644 index 00000000000..39bec2fd6e3 --- /dev/null +++ b/src/com/android/settings/derp/buttons/PowerMenuActions.java @@ -0,0 +1,253 @@ +/* + * SPDX-FileCopyrightText: 2014-2015 The CyanogenMod Project + * SPDX-FileCopyrightText: 2017-2023 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.buttons; + +import android.Manifest; +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.service.controls.ControlsProviderService; + +import androidx.preference.CheckBoxPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + +import com.android.internal.util.EmergencyAffordanceManager; +import com.android.settingslib.applications.ServiceListing; + +import com.android.internal.util.derp.PowerMenuConstants; +import com.android.internal.util.derp.PowerMenuUtils; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.internal.derp.app.LineageGlobalActions; +import com.android.settings.derp.utils.TelephonyUtils; + +import java.util.List; + +import static com.android.internal.util.derp.PowerMenuConstants.*; + +public class PowerMenuActions extends SettingsPreferenceFragment { + final static String TAG = "PowerMenuActions"; + + private static final String CATEGORY_POWER_MENU_ITEMS = "power_menu_items"; + + private PreferenceCategory mPowerMenuItemsCategory; + + private CheckBoxPreference mScreenshotPref; + private CheckBoxPreference mAirplanePref; + private CheckBoxPreference mUsersPref; + private CheckBoxPreference mBugReportPref; + private CheckBoxPreference mEmergencyPref; + private CheckBoxPreference mDeviceControlsPref; + private CheckBoxPreference mPanicPref; + private CheckBoxPreference mRestartSystemUIPref; + + private LineageGlobalActions mLineageGlobalActions; + + private EmergencyAffordanceManager mEmergencyAffordanceManager; + private boolean mForceEmergCheck = false; + + Context mContext; + private UserManager mUserManager; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + addPreferencesFromResource(R.xml.power_menu_actions_settings); + requireActivity().setTitle(R.string.power_menu_title); + mContext = requireActivity().getApplicationContext(); + mUserManager = UserManager.get(mContext); + mLineageGlobalActions = mContext.getSystemService(LineageGlobalActions.class); + mEmergencyAffordanceManager = new EmergencyAffordanceManager(mContext); + + mPowerMenuItemsCategory = findPreference(CATEGORY_POWER_MENU_ITEMS); + + for (String action : PowerMenuConstants.getAllActions()) { + if (action.equals(GLOBAL_ACTION_KEY_SCREENSHOT)) { + mScreenshotPref = findPreference(GLOBAL_ACTION_KEY_SCREENSHOT); + } else if (action.equals(GLOBAL_ACTION_KEY_AIRPLANE)) { + mAirplanePref = findPreference(GLOBAL_ACTION_KEY_AIRPLANE); + } else if (action.equals(GLOBAL_ACTION_KEY_USERS)) { + mUsersPref = findPreference(GLOBAL_ACTION_KEY_USERS); + } else if (action.equals(GLOBAL_ACTION_KEY_BUGREPORT)) { + mBugReportPref = findPreference(GLOBAL_ACTION_KEY_BUGREPORT); + } else if (action.equals(GLOBAL_ACTION_KEY_EMERGENCY)) { + mEmergencyPref = findPreference(GLOBAL_ACTION_KEY_EMERGENCY); + } else if (action.equals(GLOBAL_ACTION_KEY_DEVICECONTROLS)) { + mDeviceControlsPref = findPreference(GLOBAL_ACTION_KEY_DEVICECONTROLS); + } else if (action.equals(GLOBAL_ACTION_KEY_PANIC)) { + mPanicPref = findPreference(GLOBAL_ACTION_KEY_PANIC); + } else if (action.equals(GLOBAL_ACTION_KEY_RESTART_SYSTEMUI)) { + mRestartSystemUIPref = findPreference(GLOBAL_ACTION_KEY_RESTART_SYSTEMUI); + } + } + + if (!TelephonyUtils.isVoiceCapable(requireActivity())) { + mPowerMenuItemsCategory.removePreference(mEmergencyPref); + mEmergencyPref = null; + } + } + + @Override + public void onStart() { + super.onStart(); + + if (mScreenshotPref != null) { + mScreenshotPref.setChecked(mLineageGlobalActions.userConfigContains( + GLOBAL_ACTION_KEY_SCREENSHOT)); + } + + if (mAirplanePref != null) { + mAirplanePref.setChecked(mLineageGlobalActions.userConfigContains( + GLOBAL_ACTION_KEY_AIRPLANE)); + } + + if (mUsersPref != null) { + if (!UserHandle.MU_ENABLED || !UserManager.supportsMultipleUsers()) { + mPowerMenuItemsCategory.removePreference(mUsersPref); + mUsersPref = null; + } else { + List users = mUserManager.getUsers(); + boolean enabled = (users.size() > 1); + mUsersPref.setChecked(mLineageGlobalActions.userConfigContains( + GLOBAL_ACTION_KEY_USERS) && enabled); + mUsersPref.setEnabled(enabled); + } + } + + if (mBugReportPref != null) { + mBugReportPref.setChecked(mLineageGlobalActions.userConfigContains( + GLOBAL_ACTION_KEY_BUGREPORT)); + } + + if (mEmergencyPref != null) { + mForceEmergCheck = mEmergencyAffordanceManager.needsEmergencyAffordance(); + mEmergencyPref.setChecked(mLineageGlobalActions.userConfigContains( + GLOBAL_ACTION_KEY_EMERGENCY) || mForceEmergCheck); + mEmergencyPref.setEnabled(!mForceEmergCheck); + } + + if (mDeviceControlsPref != null) { + mDeviceControlsPref.setChecked(mLineageGlobalActions.userConfigContains( + GLOBAL_ACTION_KEY_DEVICECONTROLS)); + + // Enable preference if any device control app is installed + ServiceListing serviceListing = new ServiceListing.Builder(mContext) + .setIntentAction(ControlsProviderService.SERVICE_CONTROLS) + .setPermission(Manifest.permission.BIND_CONTROLS) + .setNoun("Controls Provider") + .setSetting("controls_providers") + .setTag("controls_providers") + .build(); + serviceListing.addCallback( + services -> mDeviceControlsPref.setEnabled(!services.isEmpty())); + serviceListing.reload(); + } + + if (mPanicPref != null) { + mPanicPref.setChecked(mLineageGlobalActions.userConfigContains( + GLOBAL_ACTION_KEY_PANIC)); + mPanicPref.setEnabled(PowerMenuUtils.isPanicAvailable(mContext)); + } + + if (mRestartSystemUIPref != null) { + mRestartSystemUIPref.setChecked(mLineageGlobalActions.userConfigContains( + GLOBAL_ACTION_KEY_RESTART_SYSTEMUI)); + } + + updatePreferences(); + } + + @Override + public void onResume() { + super.onResume(); + updatePreferences(); + } + + @Override + public boolean onPreferenceTreeClick(Preference preference) { + boolean value; + + if (preference == mScreenshotPref) { + value = mScreenshotPref.isChecked(); + mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_SCREENSHOT); + + } else if (preference == mAirplanePref) { + value = mAirplanePref.isChecked(); + mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_AIRPLANE); + + } else if (preference == mUsersPref) { + value = mUsersPref.isChecked(); + mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_USERS); + + } else if (preference == mBugReportPref) { + value = mBugReportPref.isChecked(); + mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_BUGREPORT); + Settings.Global.putInt(getContentResolver(), + Settings.Global.BUGREPORT_IN_POWER_MENU, value ? 1 : 0); + + } else if (preference == mEmergencyPref) { + value = mEmergencyPref.isChecked(); + mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_EMERGENCY); + + } else if (preference == mDeviceControlsPref) { + value = mDeviceControlsPref.isChecked(); + mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_DEVICECONTROLS); + + } else if (preference == mPanicPref) { + value = mPanicPref.isChecked(); + mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_PANIC); + + } else if (preference == mRestartSystemUIPref) { + value = mRestartSystemUIPref.isChecked(); + mLineageGlobalActions.updateUserConfig(value, GLOBAL_ACTION_KEY_RESTART_SYSTEMUI); + + } else { + return super.onPreferenceTreeClick(preference); + } + return true; + } + + private void updatePreferences() { + UserInfo currentUser = mUserManager.getUserInfo(UserHandle.myUserId()); + boolean developmentSettings = Settings.Global.getInt( + getContentResolver(), Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1; + boolean bugReport = Settings.Global.getInt( + getContentResolver(), Settings.Global.BUGREPORT_IN_POWER_MENU, 0) == 1; + boolean isPrimaryUser = currentUser == null || currentUser.isPrimary(); + if (mBugReportPref != null) { + mBugReportPref.setEnabled(developmentSettings && isPrimaryUser); + if (!developmentSettings) { + mBugReportPref.setChecked(false); + mBugReportPref.setSummary(R.string.power_menu_bug_report_devoptions_unavailable); + } else if (!isPrimaryUser) { + mBugReportPref.setChecked(false); + mBugReportPref.setSummary(R.string.power_menu_bug_report_unavailable_for_user); + } else { + mBugReportPref.setChecked(bugReport); + mBugReportPref.setSummary(null); + } + } + if (mEmergencyPref != null) { + if (mForceEmergCheck) { + mEmergencyPref.setSummary(R.string.power_menu_emergency_affordance_enabled); + } else { + mEmergencyPref.setSummary(null); + } + } + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } +} diff --git a/src/com/android/settings/derp/buttons/TorchLongPressController.java b/src/com/android/settings/derp/buttons/TorchLongPressController.java new file mode 100644 index 00000000000..ae15d69f9c8 --- /dev/null +++ b/src/com/android/settings/derp/buttons/TorchLongPressController.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 The PixelExperience 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.derp.buttons; + +import android.content.Context; +import com.android.settings.core.BasePreferenceController; + +public class TorchLongPressController extends BasePreferenceController { + + public static final String KEY = "torch_long_press_power_gesture"; + + private Context mContext; + + public TorchLongPressController(Context context, String key) { + super(context, key); + + mContext = context; + } + + public TorchLongPressController(Context context) { + this(context, KEY); + + mContext = context; + } + + @Override + public int getAvailabilityStatus() { + boolean exists = mContext.getResources().getBoolean(com.android.internal.R.bool.config_supportLongPressPowerWhenNonInteractive); + return (exists ? AVAILABLE : UNSUPPORTED_ON_DEVICE); + } + +} diff --git a/src/com/android/settings/derp/buttons/preference/BacklightTimeoutSeekBar.java b/src/com/android/settings/derp/buttons/preference/BacklightTimeoutSeekBar.java new file mode 100644 index 00000000000..c0ee7bb103a --- /dev/null +++ b/src/com/android/settings/derp/buttons/preference/BacklightTimeoutSeekBar.java @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2013 The CyanogenMod Project + * SPDX-FileCopyrightText: 2022 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.buttons.preference; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.widget.SeekBar; + +public class BacklightTimeoutSeekBar extends SeekBar { + private int mMax; + private int mGap; + + public BacklightTimeoutSeekBar(Context context) { + super(context); + } + + public BacklightTimeoutSeekBar(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public BacklightTimeoutSeekBar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + } + + @Override + public void setThumb(Drawable thumb) { + super.setThumb(thumb); + } + + @Override + public void setMax(int max) { + mMax = max; + mGap = max / 10; + super.setMax(max + 2 * mGap - 1); + } + + @Override + protected int updateTouchProgress(int lastProgress, int newProgress) { + if (newProgress < mMax) { + return newProgress; + } + if (newProgress < mMax + mGap) { + return mMax - 1; + } + return getMax(); + } +} diff --git a/src/com/android/settings/derp/buttons/preference/ButtonBacklightBrightness.java b/src/com/android/settings/derp/buttons/preference/ButtonBacklightBrightness.java new file mode 100644 index 00000000000..c7241f37b23 --- /dev/null +++ b/src/com/android/settings/derp/buttons/preference/ButtonBacklightBrightness.java @@ -0,0 +1,467 @@ +/* + * SPDX-FileCopyrightText: 2013 The CyanogenMod Project + * SPDX-FileCopyrightText: 2017-2023 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.buttons.preference; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Parcel; +import android.os.Parcelable; +import android.provider.Settings; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager.LayoutParams; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; +import androidx.preference.PreferenceManager; + +import com.android.settings.derp.utils.DeviceUtils; +import com.android.settings.derp.preference.CustomDialogPreference; +import com.android.settings.R; + +public class ButtonBacklightBrightness extends CustomDialogPreference implements + SeekBar.OnSeekBarChangeListener { + private static final int BUTTON_BRIGHTNESS_TOGGLE_MODE_ONLY = 1; + private static final int DEFAULT_BUTTON_TIMEOUT = 5; + private static final int KEYBOARD_BRIGHTNESS_TOGGLE_MODE_ONLY = 1; + + public static final String KEY_BUTTON_BACKLIGHT = "pre_navbar_button_backlight"; + + private ButtonBrightnessControl mButtonBrightness; + private BrightnessControl mKeyboardBrightness; + private BrightnessControl mActiveControl; + + private ViewGroup mTimeoutContainer; + private SeekBar mTimeoutBar; + private TextView mTimeoutValue; + + private final ContentResolver mResolver; + + private int mOriginalTimeout; + + public ButtonBacklightBrightness(Context context, AttributeSet attrs) { + super(context, attrs); + + mResolver = context.getContentResolver(); + + setDialogLayoutResource(R.layout.button_backlight); + + if (DeviceUtils.hasKeyboardBacklightSupport(context)) { + final boolean isSingleValue = KEYBOARD_BRIGHTNESS_TOGGLE_MODE_ONLY == + context.getResources().getInteger(com.android.internal.R.integer + .config_deviceSupportsKeyboardBrightnessControl); + mKeyboardBrightness = new BrightnessControl( + Settings.Secure.KEYBOARD_BRIGHTNESS, isSingleValue); + mActiveControl = mKeyboardBrightness; + } + if (DeviceUtils.hasButtonBacklightSupport(context)) { + final boolean isSingleValue = BUTTON_BRIGHTNESS_TOGGLE_MODE_ONLY == + context.getResources().getInteger(com.android.internal.R.integer + .config_deviceSupportsButtonBrightnessControl); + + float defaultBrightness = context.getResources().getFloat( + com.android.internal.R.dimen + .config_buttonBrightnessSettingDefaultFloat); + + mButtonBrightness = new ButtonBrightnessControl( + Settings.Secure.BUTTON_BRIGHTNESS, + Settings.System.BUTTON_BACKLIGHT_ONLY_WHEN_PRESSED, + isSingleValue, defaultBrightness); + mActiveControl = mButtonBrightness; + } + + updateSummary(); + } + + @Override + protected void onClick(AlertDialog d, int which) { + super.onClick(d, which); + + updateBrightnessPreview(); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder, + DialogInterface.OnClickListener listener) { + super.onPrepareDialogBuilder(builder, listener); + builder.setNeutralButton(R.string.reset, null); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.dlg_ok, null); + } + + @Override + protected boolean onDismissDialog(AlertDialog dialog, int which) { + if (which == DialogInterface.BUTTON_NEUTRAL) { + mTimeoutBar.setProgress(DEFAULT_BUTTON_TIMEOUT); + applyTimeout(DEFAULT_BUTTON_TIMEOUT); + if (mButtonBrightness != null) { + mButtonBrightness.reset(); + } + if (mKeyboardBrightness != null) { + mKeyboardBrightness.reset(); + } + return false; + } + return true; + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + mTimeoutContainer = view.findViewById(R.id.timeout_container); + mTimeoutBar = view.findViewById(R.id.timeout_seekbar); + mTimeoutValue = view.findViewById(R.id.timeout_value); + mTimeoutBar.setMax(30); + mTimeoutBar.setOnSeekBarChangeListener(this); + mOriginalTimeout = getTimeout(); + mTimeoutBar.setProgress(mOriginalTimeout); + handleTimeoutUpdate(mTimeoutBar.getProgress()); + + ViewGroup buttonContainer = view.findViewById(R.id.button_container); + if (mButtonBrightness != null) { + mButtonBrightness.init(buttonContainer); + } else { + buttonContainer.setVisibility(View.GONE); + mTimeoutContainer.setVisibility(View.GONE); + } + + ViewGroup keyboardContainer = view.findViewById(R.id.keyboard_container); + if (mKeyboardBrightness != null) { + mKeyboardBrightness.init(keyboardContainer); + } else { + keyboardContainer.setVisibility(View.GONE); + } + + if (mButtonBrightness == null || mKeyboardBrightness == null) { + view.findViewById(R.id.button_keyboard_divider).setVisibility(View.GONE); + } + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + + if (!positiveResult) { + applyTimeout(mOriginalTimeout); + return; + } + + if (mButtonBrightness != null) { + PreferenceManager.getDefaultSharedPreferences(getContext()) + .edit() + .putFloat(KEY_BUTTON_BACKLIGHT, mButtonBrightness.getBrightness(false)) + .apply(); + } + + applyTimeout(mTimeoutBar.getProgress()); + if (mButtonBrightness != null) { + mButtonBrightness.applyBrightness(); + } + if (mKeyboardBrightness != null) { + mKeyboardBrightness.applyBrightness(); + } + + updateSummary(); + } + + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + if (getDialog() == null || !getDialog().isShowing()) { + return superState; + } + + // Save the dialog state + final SavedState myState = new SavedState(superState); + myState.timeout = mTimeoutBar.getProgress(); + if (mButtonBrightness != null) { + myState.button = mButtonBrightness.getBrightness(false); + } + if (mKeyboardBrightness != null) { + myState.keyboard = mKeyboardBrightness.getBrightness(false); + } + + return myState; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state == null || !state.getClass().equals(SavedState.class)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; + } + + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + + mTimeoutBar.setProgress(myState.timeout); + if (mButtonBrightness != null) { + mButtonBrightness.setBrightness(myState.button); + } + if (mKeyboardBrightness != null) { + mKeyboardBrightness.setBrightness(myState.keyboard); + } + } + + public void updateSummary() { + if (mButtonBrightness != null) { + float buttonBrightness = mButtonBrightness.getBrightness(true); + int timeout = getTimeout(); + + if (buttonBrightness == 0.0f) { + setSummary(R.string.backlight_summary_disabled); + } else if (timeout == 0) { + setSummary(R.string.backlight_timeout_unlimited); + } else { + setSummary(getContext().getString(R.string.backlight_summary_enabled_with_timeout, + getTimeoutString(timeout))); + } + } else if (mKeyboardBrightness != null && + mKeyboardBrightness.getBrightness(true) != 0.0f) { + setSummary(R.string.backlight_summary_enabled); + } else { + setSummary(R.string.backlight_summary_disabled); + } + } + + private String getTimeoutString(int timeout) { + return getContext().getResources().getQuantityString( + R.plurals.backlight_timeout_time, timeout, timeout); + } + + private int getTimeout() { + return Settings.Secure.getInt(mResolver, + Settings.Secure.BUTTON_BACKLIGHT_TIMEOUT, DEFAULT_BUTTON_TIMEOUT * 1000) + / 1000; + } + + private void applyTimeout(int timeout) { + Settings.Secure.putInt(mResolver, + Settings.Secure.BUTTON_BACKLIGHT_TIMEOUT, timeout * 1000); + } + + private void updateBrightnessPreview() { + if (getDialog() == null || getDialog().getWindow() == null) { + return; + } + Window window = getDialog().getWindow(); + LayoutParams params = window.getAttributes(); + if (mActiveControl != null) { + params.buttonBrightness = mActiveControl.getBrightness(false); + } else { + params.buttonBrightness = -1.0f; + } + window.setAttributes(params); + } + + private void updateTimeoutEnabledState() { + float buttonBrightness = mButtonBrightness != null + ? mButtonBrightness.getBrightness(false) : 0.0f; + int count = mTimeoutContainer.getChildCount(); + for (int i = 0; i < count; i++) { + mTimeoutContainer.getChildAt(i).setEnabled(buttonBrightness != 0.0f); + } + } + + private void handleTimeoutUpdate(int timeout) { + if (timeout == 0) { + mTimeoutValue.setText(R.string.backlight_timeout_unlimited); + } else { + mTimeoutValue.setText(getTimeoutString(timeout)); + } + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + handleTimeoutUpdate(progress); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + applyTimeout(seekBar.getProgress()); + } + + private static class SavedState extends BaseSavedState { + int timeout; + float button; + float keyboard; + + public SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + timeout = source.readInt(); + button = source.readFloat(); + keyboard = source.readFloat(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(timeout); + dest.writeFloat(button); + dest.writeFloat(keyboard); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator<>() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + private class BrightnessControl implements + SeekBar.OnSeekBarChangeListener, CheckBox.OnCheckedChangeListener { + private final String mSetting; + private final boolean mIsSingleValue; + private final float mDefaultBrightness; + private CheckBox mCheckBox; + private SeekBar mSeekBar; + private TextView mValue; + + public BrightnessControl(String setting, boolean singleValue, float defaultBrightness) { + mSetting = setting; + mIsSingleValue = singleValue; + mDefaultBrightness = defaultBrightness; + } + + public BrightnessControl(String setting, boolean singleValue) { + this(setting, singleValue, 1.0f); + } + + public void init(ViewGroup container) { + float brightness = getBrightness(true); + + if (mIsSingleValue) { + container.findViewById(R.id.seekbar_container).setVisibility(View.GONE); + mCheckBox = container.findViewById(R.id.backlight_switch); + mCheckBox.setChecked(brightness != 0.0f); + mCheckBox.setOnCheckedChangeListener(this); + } else { + container.findViewById(R.id.checkbox_container).setVisibility(View.GONE); + mSeekBar = container.findViewById(R.id.seekbar); + mValue = container.findViewById(R.id.value); + + mSeekBar.setMax(100); + mSeekBar.setProgress((int)(brightness * 100.0f)); + mSeekBar.setOnSeekBarChangeListener(this); + } + + handleBrightnessUpdate((int)(brightness * 100.0f)); + } + + public float getBrightness(boolean persisted) { + if (mCheckBox != null && !persisted) { + return mCheckBox.isChecked() ? mDefaultBrightness : 0.0f; + } else if (mSeekBar != null && !persisted) { + return mSeekBar.getProgress() / 100.0f; + } + return Settings.Secure.getFloat(mResolver, mSetting, mDefaultBrightness); + } + + public void applyBrightness() { + Settings.Secure.putFloat(mResolver, mSetting, getBrightness(false)); + } + + /* Behaviors when it's a seekbar */ + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + handleBrightnessUpdate(progress); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + mActiveControl = this; + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + /* Behaviors when it's a plain checkbox */ + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mActiveControl = this; + updateBrightnessPreview(); + updateTimeoutEnabledState(); + } + + public void setBrightness(float value) { + if (mIsSingleValue) { + mCheckBox.setChecked(value != 0.0f); + } else { + mSeekBar.setProgress((int)(value * 100.0f)); + } + } + + public void reset() { + setBrightness(mDefaultBrightness); + } + + private void handleBrightnessUpdate(int brightness) { + updateBrightnessPreview(); + if (mValue != null) { + mValue.setText(String.format("%d%%", brightness)); + } + updateTimeoutEnabledState(); + } + } + + private class ButtonBrightnessControl extends BrightnessControl { + private final String mOnlyWhenPressedSetting; + private CheckBox mOnlyWhenPressedCheckBox; + + public ButtonBrightnessControl(String brightnessSetting, String onlyWhenPressedSetting, + boolean singleValue, float defaultBrightness) { + super(brightnessSetting, singleValue, defaultBrightness); + mOnlyWhenPressedSetting = onlyWhenPressedSetting; + } + + @Override + public void init(ViewGroup container) { + super.init(container); + + mOnlyWhenPressedCheckBox = + container.findViewById(R.id.backlight_only_when_pressed_switch); + mOnlyWhenPressedCheckBox.setChecked(isOnlyWhenPressedEnabled()); + mOnlyWhenPressedCheckBox.setOnCheckedChangeListener(this); + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + super.onCheckedChanged(buttonView, isChecked); + setOnlyWhenPressedEnabled(mOnlyWhenPressedCheckBox.isChecked()); + } + + public boolean isOnlyWhenPressedEnabled() { + return Settings.System.getInt(mResolver, mOnlyWhenPressedSetting, 0) == 1; + } + + public void setOnlyWhenPressedEnabled(boolean enabled) { + Settings.System.putInt(mResolver, mOnlyWhenPressedSetting, enabled ? 1 : 0); + } + } +} diff --git a/src/com/android/settings/derp/health/ChargingControlPreferenceController.java b/src/com/android/settings/derp/health/ChargingControlPreferenceController.java new file mode 100644 index 00000000000..e60474870e8 --- /dev/null +++ b/src/com/android/settings/derp/health/ChargingControlPreferenceController.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 The PixelExperience Project + * Copyright (C) 2023 The LibreMobileOS Foundation + * + * 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.derp.health; + +import android.content.Context; +import android.os.IBinder; +import android.os.ServiceManager; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.R; + +public class ChargingControlPreferenceController extends BasePreferenceController { + + public static final String KEY = "charging_control"; + + private Context mContext; + + public ChargingControlPreferenceController(Context context, String key) { + super(context, key); + + mContext = context; + } + + public ChargingControlPreferenceController(Context context) { + this(context, KEY); + + mContext = context; + } + + private boolean isNegated(String key) { + return key != null && key.startsWith("!"); + } + + @Override + public int getAvailabilityStatus() { + String rService = "lineagehealth"; + boolean negated = isNegated(rService); + if (negated) { + rService = rService.substring(1); + } + IBinder value = ServiceManager.getService(rService); + boolean available = value != null; + if (available == negated) { + return UNSUPPORTED_ON_DEVICE; + } + return AVAILABLE; + } + +} diff --git a/src/com/android/settings/derp/health/ChargingControlSettings.java b/src/com/android/settings/derp/health/ChargingControlSettings.java new file mode 100644 index 00000000000..55f0b5a91f1 --- /dev/null +++ b/src/com/android/settings/derp/health/ChargingControlSettings.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2023 The LineageOS 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.derp.health; + +import android.content.Context; +import android.content.res.Resources; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import androidx.fragment.app.DialogFragment; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; + +import com.android.settings.R; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settingslib.search.SearchIndexable; +import com.android.settings.derp.preference.CustomDialogPreference; + +import com.android.internal.derp.health.HealthInterface; +import org.derpfest.support.preferences.SystemSettingListPreference; +import org.derpfest.support.preferences.SystemSettingMainSwitchPreference; +import android.provider.Settings; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + +import static com.android.internal.derp.health.HealthInterface.MODE_AUTO; +import static com.android.internal.derp.health.HealthInterface.MODE_MANUAL; +import static com.android.internal.derp.health.HealthInterface.MODE_LIMIT; + +@SearchIndexable +public class ChargingControlSettings extends SettingsPreferenceFragment implements + Preference.OnPreferenceChangeListener { + private static final String TAG = ChargingControlSettings.class.getSimpleName(); + + private static final String CHARGING_CONTROL_PREF = "charging_control"; + private static final String CHARGING_CONTROL_ENABLED_PREF = "charging_control_enabled"; + private static final String CHARGING_CONTROL_MODE_PREF = "charging_control_mode"; + private static final String CHARGING_CONTROL_START_TIME_PREF = "charging_control_start_time"; + private static final String CHARGING_CONTROL_TARGET_TIME_PREF = "charging_control_target_time"; + private static final String CHARGING_CONTROL_LIMIT_PREF = "charging_control_charging_limit"; + + private SystemSettingMainSwitchPreference mChargingControlEnabledPref; + private SystemSettingListPreference mChargingControlModePref; + private StartTimePreference mChargingControlStartTimePref; + private TargetTimePreference mChargingControlTargetTimePref; + private ChargingLimitPreference mChargingControlLimitPref; + + private HealthInterface mHealthInterface; + + private static final int MENU_RESET = Menu.FIRST; + + @Override + public void onActivityCreated(final Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + final Resources res = getResources(); + + addPreferencesFromResource(R.xml.charging_control_settings); + getActivity().getActionBar().setTitle(R.string.charging_control_title); + + mHealthInterface = HealthInterface.getInstance(getActivity()); + + final PreferenceScreen prefSet = getPreferenceScreen(); + + mChargingControlEnabledPref = prefSet.findPreference(CHARGING_CONTROL_ENABLED_PREF); + mChargingControlEnabledPref.setOnPreferenceChangeListener(this); + mChargingControlModePref = prefSet.findPreference(CHARGING_CONTROL_MODE_PREF); + mChargingControlModePref.setOnPreferenceChangeListener(this); + mChargingControlStartTimePref = prefSet.findPreference(CHARGING_CONTROL_START_TIME_PREF); + mChargingControlTargetTimePref = prefSet.findPreference(CHARGING_CONTROL_TARGET_TIME_PREF); + mChargingControlLimitPref = prefSet.findPreference(CHARGING_CONTROL_LIMIT_PREF); + + if (mChargingControlLimitPref != null) { + if (mHealthInterface.allowFineGrainedSettings()) { + mChargingControlModePref.setEntries(concatStringArrays( + mChargingControlModePref.getEntries(), + res.getStringArray( + R.array.charging_control_mode_entries_fine_grained_control))); + mChargingControlModePref.setEntryValues(concatStringArrays( + mChargingControlModePref.getEntryValues(), + res.getStringArray( + R.array.charging_control_mode_values_fine_grained_control))); + } + } + + setHasOptionsMenu(true); + + refreshValues(); + + } + + @Override + public void onResume() { + super.onResume(); + refreshUi(); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } + + @Override + public void onDisplayPreferenceDialog(Preference preference) { + if (preference.getKey() == null) { + // Auto-key preferences that don't have a key, so the dialog can find them. + preference.setKey(UUID.randomUUID().toString()); + } + DialogFragment f = null; + if (preference instanceof CustomDialogPreference) { + f = CustomDialogPreference.CustomPreferenceDialogFragment + .newInstance(preference.getKey()); + } else { + super.onDisplayPreferenceDialog(preference); + return; + } + f.setTargetFragment(this, 0); + f.show(getFragmentManager(), "dialog_preference"); + onDialogShowing(); + } + + private void refreshValues() { + if (mChargingControlEnabledPref != null) { + mChargingControlEnabledPref.setChecked(mHealthInterface.getEnabled()); + } + + if (mChargingControlModePref != null) { + final int chargingControlMode = mHealthInterface.getMode(); + mChargingControlModePref.setValue(Integer.toString(chargingControlMode)); + refreshUi(); + } + + if (mChargingControlStartTimePref != null) { + mChargingControlStartTimePref.setValue( + mChargingControlStartTimePref.getTimeSetting()); + } + + if (mChargingControlTargetTimePref != null) { + mChargingControlTargetTimePref.setValue( + mChargingControlTargetTimePref.getTimeSetting()); + } + + if (mChargingControlLimitPref != null) { + mChargingControlLimitPref.setValue( + mChargingControlLimitPref.getSetting()); + } + } + + private void refreshUi() { + final int chargingControlMode = mHealthInterface.getMode(); + + refreshUi(chargingControlMode); + } + + private void refreshUi(final int chargingControlMode) { + String summary = null; + boolean isChargingControlStartTimePrefVisible = false; + boolean isChargingControlTargetTimePrefVisible = false; + boolean isChargingControlLimitPrefVisible = false; + + final Resources res = getResources(); + + switch (chargingControlMode) { + case MODE_AUTO: + summary = res.getString(R.string.charging_control_mode_auto_summary); + break; + case MODE_MANUAL: + summary = res.getString(R.string.charging_control_mode_custom_summary); + isChargingControlStartTimePrefVisible = true; + isChargingControlTargetTimePrefVisible = true; + break; + case MODE_LIMIT: + summary = res.getString(R.string.charging_control_mode_limit_summary); + isChargingControlLimitPrefVisible = true; + break; + default: + return; + } + + mChargingControlModePref.setSummary(summary); + + if (mChargingControlStartTimePref != null) { + mChargingControlStartTimePref.setVisible(isChargingControlStartTimePrefVisible); + } + + if (mChargingControlTargetTimePref != null) { + mChargingControlTargetTimePref.setVisible(isChargingControlTargetTimePrefVisible); + } + + if (mChargingControlLimitPref != null) { + mChargingControlLimitPref.setVisible(isChargingControlLimitPrefVisible); + } + } + + @Override + public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { + menu.add(0, MENU_RESET, 0, R.string.reset) + .setIcon(R.drawable.ic_settings_backup_restore) + .setAlphabeticShortcut('r') + .setShowAsActionFlags( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == MENU_RESET) { + resetToDefaults(); + return true; + } + return false; + } + + @Override + public boolean onPreferenceChange(final Preference preference, final Object objValue) { + if (preference == mChargingControlEnabledPref) { + mHealthInterface.setEnabled((Boolean) objValue); + } else if (preference == mChargingControlModePref) { + final int chargingControlMode = Integer.parseInt((String) objValue); + mHealthInterface.setMode(chargingControlMode); + refreshUi(chargingControlMode); + } + return true; + } + + private void resetToDefaults() { + mHealthInterface.reset(); + + refreshValues(); + } + + private CharSequence[] concatStringArrays(CharSequence[] array1, CharSequence[] array2) { + return Stream.concat(Arrays.stream(array1), Arrays.stream(array2)).toArray(size -> + (CharSequence[]) Array.newInstance(CharSequence.class, size)); + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getNonIndexableKeys(Context context) { + final List result = new ArrayList(); + if (!HealthInterface.isChargingControlSupported(context)) { + result.add(CHARGING_CONTROL_PREF); + result.add(CHARGING_CONTROL_ENABLED_PREF); + result.add(CHARGING_CONTROL_MODE_PREF); + result.add(CHARGING_CONTROL_START_TIME_PREF); + result.add(CHARGING_CONTROL_TARGET_TIME_PREF); + result.add(CHARGING_CONTROL_LIMIT_PREF); + } + return result; + } + }; + +} diff --git a/src/com/android/settings/derp/health/ChargingLimitPreference.java b/src/com/android/settings/derp/health/ChargingLimitPreference.java new file mode 100644 index 00000000000..1c299eb43cd --- /dev/null +++ b/src/com/android/settings/derp/health/ChargingLimitPreference.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2023 The LineageOS 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.derp.health; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.internal.derp.health.HealthInterface; + +import com.android.settings.R; + +public class ChargingLimitPreference extends Preference + implements SeekBar.OnSeekBarChangeListener { + private static final String TAG = ChargingLimitPreference.class.getSimpleName(); + + private TextView mChargingLimitValue; + private SeekBar mChargingLimitBar; + + private final HealthInterface mHealthInterface; + + public ChargingLimitPreference(final Context context, final AttributeSet attrs) { + super(context, attrs); + + setLayoutResource(R.layout.preference_charging_limit); + + mHealthInterface = HealthInterface.getInstance(context); + } + + @Override + public void onBindViewHolder(final PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + + mChargingLimitValue = (TextView) holder.findViewById(R.id.value); + + mChargingLimitBar = (SeekBar) holder.findViewById(R.id.seekbar_widget); + mChargingLimitBar.setOnSeekBarChangeListener(this); + + int currLimit = getSetting(); + mChargingLimitBar.setProgress(currLimit); + updateValue(currLimit); + } + + @Override + public void onStartTrackingTouch(final SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(final SeekBar seekBar) { + setSetting(seekBar.getProgress()); + } + + @Override + public void onProgressChanged(final SeekBar seekBar, final int progress, + final boolean fromUser) { + updateValue(progress); + } + + public void setValue(final int value) { + if (mChargingLimitBar != null) { + mChargingLimitBar.setProgress(value); + } + updateValue(value); + } + + protected int getSetting() { + return mHealthInterface.getLimit(); + } + + protected void setSetting(final int chargingLimit) { + mHealthInterface.setLimit(chargingLimit); + } + + private void updateValue(final int value) { + if (mChargingLimitValue != null) { + mChargingLimitValue.setText(String.format("%d%%", value)); + } + } +} diff --git a/src/com/android/settings/derp/health/StartTimePreference.java b/src/com/android/settings/derp/health/StartTimePreference.java new file mode 100644 index 00000000000..5c928c1b85a --- /dev/null +++ b/src/com/android/settings/derp/health/StartTimePreference.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The LineageOS 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.derp.health; + +import android.content.Context; +import android.util.AttributeSet; + +import com.android.settings.R; + +public class StartTimePreference extends TimePreference { + private static final String TAG = StartTimePreference.class.getSimpleName(); + + public StartTimePreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected int getSummaryResourceId() { + return R.string.charging_control_start_time_summary; + } + + @Override + protected int getTimeSetting() { + return mHealthInterface.getStartTime(); + } + + @Override + protected void setTimeSetting(int secondOfDay) { + mHealthInterface.setStartTime(secondOfDay); + } +} diff --git a/src/com/android/settings/derp/health/TargetTimePreference.java b/src/com/android/settings/derp/health/TargetTimePreference.java new file mode 100644 index 00000000000..b536a208818 --- /dev/null +++ b/src/com/android/settings/derp/health/TargetTimePreference.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The LineageOS 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.derp.health; + +import android.content.Context; +import android.util.AttributeSet; + +import com.android.settings.R; + +public class TargetTimePreference extends TimePreference { + private static final String TAG = TargetTimePreference.class.getSimpleName(); + + public TargetTimePreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected int getSummaryResourceId() { + return R.string.charging_control_target_time_summary; + } + + @Override + protected int getTimeSetting() { + return mHealthInterface.getTargetTime(); + } + + @Override + protected void setTimeSetting(int secondOfDay) { + mHealthInterface.setTargetTime(secondOfDay); + } +} diff --git a/src/com/android/settings/derp/health/TimePreference.java b/src/com/android/settings/derp/health/TimePreference.java new file mode 100644 index 00000000000..54ebe2c7d56 --- /dev/null +++ b/src/com/android/settings/derp/health/TimePreference.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2023 The LineageOS 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.derp.health; + +import static java.time.format.FormatStyle.SHORT; + +import android.content.Context; +import android.content.DialogInterface; +import android.text.format.DateFormat; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TimePicker; + +import androidx.appcompat.app.AlertDialog; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.derp.preference.CustomDialogPreference; +import com.android.settings.R; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +import com.android.internal.derp.health.HealthInterface; + +public abstract class TimePreference extends CustomDialogPreference { + private static final String TAG = TimePreference.class.getSimpleName(); + private static final DateTimeFormatter mFormatter = DateTimeFormatter.ofLocalizedTime(SHORT); + + private TimePicker mTimePicker; + private LocalTime mLocalTime; + + protected HealthInterface mHealthInterface; + + public TimePreference(final Context context, final AttributeSet attrs) { + super(context, attrs); + + setDialogLayoutResource(R.layout.dialog_time); + mHealthInterface = HealthInterface.getInstance(context); + } + + @Override + public void onBindViewHolder(final PreferenceViewHolder holder) { + mLocalTime = LocalTime.ofSecondOfDay(getTimeSetting()); + super.onBindViewHolder(holder); + } + + @Override + protected void onPrepareDialogBuilder(final AlertDialog.Builder builder, + final DialogInterface.OnClickListener listener) { + super.onPrepareDialogBuilder(builder, listener); + + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.dlg_ok, null); + } + + @Override + protected void onDialogClosed(final boolean positiveResult) { + super.onDialogClosed(positiveResult); + + if (positiveResult) { + mLocalTime = LocalTime.of(mTimePicker.getHour(), + mTimePicker.getMinute()); + setTimeSetting(mLocalTime.toSecondOfDay()); + setSummary(getSummary()); + } + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + mTimePicker = view.findViewById(R.id.time_picker); + mTimePicker.setIs24HourView(DateFormat.is24HourFormat(getContext())); + mTimePicker.setHour(mLocalTime.getHour()); + mTimePicker.setMinute(mLocalTime.getMinute()); + } + + @Override + public CharSequence getSummary() { + return String.format(getContext().getString(getSummaryResourceId()), + mLocalTime.format(mFormatter)); + } + + public void setValue(final int value) { + mLocalTime = LocalTime.ofSecondOfDay(value); + setSummary(getSummary()); + } + + protected abstract int getSummaryResourceId(); + + protected abstract int getTimeSetting(); + + protected abstract void setTimeSetting(int secondOfDay); +} diff --git a/src/com/android/settings/derp/livedisplay/DisplayColor.java b/src/com/android/settings/derp/livedisplay/DisplayColor.java new file mode 100644 index 00000000000..f54e4d35970 --- /dev/null +++ b/src/com/android/settings/derp/livedisplay/DisplayColor.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2013-2015 The CyanogenMod Project + * 2021-2022 The LineageOS 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.derp.livedisplay; + +import android.content.Context; +import android.content.DialogInterface; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.derp.preference.CustomDialogPreference; +import com.android.settings.derp.widget.IntervalSeekBar; +import com.android.settings.R; + +import com.android.internal.derp.hardware.LiveDisplayManager; + +/** + * Special preference type that allows configuration of Color settings + */ +public class DisplayColor extends CustomDialogPreference { + private static final String TAG = "ColorCalibration"; + + private final Context mContext; + private final LiveDisplayManager mLiveDisplay; + + // These arrays must all match in length and order + private static final int[] SEEKBAR_ID = new int[] { + R.id.color_red_seekbar, + R.id.color_green_seekbar, + R.id.color_blue_seekbar + }; + + private static final int[] SEEKBAR_VALUE_ID = new int[] { + R.id.color_red_value, + R.id.color_green_value, + R.id.color_blue_value + }; + + private ColorSeekBar[] mSeekBars = new ColorSeekBar[SEEKBAR_ID.length]; + + private final float[] mCurrentColors = new float[3]; + private final float[] mOriginalColors = new float[3]; + + public DisplayColor(Context context, AttributeSet attrs) { + super(context, attrs); + + mContext = context; + mLiveDisplay = mContext.getSystemService(LiveDisplayManager.class); + + setDialogLayoutResource(R.layout.display_color_calibration); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder, + DialogInterface.OnClickListener listener) { + super.onPrepareDialogBuilder(builder, listener); + + builder.setNeutralButton(R.string.reset, null); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.dlg_ok, null); + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + System.arraycopy(mLiveDisplay.getColorAdjustment(), 0, mOriginalColors, 0, 3); + System.arraycopy(mOriginalColors, 0, mCurrentColors, 0, 3); + + for (int i = 0; i < SEEKBAR_ID.length; i++) { + IntervalSeekBar seekBar = (IntervalSeekBar) view.findViewById(SEEKBAR_ID[i]); + TextView value = (TextView) view.findViewById(SEEKBAR_VALUE_ID[i]); + mSeekBars[i] = new ColorSeekBar(seekBar, value, i); + mSeekBars[i].mSeekBar.setMinimum(0.1f); + mSeekBars[i].mSeekBar.setMaximum(1.0f); + + mSeekBars[i].mSeekBar.setProgressFloat(mCurrentColors[i]); + int percent = Math.round(100F * mCurrentColors[i]); + value.setText(String.format("%d%%", percent)); + } + } + + @Override + protected boolean onDismissDialog(AlertDialog dialog, int which) { + // Can't use onPrepareDialogBuilder for this as we want the dialog + // to be kept open on click + if (which == DialogInterface.BUTTON_NEUTRAL) { + for (int i = 0; i < mSeekBars.length; i++) { + mSeekBars[i].mSeekBar.setProgressFloat(1.0f); + mCurrentColors[i] = 1.0f; + } + updateColors(mCurrentColors); + return false; + } + return true; + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + updateColors(positiveResult ? mCurrentColors : mOriginalColors); + } + + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + if (getDialog() == null || !getDialog().isShowing()) { + return superState; + } + + // Save the dialog state + final SavedState myState = new SavedState(superState); + myState.currentColors = mCurrentColors; + myState.originalColors = mOriginalColors; + + // Restore the old state when the activity or dialog is being paused + updateColors(mOriginalColors); + + return myState; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state == null || !state.getClass().equals(SavedState.class)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; + } + + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + + System.arraycopy(myState.originalColors, 0, mOriginalColors, 0, 3); + System.arraycopy(myState.currentColors, 0, mCurrentColors, 0, 3); + for (int i = 0; i < mSeekBars.length; i++) { + mSeekBars[i].mSeekBar.setProgressFloat(mCurrentColors[i]); + } + updateColors(mCurrentColors); + } + + private static class SavedState extends BaseSavedState { + float[] originalColors; + float[] currentColors; + + public SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + originalColors = source.createFloatArray(); + currentColors = source.createFloatArray(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeFloatArray(originalColors); + dest.writeFloatArray(currentColors); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + private void updateColors(float[] colors) { + mLiveDisplay.setColorAdjustment(colors); + } + + private class ColorSeekBar implements SeekBar.OnSeekBarChangeListener { + private int mIndex; + private final IntervalSeekBar mSeekBar; + private TextView mValue; + + public ColorSeekBar(IntervalSeekBar seekBar, TextView value, int index) { + mSeekBar = seekBar; + mValue = value; + mIndex = index; + + mSeekBar.setOnSeekBarChangeListener(this); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + IntervalSeekBar isb = (IntervalSeekBar)seekBar; + float fp = isb.getProgressFloat(); + if (fromUser) { + mCurrentColors[mIndex] = fp > 1.0f ? 1.0f : fp; + updateColors(mCurrentColors); + } + + int percent = Math.round(100F * fp); + mValue.setText(String.format("%d%%", percent)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + } +} diff --git a/src/com/android/settings/derp/livedisplay/DisplayTemperature.java b/src/com/android/settings/derp/livedisplay/DisplayTemperature.java new file mode 100644 index 00000000000..d0abb168b84 --- /dev/null +++ b/src/com/android/settings/derp/livedisplay/DisplayTemperature.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * 2021-2022 The LineageOS 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.derp.livedisplay; + +import android.content.Context; +import android.content.DialogInterface; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.derp.preference.CustomDialogPreference; +import com.android.settings.R; +import com.android.internal.util.derp.MathUtils; + +import com.android.internal.derp.hardware.LiveDisplayConfig; +import com.android.internal.derp.hardware.LiveDisplayManager; + +/** + * Preference for selection of color temperature range for LiveDisplay + */ +public class DisplayTemperature extends CustomDialogPreference { + private static final String TAG = "DisplayTemperature"; + + private final Context mContext; + + private ColorTemperatureSeekBar mDayTemperature; + private ColorTemperatureSeekBar mNightTemperature; + + private int mOriginalDayTemperature; + private int mOriginalNightTemperature; + + private final LiveDisplayManager mLiveDisplay; + private final LiveDisplayConfig mConfig; + + private static final int STEP = 100; + + public DisplayTemperature(Context context, AttributeSet attrs) { + super(context, attrs); + mContext = context; + mLiveDisplay = mContext.getSystemService(LiveDisplayManager.class); + mConfig = mLiveDisplay.getConfig(); + + setDialogLayoutResource(R.layout.display_temperature); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder, + DialogInterface.OnClickListener listener) { + super.onPrepareDialogBuilder(builder, listener); + + builder.setNeutralButton(R.string.reset, null); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.dlg_ok, null); + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + mOriginalDayTemperature = mLiveDisplay.getDayColorTemperature(); + mOriginalNightTemperature = mLiveDisplay.getNightColorTemperature(); + + SeekBar day = (SeekBar) view.findViewById(R.id.day_temperature_seekbar); + TextView dayText = (TextView) view.findViewById(R.id.day_temperature_value); + mDayTemperature = new ColorTemperatureSeekBar(day, dayText); + + SeekBar night = (SeekBar) view.findViewById(R.id.night_temperature_seekbar); + TextView nightText = (TextView) view.findViewById(R.id.night_temperature_value); + mNightTemperature = new ColorTemperatureSeekBar(night, nightText); + + mDayTemperature.setTemperature(mOriginalDayTemperature); + mNightTemperature.setTemperature(mOriginalNightTemperature); + } + + + @Override + protected boolean onDismissDialog(AlertDialog dialog, int which) { + // Can't use onPrepareDialogBuilder for this as we want the dialog + // to be kept open on click + if (which == DialogInterface.BUTTON_NEUTRAL) { + mDayTemperature.setTemperature(mConfig.getDefaultDayTemperature()); + mNightTemperature.setTemperature(mConfig.getDefaultNightTemperature()); + updateTemperature(true); + return false; + } + return true; + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + updateTemperature(positiveResult); + } + + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + if (getDialog() == null || !getDialog().isShowing()) { + return superState; + } + + // Save the dialog state + final SavedState myState = new SavedState(superState); + myState.originalDayTemperature = mOriginalDayTemperature; + myState.originalNightTemperature = mOriginalNightTemperature; + myState.currentDayTemperature = mDayTemperature.getTemperature(); + myState.currentNightTemperature = mNightTemperature.getTemperature(); + + // Restore the old state when the activity or dialog is being paused + updateTemperature(false); + + return myState; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state == null || !state.getClass().equals(SavedState.class)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; + } + + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + + mOriginalDayTemperature = myState.originalDayTemperature; + mOriginalNightTemperature = myState.originalNightTemperature; + mDayTemperature.setTemperature(myState.currentDayTemperature); + mNightTemperature.setTemperature(myState.currentNightTemperature);; + + updateTemperature(true); + } + + private static class SavedState extends BaseSavedState { + int originalDayTemperature; + int originalNightTemperature; + int currentDayTemperature; + int currentNightTemperature; + + public SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + originalDayTemperature = source.readInt(); + originalNightTemperature = source.readInt(); + currentDayTemperature = source.readInt(); + currentNightTemperature = source.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(originalDayTemperature); + dest.writeInt(originalNightTemperature); + dest.writeInt(currentDayTemperature); + dest.writeInt(currentNightTemperature); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + private void updateTemperature(boolean accept) { + int day = accept ? mDayTemperature.getTemperature() : mOriginalDayTemperature; + int night = accept ? mNightTemperature.getTemperature() : mOriginalNightTemperature; + callChangeListener(new Integer[] { day, night }); + + mLiveDisplay.setDayColorTemperature(day); + mLiveDisplay.setNightColorTemperature(night); + } + + int roundUp(int value) { + return ((value + STEP / 2) / STEP) * STEP; + } + + private class ColorTemperatureSeekBar implements SeekBar.OnSeekBarChangeListener { + private final SeekBar mSeekBar; + private final TextView mValue; + + private final int mMin; + private final int mMax; + + private final int mBalanceMin; + private final int mBalanceMax; + + private final int mBarMax; + + private final boolean mUseBalance; + private final double[] mBalanceCurve; + + public ColorTemperatureSeekBar(SeekBar seekBar, TextView value) { + mSeekBar = seekBar; + mValue = value; + mMin = mConfig.getColorTemperatureRange().getLower(); + mMax = mConfig.getColorTemperatureRange().getUpper(); + mBalanceMin = mConfig.getColorBalanceRange().getLower(); + mBalanceMax = mConfig.getColorBalanceRange().getUpper(); + mUseBalance = mConfig.hasFeature(LiveDisplayManager.FEATURE_COLOR_BALANCE) && + ((mBalanceMin != 0) || (mBalanceMax != 0)); + + if (mUseBalance) { + mBalanceCurve = MathUtils.powerCurve(mMin, mConfig.getDefaultDayTemperature(), mMax); + mBarMax = mBalanceMax - mBalanceMin; + } else { + mBalanceCurve = null; + mBarMax = (mMax - mMin) / STEP; + } + mSeekBar.setMax(mBarMax); + mSeekBar.setOnSeekBarChangeListener(this); + + // init text value + int p = mSeekBar.getProgress(); + onProgressChanged(mSeekBar, p, false); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + updateTemperature(true); + } + + int displayValue; + if (mUseBalance) { + displayValue = roundUp(Math.round((float)MathUtils.linearToPowerCurve( + mBalanceCurve, (double)progress / (double)mBarMax))); + } else { + displayValue = progress * STEP + mMin; + } + Log.d(TAG, "onProgressChanged: progress=" + progress + " displayValue=" + displayValue); + + mValue.setText(mContext.getResources().getString( + R.string.live_display_color_temperature_label, displayValue)); + } + + public void setTemperature(int temperature) { + if (mUseBalance) { + double z = MathUtils.powerCurveToLinear(mBalanceCurve, (double)temperature); + mSeekBar.setProgress(Math.round((float)(z * (double)mBarMax))); + return; + } + int p = Math.max(temperature, mMin) - mMin; + mSeekBar.setProgress(Math.round((float) p / STEP)); + } + + public int getTemperature() { + if (mUseBalance) { + return Math.round((float)MathUtils.linearToPowerCurve( + mBalanceCurve, (double)mSeekBar.getProgress() / (double)mBarMax)); + } + return mSeekBar.getProgress() * STEP + mMin; + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + } +} diff --git a/src/com/android/settings/derp/livedisplay/LiveDisplayController.java b/src/com/android/settings/derp/livedisplay/LiveDisplayController.java new file mode 100644 index 00000000000..091aaa0ed4d --- /dev/null +++ b/src/com/android/settings/derp/livedisplay/LiveDisplayController.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The PixelExperience 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.derp.livedisplay; + +import android.content.Context; +import com.android.settings.core.BasePreferenceController; + +public class LiveDisplayController extends BasePreferenceController { + + public static final String KEY = "livedisplay"; + + private Context mContext; + + public LiveDisplayController(Context context, String key) { + super(context, key); + + mContext = context; + } + + public LiveDisplayController(Context context) { + this(context, KEY); + + mContext = context; + } + + @Override + public int getAvailabilityStatus() { + boolean exists = mContext.getResources().getBoolean(com.android.internal.R.bool.config_enableLiveDisplay); + return (exists ? AVAILABLE : UNSUPPORTED_ON_DEVICE); + } + +} diff --git a/src/com/android/settings/derp/livedisplay/LiveDisplaySettings.java b/src/com/android/settings/derp/livedisplay/LiveDisplaySettings.java new file mode 100644 index 00000000000..59ccf0eef43 --- /dev/null +++ b/src/com/android/settings/derp/livedisplay/LiveDisplaySettings.java @@ -0,0 +1,650 @@ +/* + * Copyright (C) 2015 The CyanogenMod Project + * 2017-2021 The LineageOS 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.derp.livedisplay; + +import android.content.Context; +import android.content.res.Resources; +import android.hardware.display.ColorDisplayManager; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.fragment.app.DialogFragment; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreferenceCompat; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager.widget.ViewPager; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.ArrayUtils; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; +import com.android.settings.derp.utils.ResourceUtils; +import com.android.settingslib.widget.LayoutPreference; + +import java.util.Collections; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import com.android.internal.derp.hardware.LineageHardwareManager; +import com.android.internal.derp.hardware.DisplayMode; +import com.android.internal.derp.hardware.LiveDisplayConfig; +import com.android.internal.derp.hardware.LiveDisplayManager; +import com.android.internal.derp.preference.SettingsHelper; +import com.android.settings.derp.preference.CustomDialogPreference; + +import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_ANTI_FLICKER; +import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_CABC; +import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_COLOR_ADJUSTMENT; +import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_COLOR_ENHANCEMENT; +import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_DISPLAY_MODES; +import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_PICTURE_ADJUSTMENT; +import static com.android.internal.derp.hardware.LiveDisplayManager.FEATURE_READING_ENHANCEMENT; +import static com.android.internal.derp.hardware.LiveDisplayManager.MODE_AUTO; +import static com.android.internal.derp.hardware.LiveDisplayManager.MODE_DAY; +import static com.android.internal.derp.hardware.LiveDisplayManager.MODE_NIGHT; +import static com.android.internal.derp.hardware.LiveDisplayManager.MODE_OFF; +import static com.android.internal.derp.hardware.LiveDisplayManager.MODE_OUTDOOR; + +@SearchIndexable +public class LiveDisplaySettings extends SettingsPreferenceFragment implements + Preference.OnPreferenceChangeListener, SettingsHelper.OnSettingsChangeListener { + + private static final String TAG = "LiveDisplay"; + + private static final String KEY_SCREEN_LIVE_DISPLAY = "livedisplay"; + + private static final String KEY_CATEGORY_ADVANCED = "advanced"; + + private static final String KEY_LIVE_DISPLAY = "live_display"; + private static final String KEY_LIVE_DISPLAY_ANTI_FLICKER = "display_anti_flicker"; + private static final String KEY_LIVE_DISPLAY_AUTO_OUTDOOR_MODE = + "display_auto_outdoor_mode"; + private static final String KEY_LIVE_DISPLAY_READING_ENHANCEMENT = "display_reading_mode"; + private static final String KEY_LIVE_DISPLAY_LOW_POWER = "display_low_power"; + private static final String KEY_LIVE_DISPLAY_COLOR_ENHANCE = "display_color_enhance"; + private static final String KEY_LIVE_DISPLAY_TEMPERATURE = "live_display_color_temperature"; + + private static final String KEY_DISPLAY_COLOR = "color_calibration"; + private static final String KEY_PICTURE_ADJUSTMENT = "picture_adjustment"; + + private static final String KEY_LIVE_DISPLAY_COLOR_PROFILE = "live_display_color_profile"; + + private static final String KEY_LIVE_DISPLAY_PREVIEW = "live_display_preview"; + + private static final String COLOR_PROFILE_TITLE = + KEY_LIVE_DISPLAY_COLOR_PROFILE + "_%s_title"; + + private static final String COLOR_PROFILE_SUMMARY = + KEY_LIVE_DISPLAY_COLOR_PROFILE + "_%s_summary"; + + private final Uri DISPLAY_TEMPERATURE_DAY_URI = + Settings.System.getUriFor(Settings.System.DISPLAY_TEMPERATURE_DAY); + private final Uri DISPLAY_TEMPERATURE_NIGHT_URI = + Settings.System.getUriFor(Settings.System.DISPLAY_TEMPERATURE_NIGHT); + private final Uri DISPLAY_TEMPERATURE_MODE_URI = + Settings.System.getUriFor(Settings.System.DISPLAY_TEMPERATURE_MODE); + + private ListPreference mLiveDisplay; + + private SwitchPreferenceCompat mAntiFlicker; + private SwitchPreferenceCompat mColorEnhancement; + private SwitchPreferenceCompat mLowPower; + private SwitchPreferenceCompat mOutdoorMode; + private SwitchPreferenceCompat mReadingMode; + + private PictureAdjustment mPictureAdjustment; + private DisplayTemperature mDisplayTemperature; + private DisplayColor mDisplayColor; + + private ListPreference mColorProfile; + private String[] mColorProfileSummaries; + + private String[] mModeEntries; + private String[] mModeValues; + private String[] mModeSummaries; + + private boolean mHasDisplayModes = false; + + private LayoutPreference mPreview; + private View mViewArrowPrevious; + private View mViewArrowNext; + private ViewPager mViewPager; + + private ArrayList mPageList; + + private ImageView[] mDotIndicators; + private View[] mViewPagerImages; + private static final int DOT_INDICATOR_SIZE = 12; + private static final int DOT_INDICATOR_LEFT_PADDING = 6; + private static final int DOT_INDICATOR_RIGHT_PADDING = 6; + + private LiveDisplayManager mLiveDisplayManager; + private LiveDisplayConfig mConfig; + + private LineageHardwareManager mHardware; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Resources res = getResources(); + final boolean isNightDisplayAvailable = + ColorDisplayManager.isNightDisplayAvailable(getContext()); + + mHardware = LineageHardwareManager.getInstance(getActivity()); + mLiveDisplayManager = getActivity().getSystemService(LiveDisplayManager.class); + mConfig = mLiveDisplayManager.getConfig(); + + addPreferencesFromResource(R.xml.livedisplay); + + PreferenceScreen liveDisplayPrefs = findPreference(KEY_SCREEN_LIVE_DISPLAY); + + PreferenceCategory advancedPrefs = findPreference(KEY_CATEGORY_ADVANCED); + + int adaptiveMode = mLiveDisplayManager.getMode(); + + mLiveDisplay = findPreference(KEY_LIVE_DISPLAY); + mLiveDisplay.setValue(String.valueOf(adaptiveMode)); + + mModeEntries = res.getStringArray( + com.android.internal.R.array.live_display_entries); + mModeValues = res.getStringArray( + com.android.internal.R.array.live_display_values); + mModeSummaries = res.getStringArray( + com.android.internal.R.array.live_display_summaries); + + int[] removeIdx = null; + // Remove outdoor mode from lists if there is no support + if (!mConfig.hasFeature(MODE_OUTDOOR)) { + removeIdx = ArrayUtils.appendInt(removeIdx, + ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_OUTDOOR))); + } else if (isNightDisplayAvailable) { + final int autoIdx = ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_AUTO)); + mModeSummaries[autoIdx] = res.getString(R.string.live_display_outdoor_mode_summary); + } + + // Remove night display on HWC2 + if (isNightDisplayAvailable) { + removeIdx = ArrayUtils.appendInt(removeIdx, + ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_DAY))); + removeIdx = ArrayUtils.appendInt(removeIdx, + ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_NIGHT))); + } + + if (removeIdx != null) { + String[] entriesTemp = new String[mModeEntries.length - removeIdx.length]; + String[] valuesTemp = new String[mModeValues.length - removeIdx.length]; + String[] summariesTemp = new String[mModeSummaries.length - removeIdx.length]; + int j = 0; + for (int i = 0; i < mModeEntries.length; i++) { + if (ArrayUtils.contains(removeIdx, i)) { + continue; + } + entriesTemp[j] = mModeEntries[i]; + valuesTemp[j] = mModeValues[i]; + summariesTemp[j] = mModeSummaries[i]; + j++; + } + mModeEntries = entriesTemp; + mModeValues = valuesTemp; + mModeSummaries = summariesTemp; + } + + mLiveDisplay.setEntries(mModeEntries); + mLiveDisplay.setEntryValues(mModeValues); + mLiveDisplay.setOnPreferenceChangeListener(this); + + mPreview = findPreference(KEY_LIVE_DISPLAY_PREVIEW); + addViewPager(mPreview); + + mDisplayTemperature = findPreference(KEY_LIVE_DISPLAY_TEMPERATURE); + if (isNightDisplayAvailable) { + if (!mConfig.hasFeature(MODE_OUTDOOR)) { + liveDisplayPrefs.removePreference(mLiveDisplay); + } + liveDisplayPrefs.removePreference(mDisplayTemperature); + } + + mColorProfile = findPreference(KEY_LIVE_DISPLAY_COLOR_PROFILE); + if (liveDisplayPrefs != null && mColorProfile != null + && (!mConfig.hasFeature(FEATURE_DISPLAY_MODES) || !updateDisplayModes())) { + liveDisplayPrefs.removePreference(mColorProfile); + } else { + mHasDisplayModes = true; + mColorProfile.setOnPreferenceChangeListener(this); + } + + mOutdoorMode = findPreference(KEY_LIVE_DISPLAY_AUTO_OUTDOOR_MODE); + if (liveDisplayPrefs != null && mOutdoorMode != null + // MODE_AUTO implies automatic outdoor mode on HWC2 + && (isNightDisplayAvailable || !mConfig.hasFeature(MODE_OUTDOOR))) { + liveDisplayPrefs.removePreference(mOutdoorMode); + mOutdoorMode = null; + } + + mReadingMode = findPreference(KEY_LIVE_DISPLAY_READING_ENHANCEMENT); + if (liveDisplayPrefs != null && mReadingMode != null && + !mHardware.isSupported(LineageHardwareManager.FEATURE_READING_ENHANCEMENT)) { + liveDisplayPrefs.removePreference(mReadingMode); + mReadingMode = null; + } else { + mReadingMode.setOnPreferenceChangeListener(this); + } + + mLowPower = findPreference(KEY_LIVE_DISPLAY_LOW_POWER); + if (advancedPrefs != null && mLowPower != null + && !mConfig.hasFeature(FEATURE_CABC)) { + advancedPrefs.removePreference(mLowPower); + mLowPower = null; + } + + mColorEnhancement = findPreference(KEY_LIVE_DISPLAY_COLOR_ENHANCE); + if (advancedPrefs != null && mColorEnhancement != null + && !mConfig.hasFeature(FEATURE_COLOR_ENHANCEMENT)) { + advancedPrefs.removePreference(mColorEnhancement); + mColorEnhancement = null; + } + + mPictureAdjustment = findPreference(KEY_PICTURE_ADJUSTMENT); + if (advancedPrefs != null && mPictureAdjustment != null && + !mConfig.hasFeature(FEATURE_PICTURE_ADJUSTMENT)) { + advancedPrefs.removePreference(mPictureAdjustment); + mPictureAdjustment = null; + } + + mDisplayColor = findPreference(KEY_DISPLAY_COLOR); + if (advancedPrefs != null && mDisplayColor != null && + !mConfig.hasFeature(FEATURE_COLOR_ADJUSTMENT)) { + advancedPrefs.removePreference(mDisplayColor); + mDisplayColor = null; + } + + mAntiFlicker = findPreference(KEY_LIVE_DISPLAY_ANTI_FLICKER); + if (liveDisplayPrefs != null && mAntiFlicker != null && + !mHardware.isSupported(LineageHardwareManager.FEATURE_ANTI_FLICKER)) { + liveDisplayPrefs.removePreference(mAntiFlicker); + mAntiFlicker = null; + } + } + + @Override + public void onResume() { + super.onResume(); + updateModeSummary(); + updateTemperatureSummary(); + updateColorProfileSummary(null); + updateReadingModeStatus(); + SettingsHelper.get(getActivity()).startWatching(this, DISPLAY_TEMPERATURE_DAY_URI, + DISPLAY_TEMPERATURE_MODE_URI, DISPLAY_TEMPERATURE_NIGHT_URI); + } + + @Override + public void onPause() { + super.onPause(); + SettingsHelper.get(getActivity()).stopWatching(this); + } + + private ArrayList getViewPagerResource() { + return new ArrayList( + Arrays.asList( + R.layout.color_mode_view1, + R.layout.color_mode_view2, + R.layout.color_mode_view3)); + } + + private void addViewPager(LayoutPreference preview) { + final ArrayList tmpviewPagerList = getViewPagerResource(); + mViewPager = preview.findViewById(R.id.viewpager); + + mViewPagerImages = new View[3]; + for (int idx = 0; idx < tmpviewPagerList.size(); idx++) { + mViewPagerImages[idx] = + getLayoutInflater().inflate(tmpviewPagerList.get(idx), null /* root */); + } + + mPageList = new ArrayList(); + mPageList.add(mViewPagerImages[0]); + mPageList.add(mViewPagerImages[1]); + mPageList.add(mViewPagerImages[2]); + + mViewPager.setAdapter(new ColorPagerAdapter(mPageList)); + + mViewArrowPrevious = preview.findViewById(R.id.arrow_previous); + mViewArrowPrevious.setOnClickListener(v -> { + final int previousPos = mViewPager.getCurrentItem() - 1; + mViewPager.setCurrentItem(previousPos, true); + }); + + mViewArrowNext = preview.findViewById(R.id.arrow_next); + mViewArrowNext.setOnClickListener(v -> { + final int nextPos = mViewPager.getCurrentItem() + 1; + mViewPager.setCurrentItem(nextPos, true); + }); + + mViewPager.addOnPageChangeListener(createPageListener()); + + final ViewGroup viewGroup = (ViewGroup) preview.findViewById(R.id.viewGroup); + mDotIndicators = new ImageView[mPageList.size()]; + for (int i = 0; i < mPageList.size(); i++) { + final ImageView imageView = new ImageView(getContext()); + final ViewGroup.MarginLayoutParams lp = + new ViewGroup.MarginLayoutParams(DOT_INDICATOR_SIZE, DOT_INDICATOR_SIZE); + lp.setMargins(DOT_INDICATOR_LEFT_PADDING, 0, DOT_INDICATOR_RIGHT_PADDING, 0); + imageView.setLayoutParams(lp); + mDotIndicators[i] = imageView; + + viewGroup.addView(mDotIndicators[i]); + } + + updateIndicator(mViewPager.getCurrentItem()); + } + + private ViewPager.OnPageChangeListener createPageListener() { + return new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled( + int position, float positionOffset, int positionOffsetPixels) { + if (positionOffset != 0) { + for (int idx = 0; idx < mPageList.size(); idx++) { + mViewPagerImages[idx].setVisibility(View.VISIBLE); + } + } else { + mViewPagerImages[position].setContentDescription( + getContext().getString(R.string.colors_viewpager_content_description)); + updateIndicator(position); + } + } + + @Override + public void onPageSelected(int position) {} + + @Override + public void onPageScrollStateChanged(int state) {} + }; + } + + private void updateIndicator(int position) { + for (int i = 0; i < mPageList.size(); i++) { + if (position == i) { + mDotIndicators[i].setBackgroundResource( + R.drawable.ic_color_page_indicator_focused); + + mViewPagerImages[i].setVisibility(View.VISIBLE); + } else { + mDotIndicators[i].setBackgroundResource( + R.drawable.ic_color_page_indicator_unfocused); + + mViewPagerImages[i].setVisibility(View.INVISIBLE); + } + } + + if (position == 0) { + mViewArrowPrevious.setVisibility(View.INVISIBLE); + mViewArrowNext.setVisibility(View.VISIBLE); + } else if (position == (mPageList.size() - 1)) { + mViewArrowPrevious.setVisibility(View.VISIBLE); + mViewArrowNext.setVisibility(View.INVISIBLE); + } else { + mViewArrowPrevious.setVisibility(View.VISIBLE); + mViewArrowNext.setVisibility(View.VISIBLE); + } + } + + static class ColorPagerAdapter extends PagerAdapter { + private final ArrayList mPageViewList; + + ColorPagerAdapter(ArrayList pageViewList) { + mPageViewList = pageViewList; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + if (mPageViewList.get(position) != null) { + container.removeView(mPageViewList.get(position)); + } + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + container.addView(mPageViewList.get(position)); + return mPageViewList.get(position); + } + + @Override + public int getCount() { + return mPageViewList.size(); + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return object == view; + } + } + + private boolean updateDisplayModes() { + final DisplayMode[] modes = mHardware.getDisplayModes(); + if (modes == null || modes.length == 0) { + return false; + } + + final DisplayMode cur = mHardware.getCurrentDisplayMode() != null + ? mHardware.getCurrentDisplayMode() : mHardware.getDefaultDisplayMode(); + int curId = -1; + String[] entries = new String[modes.length]; + String[] values = new String[modes.length]; + mColorProfileSummaries = new String[modes.length]; + for (int i = 0; i < modes.length; i++) { + values[i] = String.valueOf(modes[i].id); + entries[i] = ResourceUtils.getLocalizedString( + getResources(), modes[i].name, COLOR_PROFILE_TITLE); + + // Populate summary + String summary = ResourceUtils.getLocalizedString( + getResources(), modes[i].name, COLOR_PROFILE_SUMMARY); + if (summary != null) { + summary = String.format("%s - %s", entries[i], summary); + } + mColorProfileSummaries[i] = summary; + + if (cur != null && modes[i].id == cur.id) { + curId = cur.id; + } + } + mColorProfile.setEntries(entries); + mColorProfile.setEntryValues(values); + if (curId >= 0) { + mColorProfile.setValue(String.valueOf(curId)); + } + + return true; + } + + private void updateColorProfileSummary(String value) { + if (!mHasDisplayModes) { + return; + } + + if (value == null) { + DisplayMode cur = mHardware.getCurrentDisplayMode() != null + ? mHardware.getCurrentDisplayMode() : mHardware.getDefaultDisplayMode(); + if (cur != null && cur.id >= 0) { + value = String.valueOf(cur.id); + } + } + + int idx = mColorProfile.findIndexOfValue(value); + if (idx < 0) { + Log.e(TAG, "No summary resource found for profile " + value); + mColorProfile.setSummary(null); + return; + } + + mColorProfile.setValue(value); + mColorProfile.setSummary(mColorProfileSummaries[idx]); + } + + private void updateModeSummary() { + int mode = mLiveDisplayManager.getMode(); + + int index = ArrayUtils.indexOf(mModeValues, String.valueOf(mode)); + if (index < 0) { + index = ArrayUtils.indexOf(mModeValues, String.valueOf(MODE_OFF)); + } + + mLiveDisplay.setSummary(mModeSummaries[index]); + mLiveDisplay.setValue(String.valueOf(mode)); + + if (mDisplayTemperature != null) { + mDisplayTemperature.setEnabled(mode != MODE_OFF); + } + if (mOutdoorMode != null) { + mOutdoorMode.setEnabled(mode != MODE_OFF); + } + } + + private void updateTemperatureSummary() { + int day = mLiveDisplayManager.getDayColorTemperature(); + int night = mLiveDisplayManager.getNightColorTemperature(); + + mDisplayTemperature.setSummary(getResources().getString( + R.string.live_display_color_temperature_summary, + mDisplayTemperature.roundUp(day), + mDisplayTemperature.roundUp(night))); + } + + private void updateReadingModeStatus() { + if (mReadingMode != null) { + mReadingMode.setChecked( + mHardware.get(LineageHardwareManager.FEATURE_READING_ENHANCEMENT)); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object objValue) { + if (preference == mLiveDisplay) { + mLiveDisplayManager.setMode(Integer.valueOf((String)objValue)); + } else if (preference == mColorProfile) { + int id = Integer.valueOf((String)objValue); + Log.i("LiveDisplay", "Setting mode: " + id); + for (DisplayMode mode : mHardware.getDisplayModes()) { + if (mode.id == id) { + mHardware.setDisplayMode(mode, true); + updateColorProfileSummary((String)objValue); + break; + } + } + } else if (preference == mReadingMode) { + mHardware.set(LineageHardwareManager.FEATURE_READING_ENHANCEMENT, (Boolean) objValue); + } + return true; + } + + @Override + public void onSettingsChanged(Uri uri) { + updateModeSummary(); + updateTemperatureSummary(); + updateReadingModeStatus(); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } + + @Override + public void onDisplayPreferenceDialog(Preference preference) { + if (preference.getKey() == null) { + // Auto-key preferences that don't have a key, so the dialog can find them. + preference.setKey(UUID.randomUUID().toString()); + } + DialogFragment f = null; + if (preference instanceof CustomDialogPreference) { + f = CustomDialogPreference.CustomPreferenceDialogFragment + .newInstance(preference.getKey()); + } else { + super.onDisplayPreferenceDialog(preference); + return; + } + f.setTargetFragment(this, 0); + f.show(getFragmentManager(), "dialog_preference"); + onDialogShowing(); + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.livedisplay) { + + @Override + public List getNonIndexableKeys(Context context) { + final LiveDisplayConfig config = context.getSystemService(LiveDisplayManager.class).getConfig(); + final List result = super.getNonIndexableKeys(context); + + if (!config.hasFeature(FEATURE_DISPLAY_MODES)) { + result.add(KEY_LIVE_DISPLAY_COLOR_PROFILE); + } + if (!config.hasFeature(MODE_OUTDOOR)) { + result.add(KEY_LIVE_DISPLAY_AUTO_OUTDOOR_MODE); + } + if (!config.hasFeature(FEATURE_COLOR_ENHANCEMENT)) { + result.add(KEY_LIVE_DISPLAY_COLOR_ENHANCE); + } + if (!config.hasFeature(FEATURE_CABC)) { + result.add(KEY_LIVE_DISPLAY_LOW_POWER); + } + if (!config.hasFeature(FEATURE_COLOR_ADJUSTMENT)) { + result.add(KEY_DISPLAY_COLOR); + } + if (!config.hasFeature(FEATURE_PICTURE_ADJUSTMENT)) { + result.add(KEY_PICTURE_ADJUSTMENT); + } + if (!config.hasFeature(FEATURE_READING_ENHANCEMENT)) { + result.add(KEY_LIVE_DISPLAY_READING_ENHANCEMENT); + } + if (ColorDisplayManager.isNightDisplayAvailable(context)) { + if (!config.hasFeature(MODE_OUTDOOR)) { + result.add(KEY_LIVE_DISPLAY); + } + result.add(KEY_LIVE_DISPLAY_TEMPERATURE); + } + if (!context.getResources().getBoolean( + com.android.internal.R.bool.config_enableLiveDisplay)) { + result.add(KEY_LIVE_DISPLAY_TEMPERATURE); + result.add(KEY_LIVE_DISPLAY); + } + if (!config.hasFeature(FEATURE_ANTI_FLICKER)) { + result.add(KEY_LIVE_DISPLAY_ANTI_FLICKER); + } + + return result; + } + }; +} diff --git a/src/com/android/settings/derp/livedisplay/PictureAdjustment.java b/src/com/android/settings/derp/livedisplay/PictureAdjustment.java new file mode 100644 index 00000000000..6eac238c8e6 --- /dev/null +++ b/src/com/android/settings/derp/livedisplay/PictureAdjustment.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2016 The CyanogenMod Project + * 2021-2022 The LineageOS 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.derp.livedisplay; + +import android.content.Context; +import android.content.DialogInterface; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Range; +import android.view.View; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.derp.preference.CustomDialogPreference; +import com.android.settings.derp.widget.IntervalSeekBar; +import com.android.settings.R; + +import java.util.List; + +import com.android.internal.derp.hardware.HSIC; +import com.android.internal.derp.hardware.LiveDisplayManager; + +/** + * Special preference type that allows configuration of Color settings + */ +public class PictureAdjustment extends CustomDialogPreference { + private static final String TAG = "PictureAdjustment"; + + private final Context mContext; + private final LiveDisplayManager mLiveDisplay; + private final List> mRanges; + + // These arrays must all match in length and order + private static final int[] SEEKBAR_ID = new int[] { + R.id.adj_hue_seekbar, + R.id.adj_saturation_seekbar, + R.id.adj_intensity_seekbar, + R.id.adj_contrast_seekbar + }; + + private static final int[] SEEKBAR_VALUE_ID = new int[] { + R.id.adj_hue_value, + R.id.adj_saturation_value, + R.id.adj_intensity_value, + R.id.adj_contrast_value + }; + + private ColorSeekBar[] mSeekBars = new ColorSeekBar[SEEKBAR_ID.length]; + + private final float[] mCurrentAdj = new float[5]; + private final float[] mOriginalAdj = new float[5]; + + public PictureAdjustment(Context context, AttributeSet attrs) { + super(context, attrs); + + mContext = context; + mLiveDisplay = mContext.getSystemService(LiveDisplayManager.class); + mRanges = mLiveDisplay.getConfig().getPictureAdjustmentRanges(); + + setDialogLayoutResource(R.layout.display_picture_adjustment); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder, + DialogInterface.OnClickListener listener) { + super.onPrepareDialogBuilder(builder, listener); + + builder.setNeutralButton(R.string.reset, null); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.dlg_ok, null); + } + + private void updateBars() { + for (int i = 0; i < SEEKBAR_ID.length; i++) { + mSeekBars[i].setValue(mCurrentAdj[i]); + } + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + System.arraycopy(mLiveDisplay.getPictureAdjustment().toFloatArray(), 0, mOriginalAdj, 0, 5); + System.arraycopy(mOriginalAdj, 0, mCurrentAdj, 0, 5); + + for (int i = 0; i < SEEKBAR_ID.length; i++) { + IntervalSeekBar seekBar = (IntervalSeekBar) view.findViewById(SEEKBAR_ID[i]); + TextView value = (TextView) view.findViewById(SEEKBAR_VALUE_ID[i]); + final Range range = mRanges.get(i); + mSeekBars[i] = new ColorSeekBar(seekBar, range, value, i); + } + updateBars(); + } + + @Override + protected boolean onDismissDialog(AlertDialog dialog, int which) { + // Can't use onPrepareDialogBuilder for this as we want the dialog + // to be kept open on click + if (which == DialogInterface.BUTTON_NEUTRAL) { + System.arraycopy(mLiveDisplay.getDefaultPictureAdjustment().toFloatArray(), + 0, mCurrentAdj, 0, 5); + updateBars(); + updateAdjustment(mCurrentAdj); + return false; + } + return true; + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + updateAdjustment(positiveResult ? mCurrentAdj : mOriginalAdj); + } + + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + if (getDialog() == null || !getDialog().isShowing()) { + return superState; + } + + // Save the dialog state + final SavedState myState = new SavedState(superState); + myState.currentAdj = mCurrentAdj; + myState.originalAdj = mOriginalAdj; + + // Restore the old state when the activity or dialog is being paused + updateAdjustment(mOriginalAdj); + + return myState; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state == null || !state.getClass().equals(SavedState.class)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; + } + + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + + System.arraycopy(myState.originalAdj, 0, mOriginalAdj, 0, 5); + System.arraycopy(myState.currentAdj, 0, mCurrentAdj, 0, 5); + + updateBars(); + updateAdjustment(mCurrentAdj); + } + + private static class SavedState extends BaseSavedState { + float[] originalAdj; + float[] currentAdj; + + public SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + originalAdj = source.createFloatArray(); + currentAdj = source.createFloatArray(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeFloatArray(originalAdj); + dest.writeFloatArray(currentAdj); + } + + public static final Creator CREATOR = + new Creator() { + + public PictureAdjustment.SavedState createFromParcel(Parcel in) { + return new PictureAdjustment.SavedState(in); + } + + public PictureAdjustment.SavedState[] newArray(int size) { + return new PictureAdjustment.SavedState[size]; + } + }; + } + + private void updateAdjustment(final float[] adjustment) { + mLiveDisplay.setPictureAdjustment(HSIC.fromFloatArray(adjustment)); + } + + private class ColorSeekBar implements SeekBar.OnSeekBarChangeListener { + private int mIndex; + private final IntervalSeekBar mSeekBar; + private TextView mValue; + private Range mRange; + + public ColorSeekBar(IntervalSeekBar seekBar, Range range, TextView value, int index) { + mSeekBar = seekBar; + mValue = value; + mIndex = index; + mRange = range; + mSeekBar.setMinimum(range.getLower()); + mSeekBar.setMaximum(range.getUpper()); + + mSeekBar.setOnSeekBarChangeListener(this); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + IntervalSeekBar isb = (IntervalSeekBar)seekBar; + float fp = isb.getProgressFloat(); + if (fromUser) { + mCurrentAdj[mIndex] = mRanges.get(mIndex).clamp(fp); + updateAdjustment(mCurrentAdj); + } + mValue.setText(getLabel(mCurrentAdj[mIndex])); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do nothing here + } + + private String getLabel(float value) { + if (mRange.getUpper() == 1.0f) { + return String.format("%d%%", Math.round(100F * value)); + } + return String.format("%d", Math.round(value)); + } + + public void setValue(float value) { + mSeekBar.setProgressFloat(value); + mValue.setText(getLabel(value)); + } + } +} diff --git a/src/com/android/settings/derp/notificationlight/AlphaPatternDrawable.java b/src/com/android/settings/derp/notificationlight/AlphaPatternDrawable.java new file mode 100644 index 00000000000..39f358e7d51 --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/AlphaPatternDrawable.java @@ -0,0 +1,115 @@ +/* + * SPDX-FileCopyrightText: 2010 Daniel Nilsson + * SPDX-FileCopyrightText: 2012 The CyanogenMod Project + * SPDX-FileCopyrightText: 2022 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.notificationlight; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Bitmap.Config; +import android.graphics.drawable.Drawable; + +/** + * This drawable that draws a simple white and gray chess board pattern. It's + * pattern you will often see as a background behind a partly transparent image + * in many applications. + * + * @author Daniel Nilsson + */ +public class AlphaPatternDrawable extends Drawable { + + private final int mRectangleSize; + + private final Paint mPaint = new Paint(); + private final Paint mPaintWhite = new Paint(); + private final Paint mPaintGray = new Paint(); + + private int numRectanglesHorizontal; + private int numRectanglesVertical; + + /** + * Bitmap in which the pattern will be cached. + */ + private Bitmap mBitmap; + + public AlphaPatternDrawable(int rectangleSize) { + mRectangleSize = rectangleSize; + mPaintWhite.setColor(0xffffffff); + mPaintGray.setColor(0xffcbcbcb); + } + + @Override + public void draw(Canvas canvas) { + if (mBitmap != null) { + canvas.drawBitmap(mBitmap, null, getBounds(), mPaint); + } + } + + @Override + public int getOpacity() { + return 0; + } + + @Override + public void setAlpha(int alpha) { + throw new UnsupportedOperationException("Alpha is not supported by this drawable."); + } + + @Override + public void setColorFilter(ColorFilter cf) { + throw new UnsupportedOperationException("ColorFilter is not supported by this drawable."); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + + int height = bounds.height(); + int width = bounds.width(); + + numRectanglesHorizontal = (int) Math.ceil((float) width / mRectangleSize); + numRectanglesVertical = (int) Math.ceil((float) height / mRectangleSize); + + generatePatternBitmap(); + } + + /** + * This will generate a bitmap with the pattern as big as the rectangle we + * were allow to draw on. We do this to cache the bitmap so we don't need + * to recreate it each time draw() is called since it takes a few + * milliseconds. + */ + private void generatePatternBitmap() { + + if (getBounds().width() <= 0 || getBounds().height() <= 0) { + return; + } + + mBitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888); + Canvas canvas = new Canvas(mBitmap); + + Rect r = new Rect(); + boolean verticalStartWhite = true; + for (int i = 0; i <= numRectanglesVertical; i++) { + boolean isWhite = verticalStartWhite; + for (int j = 0; j <= numRectanglesHorizontal; j++) { + r.top = i * mRectangleSize; + r.left = j * mRectangleSize; + r.bottom = r.top + mRectangleSize; + r.right = r.left + mRectangleSize; + + canvas.drawRect(r, isWhite ? mPaintWhite : mPaintGray); + + isWhite = !isWhite; + } + + verticalStartWhite = !verticalStartWhite; + } + } +} diff --git a/src/com/android/settings/derp/notificationlight/ApplicationLightPreference.java b/src/com/android/settings/derp/notificationlight/ApplicationLightPreference.java new file mode 100644 index 00000000000..e56ac5d7a63 --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/ApplicationLightPreference.java @@ -0,0 +1,304 @@ +/* + * SPDX-FileCopyrightText: 2012 The CyanogenMod Project + * SPDX-FileCopyrightText: 2017-2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.notificationlight; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.OvalShape; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.PreferenceViewHolder; + +import com.android.internal.derp.notification.LightsCapabilities; + +import com.android.settings.R; +import com.android.settings.derp.preference.CustomDialogPreference; + +public class ApplicationLightPreference extends CustomDialogPreference + implements View.OnLongClickListener { + + private static final String TAG = "AppLightPreference"; + public static final int DEFAULT_TIME = 1000; + public static final int DEFAULT_COLOR = 0xffffff; + + private ImageView mLightColorView; + private TextView mOnValueView; + private TextView mOffValueView; + + private int mColorValue; + private int mOnValue; + private int mOffValue; + private boolean mOnOffChangeable; + + private boolean mHasDefaults; + private int mDefaultColorValue; + private int mDefaultOnValue; + private int mDefaultOffValue; + + private int mLedBrightness; + + private LightSettingsDialog mDialog; + + public interface ItemLongClickListener { + boolean onItemLongClick(String key); + } + + private ItemLongClickListener mLongClickListener; + + public ApplicationLightPreference(Context context, AttributeSet attrs) { + this(context, attrs, DEFAULT_COLOR, DEFAULT_TIME, DEFAULT_TIME); + } + + public ApplicationLightPreference(Context context, AttributeSet attrs, + int color, int onValue, int offValue) { + this(context, attrs, color, onValue, offValue, + LightsCapabilities.supports(context, LightsCapabilities.LIGHTS_PULSATING_LED)); + } + + public ApplicationLightPreference(Context context, AttributeSet attrs, + int color, int onValue, int offValue, + boolean onOffChangeable) { + super(context, attrs); + mColorValue = color; + mOnValue = onValue; + mOffValue = offValue; + mOnOffChangeable = onOffChangeable; + mHasDefaults = false; + mLedBrightness = 0; // use system brightness + + setWidgetLayoutResource(R.layout.preference_application_light); + } + + @Override + public boolean onLongClick(View view) { + if (mLongClickListener != null) { + return mLongClickListener.onItemLongClick(getKey()); + } + return false; + } + + public void setOnLongClickListener(ItemLongClickListener l) { + mLongClickListener = l; + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + + mLightColorView = (ImageView) holder.findViewById(R.id.light_color); + mOnValueView = (TextView) holder.findViewById(R.id.textViewTimeOnValue); + mOffValueView = (TextView) holder.findViewById(R.id.textViewTimeOffValue); + + // Hide the summary text - it takes up too much space on a low res device + // We use it for storing the package name for the longClickListener + TextView tView = (TextView) holder.findViewById(android.R.id.summary); + tView.setVisibility(View.GONE); + + if (!LightsCapabilities.supports( + getContext(), LightsCapabilities.LIGHTS_RGB_NOTIFICATION_LED)) { + mLightColorView.setVisibility(View.GONE); + } + + updatePreferenceViews(); + holder.itemView.setOnLongClickListener(this); + } + + @Override + public void onPause() { + if (getDialog() != null) { + getDialog().onPause(); + } + } + + @Override + public void onResume() { + if (getDialog() != null) { + getDialog().onResume(); + } + } + + private void updatePreferenceViews() { + final int size = (int) getContext().getResources().getDimension( + R.dimen.oval_notification_size); + + if (mLightColorView != null) { + mLightColorView.setEnabled(true); + // adjust if necessary to prevent material whiteout + final int imageColor = ((mColorValue & 0xF0F0F0) == 0xF0F0F0) ? + (mColorValue - 0x101010) : mColorValue; + mLightColorView.setImageDrawable(createOvalShape(size, + 0xFF000000 + imageColor)); + } + if (mOnValueView != null) { + mOnValueView.setText(mapLengthValue(mOnValue)); + } + if (mOffValueView != null) { + if (mOnValue == 1 || !mOnOffChangeable) { + mOffValueView.setVisibility(View.GONE); + } else { + mOffValueView.setVisibility(View.VISIBLE); + } + mOffValueView.setText(mapSpeedValue(mOffValue)); + } + } + + @Override + protected boolean onDismissDialog(LightSettingsDialog dialog, int which) { + if (which == DialogInterface.BUTTON_NEUTRAL) { + // Reset to previously supplied defaults + mDialog.setColor(mDefaultColorValue); + if (mOnOffChangeable) { + mDialog.setPulseSpeedOn(mDefaultOnValue); + mDialog.setPulseSpeedOff(mDefaultOffValue); + } + return false; + } + return true; + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + if (positiveResult) { + mColorValue = mDialog.getColor() & 0x00FFFFFF; // strip alpha, led does not support it + mOnValue = mDialog.getPulseSpeedOn(); + mOffValue = mDialog.getPulseSpeedOff(); + updatePreferenceViews(); + callChangeListener(null); + } + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + mDialog = new LightSettingsDialog(getContext(), 0xFF000000 | mColorValue, + mOnValue, mOffValue, mOnOffChangeable, mLedBrightness); + mDialog.setAlphaSliderVisible(false); + + // Initialize the buttons with null handlers, as they will get remapped by + // CustomPreferenceDialogFragment + mDialog.setButton(AlertDialog.BUTTON_POSITIVE, + getContext().getResources().getString(R.string.dlg_ok), + (DialogInterface.OnClickListener) null); + mDialog.setButton(AlertDialog.BUTTON_NEGATIVE, + getContext().getResources().getString(R.string.cancel), + (DialogInterface.OnClickListener) null); + if (mHasDefaults) { + mDialog.setButton(AlertDialog.BUTTON_NEUTRAL, + getContext().getResources().getString(R.string.reset), + (DialogInterface.OnClickListener) null); + } + + return mDialog; + } + + + /** + * Getters and Setters + */ + public int getColor() { + return mColorValue; + } + + public void setColor(int color) { + mColorValue = color; + updatePreferenceViews(); + } + + public int getOnValue() { + return mOnValue; + } + + public int getOffValue() { + return mOffValue; + } + + public void setAllValues(int color, int onValue, int offValue) { + mColorValue = color; + mOnValue = onValue; + mOffValue = offValue; + updatePreferenceViews(); + } + + public void setAllValues(int color, int onValue, int offValue, boolean onOffChangeable) { + mColorValue = color; + mOnValue = onValue; + mOffValue = offValue; + mOnOffChangeable = onOffChangeable; + updatePreferenceViews(); + } + + public void setDefaultValues(int color, int onValue, int offValue) { + mDefaultColorValue = color; + mDefaultOnValue = onValue; + mDefaultOffValue = offValue; + mHasDefaults = true; + } + + public void setBrightness(int brightness) { + mLedBrightness = brightness; + } + + /** + * Utility methods + */ + private static ShapeDrawable createOvalShape(int size, int color) { + ShapeDrawable shape = new ShapeDrawable(new OvalShape()); + shape.setIntrinsicHeight(size); + shape.setIntrinsicWidth(size); + shape.getPaint().setColor(color); + return shape; + } + + private String mapLengthValue(Integer time) { + if (!mOnOffChangeable) { + return getContext().getResources().getString(R.string.pulse_length_always_on); + } + if (time == DEFAULT_TIME) { + return getContext().getResources().getString(R.string.default_time); + } + + String[] timeNames = getContext().getResources().getStringArray( + R.array.notification_pulse_length_entries); + String[] timeValues = getContext().getResources().getStringArray( + R.array.notification_pulse_length_values); + + for (int i = 0; i < timeValues.length; i++) { + if (Integer.decode(timeValues[i]).equals(time)) { + return timeNames[i]; + } + } + + return getContext().getResources().getString(R.string.custom_time); + } + + private String mapSpeedValue(Integer time) { + if (time == DEFAULT_TIME) { + return getContext().getResources().getString(R.string.default_time); + } + + String[] timeNames = getContext().getResources().getStringArray( + R.array.notification_pulse_speed_entries); + String[] timeValues = getContext().getResources().getStringArray( + R.array.notification_pulse_speed_values); + + for (int i = 0; i < timeValues.length; i++) { + if (Integer.decode(timeValues[i]).equals(time)) { + return timeNames[i]; + } + } + + return getContext().getResources().getString(R.string.custom_time); + } +} diff --git a/src/com/android/settings/derp/notificationlight/BatteryBrightnessPreference.java b/src/com/android/settings/derp/notificationlight/BatteryBrightnessPreference.java new file mode 100644 index 00000000000..f30ac96efc1 --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/BatteryBrightnessPreference.java @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2017-2022 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.notificationlight; + +import android.content.ContentResolver; +import android.content.Context; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.AttributeSet; + +public class BatteryBrightnessPreference extends BrightnessPreference { + private static final String TAG = "BatteryBrightnessPreference"; + + private final ContentResolver mResolver; + + public BatteryBrightnessPreference(Context context, AttributeSet attrs) { + super(context, attrs); + mResolver = context.getContentResolver(); + } + + @Override + protected int getBrightnessSetting() { + return Settings.System.getIntForUser(mResolver, + Settings.System.BATTERY_LIGHT_BRIGHTNESS_LEVEL, + LIGHT_BRIGHTNESS_MAXIMUM, UserHandle.USER_CURRENT); + } + + @Override + protected void setBrightnessSetting(int brightness) { + Settings.System.putIntForUser(mResolver, + Settings.System.BATTERY_LIGHT_BRIGHTNESS_LEVEL, + brightness, UserHandle.USER_CURRENT); + } +} diff --git a/src/com/android/settings/derp/notificationlight/BatteryBrightnessZenPreference.java b/src/com/android/settings/derp/notificationlight/BatteryBrightnessZenPreference.java new file mode 100644 index 00000000000..1721099d6aa --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/BatteryBrightnessZenPreference.java @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2017-2022 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.notificationlight; + +import android.content.ContentResolver; +import android.content.Context; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.AttributeSet; + +public class BatteryBrightnessZenPreference extends BrightnessPreference { + private static final String TAG = "BatteryBrightnessZenPreference"; + + private final ContentResolver mResolver; + + public BatteryBrightnessZenPreference(Context context, AttributeSet attrs) { + super(context, attrs); + mResolver = context.getContentResolver(); + } + + @Override + protected int getBrightnessSetting() { + return Settings.System.getIntForUser(mResolver, + Settings.System.BATTERY_LIGHT_BRIGHTNESS_LEVEL_ZEN, + LIGHT_BRIGHTNESS_MAXIMUM, UserHandle.USER_CURRENT); + } + + @Override + protected void setBrightnessSetting(int brightness) { + Settings.System.putIntForUser(mResolver, + Settings.System.BATTERY_LIGHT_BRIGHTNESS_LEVEL_ZEN, + brightness, UserHandle.USER_CURRENT); + } +} diff --git a/src/com/android/settings/derp/notificationlight/BatteryLightPreferenceController.java b/src/com/android/settings/derp/notificationlight/BatteryLightPreferenceController.java new file mode 100644 index 00000000000..95855d40f37 --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/BatteryLightPreferenceController.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The PixelExperience 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.derp.notificationlight; + +import android.content.Context; +import com.android.settings.core.BasePreferenceController; + +public class BatteryLightPreferenceController extends BasePreferenceController { + + public static final String KEY = "battery_lights"; + + private Context mContext; + + public BatteryLightPreferenceController(Context context, String key) { + super(context, key); + + mContext = context; + } + + public BatteryLightPreferenceController(Context context) { + this(context, KEY); + + mContext = context; + } + + @Override + public int getAvailabilityStatus() { + int caps = mContext.getResources().getInteger(com.android.internal.R.integer.config_deviceLightCapabilities); + return (((caps & 64) == 64) ? AVAILABLE : UNSUPPORTED_ON_DEVICE); + } + +} diff --git a/src/com/android/settings/derp/notificationlight/BatteryLightSettings.java b/src/com/android/settings/derp/notificationlight/BatteryLightSettings.java new file mode 100644 index 00000000000..b4af9b25b23 --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/BatteryLightSettings.java @@ -0,0 +1,319 @@ +/* + * SPDX-FileCopyrightText: 2012 The CyanogenMod Project + * SPDX-FileCopyrightText: 2017-2023 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.notificationlight; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.os.Bundle; +import android.provider.Settings; +import android.util.ArraySet; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import androidx.preference.Preference; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; + +import com.android.internal.derp.notification.LightsCapabilities; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +import org.derpfest.support.preferences.SystemSettingMainSwitchPreference; +import org.derpfest.support.preferences.SystemSettingSwitchPreference; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +@SearchIndexable +public class BatteryLightSettings extends SettingsPreferenceFragment implements + Preference.OnPreferenceChangeListener { + private static final String TAG = "BatteryLightSettings"; + + private static final String KEY_BATTERY_LIGHTS = "battery_lights"; + private static final String GENERAL_SECTION = "general_section"; + private static final String COLORS_SECTION = "colors_list"; + private static final String BRIGHTNESS_SECTION = "brightness_section"; + + private static final String LOW_COLOR_PREF = "low_color"; + private static final String MEDIUM_COLOR_PREF = "medium_color"; + private static final String FULL_COLOR_PREF = "full_color"; + private static final String LIGHT_ENABLED_PREF = "battery_light_enabled"; + private static final String LIGHT_FULL_CHARGE_DISABLED_PREF = + "battery_light_full_charge_disabled"; + private static final String PULSE_ENABLED_PREF = "battery_light_pulse"; + private static final String BRIGHTNESS_PREFERENCE = "battery_light_brightness_level"; + private static final String BRIGHTNESS_ZEN_PREFERENCE = "battery_light_brightness_level_zen"; + + private ApplicationLightPreference mLowColorPref; + private ApplicationLightPreference mMediumColorPref; + private ApplicationLightPreference mFullColorPref; + private SystemSettingMainSwitchPreference mLightEnabledPref; + private SystemSettingSwitchPreference mLightFullChargeDisabledPref; + private SystemSettingSwitchPreference mPulseEnabledPref; + private BatteryBrightnessPreference mBatteryBrightnessPref; + private BatteryBrightnessZenPreference mBatteryBrightnessZenPref; + private int mDefaultLowColor; + private int mDefaultMediumColor; + private int mDefaultFullColor; + private boolean mMultiColorLed; + + private static final int MENU_RESET = Menu.FIRST; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + final Context context = requireContext(); + final Resources res = getResources(); + + // Collect battery led capabilities. + mMultiColorLed = + LightsCapabilities.supports(context, LightsCapabilities.LIGHTS_RGB_BATTERY_LED); + // liblights supports brightness control + final boolean halAdjustableBrightness = LightsCapabilities.supports(context, + LightsCapabilities.LIGHTS_ADJUSTABLE_BATTERY_LED_BRIGHTNESS); + final boolean pulsatingLed = LightsCapabilities.supports(context, + LightsCapabilities.LIGHTS_PULSATING_LED); + final boolean segmentedBatteryLed = LightsCapabilities.supports(context, + LightsCapabilities.LIGHTS_SEGMENTED_BATTERY_LED); + + addPreferencesFromResource(R.xml.battery_light_settings); + requireActivity().getActionBar().setTitle(R.string.battery_light_title); + + PreferenceScreen prefSet = getPreferenceScreen(); + + PreferenceGroup generalPrefs = prefSet.findPreference(GENERAL_SECTION); + + mLightEnabledPref = prefSet.findPreference(LIGHT_ENABLED_PREF); + mLightFullChargeDisabledPref = prefSet.findPreference(LIGHT_FULL_CHARGE_DISABLED_PREF); + mPulseEnabledPref = prefSet.findPreference(PULSE_ENABLED_PREF); + mBatteryBrightnessPref = prefSet.findPreference(BRIGHTNESS_PREFERENCE); + mBatteryBrightnessZenPref = prefSet.findPreference(BRIGHTNESS_ZEN_PREFERENCE); + + mDefaultLowColor = res.getInteger( + com.android.internal.R.integer.config_notificationsBatteryLowARGB); + mDefaultMediumColor = res.getInteger( + com.android.internal.R.integer.config_notificationsBatteryMediumARGB); + mDefaultFullColor = res.getInteger( + com.android.internal.R.integer.config_notificationsBatteryFullARGB); + + int batteryBrightness = mBatteryBrightnessPref.getBrightnessSetting(); + + if (!pulsatingLed || segmentedBatteryLed) { + generalPrefs.removePreference(mPulseEnabledPref); + } + + if (mMultiColorLed) { + generalPrefs.removePreference(mLightFullChargeDisabledPref); + setHasOptionsMenu(true); + + // Low, Medium and full color preferences + mLowColorPref = prefSet.findPreference(LOW_COLOR_PREF); + mLowColorPref.setOnPreferenceChangeListener(this); + mLowColorPref.setDefaultValues(mDefaultLowColor, 0, 0); + mLowColorPref.setBrightness(batteryBrightness); + + mMediumColorPref = prefSet.findPreference(MEDIUM_COLOR_PREF); + mMediumColorPref.setOnPreferenceChangeListener(this); + mMediumColorPref.setDefaultValues(mDefaultMediumColor, 0, 0); + mMediumColorPref.setBrightness(batteryBrightness); + + mFullColorPref = prefSet.findPreference(FULL_COLOR_PREF); + mFullColorPref.setOnPreferenceChangeListener(this); + mFullColorPref.setDefaultValues(mDefaultFullColor, 0, 0); + mFullColorPref.setBrightness(batteryBrightness); + + final BrightnessPreference.OnBrightnessChangedListener brightnessListener = + brightness -> { + mLowColorPref.setBrightness(brightness); + mMediumColorPref.setBrightness(brightness); + mFullColorPref.setBrightness(brightness); + }; + mBatteryBrightnessPref.setOnBrightnessChangedListener(brightnessListener); + } else { + prefSet.removePreference(prefSet.findPreference(COLORS_SECTION)); + resetColors(); + } + + // Remove battery LED brightness controls if we can't support them. + if (!mMultiColorLed && !halAdjustableBrightness) { + prefSet.removePreference(prefSet.findPreference(BRIGHTNESS_SECTION)); + } + } + + @Override + public void onResume() { + super.onResume(); + refreshColors(); + } + + private void refreshColors() { + ContentResolver resolver = requireActivity().getContentResolver(); + + if (mLowColorPref != null) { + int lowColor = Settings.System.getInt(resolver, + Settings.System.BATTERY_LIGHT_LOW_COLOR, mDefaultLowColor); + mLowColorPref.setAllValues(lowColor, 0, 0, false); + } + + if (mMediumColorPref != null) { + int mediumColor = Settings.System.getInt(resolver, + Settings.System.BATTERY_LIGHT_MEDIUM_COLOR, mDefaultMediumColor); + mMediumColorPref.setAllValues(mediumColor, 0, 0, false); + } + + if (mFullColorPref != null) { + int fullColor = Settings.System.getInt(resolver, + Settings.System.BATTERY_LIGHT_FULL_COLOR, mDefaultFullColor); + mFullColorPref.setAllValues(fullColor, 0, 0, false); + updateBrightnessPrefColor(fullColor); + } + } + + private void updateBrightnessPrefColor(int color) { + // If the user has selected no light (ie black) for + // full charge, use white for the brightness preference. + if (color == 0) { + color = 0xFFFFFF; + } + mBatteryBrightnessPref.setLedColor(color); + mBatteryBrightnessZenPref.setLedColor(color); + } + + /** + * Updates the default or application specific notification settings. + * + * @param key of the specific setting to update + */ + protected void updateValues(String key, Integer color) { + ContentResolver resolver = requireActivity().getContentResolver(); + switch (key) { + case LOW_COLOR_PREF: + Settings.System.putInt(resolver, + Settings.System.BATTERY_LIGHT_LOW_COLOR, color); + break; + case MEDIUM_COLOR_PREF: + Settings.System.putInt(resolver, + Settings.System.BATTERY_LIGHT_MEDIUM_COLOR, color); + break; + case FULL_COLOR_PREF: + Settings.System.putInt(resolver, + Settings.System.BATTERY_LIGHT_FULL_COLOR, color); + updateBrightnessPrefColor(color); + break; + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if (mMultiColorLed) { + menu.add(0, MENU_RESET, 0, R.string.reset) + .setIcon(R.drawable.ic_settings_backup_restore) + .setAlphabeticShortcut('r') + .setShowAsActionFlags( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_RESET: + resetToDefaults(); + return true; + } + return false; + } + + protected void resetColors() { + ContentResolver resolver = requireActivity().getContentResolver(); + + // Reset to the framework default colors + Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_LOW_COLOR, + mDefaultLowColor); + Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_MEDIUM_COLOR, + mDefaultMediumColor); + Settings.System.putInt(resolver, Settings.System.BATTERY_LIGHT_FULL_COLOR, + mDefaultFullColor); + refreshColors(); + } + + protected void resetToDefaults() { + final Resources res = getResources(); + final boolean batteryLightEnabled = res.getBoolean(R.bool.def_battery_light_enabled); + final boolean batteryLightFullChargeDisabled = + res.getBoolean(R.bool.def_battery_light_full_charge_disabled); + final boolean batteryLightPulseEnabled = res.getBoolean(R.bool.def_battery_light_pulse); + + if (mLightEnabledPref != null) mLightEnabledPref.setChecked(batteryLightEnabled); + if (mLightFullChargeDisabledPref != null) { + mLightFullChargeDisabledPref.setChecked(batteryLightFullChargeDisabled); + } + if (mPulseEnabledPref != null) mPulseEnabledPref.setChecked(batteryLightPulseEnabled); + + resetColors(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object objValue) { + ApplicationLightPreference lightPref = (ApplicationLightPreference) preference; + updateValues(lightPref.getKey(), lightPref.getColor()); + return true; + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.battery_light_settings) { + + @Override + public List getNonIndexableKeys(Context context) { + final List result = new ArrayList<>(); + + if (!LightsCapabilities.supports(context, LightsCapabilities.LIGHTS_BATTERY_LED)) { + result.add(KEY_BATTERY_LIGHTS); + result.add(LIGHT_ENABLED_PREF); + result.add(GENERAL_SECTION); + result.add(LIGHT_FULL_CHARGE_DISABLED_PREF); + result.add(COLORS_SECTION); + result.add(LOW_COLOR_PREF); + result.add(MEDIUM_COLOR_PREF); + result.add(FULL_COLOR_PREF); + } else if (LightsCapabilities.supports(context, + LightsCapabilities.LIGHTS_RGB_BATTERY_LED)) { + result.add(LIGHT_FULL_CHARGE_DISABLED_PREF); + } else { + result.add(COLORS_SECTION); + result.add(LOW_COLOR_PREF); + result.add(MEDIUM_COLOR_PREF); + result.add(FULL_COLOR_PREF); + } + if (!LightsCapabilities.supports(context, + LightsCapabilities.LIGHTS_ADJUSTABLE_BATTERY_LED_BRIGHTNESS)) { + result.add(BRIGHTNESS_SECTION); + result.add(BRIGHTNESS_PREFERENCE); + result.add(BRIGHTNESS_ZEN_PREFERENCE); + } + if (!LightsCapabilities.supports(context, LightsCapabilities.LIGHTS_PULSATING_LED) || + LightsCapabilities.supports(context, + LightsCapabilities.LIGHTS_SEGMENTED_BATTERY_LED)) { + result.add(PULSE_ENABLED_PREF); + } + return result; + } + }; +} diff --git a/src/com/android/settings/derp/notificationlight/BrightnessPreference.java b/src/com/android/settings/derp/notificationlight/BrightnessPreference.java new file mode 100644 index 00000000000..e3b3bb9eead --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/BrightnessPreference.java @@ -0,0 +1,290 @@ +/* + * SPDX-FileCopyrightText: 2017-2023 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.notificationlight; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; +import androidx.preference.PreferenceViewHolder; + +import com.android.internal.derp.notification.LineageNotification; + +import com.android.settings.R; +import com.android.settings.derp.preference.CustomDialogPreference; + +public class BrightnessPreference extends CustomDialogPreference + implements SeekBar.OnSeekBarChangeListener { + + private static final String TAG = "BrightnessPreference"; + + public static final int LIGHT_BRIGHTNESS_MINIMUM = 1; + public static final int LIGHT_BRIGHTNESS_MAXIMUM = 255; + + // Minimum delay between LED notification updates + private final static long LED_UPDATE_DELAY_MS = 250; + + // Default led color used to illustrate brightness + private final static int DEFAULT_LED_COLOR = 0xFFFFFF; + + private TextView mPreferencePercent; + + private TextView mDialogPercent; + private SeekBar mBrightnessBar; + + // The user selected brightness level (past or present if dialog is OKed). + private int mSelectedBrightness; + // Current position of brightness seekbar + private int mSeekBarBrightness; + // LED brightness currently on display (0 means notification is not showing) + private int mVisibleLedBrightness; + // LED color used to illustrate brightness + private int mLedColor = DEFAULT_LED_COLOR; + + private final Context mContext; + + private final Notification.Builder mNotificationBuilder; + private final NotificationManager mNotificationManager; + + public interface OnBrightnessChangedListener { + void onBrightnessChanged(int brightness); + } + + private OnBrightnessChangedListener mListener; + + public BrightnessPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + setWidgetLayoutResource(R.layout.preference_brightness); + setDialogLayoutResource(R.layout.dialog_brightness); + + mContext = context; + + mNotificationManager = context.getSystemService(NotificationManager.class); + + // Force lights on when screen is on and also force maximum brightness. + Bundle bundle = new Bundle(); + bundle.putBoolean(LineageNotification.EXTRA_FORCE_SHOW_LIGHTS, true); + bundle.putInt(LineageNotification.EXTRA_FORCE_LIGHT_BRIGHTNESS, LIGHT_BRIGHTNESS_MAXIMUM); + + mNotificationBuilder = new Notification.Builder(mContext); + mNotificationBuilder.setExtras(bundle) + .setContentTitle(mContext.getString(R.string.led_notification_title)) + .setContentText(mContext.getString(R.string.led_notification_text)) + .setSmallIcon(R.drawable.ic_settings_24dp) + .setOngoing(true); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + + mSelectedBrightness = getBrightnessSetting(); + mSeekBarBrightness = mSelectedBrightness; + + mPreferencePercent = (TextView) holder.findViewById(R.id.brightness_percent); + mPreferencePercent.setText(percentString(mSelectedBrightness, LIGHT_BRIGHTNESS_MINIMUM, + LIGHT_BRIGHTNESS_MAXIMUM)); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder, + DialogInterface.OnClickListener listener) { + super.onPrepareDialogBuilder(builder, listener); + builder.setNeutralButton(R.string.reset, null); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.dlg_ok, null); + } + + @Override + protected boolean onDismissDialog(AlertDialog dialog, int which) { + if (which == DialogInterface.BUTTON_NEUTRAL) { + // Reset brightness to default (max). + mBrightnessBar.setProgress(LIGHT_BRIGHTNESS_MAXIMUM); + return false; + } + return true; + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + + if (positiveResult) { + mSelectedBrightness = mSeekBarBrightness; + setBrightnessSetting(mSelectedBrightness); + if (mListener != null) { + mListener.onBrightnessChanged(mSelectedBrightness); + } + mPreferencePercent.setText(percentString(mSelectedBrightness, + LIGHT_BRIGHTNESS_MINIMUM, LIGHT_BRIGHTNESS_MAXIMUM)); + } else { + mSeekBarBrightness = mSelectedBrightness; + } + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + // Locate text view for percentage value + mDialogPercent = view.findViewById(R.id.brightness_percent); + + mVisibleLedBrightness = 0; // LED notification is not showing. + + mBrightnessBar = view.findViewById(R.id.brightness_seekbar); + mBrightnessBar.setMax(LIGHT_BRIGHTNESS_MAXIMUM); + mBrightnessBar.setMin(LIGHT_BRIGHTNESS_MINIMUM); + mBrightnessBar.setOnSeekBarChangeListener(this); + mBrightnessBar.setProgress(mSeekBarBrightness); + mBrightnessBar.setThumb(mContext.getResources().getDrawable( + R.drawable.ic_brightness_thumb, mContext.getTheme())); + } + + @Override + protected void onResume() { + updateNotification(); + } + + @Override + protected void onPause() { + cancelNotification(); + } + + @Override + public void onStartTrackingTouch (SeekBar seekBar) {} + + @Override + public void onStopTrackingTouch (SeekBar seekBar) {} + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mSeekBarBrightness = progress; + updateNotification(); + mDialogPercent.setText(percentString(progress, seekBar.getMin(), seekBar.getMax())); + } + + public void setOnBrightnessChangedListener(OnBrightnessChangedListener listener) { + mListener = listener; + } + + protected int getBrightnessSetting() { + // Null implementation + return LIGHT_BRIGHTNESS_MAXIMUM; + } + + protected void setBrightnessSetting(int brightness) { + // Null implementation + } + + public void setLedColor(int color) { + mLedColor = color & 0xFFFFFF; + } + + private void updateNotification() { + // Exit if there's nothing to do or we're throttled. + if (mVisibleLedBrightness == mSeekBarBrightness || mLedHandler.hasMessages(0)) { + return; + } + + mLedHandler.sendEmptyMessageDelayed(0, LED_UPDATE_DELAY_MS); + + // Instead of canceling the notification, force it to update with the color. + // Use a white light for a better preview of the brightness. + int notificationColor = mLedColor | (mSeekBarBrightness << 24); + mNotificationBuilder.setLights(notificationColor, 1, 0); + mNotificationManager.notify(1, mNotificationBuilder.build()); + mVisibleLedBrightness = mSeekBarBrightness; + } + + private void cancelNotification() { + if (mVisibleLedBrightness > 0) { + mLedHandler.removeMessages(0); + mNotificationManager.cancel(1); + mVisibleLedBrightness = 0; + } + } + + private final Handler mLedHandler = new Handler(Looper.getMainLooper()) { + public void handleMessage(Message msg) { + updateNotification(); + } + }; + + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + if (getDialog() == null || !getDialog().isShowing()) { + return superState; + } + + // Save the dialog state + final SavedState myState = new SavedState(superState); + myState.seekBarBrightness = mSeekBarBrightness; + + return myState; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state == null || !state.getClass().equals(SavedState.class)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; + } + + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + + mSeekBarBrightness = myState.seekBarBrightness; + } + + private String percentString(int progress, int min, int max) { + final float percentage = 100f * (progress - min ) / (max - min); + return String.format("%d%%", Math.round(percentage)); + } + + private static class SavedState extends BaseSavedState { + int seekBarBrightness; + + public SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + seekBarBrightness = source.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(seekBarBrightness); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator<>() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/src/com/android/settings/derp/notificationlight/ColorPanelView.java b/src/com/android/settings/derp/notificationlight/ColorPanelView.java new file mode 100644 index 00000000000..f4f2fc59b14 --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/ColorPanelView.java @@ -0,0 +1,154 @@ +/* + * SPDX-FileCopyrightText: 2010 Daniel Nilsson + * SPDX-FileCopyrightText: 2012 The CyanogenMod Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.notificationlight; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.View; + +/** + * This class draws a panel which which will be filled with a color which can be + * set. It can be used to show the currently selected color which you will get + * from the {@link ColorPickerView}. + * + * @author Daniel Nilsson + */ +public class ColorPanelView extends View { + + /** + * The width in pixels of the border surrounding the color panel. + */ + private final static float BORDER_WIDTH_PX = 1; + + private static float mDensity = 1f; + + private int mBorderColor = 0xff6E6E6E; + private int mColor = 0xff000000; + + private Paint mBorderPaint; + private Paint mColorPaint; + + private RectF mDrawingRect; + private RectF mColorRect; + + private AlphaPatternDrawable mAlphaPattern; + + public ColorPanelView(Context context) { + this(context, null); + } + + public ColorPanelView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ColorPanelView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + init(); + } + + private void init() { + mBorderPaint = new Paint(); + mColorPaint = new Paint(); + mDensity = getContext().getResources().getDisplayMetrics().density; + } + + @Override + protected void onDraw(Canvas canvas) { + + final RectF rect = mColorRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(mDrawingRect, mBorderPaint); + } + + if (mAlphaPattern != null) { + mAlphaPattern.draw(canvas); + } + + mColorPaint.setColor(mColor); + + canvas.drawRect(rect, mColorPaint); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + setMeasuredDimension(width, height); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + mDrawingRect = new RectF(); + mDrawingRect.left = getPaddingLeft(); + mDrawingRect.right = w - getPaddingRight(); + mDrawingRect.top = getPaddingTop(); + mDrawingRect.bottom = h - getPaddingBottom(); + + setUpColorRect(); + + } + + private void setUpColorRect() { + final RectF dRect = mDrawingRect; + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX; + float right = dRect.right - BORDER_WIDTH_PX; + + mColorRect = new RectF(left, top, right, bottom); + + mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity)); + + mAlphaPattern.setBounds(Math.round(mColorRect.left), + Math.round(mColorRect.top), + Math.round(mColorRect.right), + Math.round(mColorRect.bottom)); + + } + + /** + * Set the color that should be shown by this view. + */ + public void setColor(int color) { + mColor = color; + invalidate(); + } + + /** + * Get the color currently show by this view. + */ + public int getColor() { + return mColor; + } + + /** + * Set the color of the border surrounding the panel. + */ + public void setBorderColor(int color) { + mBorderColor = color; + invalidate(); + } + + /** + * Get the color of the border surrounding the panel. + */ + public int getBorderColor() { + return mBorderColor; + } + +} diff --git a/src/com/android/settings/derp/notificationlight/ColorPickerView.java b/src/com/android/settings/derp/notificationlight/ColorPickerView.java new file mode 100644 index 00000000000..6f7e5fe4b18 --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/ColorPickerView.java @@ -0,0 +1,751 @@ +/* + * SPDX-FileCopyrightText: 2010 Daniel Nilsson + * SPDX-FileCopyrightText: 2012 The CyanogenMod Project + * SPDX-FileCopyrightText: 2022-2023 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.notificationlight; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ComposeShader; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.PorterDuff.Mode; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.Paint.Align; +import android.graphics.Paint.Style; +import android.graphics.Shader.TileMode; +import android.os.Build; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +/** + * Displays a color picker to the user and allow them to select a color. A + * slider for the alpha channel is also available. Enable it by setting + * setAlphaSliderVisible(boolean) to true. + * + * @author Daniel Nilsson + */ +public class ColorPickerView extends View { + + public interface OnColorChangedListener { + void onColorChanged(int color); + } + + private final static int PANEL_SAT_VAL = 0; + private final static int PANEL_HUE = 1; + private final static int PANEL_ALPHA = 2; + + /** + * The width in pixels of the border surrounding all color panels. + */ + private final static float BORDER_WIDTH_PX = 1; + + /** + * The width in dp of the hue panel. + */ + private float HUE_PANEL_WIDTH = 30f; + /** + * The height in dp of the alpha panel + */ + private float ALPHA_PANEL_HEIGHT = 20f; + /** + * The distance in dp between the different color panels. + */ + private float PANEL_SPACING = 10f; + /** + * The radius in dp of the color palette tracker circle. + */ + private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f; + /** + * The dp which the tracker of the hue or alpha panel will extend outside of + * its bounds. + */ + private float RECTANGLE_TRACKER_OFFSET = 2f; + + private static float mDensity = 1f; + + private OnColorChangedListener mListener; + + private Paint mSatValPaint; + private Paint mSatValTrackerPaint; + + private Paint mHuePaint; + private Paint mHueTrackerPaint; + + private Paint mAlphaPaint; + private Paint mAlphaTextPaint; + + private Paint mBorderPaint; + + private Shader mValShader; + private Shader mSatShader; + private Shader mHueShader; + private Shader mAlphaShader; + + private int mAlpha = 0xff; + private float mHue = 360f; + private float mSat = 0f; + private float mVal = 0f; + + private final static String ALPHA_SLIDER_TEXT = "Alpha"; + private final static int SLIDER_TRACKER_COLOR = 0xff1c1c1c; + private final static int BORDER_COLOR = 0xff6E6E6E; + private boolean mShowAlphaPanel = false; + + /* + * To remember which panel that has the "focus" when processing hardware + * button data. + */ + private int mLastTouchedPanel = PANEL_SAT_VAL; + + /** + * Offset from the edge we must have or else the finger tracker will get + * clipped when it is drawn outside of the view. + */ + private float mDrawingOffset; + + /* + * Distance form the edges of the view of where we are allowed to draw. + */ + private RectF mDrawingRect; + + private RectF mSatValRect; + private RectF mHueRect; + private RectF mAlphaRect; + + private AlphaPatternDrawable mAlphaPattern; + + private Point mStartTouchPoint = null; + + public ColorPickerView(Context context) { + this(context, null); + } + + public ColorPickerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ColorPickerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + mDensity = getContext().getResources().getDisplayMetrics().density; + PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity; + RECTANGLE_TRACKER_OFFSET *= mDensity; + HUE_PANEL_WIDTH *= mDensity; + ALPHA_PANEL_HEIGHT *= mDensity; + PANEL_SPACING = PANEL_SPACING * mDensity; + + mDrawingOffset = calculateRequiredOffset(); + initPaintTools(); + + // Needed for receiving track ball motion events. + setFocusableInTouchMode(true); + setFocusable(true); + setClickable(true); + } + + private void initPaintTools() { + mSatValPaint = new Paint(); + mSatValTrackerPaint = new Paint(); + mHuePaint = new Paint(); + mHueTrackerPaint = new Paint(); + mAlphaPaint = new Paint(); + mAlphaTextPaint = new Paint(); + mBorderPaint = new Paint(); + + mSatValTrackerPaint.setStyle(Style.STROKE); + mSatValTrackerPaint.setStrokeWidth(2f * mDensity); + mSatValTrackerPaint.setAntiAlias(true); + + mHueTrackerPaint.setColor(SLIDER_TRACKER_COLOR); + mHueTrackerPaint.setStyle(Style.STROKE); + mHueTrackerPaint.setStrokeWidth(2f * mDensity); + mHueTrackerPaint.setAntiAlias(true); + + mAlphaTextPaint.setColor(0xff1c1c1c); + mAlphaTextPaint.setTextSize(14f * mDensity); + mAlphaTextPaint.setAntiAlias(true); + mAlphaTextPaint.setTextAlign(Align.CENTER); + mAlphaTextPaint.setFakeBoldText(true); + } + + private float calculateRequiredOffset() { + float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET); + offset = Math.max(offset, BORDER_WIDTH_PX * mDensity); + + return offset * 1.5f; + } + + private int[] buildHueColorArray() { + int[] hue = new int[361]; + + int count = 0; + for (int i = hue.length - 1; i >= 0; i--, count++) { + hue[count] = Color.HSVToColor(new float[] { + i, 1f, 1f + }); + } + return hue; + } + + @Override + protected void onDraw(Canvas canvas) { + if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) { + return; + } + drawSatValPanel(canvas); + drawHuePanel(canvas); + drawAlphaPanel(canvas); + } + + private void drawSatValPanel(Canvas canvas) { + final RectF rect = mSatValRect; + int rgb = Color.HSVToColor(new float[] { + mHue, 1f, 1f + }); + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(BORDER_COLOR); + canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, mBorderPaint); + } + + // On Honeycomb+ we need to use software rendering to create the shader properly + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + + // Get the overlaying gradients ready and create the ComposeShader + if (mValShader == null) { + mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, + 0xffffffff, 0xff000000, TileMode.CLAMP); + } + mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, + 0xffffffff, rgb, TileMode.CLAMP); + ComposeShader mShader = new ComposeShader(mValShader, mSatShader, Mode.MULTIPLY); + mSatValPaint.setShader(mShader); + canvas.drawRect(rect, mSatValPaint); + + Point p = satValToPoint(mSat, mVal); + mSatValTrackerPaint.setColor(0xff000000); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - mDensity, mSatValTrackerPaint); + + mSatValTrackerPaint.setColor(0xffdddddd); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint); + } + + private void drawHuePanel(Canvas canvas) { + final RectF rect = mHueRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(BORDER_COLOR); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + mBorderPaint); + } + + if (mHueShader == null) { + mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, + buildHueColorArray(), null, TileMode.CLAMP); + mHuePaint.setShader(mHueShader); + } + + canvas.drawRect(rect, mHuePaint); + + float rectHeight = 4 * mDensity / 2; + + Point p = hueToPoint(mHue); + + RectF r = new RectF(); + r.left = rect.left - RECTANGLE_TRACKER_OFFSET; + r.right = rect.right + RECTANGLE_TRACKER_OFFSET; + r.top = p.y - rectHeight; + r.bottom = p.y + rectHeight; + + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + + } + + private void drawAlphaPanel(Canvas canvas) { + if (!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) { + return; + } + + final RectF rect = mAlphaRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(BORDER_COLOR); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + mBorderPaint); + } + + mAlphaPattern.draw(canvas); + + float[] hsv = new float[] { + mHue, mSat, mVal + }; + int color = Color.HSVToColor(hsv); + int acolor = Color.HSVToColor(0, hsv); + + mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, + color, acolor, TileMode.CLAMP); + + mAlphaPaint.setShader(mAlphaShader); + + canvas.drawRect(rect, mAlphaPaint); + + canvas.drawText(ALPHA_SLIDER_TEXT, rect.centerX(), rect.centerY() + 4 * mDensity, + mAlphaTextPaint); + + float rectWidth = 4 * mDensity / 2; + Point p = alphaToPoint(mAlpha); + + RectF r = new RectF(); + r.left = p.x - rectWidth; + r.right = p.x + rectWidth; + r.top = rect.top - RECTANGLE_TRACKER_OFFSET; + r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET; + + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + } + + private Point hueToPoint(float hue) { + final RectF rect = mHueRect; + final float height = rect.height(); + + Point p = new Point(); + p.y = (int) (height - (hue * height / 360f) + rect.top); + p.x = (int) rect.left; + return p; + } + + private Point satValToPoint(float sat, float val) { + + final RectF rect = mSatValRect; + final float height = rect.height(); + final float width = rect.width(); + + Point p = new Point(); + + p.x = (int) (sat * width + rect.left); + p.y = (int) ((1f - val) * height + rect.top); + + return p; + } + + private Point alphaToPoint(int alpha) { + final RectF rect = mAlphaRect; + final float width = rect.width(); + + Point p = new Point(); + p.x = (int) (width - (alpha * width / 0xff) + rect.left); + p.y = (int) rect.top; + return p; + } + + private float[] pointToSatVal(float x, float y) { + final RectF rect = mSatValRect; + float[] result = new float[2]; + float width = rect.width(); + float height = rect.height(); + + if (x < rect.left) { + x = 0f; + } else if (x > rect.right) { + x = width; + } else { + x = x - rect.left; + } + + if (y < rect.top) { + y = 0f; + } else if (y > rect.bottom) { + y = height; + } else { + y = y - rect.top; + } + + result[0] = 1.f / width * x; + result[1] = 1.f - (1.f / height * y); + return result; + } + + private float pointToHue(float y) { + final RectF rect = mHueRect; + float height = rect.height(); + + if (y < rect.top) { + y = 0f; + } else if (y > rect.bottom) { + y = height; + } else { + y = y - rect.top; + } + return 360f - (y * 360f / height); + } + + private int pointToAlpha(int x) { + final RectF rect = mAlphaRect; + final int width = (int) rect.width(); + + if (x < rect.left) { + x = 0; + } else if (x > rect.right) { + x = width; + } else { + x = x - (int) rect.left; + } + return 0xff - (x * 0xff / width); + } + + @Override + public boolean onTrackballEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + boolean update = false; + + if (event.getAction() == MotionEvent.ACTION_MOVE) { + switch (mLastTouchedPanel) { + case PANEL_SAT_VAL: + float sat, + val; + sat = mSat + x / 50f; + val = mVal - y / 50f; + if (sat < 0f) { + sat = 0f; + } else if (sat > 1f) { + sat = 1f; + } + + if (val < 0f) { + val = 0f; + } else if (val > 1f) { + val = 1f; + } + mSat = sat; + mVal = val; + update = true; + break; + case PANEL_HUE: + float hue = mHue - y * 10f; + if (hue < 0f) { + hue = 0f; + } else if (hue > 360f) { + hue = 360f; + } + mHue = hue; + update = true; + break; + case PANEL_ALPHA: + if (mShowAlphaPanel && mAlphaRect != null) { + int alpha = (int) (mAlpha - x * 10); + if (alpha < 0) { + alpha = 0; + } else if (alpha > 0xff) { + alpha = 0xff; + } + mAlpha = alpha; + update = true; + } + break; + } + } + + if (update) { + if (mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] { + mHue, mSat, mVal + })); + } + invalidate(); + return true; + } + return super.onTrackballEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean update = false; + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mStartTouchPoint = new Point((int) event.getX(), (int) event.getY()); + update = moveTrackersIfNeeded(event); + break; + case MotionEvent.ACTION_MOVE: + update = moveTrackersIfNeeded(event); + break; + case MotionEvent.ACTION_UP: + mStartTouchPoint = null; + update = moveTrackersIfNeeded(event); + break; + } + + if (update) { + requestFocus(); + if (mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] { + mHue, mSat, mVal + })); + } + invalidate(); + return true; + } + + return super.onTouchEvent(event); + } + + private boolean moveTrackersIfNeeded(MotionEvent event) { + + if (mStartTouchPoint == null) + return false; + + boolean update = false; + int startX = mStartTouchPoint.x; + int startY = mStartTouchPoint.y; + + if (mHueRect.contains(startX, startY)) { + mLastTouchedPanel = PANEL_HUE; + mHue = pointToHue(event.getY()); + update = true; + } else if (mSatValRect.contains(startX, startY)) { + mLastTouchedPanel = PANEL_SAT_VAL; + float[] result = pointToSatVal(event.getX(), event.getY()); + mSat = result[0]; + mVal = result[1]; + update = true; + } else if (mAlphaRect != null && mAlphaRect.contains(startX, startY)) { + mLastTouchedPanel = PANEL_ALPHA; + mAlpha = pointToAlpha((int) event.getX()); + update = true; + } + + return update; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = 0; + int height = 0; + + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + int widthAllowed = MeasureSpec.getSize(widthMeasureSpec); + int heightAllowed = MeasureSpec.getSize(heightMeasureSpec); + + widthAllowed = chooseWidth(widthMode, widthAllowed); + heightAllowed = chooseHeight(heightMode, heightAllowed); + + if (!mShowAlphaPanel) { + height = (int) (widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH); + + // If calculated height (based on the width) is more than the + // allowed height. + if (height > heightAllowed && heightMode != MeasureSpec.UNSPECIFIED) { + height = heightAllowed; + width = (int) (height + PANEL_SPACING + HUE_PANEL_WIDTH); + } else { + width = widthAllowed; + } + } else { + width = (int) (heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH); + + if (width > widthAllowed && widthMode != MeasureSpec.UNSPECIFIED) { + width = widthAllowed; + height = (int) (widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT); + } else { + height = heightAllowed; + } + } + setMeasuredDimension(width, height); + } + + private int chooseWidth(int mode, int size) { + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPrefferedWidth(); + } + } + + private int chooseHeight(int mode, int size) { + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPrefferedHeight(); + } + } + + private int getPrefferedWidth() { + int width = getPrefferedHeight(); + if (mShowAlphaPanel) { + width -= (PANEL_SPACING + ALPHA_PANEL_HEIGHT); + } + return (int) (width + HUE_PANEL_WIDTH + PANEL_SPACING); + } + + private int getPrefferedHeight() { + int height = (int) (200 * mDensity); + if (mShowAlphaPanel) { + height += PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + return height; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + mDrawingRect = new RectF(); + mDrawingRect.left = mDrawingOffset + getPaddingLeft(); + mDrawingRect.right = w - mDrawingOffset - getPaddingRight(); + mDrawingRect.top = mDrawingOffset + getPaddingTop(); + mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom(); + + setUpSatValRect(); + setUpHueRect(); + setUpAlphaRect(); + } + + private void setUpSatValRect() { + final RectF dRect = mDrawingRect; + float panelSide = dRect.height() - BORDER_WIDTH_PX * 2; + + if (mShowAlphaPanel) { + panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = top + panelSide; + float right = left + panelSide; + mSatValRect = new RectF(left, top, right, bottom); + } + + private void setUpHueRect() { + final RectF dRect = mDrawingRect; + + float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX; + float top = dRect.top + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX + - (mShowAlphaPanel ? (PANEL_SPACING + ALPHA_PANEL_HEIGHT) : 0); + float right = dRect.right - BORDER_WIDTH_PX; + + mHueRect = new RectF(left, top, right, bottom); + } + + private void setUpAlphaRect() { + if (!mShowAlphaPanel) { + return; + } + + final RectF dRect = mDrawingRect; + float left = dRect.left + BORDER_WIDTH_PX; + float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX; + float bottom = dRect.bottom - BORDER_WIDTH_PX; + float right = dRect.right - BORDER_WIDTH_PX; + + mAlphaRect = new RectF(left, top, right, bottom); + mAlphaPattern = new AlphaPatternDrawable((int) (5 * mDensity)); + mAlphaPattern.setBounds(Math.round(mAlphaRect.left), Math + .round(mAlphaRect.top), Math.round(mAlphaRect.right), Math + .round(mAlphaRect.bottom)); + } + + /** + * Set a OnColorChangedListener to get notified when the color selected by + * the user has changed. + */ + public void setOnColorChangedListener(OnColorChangedListener listener) { + mListener = listener; + } + + /** + * Get the current color this view is showing. + * + * @return the current color. + */ + public int getColor() { + return Color.HSVToColor(mAlpha, new float[] { + mHue, mSat, mVal + }); + } + + /** + * Set the color the view should show. + * + * @param color The color that should be selected. + */ + public void setColor(int color) { + setColor(color, false); + } + + /** + * Set the color this view should show. + * + * @param color The color that should be selected. + * @param callback If you want to get a callback to your + * OnColorChangedListener. + */ + public void setColor(int color, boolean callback) { + int alpha = Color.alpha(color); + int red = Color.red(color); + int blue = Color.blue(color); + int green = Color.green(color); + float[] hsv = new float[3]; + + Color.RGBToHSV(red, green, blue, hsv); + mAlpha = alpha; + mHue = hsv[0]; + mSat = hsv[1]; + mVal = hsv[2]; + + if (callback && mListener != null) { + mListener.onColorChanged(Color.HSVToColor(mAlpha, new float[] { + mHue, mSat, mVal + })); + } + invalidate(); + } + + /** + * Set if the user is allowed to adjust the alpha panel. Default is false. + * If it is set to false no alpha will be set. + */ + public void setAlphaSliderVisible(boolean visible) { + if (mShowAlphaPanel != visible) { + mShowAlphaPanel = visible; + + /* + * Reset all shader to force a recreation. Otherwise they will not + * look right after the size of the view has changed. + */ + mValShader = null; + mSatShader = null; + mHueShader = null; + mAlphaShader = null; + requestLayout(); + } + + } + + public boolean isAlphaSliderVisible() { + return mShowAlphaPanel; + } +} diff --git a/src/com/android/settings/derp/notificationlight/LightSettingsDialog.java b/src/com/android/settings/derp/notificationlight/LightSettingsDialog.java new file mode 100644 index 00000000000..1a0eaf5f0d3 --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/LightSettingsDialog.java @@ -0,0 +1,457 @@ +/* + * SPDX-FileCopyrightText: 2010 Daniel Nilsson + * SPDX-FileCopyrightText: 2012 The CyanogenMod Project + * SPDX-FileCopyrightText: 2017-2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.notificationlight; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.text.Editable; +import android.text.TextWatcher; +import android.text.InputFilter; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnFocusChangeListener; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.SpinnerAdapter; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; + +import com.android.internal.derp.notification.LedValues; +import com.android.internal.derp.notification.LightsCapabilities; +import com.android.internal.derp.notification.LineageNotification; + +import com.android.settings.R; + +import java.util.ArrayList; +import java.util.Locale; + +public class LightSettingsDialog extends AlertDialog implements + ColorPickerView.OnColorChangedListener, TextWatcher, OnFocusChangeListener { + + private final static String STATE_KEY_COLOR = "LightSettingsDialog:color"; + // Minimum delay between LED notification updates + private final static long LED_UPDATE_DELAY_MS = 250; + + private ColorPickerView mColorPicker; + + private EditText mHexColorInput; + private ColorPanelView mNewColor; + private PulseSpeedAdapter mPulseSpeedAdapterOn; + private PulseSpeedAdapter mPulseSpeedAdapterOff; + private Spinner mPulseSpeedOn; + private Spinner mPulseSpeedOff; + private LayoutInflater mInflater; + + private NotificationManager mNotificationManager; + + private boolean mReadyForLed; + private int mLedLastColor; + private int mLedLastSpeedOn; + private int mLedLastSpeedOff; + + private int mLedBrightness; + private int mLedLastBrightness; + + private Context mContext; + + protected LightSettingsDialog(Context context, int initialColor, int initialSpeedOn, + int initialSpeedOff) { + super(context); + + init(context, initialColor, initialSpeedOn, initialSpeedOff, true, 0); + } + + protected LightSettingsDialog(Context context, int initialColor, int initialSpeedOn, + int initialSpeedOff, boolean onOffChangeable, int brightness) { + super(context); + + init(context, initialColor, initialSpeedOn, initialSpeedOff, onOffChangeable, brightness); + } + + private void init(Context context, int color, int speedOn, int speedOff, + boolean onOffChangeable, int brightness) { + mContext = context; + mNotificationManager = mContext.getSystemService(NotificationManager.class); + + mReadyForLed = false; + mLedLastColor = 0; + + // To fight color banding. + getWindow().setFormat(PixelFormat.RGBA_8888); + setUp(color, speedOn, speedOff, onOffChangeable, brightness); + } + + /** + * This function sets up the dialog with the proper values. If the speedOff parameters + * has a -1 value disable both spinners + * + * @param color - the color to set + * @param speedOn - the flash time in ms + * @param speedOff - the flash length in ms + */ + private void setUp(int color, int speedOn, int speedOff, boolean onOffChangeable, + int brightness) { + mInflater = mContext.getSystemService(LayoutInflater.class); + View layout = mInflater.inflate(R.layout.dialog_light_settings, null); + + mColorPicker = layout.findViewById(R.id.color_picker_view); + mHexColorInput = layout.findViewById(R.id.hex_color_input); + mNewColor = layout.findViewById(R.id.color_panel); + mPulseSpeedOn = layout.findViewById(R.id.on_spinner); + mPulseSpeedOff = layout.findViewById(R.id.off_spinner); + mColorPicker.setOnColorChangedListener(this); + mColorPicker.setColor(color, true); + + mHexColorInput.setOnFocusChangeListener(this); + + if (onOffChangeable) { + mPulseSpeedAdapterOn = new PulseSpeedAdapter( + R.array.notification_pulse_length_entries, + R.array.notification_pulse_length_values, + speedOn); + mPulseSpeedOn.setAdapter(mPulseSpeedAdapterOn); + mPulseSpeedOn.setSelection(mPulseSpeedAdapterOn.getTimePosition(speedOn)); + mPulseSpeedOn.setOnItemSelectedListener(mPulseSelectionListener); + + mPulseSpeedAdapterOff = new PulseSpeedAdapter(R.array.notification_pulse_speed_entries, + R.array.notification_pulse_speed_values, + speedOff); + mPulseSpeedOff.setAdapter(mPulseSpeedAdapterOff); + mPulseSpeedOff.setSelection(mPulseSpeedAdapterOff.getTimePosition(speedOff)); + mPulseSpeedOff.setOnItemSelectedListener(mPulseSelectionListener); + } else { + View speedSettingsGroup = layout.findViewById(R.id.speed_title_view); + speedSettingsGroup.setVisibility(View.GONE); + } + + mPulseSpeedOn.setEnabled(onOffChangeable); + mPulseSpeedOff.setEnabled((speedOn != 1) && onOffChangeable); + + setView(layout); + setTitle(R.string.edit_light_settings); + + if (!LightsCapabilities.supports( + mContext, LightsCapabilities.LIGHTS_RGB_NOTIFICATION_LED)) { + mColorPicker.setVisibility(View.GONE); + LinearLayout colorPanel = layout.findViewById(R.id.color_panel_view); + colorPanel.setVisibility(View.GONE); + View lightsDialogDivider = layout.findViewById(R.id.lights_dialog_divider); + lightsDialogDivider.setVisibility(View.GONE); + } + + mLedBrightness = brightness; + mLedLastBrightness = -1; // out of range + + mReadyForLed = true; + updateLed(); + } + + private final AdapterView.OnItemSelectedListener mPulseSelectionListener = + new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (parent == mPulseSpeedOn) { + mPulseSpeedOff.setEnabled(mPulseSpeedOn.isEnabled() && getPulseSpeedOn() != 1); + } + updateLed(); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }; + + @Override + public Bundle onSaveInstanceState() { + Bundle state = super.onSaveInstanceState(); + state.putInt(STATE_KEY_COLOR, getColor()); + return state; + } + + @Override + public void onRestoreInstanceState(Bundle state) { + super.onRestoreInstanceState(state); + mColorPicker.setColor(state.getInt(STATE_KEY_COLOR), true); + } + + public void onPause() { + dismissLed(); + } + + public void onResume() { + updateLed(); + } + + @Override + public void onColorChanged(int color) { + final boolean hasAlpha = mColorPicker.isAlphaSliderVisible(); + final String format = hasAlpha ? "%08x" : "%06x"; + final int mask = hasAlpha ? 0xFFFFFFFF : 0x00FFFFFF; + + mNewColor.setColor(color); + mHexColorInput.setText(String.format(Locale.US, format, color & mask)); + + updateLed(); + } + + public void setAlphaSliderVisible(boolean visible) { + mHexColorInput.setFilters(new InputFilter[] { + new InputFilter.LengthFilter(visible ? 8 : 6) } ); + mColorPicker.setAlphaSliderVisible(visible); + } + + public int getColor() { + return mColorPicker.getColor(); + } + + public void setColor(int color) { + mColorPicker.setColor(color, true); + } + + @SuppressWarnings("unchecked") + public int getPulseSpeedOn() { + if (mPulseSpeedOn.isEnabled()) { + return ((Pair) mPulseSpeedOn.getSelectedItem()).second; + } else { + return 1; + } + } + + public void setPulseSpeedOn(int speedOn) { + mPulseSpeedOn.setSelection(mPulseSpeedAdapterOn.getTimePosition(speedOn)); + } + + @SuppressWarnings("unchecked") + public int getPulseSpeedOff() { + // return 0 if 'Always on' is selected + return getPulseSpeedOn() == 1 + ? 0 + : ((Pair) mPulseSpeedOff.getSelectedItem()).second; + } + + public void setPulseSpeedOff(int speedOff) { + mPulseSpeedOff.setSelection(mPulseSpeedAdapterOff.getTimePosition(speedOff)); + } + + private final Handler mLedHandler = new Handler(Looper.getMainLooper()) { + public void handleMessage(Message msg) { + updateLed(); + } + }; + + private void updateLed() { + if (!mReadyForLed) { + return; + } + + final int color = getColor() & 0xFFFFFF; + final int speedOn, speedOff; + if (mPulseSpeedOn.isEnabled()) { + speedOn = getPulseSpeedOn(); + speedOff = getPulseSpeedOff(); + } else { + speedOn = 1; + speedOff = 0; + } + + if (mLedLastColor == color && mLedLastSpeedOn == speedOn && mLedLastSpeedOff == speedOff + && mLedLastBrightness == mLedBrightness) { + return; + } + + // Dampen rate of consecutive LED changes + if (mLedHandler.hasMessages(0)) { + return; + } + mLedHandler.sendEmptyMessageDelayed(0, LED_UPDATE_DELAY_MS); + + // Set a notification to display the LED color + final Bundle b = new Bundle(); + b.putBoolean(LineageNotification.EXTRA_FORCE_SHOW_LIGHTS, true); + if (mLedBrightness > 0 && mLedBrightness < LedValues.LIGHT_BRIGHTNESS_MAXIMUM) { + b.putInt(LineageNotification.EXTRA_FORCE_LIGHT_BRIGHTNESS, mLedBrightness); + } + b.putInt(LineageNotification.EXTRA_FORCE_COLOR, color); + b.putInt(LineageNotification.EXTRA_FORCE_LIGHT_ON_MS, speedOn); + b.putInt(LineageNotification.EXTRA_FORCE_LIGHT_OFF_MS, speedOff); + + createNotificationChannel(); + + final String channelId = mContext.getString(R.string.channel_light_settings_id); + final Notification.Builder builder = new Notification.Builder(mContext, channelId); + builder.setLights(color, speedOn, speedOff); + builder.setExtras(b); + builder.setSmallIcon(R.drawable.ic_settings_24dp); + builder.setContentTitle(mContext.getString(R.string.led_notification_title)); + builder.setContentText(mContext.getString(R.string.led_notification_text)); + builder.setOngoing(true); + + final Notification notification = builder.build(); + mNotificationManager.notify(channelId, 1, notification); + + mLedLastColor = color; + mLedLastSpeedOn = speedOn; + mLedLastSpeedOff = speedOff; + mLedLastBrightness = mLedBrightness; + } + + public void dismissLed() { + final String channelId = mContext.getString(R.string.channel_light_settings_id); + mNotificationManager.cancel(channelId, 1); + // ensure we later reset LED if dialog is + // hidden and then made visible + mLedLastColor = 0; + } + + private void createNotificationChannel() { + final String channelId = mContext.getString(R.string.channel_light_settings_id); + final String channelName = mContext.getString(R.string.channel_light_settings_name); + final NotificationChannel notificationChannel = new NotificationChannel( + channelId, channelName, NotificationManager.IMPORTANCE_LOW); + notificationChannel.enableLights(true); + notificationChannel.enableVibration(false); + notificationChannel.setShowBadge(false); + + mNotificationManager.createNotificationChannel(notificationChannel); + } + + class PulseSpeedAdapter extends BaseAdapter implements SpinnerAdapter { + private final ArrayList> times; + + public PulseSpeedAdapter(int timeNamesResource, int timeValuesResource) { + times = new ArrayList<>(); + + String[] time_names = mContext.getResources().getStringArray(timeNamesResource); + String[] time_values = mContext.getResources().getStringArray(timeValuesResource); + + for(int i = 0; i < time_values.length; ++i) { + times.add(new Pair<>(time_names[i], Integer.decode(time_values[i]))); + } + + } + + /** + * This constructor apart from taking a usual time entry array takes the + * currently configured time value which might cause the addition of a + * "Custom" time entry in the spinner in case this time value does not + * match any of the predefined ones in the array. + * + * @param timeNamesResource The time entry names array + * @param timeValuesResource The time entry values array + * @param customTime Current time value that might be one of the + * predefined values or a totally custom value + */ + public PulseSpeedAdapter(int timeNamesResource, int timeValuesResource, + Integer customTime) { + this(timeNamesResource, timeValuesResource); + + // Check if we also need to add the custom value entry + if (getTimePosition(customTime) == -1) { + times.add(new Pair<>(mContext.getResources() + .getString(R.string.custom_time), customTime)); + } + } + + /** + * Will return the position of the spinner entry with the specified + * time. Returns -1 if there is no such entry. + * + * @param time Time in ms + * @return Position of entry with given time or -1 if not found. + */ + public int getTimePosition(Integer time) { + for (int position = 0; position < getCount(); ++position) { + if (getItem(position).second.equals(time)) { + return position; + } + } + + return -1; + } + + @Override + public int getCount() { + return times.size(); + } + + @Override + public Pair getItem(int position) { + return times.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View view, ViewGroup parent) { + if (view == null) { + view = mInflater.inflate(R.layout.pulse_time_item, parent, false); + } + + Pair entry = getItem(position); + ((TextView) view.findViewById(R.id.textViewName)).setText(entry.first); + + return view; + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + String hexColor = mHexColorInput.getText().toString(); + if (!hexColor.isEmpty()) { + try { + int color = Color.parseColor('#' + hexColor); + if (!mColorPicker.isAlphaSliderVisible()) { + color |= 0xFF000000; // set opaque + } + mColorPicker.setColor(color); + mNewColor.setColor(color); + updateLed(); + } catch (IllegalArgumentException ex) { + // Number format is incorrect, ignore + } + } + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + mHexColorInput.removeTextChangedListener(this); + InputMethodManager inputMethodManager = + mContext.getSystemService(InputMethodManager.class); + inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0); + } else { + mHexColorInput.addTextChangedListener(this); + } + } +} diff --git a/src/com/android/settings/derp/notificationlight/NotificationBrightnessPreference.java b/src/com/android/settings/derp/notificationlight/NotificationBrightnessPreference.java new file mode 100644 index 00000000000..3bbb35b575f --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/NotificationBrightnessPreference.java @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2017-2022 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.notificationlight; + +import android.content.ContentResolver; +import android.content.Context; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.AttributeSet; + +public class NotificationBrightnessPreference extends BrightnessPreference { + private static final String TAG = "NotificationBrightnessPreference"; + + private final ContentResolver mResolver; + + public NotificationBrightnessPreference(Context context, AttributeSet attrs) { + super(context, attrs); + mResolver = context.getContentResolver(); + } + + @Override + protected int getBrightnessSetting() { + return Settings.System.getIntForUser(mResolver, + Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL, + LIGHT_BRIGHTNESS_MAXIMUM, UserHandle.USER_CURRENT); + } + + @Override + protected void setBrightnessSetting(int brightness) { + Settings.System.putIntForUser(mResolver, + Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL, + brightness, UserHandle.USER_CURRENT); + } +} diff --git a/src/com/android/settings/derp/notificationlight/NotificationBrightnessZenPreference.java b/src/com/android/settings/derp/notificationlight/NotificationBrightnessZenPreference.java new file mode 100644 index 00000000000..bf6140b59bf --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/NotificationBrightnessZenPreference.java @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2017-2022 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.notificationlight; + +import android.content.ContentResolver; +import android.content.Context; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.AttributeSet; + +public class NotificationBrightnessZenPreference extends BrightnessPreference { + private static final String TAG = "NotificationBrightnessZenPreference"; + + private final ContentResolver mResolver; + + public NotificationBrightnessZenPreference(Context context, AttributeSet attrs) { + super(context, attrs); + mResolver = context.getContentResolver(); + } + + @Override + protected int getBrightnessSetting() { + return Settings.System.getIntForUser(mResolver, + Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_ZEN, + LIGHT_BRIGHTNESS_MAXIMUM, UserHandle.USER_CURRENT); + } + + @Override + protected void setBrightnessSetting(int brightness) { + Settings.System.putIntForUser(mResolver, + Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_ZEN, + brightness, UserHandle.USER_CURRENT); + } +} diff --git a/src/com/android/settings/derp/notificationlight/NotificationLightPreferenceController.java b/src/com/android/settings/derp/notificationlight/NotificationLightPreferenceController.java new file mode 100644 index 00000000000..eac27700293 --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/NotificationLightPreferenceController.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The PixelExperience 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.derp.notificationlight; + +import android.content.Context; +import com.android.settings.core.BasePreferenceController; + +public class NotificationLightPreferenceController extends BasePreferenceController { + + public static final String KEY = "notification_lights"; + + private Context mContext; + + public NotificationLightPreferenceController(Context context, String key) { + super(context, key); + + mContext = context; + } + + public NotificationLightPreferenceController(Context context) { + this(context, KEY); + + mContext = context; + } + + @Override + public int getAvailabilityStatus() { + boolean exists = mContext.getResources().getBoolean(com.android.internal.R.bool.config_intrusiveNotificationLed); + return (exists ? AVAILABLE : UNSUPPORTED_ON_DEVICE); + } + +} diff --git a/src/com/android/settings/derp/notificationlight/NotificationLightSettings.java b/src/com/android/settings/derp/notificationlight/NotificationLightSettings.java new file mode 100644 index 00000000000..304c47068eb --- /dev/null +++ b/src/com/android/settings/derp/notificationlight/NotificationLightSettings.java @@ -0,0 +1,611 @@ +/* + * SPDX-FileCopyrightText: 2012 The CyanogenMod Project + * SPDX-FileCopyrightText: 2017-2024 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.notificationlight; + +import android.app.Dialog; +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.ArraySet; +import android.widget.ListView; + +import androidx.appcompat.app.AlertDialog; +import androidx.preference.Preference; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; + +import com.android.internal.derp.notification.LightsCapabilities; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.derp.ColorUtils; + +import com.android.settings.R; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.derp.widget.PackageListAdapter; +import com.android.settings.derp.widget.PackageListAdapter.PackageItem; +import com.android.settings.SettingsPreferenceFragment; + +import com.android.settingslib.search.SearchIndexable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.derpfest.support.preferences.SystemSettingSwitchPreference; +import org.derpfest.support.preferences.SystemSettingMainSwitchPreference; + +@SearchIndexable +public class NotificationLightSettings extends SettingsPreferenceFragment implements + ApplicationLightPreference.ItemLongClickListener, Preference.OnPreferenceChangeListener { + private static final String TAG = "NotificationLightSettings"; + + private static final String KEY_NOTIFICATION_LIGHTS = "notification_lights"; + private static final String NOTIFICATION_LIGHT_PULSE = + Settings.System.NOTIFICATION_LIGHT_PULSE; + private static final String NOTIFICATION_LIGHT_COLOR_AUTO = + Settings.System.NOTIFICATION_LIGHT_COLOR_AUTO; + private static final String NOTIFICATION_LIGHT_SCREEN_ON = + Settings.System.NOTIFICATION_LIGHT_SCREEN_ON; + private static final String NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE = + Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE; + private static final String NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL = + Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL; + private static final String NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_ZEN = + Settings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_ZEN; + + private static final String ADVANCED_SECTION = "advanced_section"; + private static final String APPLICATION_SECTION = "applications_list"; + private static final String BRIGHTNESS_SECTION = "brightness_section"; + private static final String GENERAL_SECTION = "general_section"; + private static final String PHONE_SECTION = "phone_list"; + + private static final String DEFAULT_PREF = "default"; + private static final String MISSED_CALL_PREF = "missed_call"; + private static final String VOICEMAIL_PREF = "voicemail"; + private static final String ADD_APPS = "custom_apps_add"; + private static final int DIALOG_APPS = 0; + + private int mDefaultColor; + private int mDefaultLedOn; + private int mDefaultLedOff; + private PackageManager mPackageManager; + private PreferenceGroup mApplicationPrefList; + private SystemSettingMainSwitchPreference mEnabledPref; + private SystemSettingSwitchPreference mCustomEnabledPref; + private SystemSettingSwitchPreference mScreenOnLightsPref; + private SystemSettingSwitchPreference mAutoGenerateColors; + private ApplicationLightPreference mDefaultPref; + private ApplicationLightPreference mCallPref; + private ApplicationLightPreference mVoicemailPref; + private PackageListAdapter mPackageAdapter; + private String mPackageList; + private Map mPackages; + // Supports rgb color control + private boolean mMultiColorLed; + // Supports adjustable pulse + private boolean mLedCanPulse; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + final Context context = requireContext(); + + addPreferencesFromResource(R.xml.notification_light_settings); + requireActivity().getActionBar().setTitle(R.string.notification_light_title); + + PreferenceScreen prefSet = getPreferenceScreen(); + Resources resources = getResources(); + + PreferenceGroup mAdvancedPrefs = prefSet.findPreference(ADVANCED_SECTION); + PreferenceGroup mGeneralPrefs = prefSet.findPreference(GENERAL_SECTION); + + // Get the system defined default notification color + mDefaultColor = resources.getColor( + com.android.internal.R.color.config_defaultNotificationColor, null); + + mDefaultLedOn = resources.getInteger( + com.android.internal.R.integer.config_defaultNotificationLedOn); + mDefaultLedOff = resources.getInteger( + com.android.internal.R.integer.config_defaultNotificationLedOff); + + // liblights supports brightness control + final boolean halAdjustableBrightness = LightsCapabilities.supports( + context, LightsCapabilities.LIGHTS_ADJUSTABLE_NOTIFICATION_LED_BRIGHTNESS); + mLedCanPulse = LightsCapabilities.supports( + context, LightsCapabilities.LIGHTS_PULSATING_LED); + mMultiColorLed = LightsCapabilities.supports( + context, LightsCapabilities.LIGHTS_RGB_NOTIFICATION_LED); + + mEnabledPref = findPreference(NOTIFICATION_LIGHT_PULSE); + mEnabledPref.setOnPreferenceChangeListener(this); + + mDefaultPref = findPreference(DEFAULT_PREF); + + mAutoGenerateColors = findPreference(NOTIFICATION_LIGHT_COLOR_AUTO); + + // Advanced light settings + mScreenOnLightsPref = findPreference(NOTIFICATION_LIGHT_SCREEN_ON); + mScreenOnLightsPref.setOnPreferenceChangeListener(this); + mCustomEnabledPref = findPreference(NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE); + if (!mMultiColorLed && !halAdjustableBrightness) { + removePreference(BRIGHTNESS_SECTION); + } + if (!mLedCanPulse && !mMultiColorLed) { + mGeneralPrefs.removePreference(mDefaultPref); + mAdvancedPrefs.removePreference(mCustomEnabledPref); + } else { + mCustomEnabledPref.setOnPreferenceChangeListener(this); + mDefaultPref.setOnPreferenceChangeListener(this); + mDefaultPref.setDefaultValues(mDefaultColor, mDefaultLedOn, mDefaultLedOff); + } + + // Missed call and Voicemail preferences should only show on devices with voice capabilities + TelephonyManager tm = getActivity().getSystemService(TelephonyManager.class); + if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE + || (!mLedCanPulse && !mMultiColorLed)) { + removePreference(PHONE_SECTION); + } else { + mCallPref = findPreference(MISSED_CALL_PREF); + mCallPref.setOnPreferenceChangeListener(this); + mCallPref.setDefaultValues(mDefaultColor, mDefaultLedOn, mDefaultLedOff); + + mVoicemailPref = findPreference(VOICEMAIL_PREF); + mVoicemailPref.setOnPreferenceChangeListener(this); + mVoicemailPref.setDefaultValues(mDefaultColor, mDefaultLedOn, mDefaultLedOff); + } + + if (!mLedCanPulse && !mMultiColorLed) { + removePreference(APPLICATION_SECTION); + } else { + mApplicationPrefList = findPreference(APPLICATION_SECTION); + mApplicationPrefList.setOrderingAsAdded(false); + + // Get launch-able applications + mPackageManager = getActivity().getPackageManager(); + mPackageAdapter = new PackageListAdapter(getActivity()); + + mPackages = new HashMap<>(); + + Preference addPreference = prefSet.findPreference(ADD_APPS); + addPreference.setOnPreferenceClickListener(preference -> { + showDialog(DIALOG_APPS); + return true; + }); + } + + if (!mMultiColorLed) { + resetColors(); + mGeneralPrefs.removePreference(mAutoGenerateColors); + } else { + mAutoGenerateColors.setOnPreferenceChangeListener(this); + } + } + + @Override + public void onResume() { + super.onResume(); + refreshDefault(); + refreshCustomApplicationPrefs(); + requireActivity().invalidateOptionsMenu(); + } + + private void refreshDefault() { + ContentResolver resolver = requireActivity().getContentResolver(); + int color = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultColor); + int timeOn = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, mDefaultLedOn); + int timeOff = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, mDefaultLedOff); + + mDefaultPref.setAllValues(color, timeOn, timeOff); + + // Get Missed call and Voicemail values + if (mCallPref != null) { + int callColor = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR, mDefaultColor); + int callTimeOn = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_ON, mDefaultLedOn); + int callTimeOff = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF, mDefaultLedOff); + + mCallPref.setAllValues(callColor, callTimeOn, callTimeOff); + } + + if (mVoicemailPref != null) { + int vmailColor = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, mDefaultColor); + int vmailTimeOn = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON, mDefaultLedOn); + int vmailTimeOff = Settings.System.getInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF, mDefaultLedOff); + + mVoicemailPref.setAllValues(vmailColor, vmailTimeOn, vmailTimeOff); + } + + if (mLedCanPulse || mMultiColorLed) { + mApplicationPrefList = findPreference(APPLICATION_SECTION); + mApplicationPrefList.setOrderingAsAdded(false); + } + } + + private void refreshCustomApplicationPrefs() { + Context context = getActivity(); + + if (!parsePackageList()) { + maybeDisplayApplicationHint(context); + return; + } + + // Add the Application Preferences + if (mApplicationPrefList != null) { + for (int i = 0; i < mApplicationPrefList.getPreferenceCount();) { + Preference pref = mApplicationPrefList.getPreference(i); + if (ADD_APPS.equals(pref.getKey())) { + i++; + continue; + } + + mApplicationPrefList.removePreference(pref); + } + + for (Package pkg : mPackages.values()) { + try { + PackageInfo info = mPackageManager.getPackageInfo(pkg.name, + PackageManager.PackageInfoFlags.of(PackageManager.GET_META_DATA)); + ApplicationLightPreference pref = + new ApplicationLightPreference(context, null, + pkg.color, pkg.timeon, pkg.timeoff); + + pref.setKey(pkg.name); + pref.setTitle(info.applicationInfo.loadLabel(mPackageManager)); + pref.setIcon(info.applicationInfo.loadIcon(mPackageManager)); + pref.setPersistent(false); + pref.setOnPreferenceChangeListener(this); + pref.setOnLongClickListener(this); + mApplicationPrefList.addPreference(pref); + } catch (NameNotFoundException e) { + // Do nothing + } + } + + maybeDisplayApplicationHint(context); + mPackageAdapter.setExcludedPackages(new HashSet<>(mPackages.keySet())); + } + } + + private void maybeDisplayApplicationHint(Context context) { + /* Display a pref explaining how to add apps */ + if (mApplicationPrefList != null && mApplicationPrefList.getPreferenceCount() == 1) { + String summary = getResources().getString( + R.string.notification_light_add_apps_empty_summary); + String useCustom = getResources().getString( + R.string.notification_light_use_custom); + Preference pref = new Preference(context); + pref.setSummary(String.format(summary, useCustom)); + pref.setEnabled(false); + mApplicationPrefList.addPreference(pref); + } + } + + private int getInitialColorForPackage(String packageName) { + boolean autoColor = Settings.System.getInt(getActivity().getContentResolver(), + NOTIFICATION_LIGHT_COLOR_AUTO, mMultiColorLed ? 1 : 0) == 1; + int color = mDefaultColor; + if (autoColor) { + try { + Drawable icon = mPackageManager.getApplicationIcon(packageName); + color = ColorUtils.generateAlertColorFromDrawable(icon); + } catch (NameNotFoundException e) { + // shouldn't happen, but just return default + } + } + return color; + } + + private void addCustomApplicationPref(String packageName) { + Package pkg = mPackages.get(packageName); + if (pkg == null) { + int color = getInitialColorForPackage(packageName); + pkg = new Package(packageName, color, mDefaultLedOn, mDefaultLedOff); + mPackages.put(packageName, pkg); + savePackageList(false); + refreshCustomApplicationPrefs(); + } + } + + private void removeCustomApplicationPref(String packageName) { + if (mPackages.remove(packageName) != null) { + savePackageList(false); + refreshCustomApplicationPrefs(); + } + } + + private boolean parsePackageList() { + final String baseString = Settings.System.getString( + getActivity().getContentResolver(), + Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES); + + if (TextUtils.equals(mPackageList, baseString)) { + return false; + } + + mPackageList = baseString; + mPackages.clear(); + + if (baseString != null) { + final String[] array = TextUtils.split(baseString, "\\|"); + for (String item : array) { + if (TextUtils.isEmpty(item)) { + continue; + } + Package pkg = Package.fromString(item); + if (pkg != null) { + mPackages.put(pkg.name, pkg); + } + } + } + + mPackageAdapter.setExcludedPackages(new HashSet<>(mPackages.keySet())); + + return true; + } + + private void savePackageList(boolean preferencesUpdated) { + List settings = new ArrayList<>(); + for (Package app : mPackages.values()) { + settings.add(app.toString()); + } + final String value = TextUtils.join("|", settings); + if (preferencesUpdated) { + mPackageList = value; + } + Settings.System.putString(getActivity().getContentResolver(), + Settings.System.NOTIFICATION_LIGHT_PULSE_CUSTOM_VALUES, value); + } + + /** + * Updates the default or package specific notification settings. + * + * @param packageName Package name of application specific settings to update + */ + protected void updateValues(String packageName, Integer color, Integer timeOn, + Integer timeOff) { + ContentResolver resolver = requireActivity().getContentResolver(); + + switch (packageName) { + case DEFAULT_PREF: + Settings.System.putInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, color); + Settings.System.putInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_ON, timeOn); + Settings.System.putInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_LED_OFF, timeOff); + refreshDefault(); + return; + case MISSED_CALL_PREF: + Settings.System.putInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR, color); + Settings.System.putInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_ON, timeOn); + Settings.System.putInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_LED_OFF, timeOff); + refreshDefault(); + return; + case VOICEMAIL_PREF: + Settings.System.putInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, color); + Settings.System.putInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_ON, timeOn); + Settings.System.putInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_LED_OFF, timeOff); + refreshDefault(); + return; + } + + // Find the custom package and sets its new values + Package app = mPackages.get(packageName); + if (app != null) { + app.color = color; + app.timeon = timeOn; + app.timeoff = timeOff; + savePackageList(true); + } + } + + protected void resetColors() { + ContentResolver resolver = getActivity().getContentResolver(); + + // Reset to the framework default colors + Settings.System.putInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_DEFAULT_COLOR, mDefaultColor); + Settings.System.putInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_CALL_COLOR, mDefaultColor); + Settings.System.putInt(resolver, + Settings.System.NOTIFICATION_LIGHT_PULSE_VMAIL_COLOR, mDefaultColor); + + refreshDefault(); + } + + public boolean onItemLongClick(final String key) { + if (mApplicationPrefList.findPreference(key) == null) { + return false; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()) + .setTitle(R.string.dialog_delete_title) + .setMessage(R.string.dialog_delete_message) + .setIconAttribute(android.R.attr.alertDialogIcon) + .setPositiveButton(android.R.string.ok, (dialog, which) -> + removeCustomApplicationPref(key) + ) + .setNegativeButton(android.R.string.cancel, null); + + builder.show(); + return true; + } + + public boolean onPreferenceChange(Preference preference, Object objValue) { + if (preference == mEnabledPref || preference == mCustomEnabledPref || + preference == mScreenOnLightsPref || + preference == mAutoGenerateColors) { + getActivity().invalidateOptionsMenu(); + } else { + ApplicationLightPreference lightPref = (ApplicationLightPreference) preference; + updateValues(lightPref.getKey(), lightPref.getColor(), + lightPref.getOnValue(), lightPref.getOffValue()); + } + + return true; + } + + /** + * Utility classes and supporting methods + */ + @Override + public Dialog onCreateDialog(int id) { + AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()); + final Dialog dialog; + switch (id) { + case DIALOG_APPS: + Resources res = getResources(); + int paddingTop = res.getDimensionPixelOffset(R.dimen.package_list_padding_top); + + final ListView list = new ListView(requireActivity()); + list.setAdapter(mPackageAdapter); + list.setDivider(null); + list.setPadding(0, paddingTop, 0, 0); + + builder.setTitle(R.string.choose_app); + builder.setView(list); + dialog = builder.create(); + + list.setOnItemClickListener((parent, view, position, id1) -> { + // Add empty application definition, the user will be able to edit it later + PackageItem info = (PackageItem) parent.getItemAtPosition(position); + addCustomApplicationPref(info.packageName); + dialog.cancel(); + }); + break; + default: + dialog = null; + } + return dialog; + } + + @Override + public int getDialogMetricsCategory(int dialogId) { + if (dialogId == DIALOG_APPS) { + return MetricsEvent.DERP; + } + return 0; + } + + /** + * Application class + */ + private static class Package { + public String name; + public Integer color; + public Integer timeon; + public Integer timeoff; + + /** + * Stores all the application values in one call + */ + public Package(String name, Integer color, Integer timeon, Integer timeoff) { + this.name = name; + this.color = color; + this.timeon = timeon; + this.timeoff = timeoff; + } + + public String toString() { + return name + "=" + color + ";" + timeon + ";" + timeoff; + } + + public static Package fromString(String value) { + if (TextUtils.isEmpty(value)) { + return null; + } + String[] app = value.split("=", -1); + if (app.length != 2) + return null; + + String[] values = app[1].split(";", -1); + if (values.length != 3) + return null; + + try { + return new Package(app[0], Integer.parseInt(values[0]), Integer + .parseInt(values[1]), Integer.parseInt(values[2])); + } catch (NumberFormatException e) { + return null; + } + } + + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.notification_light_settings) { + + @Override + public List getNonIndexableKeys(Context context) { + final List result = new ArrayList<>(); + + TelephonyManager tm = context.getSystemService(TelephonyManager.class); + + if (!context.getResources().getBoolean(com.android.internal.R.bool + .config_intrusiveNotificationLed)) { + result.add(KEY_NOTIFICATION_LIGHTS); + result.add(NOTIFICATION_LIGHT_PULSE); + } + if (!LightsCapabilities.supports(context, LightsCapabilities.LIGHTS_PULSATING_LED) && + !LightsCapabilities.supports(context, + LightsCapabilities.LIGHTS_RGB_NOTIFICATION_LED)) { + result.add(GENERAL_SECTION); + result.add(NOTIFICATION_LIGHT_COLOR_AUTO); + result.add(DEFAULT_PREF); + result.add(ADVANCED_SECTION); + result.add(NOTIFICATION_LIGHT_SCREEN_ON); + result.add(NOTIFICATION_LIGHT_PULSE_CUSTOM_ENABLE); + result.add(PHONE_SECTION); + result.add(MISSED_CALL_PREF); + result.add(VOICEMAIL_PREF); + result.add(APPLICATION_SECTION); + result.add(ADD_APPS); + } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_NONE) { + result.add(PHONE_SECTION); + result.add(MISSED_CALL_PREF); + result.add(VOICEMAIL_PREF); + } + if (!LightsCapabilities.supports(context, + LightsCapabilities.LIGHTS_ADJUSTABLE_BATTERY_LED_BRIGHTNESS)) { + result.add(BRIGHTNESS_SECTION); + result.add(NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL); + result.add(NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_ZEN); + } + return result; + } + }; +} diff --git a/src/com/android/settings/derp/preference/CustomDialogPreference.java b/src/com/android/settings/derp/preference/CustomDialogPreference.java new file mode 100644 index 00000000000..135f12c5ef8 --- /dev/null +++ b/src/com/android/settings/derp/preference/CustomDialogPreference.java @@ -0,0 +1,208 @@ +/* + * SPDX-FileCopyrightText: 2015 The Android Open Source Project + * SPDX-FileCopyrightText: 2016 The CyanogenMod Project + * SPDX-FileCopyrightText: 2017,2019,2021-2022 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.preference; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.DialogPreference; +import androidx.preference.PreferenceDialogFragmentCompat; + +public class CustomDialogPreference extends DialogPreference { + + private CustomPreferenceDialogFragment mFragment; + + public CustomDialogPreference(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public CustomDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public CustomDialogPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CustomDialogPreference(Context context) { + super(context); + } + + public boolean isDialogOpen() { + return getDialog() != null && getDialog() instanceof Dialog && + ((Dialog)getDialog()).isShowing(); + } + + public T getDialog() { + return (T) (mFragment != null ? mFragment.getDialog() : null); + } + + protected void onPrepareDialogBuilder(AlertDialog.Builder builder, + DialogInterface.OnClickListener listener) { + } + + protected void onDialogClosed(boolean positiveResult) { + } + + protected void onClick(T dialog, int which) { + } + + protected void onBindDialogView(View view) { + } + + protected void onStart() { + } + + protected void onStop() { + } + + protected void onPause() { + } + + protected void onResume() { + } + + public Dialog onCreateDialog(Bundle savedInstanceState) { + return null; + } + + protected View onCreateDialogView(Context context) { + return null; + } + + private void setFragment(CustomPreferenceDialogFragment fragment) { + mFragment = fragment; + } + + protected boolean onDismissDialog(T dialog, int which) { + return true; + } + + public static class CustomPreferenceDialogFragment extends PreferenceDialogFragmentCompat { + + public static CustomPreferenceDialogFragment newInstance(String key) { + final CustomPreferenceDialogFragment fragment = new CustomPreferenceDialogFragment(); + final Bundle b = new Bundle(1); + b.putString(ARG_KEY, key); + fragment.setArguments(b); + return fragment; + } + + private CustomDialogPreference getCustomizablePreference() { + return (CustomDialogPreference) getPreference(); + } + + private class OnDismissListener implements View.OnClickListener { + private final int mWhich; + private final DialogInterface mDialog; + + public OnDismissListener(DialogInterface dialog, int which) { + mWhich = which; + mDialog = dialog; + } + + @Override + public void onClick(View view) { + CustomPreferenceDialogFragment.this.onClick(mDialog, mWhich); + if (getCustomizablePreference().onDismissDialog(mDialog, mWhich)) { + mDialog.dismiss(); + } + } + } + + @Override + public void onStart() { + super.onStart(); + if (getDialog() instanceof AlertDialog) { + AlertDialog a = (AlertDialog)getDialog(); + if (a.getButton(Dialog.BUTTON_NEUTRAL) != null) { + a.getButton(Dialog.BUTTON_NEUTRAL).setOnClickListener( + new OnDismissListener(a, Dialog.BUTTON_NEUTRAL)); + } + if (a.getButton(Dialog.BUTTON_POSITIVE) != null) { + a.getButton(Dialog.BUTTON_POSITIVE).setOnClickListener( + new OnDismissListener(a, Dialog.BUTTON_POSITIVE)); + } + if (a.getButton(Dialog.BUTTON_NEGATIVE) != null) { + a.getButton(Dialog.BUTTON_NEGATIVE).setOnClickListener( + new OnDismissListener(a, Dialog.BUTTON_NEGATIVE)); + } + } + getCustomizablePreference().onStart(); + } + + @Override + public void onStop() { + super.onStop(); + getCustomizablePreference().onStop(); + } + + @Override + public void onPause() { + super.onPause(); + getCustomizablePreference().onPause(); + } + + @Override + public void onResume() { + super.onResume(); + getCustomizablePreference().onResume(); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + super.onPrepareDialogBuilder(builder); + getCustomizablePreference().setFragment(this); + getCustomizablePreference().onPrepareDialogBuilder(builder, this); + } + + @Override + public void onDialogClosed(boolean positiveResult) { + getCustomizablePreference().onDialogClosed(positiveResult); + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + getCustomizablePreference().onBindDialogView(view); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + super.onClick(dialog, which); + getCustomizablePreference().onClick(dialog, which); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + getCustomizablePreference().setFragment(this); + final Dialog sub = getCustomizablePreference().onCreateDialog(savedInstanceState); + if (sub == null) { + return super.onCreateDialog(savedInstanceState); + } + return sub; + } + + @Override + protected View onCreateDialogView(Context context) { + final View v = getCustomizablePreference().onCreateDialogView(context); + if (v == null) { + return super.onCreateDialogView(context); + } + return v; + } + } +} diff --git a/src/com/android/settings/derp/statusbar/BatterySettings.java b/src/com/android/settings/derp/statusbar/BatterySettings.java new file mode 100644 index 00000000000..df7781fc729 --- /dev/null +++ b/src/com/android/settings/derp/statusbar/BatterySettings.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2024 Yet Another AOSP 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.derp.statusbar; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.UserHandle; +import android.provider.DeviceConfig; +import android.provider.Settings; +import android.text.format.DateFormat; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; + +import androidx.preference.Preference; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; +import androidx.preference.PreferenceCategory; +import androidx.preference.Preference.OnPreferenceChangeListener; +import androidx.preference.PreferenceFragment; +import androidx.preference.SwitchPreferenceCompat; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.R; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +import org.derpfest.support.preferences.SystemSettingListPreference; +import org.derpfest.support.preferences.SystemSettingSwitchPreference; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@SearchIndexable +public class BatterySettings extends DashboardFragment implements + OnPreferenceChangeListener { + + private static final String TAG = "BatterySettings"; + + private static final String BATTERY_STYLE = "status_bar_battery_style"; + private static final String SHOW_BATTERY_PERCENT = "status_bar_show_battery_percent"; + private static final String SHOW_BATTERY_PERCENT_CHARGING = "status_bar_show_battery_percent_charging"; + private static final String SHOW_BATTERY_PERCENT_INSIDE = "status_bar_show_battery_percent_inside"; + + private SystemSettingListPreference mBatteryStyle; + private SystemSettingSwitchPreference mBatteryPercent; + private SystemSettingSwitchPreference mBatteryPercentCharging; + private SystemSettingSwitchPreference mBatteryPercentInside; + + @Override + protected int getPreferenceScreenResId() { + return R.xml.battery_settings; + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + PreferenceScreen prefSet = getPreferenceScreen(); + final ContentResolver resolver = getActivity().getContentResolver(); + + mBatteryPercent = findPreference(SHOW_BATTERY_PERCENT); + final boolean percentEnabled = Settings.System.getIntForUser(resolver, + SHOW_BATTERY_PERCENT, 0, UserHandle.USER_CURRENT) == 1; + mBatteryPercent.setChecked(percentEnabled); + mBatteryPercent.setOnPreferenceChangeListener(this); + + mBatteryPercentInside = findPreference(SHOW_BATTERY_PERCENT_INSIDE); + mBatteryPercentInside.setEnabled(percentEnabled); + final boolean percentInside = Settings.System.getIntForUser(resolver, + SHOW_BATTERY_PERCENT_INSIDE, 0, UserHandle.USER_CURRENT) == 1; + mBatteryPercentInside.setChecked(percentInside); + mBatteryPercentInside.setOnPreferenceChangeListener(this); + + mBatteryStyle = findPreference(BATTERY_STYLE); + final int value = Settings.System.getIntForUser(resolver, + BATTERY_STYLE, 0, UserHandle.USER_CURRENT); + mBatteryStyle.setValue(Integer.toString(value)); + mBatteryStyle.setSummary(mBatteryStyle.getEntry()); + mBatteryStyle.setOnPreferenceChangeListener(this); + updatePercentEnablement(value != 2); + + mBatteryPercentCharging = findPreference(SHOW_BATTERY_PERCENT_CHARGING); + updatePercentChargingEnablement(value, percentEnabled, percentInside); + } + + @Override + public void onResume() { + super.onResume(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object objValue) { + final ContentResolver resolver = getActivity().getContentResolver(); + if (preference == mBatteryStyle) { + int value = Integer.valueOf((String) objValue); + int index = mBatteryStyle.findIndexOfValue((String) objValue); + mBatteryStyle.setSummary(mBatteryStyle.getEntries()[index]); + Settings.System.putIntForUser(resolver, + BATTERY_STYLE, value, UserHandle.USER_CURRENT); + updatePercentEnablement(value != 2); + updatePercentChargingEnablement(value, null, null); + return true; + } else if (preference == mBatteryPercent) { + boolean enabled = (boolean) objValue; + Settings.System.putInt(resolver, + SHOW_BATTERY_PERCENT, enabled ? 1 : 0); + mBatteryPercentInside.setEnabled(enabled); + updatePercentChargingEnablement(null, enabled, null); + return true; + } else if (preference == mBatteryPercentInside) { + boolean enabled = (boolean) objValue; + Settings.System.putInt(resolver, + SHOW_BATTERY_PERCENT_INSIDE, enabled ? 1 : 0); + // we already know style isn't text and percent is enabled + mBatteryPercentCharging.setEnabled(enabled); + return true; + } + return false; + } + + private void updatePercentEnablement(boolean enabled) { + mBatteryPercent.setEnabled(enabled); + mBatteryPercentInside.setEnabled(enabled && mBatteryPercent.isChecked()); + } + + private void updatePercentChargingEnablement(Integer style, Boolean percent, Boolean inside) { + if (style == null) style = Integer.valueOf(mBatteryStyle.getValue()); + if (percent == null) percent = mBatteryPercent.isChecked(); + if (inside == null) inside = mBatteryPercentInside.isChecked(); + mBatteryPercentCharging.setEnabled(style != 2 && (!percent || inside)); + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DERP; + } + + @Override + protected String getLogTag() { + return TAG; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.battery_settings); +} diff --git a/src/com/android/settings/derp/statusbar/NetworkTrafficSettings.java b/src/com/android/settings/derp/statusbar/NetworkTrafficSettings.java new file mode 100644 index 00000000000..2f0ce945b76 --- /dev/null +++ b/src/com/android/settings/derp/statusbar/NetworkTrafficSettings.java @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: 2017-2023 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.statusbar; + +import android.content.ContentResolver; +import android.os.Bundle; +import android.provider.Settings; +import android.widget.Toast; + +import androidx.preference.DropDownPreference; +import androidx.preference.Preference; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +import org.derpfest.support.preferences.SecureSettingMainSwitchPreference; +import org.derpfest.support.preferences.SecureSettingSwitchPreference; + +public class NetworkTrafficSettings extends SettingsPreferenceFragment + implements Preference.OnPreferenceChangeListener { + + private static final String TAG = "NetworkTrafficSettings"; + private static final String STATUS_BAR_CLOCK_STYLE = "status_bar_clock"; + + private SecureSettingMainSwitchPreference mNetTraffic; + private SecureSettingSwitchPreference mNetTrafficAutohide; + private DropDownPreference mNetTrafficUnits; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.network_traffic_settings); + getActivity().setTitle(R.string.network_traffic_settings_title); + + final ContentResolver resolver = getActivity().getContentResolver(); + + mNetTraffic = findPreference(Settings.Secure.NETWORK_TRAFFIC_MODE); + + mNetTrafficAutohide = findPreference(Settings.Secure.NETWORK_TRAFFIC_AUTOHIDE); + + mNetTrafficUnits = findPreference(Settings.Secure.NETWORK_TRAFFIC_UNITS); + mNetTrafficUnits.setOnPreferenceChangeListener(this); + int units = Settings.Secure.getInt(resolver, + Settings.Secure.NETWORK_TRAFFIC_UNITS, /* Mbps */ 1); + mNetTrafficUnits.setValue(String.valueOf(units)); + + updateForClockConflicts(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + int value = Integer.parseInt((String) newValue); + String key = preference.getKey(); + switch (key) { + case Settings.Secure.NETWORK_TRAFFIC_UNITS: + Settings.Secure.putInt(getActivity().getContentResolver(), + Settings.Secure.NETWORK_TRAFFIC_UNITS, value); + break; + } + return true; + } + + private void updateEnabledStates(boolean enabled) { + mNetTrafficAutohide.setEnabled(enabled); + mNetTrafficUnits.setEnabled(enabled); + } + + private void updateForClockConflicts() { + int clockPosition = Settings.System.getInt(getActivity().getContentResolver(), + STATUS_BAR_CLOCK_STYLE, 2); + + if (clockPosition != 1) { + return; + } + + mNetTraffic.setEnabled(false); + Toast.makeText(getActivity(), + R.string.network_traffic_disabled_clock, + Toast.LENGTH_LONG).show(); + updateEnabledStates(false); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } +} diff --git a/src/com/android/settings/derp/statusbar/QSPanelSettings.java b/src/com/android/settings/derp/statusbar/QSPanelSettings.java new file mode 100644 index 00000000000..816ae108063 --- /dev/null +++ b/src/com/android/settings/derp/statusbar/QSPanelSettings.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The LeafOS 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.derp.statusbar; + +import android.os.Bundle; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +@SearchIndexable +public class QSPanelSettings extends DashboardFragment { + + private static final String KEY_QS_SHOW_AUTO_BRIGHTNESS = "qs_show_auto_brightness"; + private static final String TAG = "QSPanelSettings"; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + PreferenceScreen preferenceScreen = getPreferenceScreen(); + Preference qsShowAutoBrightnessPreference = preferenceScreen.findPreference(KEY_QS_SHOW_AUTO_BRIGHTNESS); + + if (qsShowAutoBrightnessPreference != null) { + boolean automaticBrightnessAvailable = getContext().getResources().getBoolean( + com.android.internal.R.bool.config_automatic_brightness_available); + if (!automaticBrightnessAvailable) { + qsShowAutoBrightnessPreference.setVisible(false); + } + } + } + + @Override + public int getMetricsCategory() { + return METRICS_CATEGORY_UNKNOWN; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.qs_panel_settings; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.qs_panel_settings); +} diff --git a/src/com/android/settings/derp/statusbar/StatusBarSettings.java b/src/com/android/settings/derp/statusbar/StatusBarSettings.java new file mode 100644 index 00000000000..82920759f06 --- /dev/null +++ b/src/com/android/settings/derp/statusbar/StatusBarSettings.java @@ -0,0 +1,184 @@ +/* + * SPDX-FileCopyrightText: 2014-2015 The CyanogenMod Project + * SPDX-FileCopyrightText: 2017-2023 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ +package com.android.settings.derp.statusbar; + +import android.os.Bundle; +import android.provider.Settings; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.view.View; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.derp.utils.DeviceUtils; +import com.android.settings.R; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settingslib.search.SearchIndexable; + +import org.derpfest.support.preferences.SystemSettingListPreference; + +import java.util.Set; + +@SearchIndexable +public class StatusBarSettings extends SettingsPreferenceFragment + implements Preference.OnPreferenceChangeListener { + + private static final String CATEGORY_CLOCK = "status_bar_clock_key"; + + private static final String ICON_BLACKLIST = "icon_blacklist"; + + private static final String STATUS_BAR_CLOCK_STYLE = "status_bar_clock"; + private static final String STATUS_BAR_AM_PM = "status_bar_am_pm"; + private static final String STATUS_BAR_QUICK_QS_PULLDOWN = "qs_quick_pulldown"; + + private static final int PULLDOWN_DIR_NONE = 0; + private static final int PULLDOWN_DIR_RIGHT = 1; + private static final int PULLDOWN_DIR_LEFT = 2; + + private static final String NETWORK_TRAFFIC_SETTINGS = "network_traffic_settings"; + + private SystemSettingListPreference mQuickPulldown; + private SystemSettingListPreference mStatusBarClock; + private SystemSettingListPreference mStatusBarAmPm; + + private PreferenceCategory mStatusBarClockCategory; + private Preference mNetworkTrafficPref; + + private boolean mHasCenteredCutout; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.status_bar_settings); + + mNetworkTrafficPref = findPreference(NETWORK_TRAFFIC_SETTINGS); + + mHasCenteredCutout = DeviceUtils.hasCenteredCutout(getActivity()); + + mStatusBarAmPm = findPreference(STATUS_BAR_AM_PM); + mStatusBarClock = findPreference(STATUS_BAR_CLOCK_STYLE); + mStatusBarClock.setOnPreferenceChangeListener(this); + + mStatusBarClockCategory = getPreferenceScreen().findPreference(CATEGORY_CLOCK); + + mQuickPulldown = findPreference(STATUS_BAR_QUICK_QS_PULLDOWN); + mQuickPulldown.setOnPreferenceChangeListener(this); + updateQuickPulldownSummary(mQuickPulldown.getIntValue(0)); + } + + @Override + public void onResume() { + super.onResume(); + + final String curIconBlacklist = Settings.Secure.getString(getContext().getContentResolver(), + ICON_BLACKLIST); + + if (TextUtils.delimitedStringContains(curIconBlacklist, ',', "clock")) { + getPreferenceScreen().removePreference(mStatusBarClockCategory); + } else { + getPreferenceScreen().addPreference(mStatusBarClockCategory); + } + + if (DateFormat.is24HourFormat(getActivity())) { + mStatusBarAmPm.setEnabled(false); + mStatusBarAmPm.setSummary(R.string.status_bar_am_pm_info); + } + + final boolean disallowCenteredClock = mHasCenteredCutout || getNetworkTrafficStatus() != 0; + + // Adjust status bar preferences for RTL + if (getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + if (disallowCenteredClock) { + mStatusBarClock.setEntries(R.array.status_bar_clock_position_entries_notch_rtl); + mStatusBarClock.setEntryValues(R.array.status_bar_clock_position_values_notch); + } else { + mStatusBarClock.setEntries(R.array.status_bar_clock_position_entries_rtl); + mStatusBarClock.setEntryValues(R.array.status_bar_clock_position_values); + } + mQuickPulldown.setEntries(R.array.status_bar_quick_qs_pulldown_entries_rtl); + } else { + if (disallowCenteredClock) { + mStatusBarClock.setEntries(R.array.status_bar_clock_position_entries_notch); + mStatusBarClock.setEntryValues(R.array.status_bar_clock_position_values_notch); + } else { + mStatusBarClock.setEntries(R.array.status_bar_clock_position_entries); + mStatusBarClock.setEntryValues(R.array.status_bar_clock_position_values); + } + mQuickPulldown.setEntries(R.array.status_bar_quick_qs_pulldown_entries); + } + + // Disable network traffic preferences if clock is centered in the status bar + updateNetworkTrafficStatus(getClockPosition()); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + int value = Integer.parseInt((String) newValue); + String key = preference.getKey(); + switch (key) { + case STATUS_BAR_QUICK_QS_PULLDOWN: + updateQuickPulldownSummary(value); + break; + case STATUS_BAR_CLOCK_STYLE: + updateNetworkTrafficStatus(value); + break; + } + return true; + } + + private void updateQuickPulldownSummary(int value) { + String summary=""; + switch (value) { + case PULLDOWN_DIR_NONE: + summary = getResources().getString( + R.string.status_bar_quick_qs_pulldown_off); + break; + + case PULLDOWN_DIR_LEFT: + case PULLDOWN_DIR_RIGHT: + summary = getResources().getString( + R.string.status_bar_quick_qs_pulldown_summary, + getResources().getString( + (value == PULLDOWN_DIR_LEFT) ^ + (getResources().getConfiguration().getLayoutDirection() + == View.LAYOUT_DIRECTION_RTL) + ? R.string.status_bar_quick_qs_pulldown_summary_left + : R.string.status_bar_quick_qs_pulldown_summary_right)); + break; + } + mQuickPulldown.setSummary(summary); + } + + private void updateNetworkTrafficStatus(int clockPosition) { + boolean isClockCentered = clockPosition == 1; + mNetworkTrafficPref.setEnabled(!isClockCentered); + mNetworkTrafficPref.setSummary(getResources().getString(isClockCentered ? + R.string.network_traffic_disabled_clock : + R.string.network_traffic_settings_summary + )); + } + + private int getNetworkTrafficStatus() { + return Settings.Secure.getInt(getActivity().getContentResolver(), + Settings.Secure.NETWORK_TRAFFIC_MODE, 0); + } + + private int getClockPosition() { + return Settings.System.getInt(getActivity().getContentResolver(), + STATUS_BAR_CLOCK_STYLE, 2); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.status_bar_settings); +} diff --git a/src/com/android/settings/derp/statusbar/StatusbarLyricSettings.java b/src/com/android/settings/derp/statusbar/StatusbarLyricSettings.java new file mode 100644 index 00000000000..855d225299f --- /dev/null +++ b/src/com/android/settings/derp/statusbar/StatusbarLyricSettings.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 Project Kaleidoscope + * + * 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.derp.statusbar; + +import android.os.Bundle; + +import androidx.preference.Preference; + +import com.android.internal.logging.nano.MetricsProto; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +public class StatusbarLyricSettings extends SettingsPreferenceFragment { + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + addPreferencesFromResource(R.xml.status_bar_lyric_settings); + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DERP; + } +} diff --git a/src/com/android/settings/derp/touch/HighTouchPollingRateSettingsPreferenceController.java b/src/com/android/settings/derp/touch/HighTouchPollingRateSettingsPreferenceController.java new file mode 100644 index 00000000000..3c512dfb745 --- /dev/null +++ b/src/com/android/settings/derp/touch/HighTouchPollingRateSettingsPreferenceController.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2010 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.derp.touch; + +import android.content.Context; +import com.android.internal.derp.hardware.LineageHardwareManager; +import com.android.settings.core.BasePreferenceController; + +public class HighTouchPollingRateSettingsPreferenceController extends BasePreferenceController { + + public static final String KEY = "high_touch_polling_rate_enable"; + + private final LineageHardwareManager mHardware; + + public HighTouchPollingRateSettingsPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + + mHardware = LineageHardwareManager.getInstance(context); + } + + public HighTouchPollingRateSettingsPreferenceController(Context context) { + this(context, KEY); + } + + @Override + public int getAvailabilityStatus() { + if (!mHardware.isSupported(LineageHardwareManager.FEATURE_HIGH_TOUCH_POLLING_RATE)){ + return UNSUPPORTED_ON_DEVICE; + } + return AVAILABLE; + } +} diff --git a/src/com/android/settings/derp/touch/HighTouchSensitivitySettingsPreferenceController.java b/src/com/android/settings/derp/touch/HighTouchSensitivitySettingsPreferenceController.java new file mode 100644 index 00000000000..ee2256d66bf --- /dev/null +++ b/src/com/android/settings/derp/touch/HighTouchSensitivitySettingsPreferenceController.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 PixelExperience + * + * 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.derp.touch; + +import android.content.Context; +import com.android.internal.derp.hardware.LineageHardwareManager; +import com.android.settings.core.BasePreferenceController; + +public class HighTouchSensitivitySettingsPreferenceController extends BasePreferenceController { + + public static final String KEY = "high_touch_sensitivity_enable"; + + private final LineageHardwareManager mHardware; + + public HighTouchSensitivitySettingsPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + + mHardware = LineageHardwareManager.getInstance(context); + } + + public HighTouchSensitivitySettingsPreferenceController(Context context) { + this(context, KEY); + } + + @Override + public int getAvailabilityStatus() { + if (!mHardware.isSupported(LineageHardwareManager.FEATURE_HIGH_TOUCH_SENSITIVITY)){ + return UNSUPPORTED_ON_DEVICE; + } + return AVAILABLE; + } +} diff --git a/src/com/android/settings/derp/touch/TouchHoveringSettingsPreferenceController.java b/src/com/android/settings/derp/touch/TouchHoveringSettingsPreferenceController.java new file mode 100644 index 00000000000..4bde32cbe02 --- /dev/null +++ b/src/com/android/settings/derp/touch/TouchHoveringSettingsPreferenceController.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 PixelExperience + * + * 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.derp.touch; + +import android.content.Context; +import com.android.internal.derp.hardware.LineageHardwareManager; +import com.android.settings.core.BasePreferenceController; + +public class TouchHoveringSettingsPreferenceController extends BasePreferenceController { + + public static final String KEY = "feature_touch_hovering"; + + private final LineageHardwareManager mHardware; + + public TouchHoveringSettingsPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + + mHardware = LineageHardwareManager.getInstance(context); + } + + public TouchHoveringSettingsPreferenceController(Context context) { + this(context, KEY); + } + + @Override + public int getAvailabilityStatus() { + if (!mHardware.isSupported(LineageHardwareManager.FEATURE_TOUCH_HOVERING)){ + return UNSUPPORTED_ON_DEVICE; + } + return AVAILABLE; + } +} diff --git a/src/com/android/settings/derp/utils/DeviceUtils.java b/src/com/android/settings/derp/utils/DeviceUtils.java new file mode 100644 index 00000000000..2ef05fcbe0c --- /dev/null +++ b/src/com/android/settings/derp/utils/DeviceUtils.java @@ -0,0 +1,285 @@ +/* + * SPDX-FileCopyrightText: 2016 The CyanogenMod project + * SPDX-FileCopyrightText: 2017-2023 The LineageOS project + * SPDX-License-Identifier: Apache-2.0 + */ +package com.android.settings.derp.utils; + +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; + +import android.app.Activity; +import android.bluetooth.BluetoothManager; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.nfc.NfcAdapter; +import android.os.Build; +import android.os.SystemProperties; +import android.telephony.TelephonyManager; +import android.telephony.SubscriptionManager; +import android.text.TextUtils; +import android.view.Display; +import android.view.DisplayCutout; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.Surface; + +import static com.android.internal.util.derp.DeviceKeysConstants.*; + +import androidx.annotation.NonNull; + +public class DeviceUtils { + + /* returns whether the device has a centered display cutout or not. */ + public static boolean hasCenteredCutout(Context context) { + Display display = context.getDisplay(); + DisplayCutout cutout = display.getCutout(); + if (cutout != null) { + Point realSize = new Point(); + display.getRealSize(realSize); + + switch (display.getRotation()) { + case Surface.ROTATION_0: { + Rect rect = cutout.getBoundingRectTop(); + return !(rect.left <= 0 || rect.right >= realSize.x); + } + case Surface.ROTATION_90: { + Rect rect = cutout.getBoundingRectLeft(); + return !(rect.top <= 0 || rect.bottom >= realSize.y); + } + case Surface.ROTATION_180: { + Rect rect = cutout.getBoundingRectBottom(); + return !(rect.left <= 0 || rect.right >= realSize.x); + } + case Surface.ROTATION_270: { + Rect rect = cutout.getBoundingRectRight(); + return !(rect.top <= 0 || rect.bottom >= realSize.y); + } + } + } + return false; + } + + public static int getDeviceKeys(Context context) { + return context.getResources().getInteger( + com.android.internal.R.integer.config_deviceHardwareKeys); + } + + public static int getDeviceWakeKeys(Context context) { + return context.getResources().getInteger( + com.android.internal.R.integer.config_deviceHardwareWakeKeys); + } + + /* returns whether the device has power key or not. */ + public static boolean hasPowerKey() { + return KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER); + } + + /* returns whether the device has home key or not. */ + public static boolean hasHomeKey(Context context) { + return (getDeviceKeys(context) & KEY_MASK_HOME) != 0; + } + + /* returns whether the device has back key or not. */ + public static boolean hasBackKey(Context context) { + return (getDeviceKeys(context) & KEY_MASK_BACK) != 0; + } + + /* returns whether the device has menu key or not. */ + public static boolean hasMenuKey(Context context) { + return (getDeviceKeys(context) & KEY_MASK_MENU) != 0; + } + + /* returns whether the device has assist key or not. */ + public static boolean hasAssistKey(Context context) { + return (getDeviceKeys(context) & KEY_MASK_ASSIST) != 0; + } + + /* returns whether the device has app switch key or not. */ + public static boolean hasAppSwitchKey(Context context) { + return (getDeviceKeys(context) & KEY_MASK_APP_SWITCH) != 0; + } + + /* returns whether the device has camera key or not. */ + public static boolean hasCameraKey(Context context) { + return (getDeviceKeys(context) & KEY_MASK_CAMERA) != 0; + } + + /* returns whether the device has volume rocker or not. */ + public static boolean hasVolumeKeys(Context context) { + return (getDeviceKeys(context) & KEY_MASK_VOLUME) != 0; + } + + /* returns whether the device can be waken using the home key or not. */ + public static boolean canWakeUsingHomeKey(Context context) { + return (getDeviceWakeKeys(context) & KEY_MASK_HOME) != 0; + } + + /* returns whether the device can be waken using the back key or not. */ + public static boolean canWakeUsingBackKey(Context context) { + return (getDeviceWakeKeys(context) & KEY_MASK_BACK) != 0; + } + + /* returns whether the device can be waken using the menu key or not. */ + public static boolean canWakeUsingMenuKey(Context context) { + return (getDeviceWakeKeys(context) & KEY_MASK_MENU) != 0; + } + + /* returns whether the device can be waken using the assist key or not. */ + public static boolean canWakeUsingAssistKey(Context context) { + return (getDeviceWakeKeys(context) & KEY_MASK_ASSIST) != 0; + } + + /* returns whether the device can be waken using the app switch key or not. */ + public static boolean canWakeUsingAppSwitchKey(Context context) { + return (getDeviceWakeKeys(context) & KEY_MASK_APP_SWITCH) != 0; + } + + /* returns whether the device can be waken using the camera key or not. */ + public static boolean canWakeUsingCameraKey(Context context) { + return (getDeviceWakeKeys(context) & KEY_MASK_CAMERA) != 0; + } + + /* returns whether the device can be waken using the volume rocker or not. */ + public static boolean canWakeUsingVolumeKeys(Context context) { + return (getDeviceWakeKeys(context) & KEY_MASK_VOLUME) != 0; + } + + /* returns whether the device supports button backlight adjusment or not. */ + public static boolean hasButtonBacklightSupport(Context context) { + final boolean buttonBrightnessControlSupported = context.getResources().getInteger( + com.android.internal.R.integer + .config_deviceSupportsButtonBrightnessControl) != 0; + + // All hardware keys besides volume and camera can possibly have a backlight + return buttonBrightnessControlSupported + && (hasHomeKey(context) || hasBackKey(context) || hasMenuKey(context) + || hasAssistKey(context) || hasAppSwitchKey(context)); + } + + /* returns whether the device supports keyboard backlight adjustment or not. */ + public static boolean hasKeyboardBacklightSupport(Context context) { + return context.getResources().getInteger(com.android.internal.R.integer + .config_deviceSupportsKeyboardBrightnessControl) != 0; + } + + public static boolean isPackageInstalled(Context context, String pkg, boolean ignoreState) { + if (pkg != null) { + try { + PackageInfo pi = context.getPackageManager().getPackageInfo(pkg, + PackageManager.PackageInfoFlags.of(0)); + if (!pi.applicationInfo.enabled && !ignoreState) { + return false; + } + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + return true; + } + + /** + * Locks the activity orientation to the current device orientation + */ + public static void lockCurrentOrientation(Activity activity) { + int currentRotation = activity.getDisplay().getRotation(); + int orientation = activity.getResources().getConfiguration().orientation; + int frozenRotation = 0; + switch (currentRotation) { + case Surface.ROTATION_0: + frozenRotation = orientation == Configuration.ORIENTATION_LANDSCAPE + ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + break; + case Surface.ROTATION_90: + frozenRotation = orientation == Configuration.ORIENTATION_PORTRAIT + ? ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT + : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + break; + case Surface.ROTATION_180: + frozenRotation = orientation == Configuration.ORIENTATION_LANDSCAPE + ? ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE + : ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; + break; + case Surface.ROTATION_270: + frozenRotation = orientation == Configuration.ORIENTATION_PORTRAIT + ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + : ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + break; + } + activity.setRequestedOrientation(frozenRotation); + } + + public static boolean isDozeAvailable(Context context) { + String name = Build.IS_DEBUGGABLE ? SystemProperties.get("debug.doze.component") : null; + if (TextUtils.isEmpty(name)) { + name = context.getResources().getString( + com.android.internal.R.string.config_dozeComponent); + } + return !TextUtils.isEmpty(name); + } + + public static boolean deviceSupportsMobileData(Context ctx) { + TelephonyManager telephonyManager = ctx.getSystemService(TelephonyManager.class); + return telephonyManager.isDataCapable(); + } + + public static boolean deviceSupportsBluetooth(Context ctx) { + BluetoothManager bluetoothManager = (BluetoothManager) + ctx.getSystemService(Context.BLUETOOTH_SERVICE); + return (bluetoothManager.getAdapter() != null); + } + + public static boolean deviceSupportsNfc(Context ctx) { + return NfcAdapter.getDefaultAdapter(ctx) != null; + } + + public static boolean deviceSupportsFlashLight(@NonNull Context context) { + CameraManager cameraManager = context.getSystemService(CameraManager.class); + try { + String[] ids = cameraManager.getCameraIdList(); + for (String id : ids) { + CameraCharacteristics c = cameraManager.getCameraCharacteristics(id); + Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); + Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING); + if (flashAvailable != null + && flashAvailable + && lensFacing != null + && lensFacing == CameraCharacteristics.LENS_FACING_BACK) { + return true; + } + } + } catch (CameraAccessException | AssertionError e) { + // Ignore + } + return false; + } + + public static boolean isMobileDataEnabled(Context context) { + TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); + int subId = SubscriptionManager.getDefaultDataSubscriptionId(); + return telephonyManager.createForSubscriptionId(subId).isDataEnabled(); + } + + public static boolean isSwipeUpEnabled(Context context) { + if (isEdgeToEdgeEnabled(context)) { + return false; + } + return NAV_BAR_MODE_2BUTTON == context.getResources().getInteger( + com.android.internal.R.integer.config_navBarInteractionMode); + } + + public static boolean isEdgeToEdgeEnabled(Context context) { + return NAV_BAR_MODE_GESTURAL == context.getResources().getInteger( + com.android.internal.R.integer.config_navBarInteractionMode); + } +} diff --git a/src/com/android/settings/derp/utils/ResourceUtils.java b/src/com/android/settings/derp/utils/ResourceUtils.java new file mode 100644 index 00000000000..4fbc81616c6 --- /dev/null +++ b/src/com/android/settings/derp/utils/ResourceUtils.java @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2016 The CyanogenMod 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.derp.utils; + +import android.content.res.Resources; +import android.util.Log; + +public class ResourceUtils { + + private static final String TAG = ResourceUtils.class.getSimpleName(); + + public static String getLocalizedString(final Resources res, + final String stringName, + final String stringFormat) { + final String name = stringName.toLowerCase().replace(" ", "_"); + final String nameRes = String.format(stringFormat, name); + return getStringForResourceName(res, nameRes, stringName); + } + + public static String getStringForResourceName(final Resources res, + final String resourceName, + final String defaultValue) { + final int resId = res.getIdentifier(resourceName, "string", "com.android.settings"); + if (resId <= 0) { + Log.e(TAG, "No resource found for " + resourceName); + return defaultValue; + } else { + return res.getString(resId); + } + } +} diff --git a/src/com/android/settings/derp/utils/TelephonyUtils.java b/src/com/android/settings/derp/utils/TelephonyUtils.java new file mode 100644 index 00000000000..6ff430433ef --- /dev/null +++ b/src/com/android/settings/derp/utils/TelephonyUtils.java @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2016 The CyanogenMod Project + * SPDX-FileCopyrightText: 2023 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ +package com.android.settings.derp.utils; + +import android.content.Context; +import android.telephony.TelephonyManager; + +import androidx.annotation.NonNull; + +public class TelephonyUtils { + + private static final String TAG = TelephonyUtils.class.getSimpleName(); + + /** + * Returns whether the device is voice-capable (meaning, it is also a phone). + */ + public static boolean isVoiceCapable(@NonNull Context context) { + TelephonyManager telephony = context.getSystemService(TelephonyManager.class); + return telephony != null && telephony.isVoiceCapable(); + } +} diff --git a/src/com/android/settings/derp/widget/IntervalSeekBar.java b/src/com/android/settings/derp/widget/IntervalSeekBar.java new file mode 100644 index 00000000000..35c986f2bb3 --- /dev/null +++ b/src/com/android/settings/derp/widget/IntervalSeekBar.java @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2016 The CyanogenMod Project + * SPDX-FileCopyrightText: 2017-2022 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ +package com.android.settings.derp.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.SeekBar; + +import com.android.settings.R; + +/** + * Custom SeekBar that allows setting both a minimum and maximum value. + * This also handles floating point values (to 2 decimal places) through + * integer conversions. + */ +public class IntervalSeekBar extends SeekBar { + private float mMin; + private float mMax; + private final float mDefault; + private final float mMultiplier; + + public IntervalSeekBar(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray seekBarType = context.obtainStyledAttributes(attrs, + R.styleable.IntervalSeekBar, 0, 0); + + mMax = seekBarType.getFloat(R.styleable.IntervalSeekBar_maxValue, 1.5f); + mMin = seekBarType.getFloat(R.styleable.IntervalSeekBar_minValue, 0.5f); + mDefault = seekBarType.getFloat(R.styleable.IntervalSeekBar_defaultValue, 1.0f); + + int digits = seekBarType.getInt(R.styleable.IntervalSeekBar_digits, 0); + mMultiplier = (float) Math.pow(10, digits); + + if (mMin > mMax) { + float temp = mMax; + mMax = mMin; + mMin = temp; + } + + setMax(convertFloatToProgress(mMax)); + setProgressFloat(mDefault); + + seekBarType.recycle(); + } + + /* + * Converts from SeekBar units (which the SeekBar uses), to scale units + * (which are saved). + * This operation is the inverse of setFontScaling. + */ + public float getProgressFloat() { + return (getProgress() / mMultiplier) + mMin; + } + + /* + * Converts from scale units (which are saved), to SeekBar units + * (which the SeekBar uses). This also sets the SeekBar progress. + * This operation is the inverse of getProgressFloat. + */ + public void setProgressFloat(float progress) { + setProgress(convertFloatToProgress(progress)); + } + + private int convertFloatToProgress(float value) { + return Math.round((value - mMin) * mMultiplier); + } + + public float getMinimum() { + return mMin; + } + + public float getMaximum() { + return mMax; + } + + public float getDefault() { + return mDefault; + } + + public void setMaximum(float max) { + mMax = max; + setMax(convertFloatToProgress(mMax)); + } + + public void setMinimum(float min) { + mMin = min; + } +} diff --git a/src/com/android/settings/derp/widget/PackageListAdapter.java b/src/com/android/settings/derp/widget/PackageListAdapter.java new file mode 100644 index 00000000000..b6ddf23427d --- /dev/null +++ b/src/com/android/settings/derp/widget/PackageListAdapter.java @@ -0,0 +1,194 @@ +/* + * SPDX-FileCopyrightText: 2012-2014 The CyanogenMod Project + * SPDX-FileCopyrightText: 2022-2023 The LineageOS Project + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.android.settings.derp.widget; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.settings.R; + +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +public class PackageListAdapter extends BaseAdapter implements Runnable { + private final PackageManager mPm; + private final LayoutInflater mInflater; + private final List mInstalledPackages = new LinkedList<>(); + private Set mExcludedPackages = new HashSet<>(); + + // Packages which don't have launcher icons, but which we want to show nevertheless + private static final String[] PACKAGE_WHITELIST = new String[] { + "android", /* system server */ + "com.android.systemui", /* system UI */ + "com.android.providers.downloads" /* download provider */ + }; + + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + PackageItem item = (PackageItem) msg.obj; + int index = Collections.binarySearch(mInstalledPackages, item); + if (index < 0) { + mInstalledPackages.add(-index - 1, item); + } else { + mInstalledPackages.get(index).activityTitles.addAll(item.activityTitles); + } + notifyDataSetChanged(); + } + }; + + public static class PackageItem implements Comparable { + public final String packageName; + public final CharSequence title; + private final TreeSet activityTitles = new TreeSet<>(); + public final Drawable icon; + + PackageItem(String packageName, CharSequence title, Drawable icon) { + this.packageName = packageName; + this.title = title; + this.icon = icon; + } + + @Override + public int compareTo(PackageItem another) { + int result = title.toString().compareToIgnoreCase(another.title.toString()); + return result != 0 ? result : packageName.compareTo(another.packageName); + } + } + + public PackageListAdapter(Context context) { + mPm = context.getPackageManager(); + mInflater = LayoutInflater.from(context); + reloadList(); + } + + @Override + public int getCount() { + synchronized (mInstalledPackages) { + return mInstalledPackages.size(); + } + } + + @Override + public PackageItem getItem(int position) { + synchronized (mInstalledPackages) { + return mInstalledPackages.get(position); + } + } + + @Override + public long getItemId(int position) { + synchronized (mInstalledPackages) { + // packageName is guaranteed to be unique in mInstalledPackages + return mInstalledPackages.get(position).packageName.hashCode(); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder holder; + if (convertView != null) { + holder = (ViewHolder) convertView.getTag(); + } else { + convertView = mInflater.inflate(R.layout.preference_icon, null, false); + holder = new ViewHolder(); + convertView.setTag(holder); + holder.title = convertView.findViewById(com.android.internal.R.id.title); + holder.summary = convertView.findViewById(com.android.internal.R.id.summary); + holder.icon = convertView.findViewById(com.android.internal.R.id.icon); + } + + PackageItem applicationInfo = getItem(position); + holder.title.setText(applicationInfo.title); + holder.icon.setImageDrawable(applicationInfo.icon); + + boolean needSummary = applicationInfo.activityTitles.size() > 0; + if (applicationInfo.activityTitles.size() == 1) { + if (TextUtils.equals(applicationInfo.title, applicationInfo.activityTitles.first())) { + needSummary = false; + } + } + + if (needSummary) { + holder.summary.setText(TextUtils.join(", ", applicationInfo.activityTitles)); + holder.summary.setVisibility(View.VISIBLE); + } else { + holder.summary.setVisibility(View.GONE); + } + + return convertView; + } + + private void reloadList() { + mInstalledPackages.clear(); + new Thread(this).start(); + } + + @Override + public void run() { + final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); + mainIntent.addCategory(Intent.CATEGORY_LAUNCHER); + List installedAppsInfo = mPm.queryIntentActivities(mainIntent, + PackageManager.ResolveInfoFlags.of(0)); + + for (ResolveInfo info : installedAppsInfo) { + ApplicationInfo appInfo = info.activityInfo.applicationInfo; + if (mExcludedPackages.contains(appInfo.packageName)) { + continue; + } + + final PackageItem item = new PackageItem(appInfo.packageName, + appInfo.loadLabel(mPm), appInfo.loadIcon(mPm)); + item.activityTitles.add(info.loadLabel(mPm)); + mHandler.obtainMessage(0, item).sendToTarget(); + } + + for (String packageName : PACKAGE_WHITELIST) { + if (mExcludedPackages.contains(packageName)) { + continue; + } + try { + ApplicationInfo appInfo = mPm.getApplicationInfo(packageName, + PackageManager.ApplicationInfoFlags.of(0)); + final PackageItem item = new PackageItem(appInfo.packageName, + appInfo.loadLabel(mPm), appInfo.loadIcon(mPm)); + mHandler.obtainMessage(0, item).sendToTarget(); + } catch (PackageManager.NameNotFoundException ignored) { + // package not present, so nothing to add -> ignore it + } + } + } + + public void setExcludedPackages(HashSet packages) { + mExcludedPackages = packages; + reloadList(); + } + + private static class ViewHolder { + TextView title; + TextView summary; + ImageView icon; + } +} diff --git a/src/com/android/settings/development/AdbRootPreferenceController.java b/src/com/android/settings/development/AdbRootPreferenceController.java new file mode 100644 index 00000000000..1c2ff6b87b3 --- /dev/null +++ b/src/com/android/settings/development/AdbRootPreferenceController.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 The LineageOS 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.development; + +import android.adb.ADBRootService; +import android.content.Context; +import android.os.UserManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreferenceCompat; + +import com.android.settings.R; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.development.DeveloperOptionsPreferenceController; + +public class AdbRootPreferenceController extends DeveloperOptionsPreferenceController + implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin { + + private static final String TAG = "AdbRootPreferenceController"; + private static final String PREF_KEY = "enable_adb_root"; + + private final ADBRootService mADBRootService; + + public AdbRootPreferenceController(Context context, + DevelopmentSettingsDashboardFragment fragment) { + super(context); + + mADBRootService = new ADBRootService(); + } + + @Override + public String getPreferenceKey() { + return PREF_KEY; + } + + @Override + public boolean isAvailable() { + return mADBRootService.isSupported(); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + ((SwitchPreferenceCompat) mPreference).setChecked(mADBRootService.getEnabled()); + + if (!isAdminUser()) { + mPreference.setEnabled(false); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean rootEnabled = (Boolean) newValue; + mADBRootService.setEnabled(rootEnabled); + return true; + } + + @Override + protected void onDeveloperOptionsSwitchDisabled() { + super.onDeveloperOptionsSwitchDisabled(); + + mADBRootService.setEnabled(false); + ((SwitchPreferenceCompat) mPreference).setChecked(false); + } + + @Override + protected void onDeveloperOptionsSwitchEnabled() { + if (isAdminUser()) { + mPreference.setEnabled(true); + } + } + + boolean isAdminUser() { + return ((UserManager) mContext.getSystemService(Context.USER_SERVICE)).isAdminUser(); + } +} diff --git a/src/com/android/settings/development/AutomaticSystemServerHeapDumpPreferenceController.java b/src/com/android/settings/development/AutomaticSystemServerHeapDumpPreferenceController.java index 05a5dc90b4c..877b7fe7abf 100644 --- a/src/com/android/settings/development/AutomaticSystemServerHeapDumpPreferenceController.java +++ b/src/com/android/settings/development/AutomaticSystemServerHeapDumpPreferenceController.java @@ -49,7 +49,7 @@ public AutomaticSystemServerHeapDumpPreferenceController(Context context) { @Override public boolean isAvailable() { - return Build.IS_DEBUGGABLE && mIsConfigEnabled + return Build.IS_ENG && mIsConfigEnabled && !mUserManager.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES); } diff --git a/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java b/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java index f460b9e33fe..7d4b7a6fdda 100644 --- a/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java +++ b/src/com/android/settings/development/BluetoothA2dpHwOffloadPreferenceController.java @@ -74,23 +74,10 @@ public void updateState(Preference preference) { } } - @Override - protected void onDeveloperOptionsSwitchDisabled() { - super.onDeveloperOptionsSwitchDisabled(); - final boolean offloadSupported = - SystemProperties.getBoolean(A2DP_OFFLOAD_SUPPORTED_PROPERTY, false); - if (offloadSupported) { - ((TwoStatePreference) mPreference).setChecked(false); - SystemProperties.set(A2DP_OFFLOAD_DISABLED_PROPERTY, "false"); - } - } - public boolean isDefaultValue() { - final boolean offloadSupported = - SystemProperties.getBoolean(A2DP_OFFLOAD_SUPPORTED_PROPERTY, false); - final boolean offloadDisabled = - SystemProperties.getBoolean(A2DP_OFFLOAD_DISABLED_PROPERTY, false); - return offloadSupported ? !offloadDisabled : true; + // Always return true here to avoid needing to reboot when disabling + // developer options, since we aren't turning this off when doing so anymore. + return true; } /** diff --git a/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java b/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java index 9e058913de3..f79b0b83dcd 100644 --- a/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java +++ b/src/com/android/settings/development/BluetoothSnoopLogPreferenceController.java @@ -57,7 +57,7 @@ public BluetoothSnoopLogPreferenceController( // Default mode is DISABLED. It can also be changed by modifying the global setting. public int getDefaultModeIndex() { - if (!Build.IS_DEBUGGABLE) { + if (!Build.IS_ENG) { return BTSNOOP_LOG_MODE_DISABLED_INDEX; } diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 38cb6c72e96..7ed53fbfa38 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -32,6 +32,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.database.ContentObserver; import android.net.Uri; import android.os.Bundle; @@ -670,11 +671,12 @@ private static List buildPreferenceControllers(Con controllers.add(new PictureColorModePreferenceController(context, lifecycle)); controllers.add(new WebViewAppPreferenceController(context)); controllers.add(new CoolColorTemperaturePreferenceController(context)); - controllers.add(new DisableAutomaticUpdatesPreferenceController(context)); + // controllers.add(new DisableAutomaticUpdatesPreferenceController(context)); controllers.add(new SelectDSUPreferenceController(context)); controllers.add(new AdbPreferenceController(context, fragment)); controllers.add(new ClearAdbKeysPreferenceController(context, fragment)); controllers.add(new WirelessDebuggingPreferenceController(context, lifecycle)); + controllers.add(new AdbRootPreferenceController(context, fragment)); controllers.add(new AdbAuthorizationTimeoutPreferenceController(context)); controllers.add(new LocalTerminalPreferenceController(context)); controllers.add(new BugReportInPowerPreferenceController(context)); @@ -693,10 +695,12 @@ private static List buildPreferenceControllers(Con controllers.add(new LogdSizePreferenceController(context)); controllers.add(new LogPersistPreferenceController(context, fragment, lifecycle)); controllers.add(new CameraLaserSensorPreferenceController(context)); - controllers.add(new WifiDisplayCertificationPreferenceController(context)); - controllers.add(new WifiVerboseLoggingPreferenceController(context)); - controllers.add(new WifiScanThrottlingPreferenceController(context)); - controllers.add(new WifiNonPersistentMacRandomizationPreferenceController(context)); + if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI)) { + controllers.add(new WifiDisplayCertificationPreferenceController(context)); + controllers.add(new WifiVerboseLoggingPreferenceController(context)); + controllers.add(new WifiScanThrottlingPreferenceController(context)); + controllers.add(new WifiNonPersistentMacRandomizationPreferenceController(context)); + } controllers.add(new MobileDataAlwaysOnPreferenceController(context)); controllers.add(new TetheringHardwareAccelPreferenceController(context)); controllers.add(new BluetoothDeviceNoNamePreferenceController(context)); @@ -731,8 +735,6 @@ private static List buildPreferenceControllers(Con controllers.add(new DebugGpuOverdrawPreferenceController(context)); controllers.add(new DebugNonRectClipOperationsPreferenceController(context)); controllers.add(new GameDefaultFrameRatePreferenceController(context)); - controllers.add(new ForceDarkPreferenceController(context)); - controllers.add(new EnableBlursPreferenceController(context)); controllers.add(new ForceMSAAPreferenceController(context)); controllers.add(new HardwareOverlaysPreferenceController(context)); controllers.add(new SimulateColorSpacePreferenceController(context)); diff --git a/src/com/android/settings/development/DisableAutomaticUpdatesPreferenceController.java b/src/com/android/settings/development/DisableAutomaticUpdatesPreferenceController.java index 4f7fb4d48fd..006a1e6338e 100644 --- a/src/com/android/settings/development/DisableAutomaticUpdatesPreferenceController.java +++ b/src/com/android/settings/development/DisableAutomaticUpdatesPreferenceController.java @@ -73,4 +73,9 @@ protected void onDeveloperOptionsSwitchDisabled() { Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, DISABLE_UPDATES_SETTING); ((TwoStatePreference) mPreference).setChecked(false); } + + @Override + public boolean isAvailable() { + return false; + } } diff --git a/src/com/android/settings/development/EnableBlursPreferenceController.java b/src/com/android/settings/development/EnableBlursPreferenceController.java deleted file mode 100644 index d3a521a7798..00000000000 --- a/src/com/android/settings/development/EnableBlursPreferenceController.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.development; - -import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED; - -import android.content.Context; -import android.provider.Settings; - -import androidx.annotation.VisibleForTesting; -import androidx.preference.Preference; -import androidx.preference.TwoStatePreference; - -import com.android.settings.core.PreferenceControllerMixin; -import com.android.settingslib.development.DeveloperOptionsPreferenceController; - -/** - * Controller that toggles window blurs on devices that support it. - */ -public final class EnableBlursPreferenceController extends DeveloperOptionsPreferenceController - implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin { - - private static final String ENABLE_BLURS_ON_WINDOWS = "enable_blurs_on_windows"; - private final boolean mBlurSupported; - - public EnableBlursPreferenceController(Context context) { - this(context, CROSS_WINDOW_BLUR_SUPPORTED); - } - - @VisibleForTesting - public EnableBlursPreferenceController(Context context, boolean blurSupported) { - super(context); - mBlurSupported = blurSupported; - } - - @Override - public String getPreferenceKey() { - return ENABLE_BLURS_ON_WINDOWS; - } - - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - boolean enabled = (Boolean) newValue; - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.DISABLE_WINDOW_BLURS, enabled ? 0 : 1); - return true; - } - - @Override - public boolean isAvailable() { - return mBlurSupported; - } - - @Override - public void updateState(Preference preference) { - boolean isEnabled = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.DISABLE_WINDOW_BLURS, 0) == 0; - ((TwoStatePreference) mPreference).setChecked(isEnabled); - } - - @Override - protected void onDeveloperOptionsSwitchDisabled() { - super.onDeveloperOptionsSwitchDisabled(); - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.DISABLE_WINDOW_BLURS, 0); - updateState(null); - } -} diff --git a/src/com/android/settings/development/OverlayCategoryPreferenceController.java b/src/com/android/settings/development/OverlayCategoryPreferenceController.java index ce51b54cf8c..0c20c0329a0 100644 --- a/src/com/android/settings/development/OverlayCategoryPreferenceController.java +++ b/src/com/android/settings/development/OverlayCategoryPreferenceController.java @@ -54,7 +54,7 @@ public class OverlayCategoryPreferenceController extends DeveloperOptionsPrefere static final String PACKAGE_DEVICE_DEFAULT = "package_device_default"; private static final String OVERLAY_TARGET_PACKAGE = "android"; private static final Comparator OVERLAY_INFO_COMPARATOR = - Comparator.comparingInt(a -> a.priority); + Comparator.comparing(OverlayInfo::getPackageName); private final IOverlayManager mOverlayManager; private final boolean mAvailable; private final String mCategory; diff --git a/src/com/android/settings/development/SystemServerHeapDumpPreferenceController.java b/src/com/android/settings/development/SystemServerHeapDumpPreferenceController.java index e6701ce248c..769439c819e 100644 --- a/src/com/android/settings/development/SystemServerHeapDumpPreferenceController.java +++ b/src/com/android/settings/development/SystemServerHeapDumpPreferenceController.java @@ -53,7 +53,7 @@ public SystemServerHeapDumpPreferenceController(Context context) { @Override public boolean isAvailable() { - return Build.IS_DEBUGGABLE + return Build.IS_ENG && !mUserManager.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES); } diff --git a/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java b/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java index f0b7961bb65..f7361f8f0b8 100644 --- a/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java +++ b/src/com/android/settings/development/featureflags/FeatureFlagsPreferenceController.java @@ -40,7 +40,7 @@ public FeatureFlagsPreferenceController(Context context, String key) { @Override public int getAvailabilityStatus() { - return Build.IS_DEBUGGABLE ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + return Build.IS_ENG ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } @Override diff --git a/src/com/android/settings/development/qstile/DevelopmentTilePreferenceController.java b/src/com/android/settings/development/qstile/DevelopmentTilePreferenceController.java index 7fc9d9e5d9f..113e164a278 100644 --- a/src/com/android/settings/development/qstile/DevelopmentTilePreferenceController.java +++ b/src/com/android/settings/development/qstile/DevelopmentTilePreferenceController.java @@ -127,15 +127,17 @@ public OnChangeHandler(Context context) { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - boolean enabled = ((Boolean) newValue).booleanValue(); - ComponentName componentName = new ComponentName( + ComponentName cn = new ComponentName( + //boolean enabled = ((Boolean) newValue).booleanValue(); + //ComponentName componentName = new ComponentName( mContext.getPackageName(), preference.getKey()); - mPackageManager.setComponentEnabledSetting(componentName, enabled + mPackageManager.setComponentEnabledSetting(cn, (Boolean) newValue + //mPackageManager.setComponentEnabledSetting(componentName, enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); - try { + /*try { if (mStatusBarService != null) { if (enabled) { mStatusBarService.addTile(componentName); @@ -146,7 +148,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } catch (RemoteException e) { Log.e(TAG, "Failed to modify QS tile for component " + componentName.toString(), e); - } + }*/ return true; } } diff --git a/src/com/android/settings/development/qstile/DevelopmentTiles.java b/src/com/android/settings/development/qstile/DevelopmentTiles.java index cf0d4d1847f..dfb0bf47587 100644 --- a/src/com/android/settings/development/qstile/DevelopmentTiles.java +++ b/src/com/android/settings/development/qstile/DevelopmentTiles.java @@ -29,7 +29,9 @@ import android.hardware.SensorPrivacyManager; import android.net.Uri; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; +import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; @@ -339,4 +341,64 @@ protected void setIsEnabled(boolean isEnabled) { Settings.System.SHOW_TOUCHES, isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF); } } + + /** + * Tile for refresh rate indicator + */ + public static class ShowRefreshRate extends DevelopmentTiles { + + private static final int SETTING_VALUE_QUERY = 2; + private static final int SETTING_VALUE_ON = 1; + private static final int SETTING_VALUE_OFF = 0; + + private static final String SURFACE_FLINGER_SERVICE_KEY = "SurfaceFlinger"; + private static final String SURFACE_COMPOSER_INTERFACE_KEY = "android.ui.ISurfaceComposer"; + private static final int SURFACE_FLINGER_CODE = 1034; + + private IBinder mSurfaceFlinger; + + @Override + public void onCreate() { + super.onCreate(); + mSurfaceFlinger = ServiceManager.getService(SURFACE_FLINGER_SERVICE_KEY); + } + + @Override + protected boolean isEnabled() { + boolean enabled = false; + // magic communication with surface flinger. + try { + if (mSurfaceFlinger != null) { + final Parcel data = Parcel.obtain(); + final Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(SURFACE_COMPOSER_INTERFACE_KEY); + data.writeInt(SETTING_VALUE_QUERY); + mSurfaceFlinger.transact(SURFACE_FLINGER_CODE, data, reply, 0 /* flags */); + enabled = reply.readBoolean(); + reply.recycle(); + data.recycle(); + } + } catch (RemoteException ex) { + // intentional no-op + } + return enabled; + } + + @Override + protected void setIsEnabled(boolean isEnabled) { + try { + if (mSurfaceFlinger != null) { + final Parcel data = Parcel.obtain(); + data.writeInterfaceToken(SURFACE_COMPOSER_INTERFACE_KEY); + final int showRefreshRate = isEnabled ? SETTING_VALUE_ON : SETTING_VALUE_OFF; + data.writeInt(showRefreshRate); + mSurfaceFlinger.transact(SURFACE_FLINGER_CODE, data, + null /* reply */, 0 /* flags */); + data.recycle(); + } + } catch (RemoteException ex) { + // intentional no-op + } + } + } } diff --git a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java index 6fe3ca4521b..29d000302b1 100644 --- a/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java +++ b/src/com/android/settings/deviceinfo/BuildNumberPreferenceController.java @@ -50,6 +50,8 @@ import com.google.android.setupcompat.util.WizardManagerHelper; +import java.util.Random; + public class BuildNumberPreferenceController extends BasePreferenceController implements LifecycleObserver, OnStart { @@ -67,6 +69,29 @@ public class BuildNumberPreferenceController extends BasePreferenceController im private int mDevHitCountdown; private boolean mProcessingLastDevHit; + public final static java.lang.String[] insults = { + "Hahaha, n00b!", + "What are you doing??", + "n00b alert!", + "What is this...? Amateur hour!?", + "This is not Windows", + "Please step away from the device!", + "error code: 1D10T", + "Go outside", + "¯\\_(ツ)_/¯", + "Pro tip: Stop doing this!", + "Y u no speak computer???", + "Why are you so stupid?!", + "Perhaps this Android thing is not for you...", + "Don't you have anything better to do?!", + "This is why nobody likes you...", + "Are you even trying?!", + "Looks like you're derping... BUT THATS OUR BUSINESS!!!", + "This won't make you look cooler to your friends", + "Go back to your stock ROM", + "You look like a person who plays PUBG on his phone", + }; + public BuildNumberPreferenceController(Context context, String key) { super(context, key); mUm = (UserManager) context.getSystemService(Context.USER_SERVICE); @@ -198,7 +223,9 @@ public boolean handlePreferenceTreeClick(Preference preference) { if (mDevHitToast != null) { mDevHitToast.cancel(); } - mDevHitToast = Toast.makeText(mContext, R.string.show_dev_already, + Random randomInsult = new Random(); + final int toasts = randomInsult.nextInt(insults.length); + mDevHitToast = Toast.makeText(mContext, insults[toasts], Toast.LENGTH_LONG); mDevHitToast.show(); mMetricsFeatureProvider.action( diff --git a/src/com/android/settings/deviceinfo/FeedbackPreferenceController.java b/src/com/android/settings/deviceinfo/FeedbackPreferenceController.java index d94586be094..51151c9de32 100644 --- a/src/com/android/settings/deviceinfo/FeedbackPreferenceController.java +++ b/src/com/android/settings/deviceinfo/FeedbackPreferenceController.java @@ -41,7 +41,7 @@ public FeedbackPreferenceController(Fragment host, Context context) { @Override public boolean isAvailable() { - return !TextUtils.isEmpty(DeviceInfoUtils.getFeedbackReporterPackage(mContext)); + return false; } @Override diff --git a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java index 6df100ca4ec..a3288db1df2 100644 --- a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java +++ b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java @@ -55,6 +55,28 @@ public int getAvailabilityStatus() { AVAILABLE : UNSUPPORTED_ON_DEVICE; } + @Override + public CharSequence getSummary() { + return mContext.getString(R.string.device_info_protected_single_press); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + String prefKey = preference.getKey(); + if (prefKey.startsWith(KEY_PHONE_NUMBER)) { + int simSlotNumber = 0; + if (!TextUtils.equals(prefKey, KEY_PHONE_NUMBER)) { + // Get multisim slot number from preference key. + // Multisim preference key is KEY_PHONE_NUMBER + simSlotNumber + simSlotNumber = Integer.parseInt( + prefKey.replaceAll("[^0-9]", "")); + } + final Preference simStatusPreference = mPreferenceList.get(simSlotNumber); + simStatusPreference.setSummary(getPhoneNumber(simSlotNumber)); + } + return super.handlePreferenceTreeClick(preference); + } + @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); @@ -84,7 +106,7 @@ public void updateState(Preference preference) { for (int simSlotNumber = 0; simSlotNumber < mPreferenceList.size(); simSlotNumber++) { final Preference simStatusPreference = mPreferenceList.get(simSlotNumber); simStatusPreference.setTitle(getPreferenceTitle(simSlotNumber)); - simStatusPreference.setSummary(getPhoneNumber(simSlotNumber)); + simStatusPreference.setSummary(getSummary()); } } @@ -134,7 +156,7 @@ protected SubscriptionInfo getSubscriptionInfo(int simSlot) { } @VisibleForTesting - protected String getFormattedPhoneNumber(SubscriptionInfo subscriptionInfo) { + protected CharSequence getFormattedPhoneNumber(SubscriptionInfo subscriptionInfo) { final String phoneNumber = SubscriptionUtil.getBidiFormattedPhoneNumber(mContext, subscriptionInfo); return TextUtils.isEmpty(phoneNumber) ? mContext.getString(R.string.device_info_default) @@ -143,6 +165,6 @@ protected String getFormattedPhoneNumber(SubscriptionInfo subscriptionInfo) { @VisibleForTesting protected Preference createNewPreference(Context context) { - return new Preference(context); + return new PhoneNumberSummaryPreference(context); } } diff --git a/src/com/android/settings/deviceinfo/RadioInfoPreferenceController.java b/src/com/android/settings/deviceinfo/RadioInfoPreferenceController.java new file mode 100644 index 00000000000..35523025cf3 --- /dev/null +++ b/src/com/android/settings/deviceinfo/RadioInfoPreferenceController.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 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.deviceinfo; + +import android.content.Context; +import android.telephony.TelephonyManager; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +public class RadioInfoPreferenceController extends BasePreferenceController { + + private TelephonyManager mTelephonyManager; + + public RadioInfoPreferenceController(Context context, String key) { + super(context, key); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + } + + @Override + public int getAvailabilityStatus() { + return mTelephonyManager.isVoiceCapable() + ? AVAILABLE + : UNSUPPORTED_ON_DEVICE; + } +} diff --git a/src/com/android/settings/deviceinfo/UpdatePreferenceController.java b/src/com/android/settings/deviceinfo/UpdatePreferenceController.java new file mode 100644 index 00000000000..8a9132d82d3 --- /dev/null +++ b/src/com/android/settings/deviceinfo/UpdatePreferenceController.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2017 The LineageOS 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.deviceinfo; + +import android.content.Context; +import androidx.preference.Preference; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; + +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settings.core.PreferenceControllerMixin; + + +public class UpdatePreferenceController extends AbstractPreferenceController implements + PreferenceControllerMixin { + + private static final String KEY_UPDATE_SETTING = "update_settings"; + + public UpdatePreferenceController(Context context) { + super(context); + } + + @Override + public boolean isAvailable() { + String packagename = mContext.getResources().getString( + com.android.settings.R.string.update_package); + try { + ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(packagename, 0); + return true; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + @Override + public String getPreferenceKey() { + return KEY_UPDATE_SETTING; + } +} diff --git a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java index 50b60973d5b..500235f1a92 100644 --- a/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java +++ b/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java @@ -44,6 +44,7 @@ import com.android.settings.deviceinfo.simstatus.SimEidPreferenceController; import com.android.settings.deviceinfo.simstatus.SimStatusPreferenceController; import com.android.settings.deviceinfo.simstatus.SlotSimStatus; +import com.android.settings.deviceinfo.UpdatePreferenceController; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.core.AbstractPreferenceController; @@ -88,7 +89,6 @@ public void onAttach(Context context) { @Override public void onStart() { super.onStart(); - initHeader(); } @Override @@ -125,6 +125,7 @@ private static List buildPreferenceControllers( controllers.add(new FeedbackPreferenceController(fragment, context)); controllers.add(new FccEquipmentIdPreferenceController(context)); controllers.add(new UptimePreferenceController(context, lifecycle)); + controllers.add(new UpdatePreferenceController(context)); Consumer imeiInfoList = imeiKey -> { ImeiInfoPreferenceController imeiRecord = diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceController.java index b022fcf7df2..f7dff9d3d8f 100644 --- a/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceController.java +++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryCycleCountPreferenceController.java @@ -36,7 +36,8 @@ public BatteryCycleCountPreferenceController(Context context, @Override public int getAvailabilityStatus() { - return AVAILABLE; + return mContext.getResources().getBoolean(R.bool.config_show_battery_cycle_count) + ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } @Override @@ -44,7 +45,7 @@ public CharSequence getSummary() { final Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext); final int cycleCount = batteryIntent.getIntExtra(BatteryManager.EXTRA_CYCLE_COUNT, -1); - return cycleCount == -1 + return cycleCount <= 0 ? mContext.getText(R.string.battery_cycle_count_not_available) : Integer.toString(cycleCount); } diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryDesignCapacityPreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryDesignCapacityPreferenceController.java new file mode 100644 index 00000000000..ab40d30607c --- /dev/null +++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryDesignCapacityPreferenceController.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 Paranoid Android + * + * 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.deviceinfo.batteryinfo; + +import android.content.Context; +import android.content.Intent; +import android.os.BatteryManager; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.fuelgauge.BatteryUtils; + +/** + * A controller that manages the information about battery design capacity. + */ +public class BatteryDesignCapacityPreferenceController extends BasePreferenceController { + + public BatteryDesignCapacityPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext); + final int designCapacityUah = + batteryIntent.getIntExtra(BatteryManager.EXTRA_DESIGN_CAPACITY, -1); + + if (designCapacityUah > 0) { + int designCapacity = designCapacityUah / 1_000; + return mContext.getString(R.string.battery_design_capacity_summary, designCapacity); + } + + return mContext.getString(R.string.battery_design_capacity_not_available); + } +} diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryHealthPreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryHealthPreferenceController.java new file mode 100644 index 00000000000..71825f884a8 --- /dev/null +++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryHealthPreferenceController.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 Paranoid Android + * + * 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.deviceinfo.batteryinfo; + +import android.content.Context; +import android.content.Intent; +import android.os.BatteryManager; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.fuelgauge.BatteryUtils; + +/** + * A controller that manages the information about battery health. + */ +public class BatteryHealthPreferenceController extends BasePreferenceController { + + public BatteryHealthPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + final Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext); + final int health = + batteryIntent.getIntExtra( + BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN); + + switch (health) { + case BatteryManager.BATTERY_HEALTH_GOOD: + return mContext.getString(R.string.battery_health_good); + case BatteryManager.BATTERY_HEALTH_OVERHEAT: + return mContext.getString(R.string.battery_health_overheat); + case BatteryManager.BATTERY_HEALTH_DEAD: + return mContext.getString(R.string.battery_health_dead); + case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE: + return mContext.getString(R.string.battery_health_over_voltage); + case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE: + return mContext.getString(R.string.battery_health_unspecified_failure); + case BatteryManager.BATTERY_HEALTH_COLD: + return mContext.getString(R.string.battery_health_cold); + default: + return mContext.getString(R.string.battery_health_unknown); + } + } +} diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryInfoFragment.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryInfoFragment.java index c0170227bae..882901d9b7b 100644 --- a/src/com/android/settings/deviceinfo/batteryinfo/BatteryInfoFragment.java +++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryInfoFragment.java @@ -18,18 +18,23 @@ import android.app.settings.SettingsEnums; import android.content.Context; +import android.os.Bundle; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.search.SearchIndexable; +import com.android.settingslib.widget.FooterPreference; /** A fragment that shows battery hardware information. */ @SearchIndexable public class BatteryInfoFragment extends DashboardFragment { public static final String TAG = "BatteryInfo"; + private static final String KEY_BATTERY_INFO_FOOTER = "battery_info_footer"; + + private FooterPreference mFooterPreference; @Override public int getMetricsCategory() { @@ -41,6 +46,19 @@ protected int getPreferenceScreenResId() { return R.xml.battery_info; } + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + mFooterPreference = findPreference(KEY_BATTERY_INFO_FOOTER); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mFooterPreference.setVisible( + getContext().getResources().getBoolean(R.bool.config_show_battery_cycle_count)); + } + @Override protected String getLogTag() { return TAG; diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryMaximumCapacityPreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryMaximumCapacityPreferenceController.java new file mode 100644 index 00000000000..186945efde8 --- /dev/null +++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryMaximumCapacityPreferenceController.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 Paranoid Android + * + * 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.deviceinfo.batteryinfo; + +import android.content.Context; +import android.content.Intent; +import android.os.BatteryManager; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.fuelgauge.BatteryUtils; + +/** + * A controller that manages the information about battery maximum capacity. + */ +public class BatteryMaximumCapacityPreferenceController extends BasePreferenceController { + + public BatteryMaximumCapacityPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext); + final int maxCapacityUah = + batteryIntent.getIntExtra(BatteryManager.EXTRA_MAXIMUM_CAPACITY, -1); + final int designCapacityUah = + batteryIntent.getIntExtra(BatteryManager.EXTRA_DESIGN_CAPACITY, -1); + + if (maxCapacityUah > 0 && designCapacityUah > 0) { + int maxCapacity = maxCapacityUah / 1_000; + int designCapacity = designCapacityUah / 1_000; + int percentage = (maxCapacity * 100) / designCapacity; + + return mContext.getString( + R.string.battery_maximum_capacity_summary, maxCapacity, percentage); + } + + return mContext.getString(R.string.battery_maximum_capacity_not_available); + } +} diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryTechnologyPreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryTechnologyPreferenceController.java new file mode 100644 index 00000000000..d9c42511cdf --- /dev/null +++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryTechnologyPreferenceController.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 Paranoid Android + * + * 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.deviceinfo.batteryinfo; + +import android.content.Context; +import android.content.Intent; +import android.os.BatteryManager; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.fuelgauge.BatteryUtils; + +/** + * A controller that manages the information about battery technology. + */ +public class BatteryTechnologyPreferenceController extends BasePreferenceController { + + public BatteryTechnologyPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + final Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext); + final String technology = batteryIntent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY); + + return technology != null + ? technology + : mContext.getText(R.string.battery_technology_not_available); + } +} diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryTemperaturePreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryTemperaturePreferenceController.java new file mode 100644 index 00000000000..8049f325114 --- /dev/null +++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryTemperaturePreferenceController.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 Paranoid Android + * + * 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.deviceinfo.batteryinfo; + +import android.content.Context; +import android.content.Intent; +import android.icu.text.MeasureFormat; +import android.icu.util.Measure; +import android.icu.util.MeasureUnit; +import android.os.BatteryManager; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.fuelgauge.BatteryUtils; + +import java.util.Locale; + +/** + * A controller that manages the information about battery temperature. + */ +public class BatteryTemperaturePreferenceController extends BasePreferenceController { + + public BatteryTemperaturePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + final Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext); + final int temperatureTenths = + batteryIntent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1); + + if (temperatureTenths > 0) { + float temperature = temperatureTenths / 10f; + + return MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.SHORT) + .format(new Measure(temperature, MeasureUnit.CELSIUS)); + } + + return mContext.getText(R.string.battery_temperature_not_available); + } +} diff --git a/src/com/android/settings/deviceinfo/batteryinfo/BatteryVoltagePreferenceController.java b/src/com/android/settings/deviceinfo/batteryinfo/BatteryVoltagePreferenceController.java new file mode 100644 index 00000000000..0fc408f4ec2 --- /dev/null +++ b/src/com/android/settings/deviceinfo/batteryinfo/BatteryVoltagePreferenceController.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 Paranoid Android + * + * 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.deviceinfo.batteryinfo; + +import android.content.Context; +import android.content.Intent; +import android.icu.text.MeasureFormat; +import android.icu.util.Measure; +import android.icu.util.MeasureUnit; +import android.os.BatteryManager; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.fuelgauge.BatteryUtils; + +import java.util.Locale; + +/** + * A controller that manages the information about battery voltage. + */ +public class BatteryVoltagePreferenceController extends BasePreferenceController { + + public BatteryVoltagePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + final Intent batteryIntent = BatteryUtils.getBatteryIntent(mContext); + final int voltageMillivolts = batteryIntent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1); + + if (voltageMillivolts > 0) { + float voltage = voltageMillivolts / 1_000f; + + return MeasureFormat.getInstance(Locale.getDefault(), MeasureFormat.FormatWidth.SHORT) + .format(new Measure(voltage, MeasureUnit.VOLT)); + } + + return mContext.getText(R.string.battery_voltage_not_available); + } +} diff --git a/src/com/android/settings/deviceinfo/firmwareversion/BasebandVersionPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/BasebandVersionPreferenceController.java index dd3d560282a..a81993b2e64 100644 --- a/src/com/android/settings/deviceinfo/firmwareversion/BasebandVersionPreferenceController.java +++ b/src/com/android/settings/deviceinfo/firmwareversion/BasebandVersionPreferenceController.java @@ -18,6 +18,7 @@ import android.content.Context; import android.os.SystemProperties; +import android.text.TextUtils; import androidx.annotation.VisibleForTesting; @@ -41,7 +42,13 @@ public int getAvailabilityStatus() { @Override public CharSequence getSummary() { - return SystemProperties.get(BASEBAND_PROPERTY, + String baseband = SystemProperties.get(BASEBAND_PROPERTY, mContext.getString(R.string.device_info_default)); + for (String str : baseband.split(",")) { + if (!TextUtils.isEmpty(str)) { + return str; + } + } + return baseband; } } diff --git a/src/com/android/settings/deviceinfo/firmwareversion/DerpFestLogoPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/DerpFestLogoPreferenceController.java new file mode 100644 index 00000000000..2f941e37260 --- /dev/null +++ b/src/com/android/settings/deviceinfo/firmwareversion/DerpFestLogoPreferenceController.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.deviceinfo.firmwareversion; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.SystemProperties; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; + +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +public class DerpFestLogoPreferenceController extends BasePreferenceController { + + private static final Uri INTENT_URI_DATA = Uri.parse("https://derpfest.org/"); + private static final String TAG = "DerpFestLogoPreferenceCtrl"; + + private final PackageManager mPackageManager; + + public DerpFestLogoPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mPackageManager = mContext.getPackageManager(); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return false; + } + + final Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(INTENT_URI_DATA); + if (mPackageManager.queryIntentActivities(intent, 0).isEmpty()) { + // Don't send out the intent to stop crash + Log.w(TAG, "queryIntentActivities() returns empty"); + return true; + } + + mContext.startActivity(intent); + return true; + } +} diff --git a/src/com/android/settings/deviceinfo/firmwareversion/DerpFestVersionPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/DerpFestVersionPreferenceController.java new file mode 100644 index 00000000000..6f0d5fbd240 --- /dev/null +++ b/src/com/android/settings/deviceinfo/firmwareversion/DerpFestVersionPreferenceController.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2019 The LineageOS 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.deviceinfo.firmwareversion; + +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.slices.Sliceable; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtilsInternal; + +public class DerpFestVersionPreferenceController extends BasePreferenceController { + + private static final String TAG = "derpfestVersionDialogCtrl"; + private static final int DELAY_TIMER_MILLIS = 500; + private static final int ACTIVITY_TRIGGER_COUNT = 3; + + private static final String KEY_DERP_VERSION_PROP = "ro.derp.version"; + + private final UserManager mUserManager; + private final long[] mHits = new long[ACTIVITY_TRIGGER_COUNT]; + + private RestrictedLockUtils.EnforcedAdmin mFunDisallowedAdmin; + private boolean mFunDisallowedBySystem; + + public DerpFestVersionPreferenceController(Context context, String key) { + super(context, key); + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + initializeAdminPermissions(); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean useDynamicSliceSummary() { + return true; + } + + @Override + public boolean isSliceable() { + return true; + } + + @Override + public CharSequence getSummary() { + return SystemProperties.get(KEY_DERP_VERSION_PROP, + mContext.getString(R.string.unknown)); + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return false; + } + if (Utils.isMonkeyRunning()) { + return false; + } + arrayCopy(); + mHits[mHits.length - 1] = SystemClock.uptimeMillis(); + if (mHits[0] >= (SystemClock.uptimeMillis() - DELAY_TIMER_MILLIS)) { + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_FUN)) { + if (mFunDisallowedAdmin != null && !mFunDisallowedBySystem) { + RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, + mFunDisallowedAdmin); + } + Log.d(TAG, "Sorry, no fun for you!"); + return true; + } + + final Intent intent = new Intent(Intent.ACTION_MAIN) + .setClassName( + "android", com.android.internal.app.PlatLogoActivity.class.getName()); + try { + mContext.startActivity(intent); + } catch (Exception e) { + Log.e(TAG, "Unable to start activity " + intent.toString()); + } + } + return true; + } + + /** + * Copies the array onto itself to remove the oldest hit. + */ + @VisibleForTesting + void arrayCopy() { + System.arraycopy(mHits, 1, mHits, 0, mHits.length - 1); + } + + @VisibleForTesting + void initializeAdminPermissions() { + mFunDisallowedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced( + mContext, UserManager.DISALLOW_FUN, UserHandle.myUserId()); + mFunDisallowedBySystem = RestrictedLockUtilsInternal.hasBaseUserRestriction( + mContext, UserManager.DISALLOW_FUN, UserHandle.myUserId()); + } +} diff --git a/src/com/android/settings/deviceinfo/firmwareversion/KernelVersionPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/KernelVersionPreferenceController.java index 0500c89371a..cc7cbf24721 100644 --- a/src/com/android/settings/deviceinfo/firmwareversion/KernelVersionPreferenceController.java +++ b/src/com/android/settings/deviceinfo/firmwareversion/KernelVersionPreferenceController.java @@ -20,9 +20,21 @@ import com.android.settings.core.BasePreferenceController; import com.android.settingslib.DeviceInfoUtils; +import androidx.preference.Preference; +import android.text.TextUtils; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; public class KernelVersionPreferenceController extends BasePreferenceController { + private static final String KEY_KERNEL_VERSION = "kernel_version"; + private static final String FILENAME_PROC_VERSION = "/proc/version"; + private static final String LOG_TAG = "KernelVersionPreferenceController"; + private boolean fullKernelVersion = false; + public KernelVersionPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); } @@ -36,4 +48,51 @@ public int getAvailabilityStatus() { public CharSequence getSummary() { return DeviceInfoUtils.getFormattedKernelVersion(mContext); } + + @Override + public String getPreferenceKey() { + return KEY_KERNEL_VERSION; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), KEY_KERNEL_VERSION)) { + return false; + } + if(fullKernelVersion) { + preference.setSummary(DeviceInfoUtils.getFormattedKernelVersion(mContext)); + fullKernelVersion = false; + } else { + preference.setSummary(getFullKernelVersion()); + fullKernelVersion = true; + } + return false; + } + + private String getFullKernelVersion() { + String procVersionStr; + try { + procVersionStr = readLine(FILENAME_PROC_VERSION); + return procVersionStr; + } catch (IOException e) { + Log.e(LOG_TAG, + "IO Exception when getting kernel version for Device Info screen", e); + return "Unavailable"; + } + } + + /** + * Reads a line from the specified file. + * @param filename the file to read from + * @return the first line, if any. + * @throws IOException if the file couldn't be read + */ + private static String readLine(String filename) throws IOException { + BufferedReader reader = new BufferedReader(new FileReader(filename), 256); + try { + return reader.readLine(); + } finally { + reader.close(); + } + } } diff --git a/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java index 4c02feb044f..4171a9c4fea 100644 --- a/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java +++ b/src/com/android/settings/deviceinfo/firmwareversion/MainlineModuleVersionPreferenceController.java @@ -74,8 +74,7 @@ public int getAvailabilityStatus() { } private void initModules() { - final String moduleProvider = mContext.getString( - com.android.internal.R.string.config_defaultModuleMetadataProvider); + final String moduleProvider = "com.google.android.modulemetadata"; if (!TextUtils.isEmpty(moduleProvider)) { try { mModuleVersion = diff --git a/src/com/android/settings/deviceinfo/firmwareversion/SelinuxStatusPreferenceController.java b/src/com/android/settings/deviceinfo/firmwareversion/SelinuxStatusPreferenceController.java new file mode 100644 index 00000000000..38b1976aa07 --- /dev/null +++ b/src/com/android/settings/deviceinfo/firmwareversion/SelinuxStatusPreferenceController.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 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.deviceinfo.firmwareversion; + +import android.content.Context; +import android.os.SELinux; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +public class SelinuxStatusPreferenceController extends BasePreferenceController { + + private static final String TAG = "SelinuxStatusCtrl"; + + public SelinuxStatusPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + if (!SELinux.isSELinuxEnabled()) { + return (CharSequence) mContext.getString(R.string.selinux_status_disabled); + } else if (!SELinux.isSELinuxEnforced()) { + return (CharSequence) mContext.getString(R.string.selinux_status_permissive); + } else { + return (CharSequence) mContext.getString(R.string.selinux_status_enforcing); + } + } +} diff --git a/src/com/android/settings/deviceinfo/hardwareinfo/SoCModelPreferenceController.java b/src/com/android/settings/deviceinfo/hardwareinfo/SoCModelPreferenceController.java new file mode 100644 index 00000000000..04bbba2c6e6 --- /dev/null +++ b/src/com/android/settings/deviceinfo/hardwareinfo/SoCModelPreferenceController.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.deviceinfo.hardwareinfo; + +import android.content.Context; +import android.os.Build; +import android.os.SystemProperties; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.slices.Sliceable; + +public class SoCModelPreferenceController extends BasePreferenceController { + + public SoCModelPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return mContext.getResources().getBoolean(R.bool.config_show_device_model) + ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public boolean useDynamicSliceSummary() { + return true; + } + + @Override + public CharSequence getSummary() { + if (!Build.SOC_MODEL.equals(Build.UNKNOWN)) { + if (!Build.SOC_MANUFACTURER.equals(Build.UNKNOWN)) { + return Build.SOC_MANUFACTURER + " " + Build.SOC_MODEL; + } + return Build.SOC_MODEL; + } + return SystemProperties.get("ro.board.platform"); + } +} diff --git a/src/com/android/settings/deviceinfo/hardwareinfo/TotalRAMPreferenceController.java b/src/com/android/settings/deviceinfo/hardwareinfo/TotalRAMPreferenceController.java new file mode 100644 index 00000000000..9009610af14 --- /dev/null +++ b/src/com/android/settings/deviceinfo/hardwareinfo/TotalRAMPreferenceController.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2023 The LeafOS 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.deviceinfo.hardwareinfo; + +import android.content.Context; +import android.text.format.Formatter; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.applications.ProcStatsData; +import com.android.settings.applications.ProcessStatsBase; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.utils.ThreadUtils; + +public class TotalRAMPreferenceController extends BasePreferenceController implements + PreferenceControllerMixin { + + private ProcStatsData mProcStatsData; + private PreferenceScreen mPreferenceScreen; + + public TotalRAMPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return mContext.getResources().getBoolean(R.bool.config_show_device_model) + ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mProcStatsData = getProcStatsData(); + mPreferenceScreen = screen; + setDuration(); + } + + @Override + public void updateState(Preference preference) { + // This is posted on the background thread to speed up fragment launch time for dev options + // mProcStasData.refreshStats(true) takes ~20ms to run. + ThreadUtils.postOnBackgroundThread(() -> { + mProcStatsData.refreshStats(true); + final ProcStatsData.MemInfo memInfo = mProcStatsData.getMemInfo(); + final String totalResult = Formatter.formatShortFileSize(mContext, + (long) memInfo.realTotalRam); + ThreadUtils.postOnMainThread( + () -> mPreferenceScreen.findPreference(mPreferenceKey).setSummary(totalResult)); + }); + } + + @VisibleForTesting + void setDuration() { + mProcStatsData.setDuration(ProcessStatsBase.sDurations[0] /* 3 hours */); + } + + @VisibleForTesting + ProcStatsData getProcStatsData() { + return new ProcStatsData(mContext, false); + } +} diff --git a/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java b/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java index ff55184741a..2045b9661c0 100644 --- a/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java +++ b/src/com/android/settings/deviceinfo/imei/ImeiInfoPreferenceController.java @@ -33,6 +33,7 @@ import com.android.settings.R; import com.android.settings.core.BasePreferenceController; +import com.android.settings.deviceinfo.PhoneNumberSummaryPreference; import com.android.settings.deviceinfo.simstatus.SlotSimStatus; import com.android.settings.network.SubscriptionUtil; import com.android.settingslib.Utils; @@ -102,7 +103,6 @@ public void displayPreference(PreferenceScreen screen) { multiImeiPreference.setKey(DEFAULT_KEY + (1 + simSlotNumber)); multiImeiPreference.setEnabled(true); multiImeiPreference.setCopyingEnabled(true); - category.addPreference(multiImeiPreference); } } @@ -112,6 +112,11 @@ public void updateState(Preference preference) { updatePreference(preference, keyToSlotIndex(preference.getKey())); } + @Override + public CharSequence getSummary() { + return mContext.getString(R.string.device_info_protected_single_press); + } + private CharSequence getSummary(int simSlot) { final int phoneType = getPhoneType(simSlot); return phoneType == PHONE_TYPE_CDMA ? mTelephonyManager.getMeid(simSlot) @@ -145,12 +150,8 @@ public boolean useDynamicSliceSummary() { @VisibleForTesting protected void updatePreference(Preference preference, int simSlot) { - if (simSlot < 0) { - preference.setVisible(false); - return; - } preference.setTitle(getTitle(simSlot)); - preference.setSummary(getSummary(simSlot)); + preference.setSummary(getSummary()); } private CharSequence getTitleForGsmPhone(int simSlot, boolean isPrimaryImei) { @@ -194,6 +195,6 @@ public int getPhoneType(int slotIndex) { @VisibleForTesting Preference createNewPreference(Context context) { - return new Preference(context); + return new PhoneNumberSummaryPreference(context); } } diff --git a/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.kt b/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.kt index f765d8c6cca..e0376dc1944 100644 --- a/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.kt +++ b/src/com/android/settings/deviceinfo/simstatus/SimEidPreferenceController.kt @@ -92,7 +92,6 @@ class SimEidPreferenceController(context: Context, preferenceKey: String) : } preference.title = title preference.dialogTitle = title - preference.summary = eid updateDialog() } } @@ -131,6 +130,9 @@ class SimEidPreferenceController(context: Context, preferenceKey: String) : val qrCodeView = dialog.requireViewById(R.id.esim_id_qrcode) qrCodeView.setImageBitmap(getEidQrCode(eid)) + + // After "Tap to show", eid is displayed on preference. + preference.summary = textView.text } override fun handlePreferenceTreeClick(preference: Preference): Boolean { diff --git a/src/com/android/settings/display/AODSchedule.java b/src/com/android/settings/display/AODSchedule.java new file mode 100644 index 00000000000..dffab166245 --- /dev/null +++ b/src/com/android/settings/display/AODSchedule.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2021 Yet Another AOSP 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.display; + +import static com.android.settings.display.AODSchedulePreferenceController.MODE_DISABLED; +import static com.android.settings.display.AODSchedulePreferenceController.MODE_NIGHT; +import static com.android.settings.display.AODSchedulePreferenceController.MODE_TIME; +import static com.android.settings.display.AODSchedulePreferenceController.MODE_MIXED_SUNSET; +import static com.android.settings.display.AODSchedulePreferenceController.MODE_MIXED_SUNRISE; + +import android.app.TimePickerDialog; +import android.os.Bundle; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.format.DateFormat; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +import java.time.format.DateTimeFormatter; +import java.time.LocalTime; + +@SearchIndexable +public class AODSchedule extends SettingsPreferenceFragment implements + Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.always_on_display_schedule); + + private static final String MODE_KEY = "doze_always_on_auto_mode"; + private static final String SINCE_PREF_KEY = "doze_always_on_auto_since"; + private static final String TILL_PREF_KEY = "doze_always_on_auto_till"; + + private ListPreference mModePref; + private Preference mSincePref; + private Preference mTillPref; + + @Override + protected int getPreferenceScreenResId() { + return R.xml.always_on_display_schedule; + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DERP; + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + super.onCreatePreferences(savedInstanceState, rootKey); + + mSincePref = findPreference(SINCE_PREF_KEY); + mSincePref.setOnPreferenceClickListener(this); + mTillPref = findPreference(TILL_PREF_KEY); + mTillPref.setOnPreferenceClickListener(this); + + final int mode = Settings.Secure.getIntForUser(getActivity().getContentResolver(), + MODE_KEY, MODE_DISABLED, UserHandle.USER_CURRENT); + mModePref = findPreference(MODE_KEY); + mModePref.setValue(String.valueOf(mode)); + mModePref.setSummary(mModePref.getEntry()); + mModePref.setOnPreferenceChangeListener(this); + + updateTimeEnablement(mode); + updateTimeSummary(mode); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object objValue) { + final int value = Integer.parseInt((String) objValue); + final int index = mModePref.findIndexOfValue((String) objValue); + mModePref.setSummary(mModePref.getEntries()[index]); + Settings.Secure.putIntForUser(getActivity().getContentResolver(), + MODE_KEY, value, UserHandle.USER_CURRENT); + updateTimeEnablement(value); + updateTimeSummary(value); + return true; + } + + @Override + public boolean onPreferenceClick(Preference preference) { + final String[] times = getCustomTimeSetting(); + final boolean isSince = preference == mSincePref; + final int hour, minute; + if (isSince) { + final String[] sinceValues = times[0].split(":", 0); + hour = Integer.parseInt(sinceValues[0]); + minute = Integer.parseInt(sinceValues[1]); + } else { + final String[] tillValues = times[1].split(":", 0); + hour = Integer.parseInt(tillValues[0]); + minute = Integer.parseInt(tillValues[1]); + } + final TimePickerDialog.OnTimeSetListener listener = (view, hourOfDay, minute1) -> + updateTimeSetting(isSince, hourOfDay, minute1); + final TimePickerDialog dialog = new TimePickerDialog(getContext(), listener, + hour, minute, DateFormat.is24HourFormat(getContext())); + dialog.show(); + return true; + } + + private String[] getCustomTimeSetting() { + final String value = Settings.Secure.getStringForUser(getActivity().getContentResolver(), + Settings.Secure.DOZE_ALWAYS_ON_AUTO_TIME, UserHandle.USER_CURRENT); + if (value == null || value.isEmpty()) return new String[] { "20:00", "07:00" }; + return value.split(",", 0); + } + + private void updateTimeEnablement(int mode) { + mSincePref.setEnabled(mode == MODE_TIME || mode == MODE_MIXED_SUNRISE); + mTillPref.setEnabled(mode == MODE_TIME || mode == MODE_MIXED_SUNSET); + } + + private void updateTimeSummary(int mode) { + updateTimeSummary(getCustomTimeSetting(), mode); + } + + private void updateTimeSummary(String[] times, int mode) { + if (mode == MODE_DISABLED) { + mSincePref.setSummary("-"); + mTillPref.setSummary("-"); + return; + } + + if (mode == MODE_NIGHT) { + mSincePref.setSummary(R.string.always_on_display_schedule_sunset); + mTillPref.setSummary(R.string.always_on_display_schedule_sunrise); + return; + } + + final String outputFormat = DateFormat.is24HourFormat(getContext()) ? "HH:mm" : "hh:mm a"; + final DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern(outputFormat); + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm"); + final LocalTime sinceDT = LocalTime.parse(times[0], formatter); + final LocalTime tillDT = LocalTime.parse(times[1], formatter); + + if (mode == MODE_MIXED_SUNSET) { + mSincePref.setSummary(R.string.always_on_display_schedule_sunset); + mTillPref.setSummary(tillDT.format(outputFormatter)); + } else if (mode == MODE_MIXED_SUNRISE) { + mTillPref.setSummary(R.string.always_on_display_schedule_sunrise); + mSincePref.setSummary(sinceDT.format(outputFormatter)); + } else { + mSincePref.setSummary(sinceDT.format(outputFormatter)); + mTillPref.setSummary(tillDT.format(outputFormatter)); + } + } + + private void updateTimeSetting(boolean since, int hour, int minute) { + final String[] times = getCustomTimeSetting(); + String nHour = ""; + String nMinute = ""; + if (hour < 10) nHour += "0"; + if (minute < 10) nMinute += "0"; + nHour += String.valueOf(hour); + nMinute += String.valueOf(minute); + times[since ? 0 : 1] = nHour + ":" + nMinute; + Settings.Secure.putStringForUser(getActivity().getContentResolver(), + Settings.Secure.DOZE_ALWAYS_ON_AUTO_TIME, + times[0] + "," + times[1], UserHandle.USER_CURRENT); + updateTimeSummary(times, Integer.parseInt(mModePref.getValue())); + } +} diff --git a/src/com/android/settings/display/AODSchedulePreferenceController.kt b/src/com/android/settings/display/AODSchedulePreferenceController.kt new file mode 100644 index 00000000000..0b62f22518a --- /dev/null +++ b/src/com/android/settings/display/AODSchedulePreferenceController.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 Yet Another AOSP Project + * Copyright (C) 2022 FlamingoOS 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.display + +import android.content.Context +import android.hardware.display.AmbientDisplayConfiguration +import android.os.SystemProperties +import android.os.UserHandle +import android.provider.Settings + +import com.android.settings.R +import com.android.settings.core.BasePreferenceController + +class AODSchedulePreferenceController( + context: Context, + key: String, +) : BasePreferenceController(context, key) { + + private var ambientDisplayConfig: AmbientDisplayConfiguration? = null + + override fun getAvailabilityStatus() = + if (isAODAvailable()) AVAILABLE else UNSUPPORTED_ON_DEVICE + + override fun getSummary(): CharSequence { + val mode = Settings.Secure.getIntForUser(mContext.contentResolver, + Settings.Secure.DOZE_ALWAYS_ON_AUTO_MODE, 0, UserHandle.USER_CURRENT) + return when (mode) { + MODE_NIGHT -> mContext.getString(R.string.night_display_auto_mode_twilight) + MODE_TIME -> mContext.getString(R.string.night_display_auto_mode_custom) + MODE_MIXED_SUNSET -> mContext.getString(R.string.always_on_display_schedule_mixed_sunset) + MODE_MIXED_SUNRISE -> mContext.getString(R.string.always_on_display_schedule_mixed_sunrise) + else -> mContext.getString(R.string.disabled) + } + } + + fun setConfig(config: AmbientDisplayConfiguration): AODSchedulePreferenceController { + ambientDisplayConfig = config + return this + } + + private fun getConfig(): AmbientDisplayConfiguration { + if (ambientDisplayConfig == null) { + ambientDisplayConfig = AmbientDisplayConfiguration(mContext) + } + return ambientDisplayConfig!! + } + + private fun isAODAvailable(): Boolean { + return getConfig().alwaysOnAvailableForUser(UserHandle.myUserId()) && + !SystemProperties.getBoolean(PROP_AWARE_AVAILABLE, false) + } + + companion object { + const val MODE_DISABLED = 0 + const val MODE_NIGHT = 1 + const val MODE_TIME = 2 + const val MODE_MIXED_SUNSET = 3 + const val MODE_MIXED_SUNRISE = 4 + + private const val PROP_AWARE_AVAILABLE = "ro.vendor.aware_available" + } +} diff --git a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java index 245803493e2..bc186ba136c 100644 --- a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java +++ b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java @@ -74,7 +74,7 @@ public int getSliceHighlightMenuRes() { @Override public boolean isChecked() { - return getConfig().alwaysOnEnabled(MY_USER); + return getConfig().alwaysOnEnabledSetting(MY_USER); } @Override diff --git a/src/com/android/settings/display/AutoBrightnessObserver.java b/src/com/android/settings/display/AutoBrightnessObserver.java new file mode 100644 index 00000000000..ba27b683527 --- /dev/null +++ b/src/com/android/settings/display/AutoBrightnessObserver.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 Yet Another AOSP 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.display; + +import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE; + +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; + +public class AutoBrightnessObserver { + private final ContentObserver mContentObserver; + private final Context mContext; + private Runnable mCallback; + + public AutoBrightnessObserver(Context context) { + mContext = context; + mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + mCallback.run(); + } + }; + } + + public void subscribe(Runnable callback) { + mCallback = callback; + mContext.getContentResolver().registerContentObserver( + Settings.System.getUriFor(SCREEN_BRIGHTNESS_MODE), + false, mContentObserver); + } + + public void unsubscribe() { + mContext.getContentResolver().unregisterContentObserver(mContentObserver); + } +} diff --git a/src/com/android/settings/display/AutoBrightnessOneShotPreferenceController.java b/src/com/android/settings/display/AutoBrightnessOneShotPreferenceController.java new file mode 100644 index 00000000000..0decc50aced --- /dev/null +++ b/src/com/android/settings/display/AutoBrightnessOneShotPreferenceController.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 The LineageOS 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.display; + +import android.content.Context; + +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.core.TogglePreferenceController; + +public class AutoBrightnessOneShotPreferenceController extends TogglePreferenceController { + + public AutoBrightnessOneShotPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public boolean isChecked() { + return Settings.System.getInt(mContext.getContentResolver(), + Settings.System.AUTO_BRIGHTNESS_ONE_SHOT, 0) == 1; + } + + @Override + public boolean setChecked(boolean isChecked) { + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.AUTO_BRIGHTNESS_ONE_SHOT, isChecked ? 1 : 0); + return true; + } + + @Override + @AvailabilityStatus + public int getAvailabilityStatus() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_automatic_brightness_available) + ? AVAILABLE_UNSEARCHABLE + : UNSUPPORTED_ON_DEVICE; + } + + @Override + public CharSequence getSummary() { + return mContext.getText(isChecked() + ? R.string.auto_brightness_summary_on + : R.string.auto_brightness_summary_off); + } + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_display; + } +} diff --git a/src/com/android/settings/display/AutoBrightnessPreference.java b/src/com/android/settings/display/AutoBrightnessPreference.java new file mode 100644 index 00000000000..7a8344391b5 --- /dev/null +++ b/src/com/android/settings/display/AutoBrightnessPreference.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 Yet Another AOSP 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.display; + +import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE; +import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; + +import android.content.Context; +import android.provider.Settings; +import android.util.AttributeSet; + +import com.android.settings.display.AutoBrightnessObserver; + +import com.android.settingslib.PrimarySwitchPreference; + +/** + * component for the display auto brightness + */ +public class AutoBrightnessPreference extends PrimarySwitchPreference { + + private final AutoBrightnessObserver mAutoBrightnessObserver; + + private final Runnable mCallback = () -> { + final int value = Settings.System.getInt( + getContext().getContentResolver(), + SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + setChecked(value == SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + }; + + public AutoBrightnessPreference(Context context, AttributeSet attrs) { + super(context, attrs); + mAutoBrightnessObserver = new AutoBrightnessObserver(context); + } + + @Override + public void onAttached() { + super.onAttached(); + mAutoBrightnessObserver.subscribe(mCallback); + } + + @Override + public void onDetached() { + super.onDetached(); + mAutoBrightnessObserver.unsubscribe(); + } +} diff --git a/src/com/android/settings/display/AutoBrightnessSettings.java b/src/com/android/settings/display/AutoBrightnessSettings.java index 0c594730d5c..43ad7fa7415 100644 --- a/src/com/android/settings/display/AutoBrightnessSettings.java +++ b/src/com/android/settings/display/AutoBrightnessSettings.java @@ -16,12 +16,18 @@ package com.android.settings.display; +import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE; +import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; + import android.app.settings.SettingsEnums; import android.os.Bundle; +import android.provider.Settings; +import com.android.settings.display.AutoBrightnessObserver; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.widget.SettingsMainSwitchPreference; import com.android.settingslib.search.SearchIndexable; @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) @@ -29,9 +35,33 @@ public class AutoBrightnessSettings extends DashboardFragment { private static final String TAG = "AutoBrightnessSettings"; + private AutoBrightnessObserver mAutoBrightnessObserver; + + private final Runnable mCallback = () -> { + final int value = Settings.System.getInt( + getContext().getContentResolver(), + SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + SettingsMainSwitchPreference pref = findPreference("auto_brightness"); + if (pref == null) return; + pref.setChecked(value == SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + }; + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); + mAutoBrightnessObserver = new AutoBrightnessObserver(getContext()); + } + + @Override + public void onStart() { + super.onStart(); + mAutoBrightnessObserver.subscribe(mCallback); + } + + @Override + public void onStop() { + super.onStop(); + mAutoBrightnessObserver.unsubscribe(); } @Override diff --git a/src/com/android/settings/display/AutoRotatePreferenceController.java b/src/com/android/settings/display/AutoRotatePreferenceController.java index ec2592d38b5..707579d0bcd 100644 --- a/src/com/android/settings/display/AutoRotatePreferenceController.java +++ b/src/com/android/settings/display/AutoRotatePreferenceController.java @@ -18,6 +18,7 @@ import android.text.TextUtils; import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; import com.android.internal.view.RotationPolicy; import com.android.settings.R; @@ -29,12 +30,17 @@ import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; +import org.derpfest.support.preferences.SystemSettingSwitchPreference; + public class AutoRotatePreferenceController extends TogglePreferenceController implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener, LifecycleObserver, OnResume, OnPause { + private final String FLOATING_BUTTON_PREF_KEY = "enable_floating_rotation_button"; + private final MetricsFeatureProvider mMetricsFeatureProvider; private Preference mPreference; + private SystemSettingSwitchPreference mFloatingPref; private RotationPolicy.RotationPolicyListener mRotationPolicyListener; public AutoRotatePreferenceController(Context context, String key) { @@ -42,9 +48,16 @@ public AutoRotatePreferenceController(Context context, String key) { mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider(); } + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mFloatingPref = screen.findPreference(FLOATING_BUTTON_PREF_KEY); + } + @Override public void updateState(Preference preference) { mPreference = preference; + if (mFloatingPref != null) mFloatingPref.setEnabled(!isChecked()); super.updateState(preference); } @@ -105,6 +118,7 @@ public boolean setChecked(boolean isChecked) { isLocked); RotationPolicy.setRotationLock(mContext, isLocked, /* caller= */ "AutoRotatePreferenceController#setChecked"); + if (mFloatingPref != null) mFloatingPref.setEnabled(!isChecked); return true; } } diff --git a/src/com/android/settings/display/BatteryPercentagePreferenceController.java b/src/com/android/settings/display/BatteryPercentagePreferenceController.java index a7113b3d490..398c8279912 100644 --- a/src/com/android/settings/display/BatteryPercentagePreferenceController.java +++ b/src/com/android/settings/display/BatteryPercentagePreferenceController.java @@ -56,12 +56,7 @@ public void displayPreference(PreferenceScreen screen) { @Override public int getAvailabilityStatus() { - if (!Utils.isBatteryPresent(mContext)) { - return CONDITIONALLY_UNAVAILABLE; - } - return mContext.getResources().getBoolean( - R.bool.config_battery_percentage_setting_available) ? AVAILABLE - : UNSUPPORTED_ON_DEVICE; + return UNSUPPORTED_ON_DEVICE; } @Override diff --git a/src/com/android/settings/display/ColorModePreferenceController.java b/src/com/android/settings/display/ColorModePreferenceController.java index 6cd4867b588..14a2241481c 100644 --- a/src/com/android/settings/display/ColorModePreferenceController.java +++ b/src/com/android/settings/display/ColorModePreferenceController.java @@ -28,8 +28,11 @@ public ColorModePreferenceController(Context context, String key) { @Override public int getAvailabilityStatus() { + final int[] availableColorModes = mContext.getResources().getIntArray( + com.android.internal.R.array.config_availableColorModes); return mContext.getSystemService(ColorDisplayManager.class) .isDeviceColorManaged() + && availableColorModes.length > 0 && !ColorDisplayManager.areAccessibilityTransformsEnabled(mContext) ? AVAILABLE : DISABLED_FOR_USER; } diff --git a/src/com/android/settings/display/ControlsPrivacyPreferenceController.java b/src/com/android/settings/display/ControlsPrivacyPreferenceController.java index b9a2bfa84f6..1345bf9267f 100644 --- a/src/com/android/settings/display/ControlsPrivacyPreferenceController.java +++ b/src/com/android/settings/display/ControlsPrivacyPreferenceController.java @@ -92,7 +92,7 @@ private boolean isSecure() { .getSecurityFeatureProvider() .getLockPatternUtils(mContext); final int userId = UserHandle.myUserId(); - return utils.isSecure(userId); + return !utils.isLockScreenDisabled(userId); } private boolean isControlsAvailable() { diff --git a/src/com/android/settings/display/DisplayRotationPreferenceController.java b/src/com/android/settings/display/DisplayRotationPreferenceController.java new file mode 100644 index 00000000000..31713b7cb43 --- /dev/null +++ b/src/com/android/settings/display/DisplayRotationPreferenceController.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 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.display; + +import android.content.Context; + +import androidx.preference.Preference; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.PreferenceControllerMixin; + +import com.android.internal.view.RotationPolicy; + +import com.android.settings.R; + +public class DisplayRotationPreferenceController extends BasePreferenceController implements + PreferenceControllerMixin { + + public DisplayRotationPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void updateState(Preference preference) { + String summary = RotationPolicy.isRotationLocked(mContext) ? + mContext.getString(R.string.display_rotation_disabled) : + mContext.getString(R.string.display_rotation_enabled); + preference.setSummary(summary); + } +} diff --git a/src/com/android/settings/display/DozeOnChargePreferenceController.kt b/src/com/android/settings/display/DozeOnChargePreferenceController.kt new file mode 100644 index 00000000000..bcecad88f06 --- /dev/null +++ b/src/com/android/settings/display/DozeOnChargePreferenceController.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * Copyright (C) 2022 FlamingoOS 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.display + +import android.content.Context +import android.database.ContentObserver +import android.hardware.display.AmbientDisplayConfiguration +import android.os.Handler +import android.os.Looper +import android.os.UserHandle +import android.provider.Settings + +import androidx.lifecycle.Lifecycle.Event +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import androidx.preference.Preference +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreferenceCompat + +import com.android.settings.R +import com.android.settings.core.TogglePreferenceController +import com.android.settingslib.core.lifecycle.Lifecycle + +class DozeOnChargePreferenceController( + context: Context, + lifecycle: Lifecycle?, +) : TogglePreferenceController(context, KEY), + LifecycleEventObserver { + + private var config: AmbientDisplayConfiguration = AmbientDisplayConfiguration(context) + private var preference: Preference? = null + + private val settingsObserver = object : ContentObserver( + Handler(Looper.getMainLooper()) + ) { + override fun onChange(selfChange: Boolean) { + preference?.let{ updateState(it) } + } + } + + init { + lifecycle?.addObserver(this) + } + + override fun getAvailabilityStatus(): Int { + return if (config.alwaysOnAvailableForUser(UserHandle.USER_CURRENT)) { + if (config.alwaysOnEnabledSetting(UserHandle.USER_CURRENT)) + DISABLED_DEPENDENT_SETTING + else + AVAILABLE + } else { + UNSUPPORTED_ON_DEVICE + } + } + + override fun displayPreference(screen: PreferenceScreen) { + super.displayPreference(screen) + preference = screen.findPreference(preferenceKey) + } + + override fun isChecked(): Boolean = + Settings.Secure.getIntForUser(mContext.contentResolver, + Settings.Secure.DOZE_ON_CHARGE, 0, UserHandle.USER_CURRENT) == 1 + + override fun setChecked(isChecked: Boolean): Boolean = + Settings.Secure.putIntForUser( + mContext.contentResolver, + Settings.Secure.DOZE_ON_CHARGE, + if (isChecked) 1 else 0, + UserHandle.USER_CURRENT + ) + + override fun getSliceHighlightMenuRes() = R.string.menu_key_display + + override fun updateState(preference: Preference) { + preference.setEnabled(getAvailabilityStatus() == AVAILABLE) + super.updateState(preference) + } + + override fun onStateChanged(owner: LifecycleOwner, event: Event) { + if (event == Event.ON_START) { + mContext.contentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.DOZE_ALWAYS_ON), + false, + settingsObserver + ) + } else if (event == Event.ON_STOP) { + mContext.contentResolver.unregisterContentObserver(settingsObserver) + } + } + + fun setConfig( + config: AmbientDisplayConfiguration + ): DozeOnChargePreferenceController { + this.config = config + return this + } + + companion object { + private const val KEY = "doze_on_charge" + } +} diff --git a/src/com/android/settings/display/EnableBlursPreferenceController.java b/src/com/android/settings/display/EnableBlursPreferenceController.java new file mode 100644 index 00000000000..2776f0d6121 --- /dev/null +++ b/src/com/android/settings/display/EnableBlursPreferenceController.java @@ -0,0 +1,116 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.display; + +import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.PowerManager; +import android.provider.Settings; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreferenceCompat; + +import com.android.settings.R; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +/** + * Controller that toggles window blurs on devices that support it. + */ +public final class EnableBlursPreferenceController extends TogglePreferenceController + implements Preference.OnPreferenceChangeListener, PreferenceControllerMixin, + LifecycleObserver, OnStart, OnStop { + + private Preference mPreference; + private final PowerManager mPowerManager; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mPreference != null) + updateState(mPreference); + } + }; + + public EnableBlursPreferenceController(Context context, String key) { + super(context, key); + mPowerManager = context.getSystemService(PowerManager.class); + } + + @Override + public void onStart() { + mContext.registerReceiver(mReceiver, + new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); + } + + @Override + public void onStop() { + mContext.unregisterReceiver(mReceiver); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public CharSequence getSummary() { + return mContext.getString(mPowerManager.isPowerSaveMode() + ? R.string.dark_ui_mode_disabled_summary_dark_theme_on + : R.string.enable_blurs_on_windows_summary); + } + + @Override + public int getAvailabilityStatus() { + return CROSS_WINDOW_BLUR_SUPPORTED ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public boolean isChecked() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.DISABLE_WINDOW_BLURS, 0) == 0; + } + + @Override + public boolean setChecked(boolean isChecked) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.DISABLE_WINDOW_BLURS, isChecked ? 0 : 1); + return true; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + refreshSummary(preference); + preference.setEnabled(!mPowerManager.isPowerSaveMode()); + } + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_display; + } + +} diff --git a/src/com/android/settings/development/ForceDarkPreferenceController.java b/src/com/android/settings/display/ForceDarkPreferenceController.java similarity index 98% rename from src/com/android/settings/development/ForceDarkPreferenceController.java rename to src/com/android/settings/display/ForceDarkPreferenceController.java index c81c16f3263..cbd02b00ac1 100644 --- a/src/com/android/settings/development/ForceDarkPreferenceController.java +++ b/src/com/android/settings/display/ForceDarkPreferenceController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settings.development; +package com.android.settings.display; import android.content.Context; import android.os.SystemProperties; diff --git a/src/com/android/settings/display/LidBehaviorPreferenceController.java b/src/com/android/settings/display/LidBehaviorPreferenceController.java new file mode 100644 index 00000000000..71f3a93dd93 --- /dev/null +++ b/src/com/android/settings/display/LidBehaviorPreferenceController.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 The LineageOS 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.display; + +import android.content.Context; +import android.content.res.Resources; +import android.provider.Settings; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; + +public class LidBehaviorPreferenceController extends BasePreferenceController + implements Preference.OnPreferenceChangeListener { + + public LidBehaviorPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + Resources res = mContext.getResources(); + if (res.getBoolean(com.android.internal.R.bool.config_lidControlsScreenLock) || + res.getBoolean(com.android.internal.R.bool.config_lidControlsSleep)) { + return AVAILABLE; + } + return UNSUPPORTED_ON_DEVICE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + screen.findPreference(getPreferenceKey()).setOnPreferenceChangeListener(this); + super.displayPreference(screen); + } + + @Override + public void updateState(Preference preference) { + final ListPreference listPreference = (ListPreference) preference; + listPreference.setValue(String.valueOf(Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.LID_BEHAVIOR, + 0 /* LID_BEHAVIOR_NONE */))); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.LID_BEHAVIOR, + Integer.valueOf((String) newValue)); + return true; + } +} diff --git a/src/com/android/settings/display/MinRefreshRatePreferenceController.java b/src/com/android/settings/display/MinRefreshRatePreferenceController.java new file mode 100644 index 00000000000..b9c63040f26 --- /dev/null +++ b/src/com/android/settings/display/MinRefreshRatePreferenceController.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2020 The LineageOS 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.display; + +import static android.provider.Settings.System.MIN_REFRESH_RATE; + +import android.content.Context; +import android.provider.Settings; +import android.view.Display; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +public class MinRefreshRatePreferenceController extends BasePreferenceController + implements Preference.OnPreferenceChangeListener { + + private static final String KEY_MIN_REFRESH_RATE = "min_refresh_rate"; + + private ListPreference mListPreference; + + private List mEntries = new ArrayList<>(); + private List mValues = new ArrayList<>(); + + public MinRefreshRatePreferenceController(Context context) { + super(context, KEY_MIN_REFRESH_RATE); + + if (mContext.getResources().getBoolean(R.bool.config_show_min_refresh_rate_switch)) { + Display.Mode mode = mContext.getDisplay().getMode(); + Display.Mode[] modes = mContext.getDisplay().getSupportedModes(); + Arrays.sort(modes, (mode1, mode2) -> + Float.compare(mode2.getRefreshRate(), mode1.getRefreshRate())); + for (Display.Mode m : modes) { + if (m.getPhysicalWidth() == mode.getPhysicalWidth() && + m.getPhysicalHeight() == mode.getPhysicalHeight()) { + mEntries.add(String.format("%.02fHz", m.getRefreshRate()) + .replaceAll("[\\.,]00", "")); + mValues.add(String.format(Locale.US, "%.02f", m.getRefreshRate())); + } + } + } + } + + @Override + public int getAvailabilityStatus() { + return mEntries.size() > 1 ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public String getPreferenceKey() { + return KEY_MIN_REFRESH_RATE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + mListPreference = screen.findPreference(getPreferenceKey()); + mListPreference.setEntries(mEntries.toArray(new String[mEntries.size()])); + mListPreference.setEntryValues(mValues.toArray(new String[mValues.size()])); + + super.displayPreference(screen); + } + + @Override + public void updateState(Preference preference) { + final float currentValue = Settings.System.getFloat(mContext.getContentResolver(), + MIN_REFRESH_RATE, 60.00f); + int index = mListPreference.findIndexOfValue( + String.format(Locale.US, "%.02f", currentValue)); + if (index < 0) index = 0; + mListPreference.setValueIndex(index); + mListPreference.setSummary(mListPreference.getEntries()[index]); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Settings.System.putFloat(mContext.getContentResolver(), MIN_REFRESH_RATE, + Float.valueOf((String) newValue)); + updateState(preference); + return true; + } + +} diff --git a/src/com/android/settings/display/MonetSettings.java b/src/com/android/settings/display/MonetSettings.java new file mode 100644 index 00000000000..5db8fc9b600 --- /dev/null +++ b/src/com/android/settings/display/MonetSettings.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2022 Yet Another AOSP 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.display; + +import android.content.ContentResolver; +import android.os.Bundle; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import androidx.preference.Preference.OnPreferenceChangeListener; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.R; +import com.android.settingslib.search.SearchIndexable; + +import org.derpfest.support.colorpicker.ColorPickerPreference; +import org.derpfest.support.preferences.ProperSeekBarPreference; +import org.derpfest.support.preferences.SwitchPreferenceCompat; + +import java.lang.CharSequence; + +import org.json.JSONException; +import org.json.JSONObject; + +@SearchIndexable +public class MonetSettings extends SettingsPreferenceFragment implements + OnPreferenceChangeListener { + + private static final String OVERLAY_CATEGORY_ACCENT_COLOR = + "android.theme.customization.accent_color"; + private static final String OVERLAY_CATEGORY_SYSTEM_PALETTE = + "android.theme.customization.system_palette"; + private static final String OVERLAY_CATEGORY_THEME_STYLE = + "android.theme.customization.theme_style"; + private static final String OVERLAY_CATEGORY_BG_COLOR = + "android.theme.customization.bg_color"; + private static final String OVERLAY_COLOR_SOURCE = + "android.theme.customization.color_source"; + private static final String OVERLAY_COLOR_BOTH = + "android.theme.customization.color_both"; + private static final String OVERLAY_LUMINANCE_FACTOR = + "android.theme.customization.luminance_factor"; + private static final String OVERLAY_CHROMA_FACTOR = + "android.theme.customization.chroma_factor"; + private static final String OVERLAY_WHOLE_PALETTE = + "android.theme.customization.whole_palette"; + private static final String OVERLAY_TINT_BACKGROUND = + "android.theme.customization.tint_background"; + private static final String COLOR_SOURCE_PRESET = "preset"; + private static final String COLOR_SOURCE_HOME = "home_wallpaper"; + private static final String COLOR_SOURCE_LOCK = "lock_wallpaper"; + + private static final String PREF_THEME_STYLE = "theme_style"; + private static final String PREF_COLOR_SOURCE = "color_source"; + private static final String PREF_ACCENT_COLOR = "accent_color"; + private static final String PREF_ACCENT_BACKGROUND = "accent_background"; + private static final String PREF_BG_COLOR = "bg_color"; + private static final String PREF_LUMINANCE_FACTOR = "luminance_factor"; + private static final String PREF_CHROMA_FACTOR = "chroma_factor"; + private static final String PREF_WHOLE_PALETTE = "whole_palette"; + private static final String PREF_TINT_BACKGROUND = "tint_background"; + + private ListPreference mThemeStylePref; + private ListPreference mColorSourcePref; + private ColorPickerPreference mAccentColorPref; + private ColorPickerPreference mBgColorPref; + private ProperSeekBarPreference mLuminancePref; + private ProperSeekBarPreference mChromaPref; + private SwitchPreferenceCompat mAccentBackgroundPref; + private SwitchPreferenceCompat mWholePalettePref; + private SwitchPreferenceCompat mTintBackgroundPref; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + addPreferencesFromResource(R.xml.monet_settings); + + mThemeStylePref = findPreference(PREF_THEME_STYLE); + mColorSourcePref = findPreference(PREF_COLOR_SOURCE); + mAccentColorPref = findPreference(PREF_ACCENT_COLOR); + mAccentBackgroundPref = findPreference(PREF_ACCENT_BACKGROUND); + mBgColorPref = findPreference(PREF_BG_COLOR); + mLuminancePref = findPreference(PREF_LUMINANCE_FACTOR); + mChromaPref = findPreference(PREF_CHROMA_FACTOR); + mWholePalettePref = findPreference(PREF_WHOLE_PALETTE); + mTintBackgroundPref = findPreference(PREF_TINT_BACKGROUND); + + updatePreferences(); + + mThemeStylePref.setOnPreferenceChangeListener(this); + mColorSourcePref.setOnPreferenceChangeListener(this); + mAccentColorPref.setOnPreferenceChangeListener(this); + mAccentBackgroundPref.setOnPreferenceChangeListener(this); + mBgColorPref.setOnPreferenceChangeListener(this); + mLuminancePref.setOnPreferenceChangeListener(this); + mChromaPref.setOnPreferenceChangeListener(this); + mWholePalettePref.setOnPreferenceChangeListener(this); + mTintBackgroundPref.setOnPreferenceChangeListener(this); + } + + @Override + public void onResume() { + super.onResume(); + updatePreferences(); + } + + private void updatePreferences() { + final String overlayPackageJson = Settings.Secure.getStringForUser( + getActivity().getContentResolver(), + Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + UserHandle.USER_CURRENT); + if (overlayPackageJson != null && !overlayPackageJson.isEmpty()) { + try { + final JSONObject object = new JSONObject(overlayPackageJson); + final String style = object.optString(OVERLAY_CATEGORY_THEME_STYLE, null); + final String source = object.optString(OVERLAY_COLOR_SOURCE, null); + final String color = object.optString(OVERLAY_CATEGORY_SYSTEM_PALETTE, null); + final int bgColor = object.optInt(OVERLAY_CATEGORY_BG_COLOR); + final boolean both = object.optInt(OVERLAY_COLOR_BOTH, 0) == 1; + final boolean wholePalette = object.optInt(OVERLAY_WHOLE_PALETTE, 0) == 1; + final boolean tintBG = object.optInt(OVERLAY_TINT_BACKGROUND, 0) == 1; + final float lumin = (float) object.optDouble(OVERLAY_LUMINANCE_FACTOR, 1d); + final float chroma = (float) object.optDouble(OVERLAY_CHROMA_FACTOR, 1d); + // style handling + boolean styleUpdated = false; + if (style != null && !style.isEmpty()) { + for (CharSequence value : mThemeStylePref.getEntryValues()) { + if (value.toString().equals(style)) { + styleUpdated = true; + break; + } + } + if (styleUpdated) { + updateListByValue(mThemeStylePref, style); + } + } + if (!styleUpdated) { + updateListByValue(mThemeStylePref, + mThemeStylePref.getEntryValues()[0].toString()); + } + // color handling + final String sourceVal = (source == null || source.isEmpty() || + (source.equals(COLOR_SOURCE_HOME) && both)) ? "both" : source; + updateListByValue(mColorSourcePref, sourceVal); + final boolean enabled = updateAccentEnablement(sourceVal); + if (enabled && color != null && !color.isEmpty()) { + mAccentColorPref.setNewPreviewColor( + ColorPickerPreference.convertToColorInt(color)); + } + final boolean bgEnabled = enabled && bgColor != 0; + if (bgEnabled) { + mBgColorPref.setNewPreviewColor(bgColor); + } else if (!enabled) { + mAccentBackgroundPref.setEnabled(false); + } + mAccentBackgroundPref.setChecked(bgEnabled); + mBgColorPref.setEnabled(bgEnabled); + // etc + int luminV = 0; + if (lumin > 1d) luminV = Math.round((lumin - 1f) * 100f); + else if (lumin < 1d) luminV = -1 * Math.round((1f - lumin) * 100f); + mLuminancePref.setValue(luminV); + int chromaV = 0; + if (chroma > 1d) chromaV = Math.round((chroma - 1f) * 100f); + else if (chroma < 1d) chromaV = -1 * Math.round((1f - chroma) * 100f); + mChromaPref.setValue(chromaV); + mWholePalettePref.setChecked(wholePalette); + mTintBackgroundPref.setChecked(tintBG); + } catch (JSONException | IllegalArgumentException ignored) {} + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final ContentResolver resolver = getActivity().getContentResolver(); + if (preference == mThemeStylePref) { + String value = (String) newValue; + setStyleValue(value); + updateListByValue(mThemeStylePref, value, false); + return true; + } else if (preference == mColorSourcePref) { + String value = (String) newValue; + setSourceValue(value); + updateListByValue(mColorSourcePref, value, false); + updateAccentEnablement(value); + return true; + } else if (preference == mAccentColorPref) { + int value = (Integer) newValue; + setColorValue(value); + return true; + } else if (preference == mAccentBackgroundPref) { + boolean value = (Boolean) newValue; + if (!value) setBgColorValue(0); + mBgColorPref.setEnabled(value); + return true; + } else if (preference == mBgColorPref) { + int value = (Integer) newValue; + setBgColorValue(value); + return true; + } else if (preference == mLuminancePref) { + int value = (Integer) newValue; + setLuminanceValue(value); + return true; + } else if (preference == mChromaPref) { + int value = (Integer) newValue; + setChromaValue(value); + return true; + } else if (preference == mWholePalettePref) { + boolean value = (Boolean) newValue; + setWholePaletteValue(value); + return true; + } else if (preference == mTintBackgroundPref) { + boolean value = (Boolean) newValue; + setTintBackgroundValue(value); + return true; + } + return false; + } + + private void updateListByValue(ListPreference pref, String value) { + updateListByValue(pref, value, true); + } + + private void updateListByValue(ListPreference pref, String value, boolean set) { + if (set) pref.setValue(value); + final int index = pref.findIndexOfValue(value); + pref.setSummary(pref.getEntries()[index]); + } + + private boolean updateAccentEnablement(String source) { + final boolean shouldEnable = source != null && source.equals(COLOR_SOURCE_PRESET); + mAccentColorPref.setEnabled(shouldEnable); + mAccentBackgroundPref.setEnabled(shouldEnable); + if (!shouldEnable) { + mBgColorPref.setEnabled(false); + mAccentBackgroundPref.setEnabled(false); + mAccentBackgroundPref.setChecked(false); + } + return shouldEnable; + } + + private JSONObject getSettingsJson() throws JSONException { + final String overlayPackageJson = Settings.Secure.getStringForUser( + getActivity().getContentResolver(), + Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + UserHandle.USER_CURRENT); + JSONObject object; + if (overlayPackageJson == null || overlayPackageJson.isEmpty()) + return new JSONObject(); + return new JSONObject(overlayPackageJson); + } + + private void putSettingsJson(JSONObject object) { + Settings.Secure.putStringForUser( + getActivity().getContentResolver(), + Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, + object.toString(), UserHandle.USER_CURRENT); + } + + private void setStyleValue(String style) { + try { + JSONObject object = getSettingsJson(); + object.putOpt(OVERLAY_CATEGORY_THEME_STYLE, style); + putSettingsJson(object); + } catch (JSONException | IllegalArgumentException ignored) {} + } + + private void setSourceValue(String source) { + try { + JSONObject object = getSettingsJson(); + if (source.equals("both")) { + object.putOpt(OVERLAY_COLOR_BOTH, 1); + object.putOpt(OVERLAY_COLOR_SOURCE, COLOR_SOURCE_HOME); + } else { + object.remove(OVERLAY_COLOR_BOTH); + object.putOpt(OVERLAY_COLOR_SOURCE, source); + } + if (!source.equals(COLOR_SOURCE_PRESET)) { + object.remove(OVERLAY_CATEGORY_ACCENT_COLOR); + object.remove(OVERLAY_CATEGORY_SYSTEM_PALETTE); + } + putSettingsJson(object); + } catch (JSONException | IllegalArgumentException ignored) {} + } + + private void setColorValue(int color) { + try { + JSONObject object = getSettingsJson(); + final String rgbColor = ColorPickerPreference.convertToRGB(color).replace("#", ""); + object.putOpt(OVERLAY_CATEGORY_ACCENT_COLOR, rgbColor); + object.putOpt(OVERLAY_CATEGORY_SYSTEM_PALETTE, rgbColor); + object.putOpt(OVERLAY_COLOR_SOURCE, COLOR_SOURCE_PRESET); + putSettingsJson(object); + } catch (JSONException | IllegalArgumentException ignored) {} + } + + private void setBgColorValue(int color) { + try { + JSONObject object = getSettingsJson(); + if (color != 0) object.putOpt(OVERLAY_CATEGORY_BG_COLOR, color); + else object.remove(OVERLAY_CATEGORY_BG_COLOR); + putSettingsJson(object); + } catch (JSONException | IllegalArgumentException ignored) {} + } + + private void setLuminanceValue(int lumin) { + try { + JSONObject object = getSettingsJson(); + if (lumin == 0) + object.remove(OVERLAY_LUMINANCE_FACTOR); + else + object.putOpt(OVERLAY_LUMINANCE_FACTOR, 1d + ((double) lumin / 100d)); + putSettingsJson(object); + } catch (JSONException | IllegalArgumentException ignored) {} + } + + private void setChromaValue(int chroma) { + try { + JSONObject object = getSettingsJson(); + if (chroma == 0) + object.remove(OVERLAY_CHROMA_FACTOR); + else + object.putOpt(OVERLAY_CHROMA_FACTOR, 1d + ((double) chroma / 100d)); + putSettingsJson(object); + } catch (JSONException | IllegalArgumentException ignored) {} + } + + private void setWholePaletteValue(boolean whole) { + try { + JSONObject object = getSettingsJson(); + if (!whole) object.remove(OVERLAY_WHOLE_PALETTE); + else object.putOpt(OVERLAY_WHOLE_PALETTE, 1); + putSettingsJson(object); + } catch (JSONException | IllegalArgumentException ignored) {} + } + + private void setTintBackgroundValue(boolean tint) { + try { + JSONObject object = getSettingsJson(); + if (!tint) object.remove(OVERLAY_TINT_BACKGROUND); + else object.putOpt(OVERLAY_TINT_BACKGROUND, 1); + putSettingsJson(object); + } catch (JSONException | IllegalArgumentException ignored) {} + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DERP; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.monet_settings); +} diff --git a/src/com/android/settings/display/NightDisplayAutoModePreferenceController.java b/src/com/android/settings/display/NightDisplayAutoModePreferenceController.java index ef11e00cebb..68c4b98b908 100644 --- a/src/com/android/settings/display/NightDisplayAutoModePreferenceController.java +++ b/src/com/android/settings/display/NightDisplayAutoModePreferenceController.java @@ -20,7 +20,7 @@ import android.hardware.display.ColorDisplayManager; import android.location.LocationManager; -import androidx.preference.DropDownPreference; +import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @@ -31,7 +31,7 @@ public class NightDisplayAutoModePreferenceController extends BasePreferenceCont implements Preference.OnPreferenceChangeListener { private final LocationManager mLocationManager; - private DropDownPreference mPreference; + private ListPreference mPreference; private ColorDisplayManager mColorDisplayManager; public NightDisplayAutoModePreferenceController(Context context, String key) { diff --git a/src/com/android/settings/display/PeakRefreshRateListPreferenceController.java b/src/com/android/settings/display/PeakRefreshRateListPreferenceController.java new file mode 100644 index 00000000000..e51f5d45110 --- /dev/null +++ b/src/com/android/settings/display/PeakRefreshRateListPreferenceController.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 The LineageOS 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.display; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.provider.DeviceConfig; +import android.provider.Settings; +import android.util.Log; +import android.view.Display; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.Executor; +import java.util.List; +import java.util.Locale; + +public class PeakRefreshRateListPreferenceController extends BasePreferenceController + implements LifecycleObserver, OnStart, OnStop, Preference.OnPreferenceChangeListener { + + private static float DEFAULT_REFRESH_RATE = 60f; + + private static final String TAG = "PeakRefreshRatePrefCtr"; + private static final float INVALIDATE_REFRESH_RATE = -1f; + + private final Handler mHandler; + private final IDeviceConfigChange mOnDeviceConfigChange; + private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; + private ListPreference mListPreference; + + private List mEntries = new ArrayList<>(); + private List mValues = new ArrayList<>(); + + private interface IDeviceConfigChange { + void onDefaultRefreshRateChanged(); + } + + public PeakRefreshRateListPreferenceController(Context context, String key) { + super(context, key); + mHandler = new Handler(context.getMainLooper()); + mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); + mOnDeviceConfigChange = + new IDeviceConfigChange() { + public void onDefaultRefreshRateChanged() { + updateState(mListPreference); + } + }; + + final DisplayManager dm = mContext.getSystemService(DisplayManager.class); + final Display display = dm.getDisplay(Display.DEFAULT_DISPLAY); + + if (display == null) { + Log.w(TAG, "No valid default display device"); + } else { + Display.Mode mode = display.getMode(); + Display.Mode[] modes = display.getSupportedModes(); + Arrays.sort(modes, (mode1, mode2) -> + Float.compare(mode2.getRefreshRate(), mode1.getRefreshRate())); + for (Display.Mode m : modes) { + if (m.getPhysicalWidth() == mode.getPhysicalWidth() && + m.getPhysicalHeight() == mode.getPhysicalHeight()) { + mEntries.add(String.format("%.02fHz", m.getRefreshRate()) + .replaceAll("[\\.,]00", "")); + mValues.add(String.format(Locale.US, "%.02f", m.getRefreshRate())); + } + } + } + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mListPreference = screen.findPreference(getPreferenceKey()); + mListPreference.setEntries(mEntries.toArray(new String[mEntries.size()])); + mListPreference.setEntryValues(mValues.toArray(new String[mValues.size()])); + } + + @Override + public int getAvailabilityStatus() { + if (mContext.getResources().getBoolean(R.bool.config_show_peak_refresh_rate_switch)) { + return AVAILABLE; + } else { + return UNSUPPORTED_ON_DEVICE; + } + } + + @Override + public void updateState(Preference preference) { + final float currentValue = Settings.System.getFloat(mContext.getContentResolver(), + Settings.System.PEAK_REFRESH_RATE, getDefaultPeakRefreshRate()); + int index = mListPreference.findIndexOfValue( + String.format(Locale.US, "%.02f", currentValue)); + if (index < 0) index = 0; + mListPreference.setValueIndex(index); + mListPreference.setSummary(mListPreference.getEntries()[index]); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Settings.System.putFloat(mContext.getContentResolver(), Settings.System.PEAK_REFRESH_RATE, + Float.valueOf((String) newValue)); + updateState(preference); + return true; + } + + @Override + public void onStart() { + mDeviceConfigDisplaySettings.startListening(); + } + + @Override + public void onStop() { + mDeviceConfigDisplaySettings.stopListening(); + } + + private float findPeakRefreshRate(Display.Mode[] modes) { + float peakRefreshRate = DEFAULT_REFRESH_RATE; + for (Display.Mode mode : modes) { + if (Math.round(mode.getRefreshRate()) > DEFAULT_REFRESH_RATE) { + peakRefreshRate = mode.getRefreshRate(); + } + } + return peakRefreshRate; + } + + private class DeviceConfigDisplaySettings + implements DeviceConfig.OnPropertiesChangedListener, Executor { + public void startListening() { + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + this /* Executor */, + this /* Listener */); + } + + public void stopListening() { + DeviceConfig.removeOnPropertiesChangedListener(this); + } + + public float getDefaultPeakRefreshRate() { + float defaultPeakRefreshRate = + DeviceConfig.getFloat( + DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, + INVALIDATE_REFRESH_RATE); + Log.d(TAG, "DeviceConfig getDefaultPeakRefreshRate : " + defaultPeakRefreshRate); + + return defaultPeakRefreshRate; + } + + @Override + public void onPropertiesChanged(DeviceConfig.Properties properties) { + // Got notified if any property has been changed in NAMESPACE_DISPLAY_MANAGER. The + // KEY_PEAK_REFRESH_RATE_DEFAULT value could be added, changed, removed or unchanged. + // Just force a UI update for any case. + if (mOnDeviceConfigChange != null) { + mOnDeviceConfigChange.onDefaultRefreshRateChanged(); + updateState(mListPreference); + } + } + + @Override + public void execute(Runnable runnable) { + if (mHandler != null) { + mHandler.post(runnable); + } + } + } + + private float getDefaultPeakRefreshRate() { + float defaultPeakRefreshRate = mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate(); + if (defaultPeakRefreshRate == INVALIDATE_REFRESH_RATE) { + defaultPeakRefreshRate = (float) mContext.getResources().getInteger( + com.android.internal.R.integer.config_defaultPeakRefreshRate); + } + + return defaultPeakRefreshRate; + } +} diff --git a/src/com/android/settings/display/PocketJudgePreferenceController.java b/src/com/android/settings/display/PocketJudgePreferenceController.java new file mode 100644 index 00000000000..01d5e3f0810 --- /dev/null +++ b/src/com/android/settings/display/PocketJudgePreferenceController.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018 Paranoid Android + * + * 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.display; + +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; +import androidx.preference.SwitchPreferenceCompat; +import androidx.preference.Preference; + +import com.android.settings.DisplaySettings; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.R; +import com.android.settingslib.core.AbstractPreferenceController; + +import static android.provider.Settings.System.POCKET_JUDGE; + +public class PocketJudgePreferenceController extends AbstractPreferenceController implements + PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + private static final String KEY_POCKET_JUDGE = "pocket_judge"; + + public PocketJudgePreferenceController(Context context) { + super(context); + } + + @Override + public String getPreferenceKey() { + return KEY_POCKET_JUDGE; + } + + @Override + public void updateState(Preference preference) { + int pocketJudgeValue = Settings.System.getInt(mContext.getContentResolver(), + POCKET_JUDGE, 0); + ((SwitchPreferenceCompat) preference).setChecked(pocketJudgeValue != 0); + } + + @Override + public boolean isAvailable() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_pocketModeSupported); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + boolean pocketJudgeValue = (Boolean) newValue; + Settings.System.putInt(mContext.getContentResolver(), POCKET_JUDGE, pocketJudgeValue ? 1 : 0); + return true; + } +} diff --git a/src/com/android/settings/display/QRCodeScannerPreferenceController.java b/src/com/android/settings/display/QRCodeScannerPreferenceController.java index cb022a74fa8..e15860ec026 100644 --- a/src/com/android/settings/display/QRCodeScannerPreferenceController.java +++ b/src/com/android/settings/display/QRCodeScannerPreferenceController.java @@ -64,7 +64,7 @@ public void displayPreference(PreferenceScreen screen) { @OnLifecycleEvent(ON_START) public void onStart() { mContentResolver.registerContentObserver( - Settings.Global.getUriFor(SHOW_QR_CODE_SCANNER_SETTING), false, + Settings.Secure.getUriFor(SHOW_QR_CODE_SCANNER_SETTING), false, mSettingsObserver); } diff --git a/src/com/android/settings/display/RefreshRatePreferenceController.java b/src/com/android/settings/display/RefreshRatePreferenceController.java new file mode 100644 index 00000000000..b63f12fbc5f --- /dev/null +++ b/src/com/android/settings/display/RefreshRatePreferenceController.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2023 Paranoid Android + * + * 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.display; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.PowerManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +public class RefreshRatePreferenceController extends BasePreferenceController + implements LifecycleObserver, OnStart, OnStop { + + private static final String TAG = "RefreshRatePreferenceController"; + + private RefreshRateUtils mUtils; + private Preference mPreference; + private final PowerManager mPowerManager; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mPreference != null) + updateState(mPreference); + } + }; + + public RefreshRatePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mUtils = new RefreshRateUtils(context); + mPowerManager = context.getSystemService(PowerManager.class); + } + + @Override + public void onStart() { + mContext.registerReceiver(mReceiver, + new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); + } + + @Override + public void onStop() { + mContext.unregisterReceiver(mReceiver); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public CharSequence getSummary() { + if (mPowerManager.isPowerSaveMode()) { + return mContext.getString(R.string.dark_ui_mode_disabled_summary_dark_theme_on); + } + return mContext.getString(mUtils.isVrrEnabled() ? R.string.refresh_rate_summary_vrr_on + : R.string.refresh_rate_summary_vrr_off, mUtils.getCurrentRefreshRate()); + } + + @Override + public int getAvailabilityStatus() { + return mUtils.isHighRefreshRateAvailable() ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + preference.setEnabled(!mPowerManager.isPowerSaveMode()); + } +} diff --git a/src/com/android/settings/display/RefreshRateSettings.java b/src/com/android/settings/display/RefreshRateSettings.java new file mode 100644 index 00000000000..3fced007a1a --- /dev/null +++ b/src/com/android/settings/display/RefreshRateSettings.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2023 Paranoid Android + * + * 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.display; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreferenceCompat; + +import com.android.settings.R; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.widget.RadioButtonPickerFragment; +import com.android.settingslib.search.SearchIndexable; +import com.android.settingslib.widget.CandidateInfo; +import com.android.settingslib.widget.FooterPreference; +import com.android.settingslib.widget.MainSwitchPreference; +import com.android.settingslib.widget.SelectorWithWidgetPreference; +import com.android.settingslib.widget.TopIntroPreference; + +import java.util.List; +import java.util.stream.Collectors; + +/** Preference fragment used for switching refresh rate */ +@SearchIndexable +public class RefreshRateSettings extends RadioButtonPickerFragment { + + private static final String TAG = "RefreshRateSettings"; + private static final String KEY_VRR_PREF = "refresh_rate_vrr"; + + private Context mContext; + private RefreshRateUtils mUtils; + private SwitchPreferenceCompat mVrrSwitchPref; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + mContext = context; + mUtils = new RefreshRateUtils(context); + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.refresh_rate_settings; + } + + @Override + protected void addStaticPreferences(PreferenceScreen screen) { + mVrrSwitchPref = new SwitchPreferenceCompat(screen.getContext()); + mVrrSwitchPref.setKey(KEY_VRR_PREF); + mVrrSwitchPref.setTitle(R.string.refresh_rate_vrr_title); + mVrrSwitchPref.setSummary(R.string.refresh_rate_vrr_summary); + mVrrSwitchPref.setOnPreferenceChangeListener((pref, newValue) -> { + mUtils.setVrrEnabled((Boolean) newValue); + return true; + }); + screen.addPreference(mVrrSwitchPref); + updateVrrPref(); + + final FooterPreference footerPreference = new FooterPreference(screen.getContext()); + footerPreference.setTitle(R.string.refresh_rate_footer); + footerPreference.setSelectable(false); + footerPreference.setLayoutResource(com.android.settingslib.widget.preference.footer.R.layout.preference_footer); + screen.addPreference(footerPreference); + } + + @Override + protected List getCandidates() { + return mUtils.getRefreshRates().stream() + .filter(r -> r >= RefreshRateUtils.DEFAULT_REFRESH_RATE) + .map(RefreshRateCandidateInfo::new) + .collect(Collectors.toList()); + } + + private void updateVrrPref() { + if (mVrrSwitchPref == null) return; + mVrrSwitchPref.setEnabled(mUtils.isVrrPossible()); + mVrrSwitchPref.setChecked(mUtils.isVrrEnabled()); + } + + @Override + protected String getDefaultKey() { + return String.valueOf(mUtils.getCurrentRefreshRate()); + } + + @Override + protected boolean setDefaultKey(final String key) { + final int refreshRate = Integer.parseInt(key); + mUtils.setCurrentRefreshRate(refreshRate); + updateVrrPref(); + return true; + } + + @Override + public int getMetricsCategory() { + return -1; + } + + private class RefreshRateCandidateInfo extends CandidateInfo { + private final CharSequence mLabel; + private final String mKey; + + RefreshRateCandidateInfo(Integer refreshRate) { + super(true); + mLabel = String.format("%d Hz", refreshRate.intValue()); + mKey = refreshRate.toString(); + } + + @Override + public CharSequence loadLabel() { + return mLabel; + } + + @Override + public Drawable loadIcon() { + return null; + } + + @Override + public String getKey() { + return mKey; + } + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.refresh_rate_settings) { + @Override + protected boolean isPageSearchEnabled(Context context) { + return new RefreshRateUtils(context).isHighRefreshRateAvailable(); + } + }; +} diff --git a/src/com/android/settings/display/RefreshRateUtils.java b/src/com/android/settings/display/RefreshRateUtils.java new file mode 100644 index 00000000000..eeb17d013cd --- /dev/null +++ b/src/com/android/settings/display/RefreshRateUtils.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2024 Paranoid Android + * + * 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.display; + +import android.content.Context; +import android.provider.Settings; +import android.util.Log; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import com.android.settings.R; + +public class RefreshRateUtils { + + private static final String TAG = "RefreshRateUtils"; + + static final int DEFAULT_REFRESH_RATE = 60; + static final int DEFAULT_MIN_REFRESH_RATE = 60; + + private Context mContext; + private List mRefreshRates; + private int mMinRefreshRate, mMaxRefreshRate; + + RefreshRateUtils(Context context) { + mContext = context; + mRefreshRates = getRefreshRates(); + mMinRefreshRate = getMinRefreshRateFromConfig(); + mMaxRefreshRate = Collections.max(mRefreshRates); + } + + List getRefreshRates() { + return Arrays.stream(mContext.getDisplay().getSupportedModes()) + .map(m -> Math.round(m.getRefreshRate())) + .sorted().distinct().collect(Collectors.toList()); + } + + boolean isHighRefreshRateAvailable() { + return mRefreshRates.stream() + .filter(r -> r > DEFAULT_REFRESH_RATE) + .count() > 0; + } + + private int roundToNearestRefreshRate(int refreshRate, boolean floor) { + if (mRefreshRates.contains(refreshRate)) return refreshRate; + int findRefreshRate = mMinRefreshRate; + for (Integer knownRefreshRate : mRefreshRates) { + if (!floor) findRefreshRate = knownRefreshRate; + if (knownRefreshRate > refreshRate) break; + if (floor) findRefreshRate = knownRefreshRate; + } + return findRefreshRate; + } + + private float getDefaultPeakRefreshRate() { + return (float) mContext.getResources().getInteger( + com.android.internal.R.integer.config_defaultPeakRefreshRate); + } + + private int getPeakRefreshRate() { + final int peakRefreshRate = Math.round(Settings.System.getFloat( + mContext.getContentResolver(), + Settings.System.PEAK_REFRESH_RATE, getDefaultPeakRefreshRate())); + return peakRefreshRate < mMinRefreshRate ? mMaxRefreshRate + : roundToNearestRefreshRate(peakRefreshRate, true); + } + + private void setPeakRefreshRate(int refreshRate) { + Settings.System.putFloat(mContext.getContentResolver(), + Settings.System.PEAK_REFRESH_RATE, (float) refreshRate); + } + + private int getMinRefreshRateFromConfig() { + int minRefreshRate; + try { + minRefreshRate = mContext.getResources().getInteger(R.integer.default_min_refresh_rate); + } catch (Exception e) { + Log.e(TAG, "Failed to retrieve default_min_refresh_rate from config, using default", e); + minRefreshRate = getMinRefreshRate(); + } + return minRefreshRate; + } + + private int getMinRefreshRate() { + final int minRefreshRate = Math.round(Settings.System.getFloat( + mContext.getContentResolver(), Settings.System.MIN_REFRESH_RATE, + (float) DEFAULT_MIN_REFRESH_RATE)); + return minRefreshRate == mMinRefreshRate ? mMinRefreshRate + : roundToNearestRefreshRate(minRefreshRate, false); + } + + private void setMinRefreshRate(int refreshRate) { + Settings.System.putFloat(mContext.getContentResolver(), + Settings.System.MIN_REFRESH_RATE, (float) refreshRate); + } + + int getCurrentRefreshRate() { + return Math.max(getMinRefreshRate(), getPeakRefreshRate()); + } + + void setCurrentRefreshRate(int refreshRate) { + setPeakRefreshRate(refreshRate); + setMinRefreshRate(isVrrEnabled() ? mMinRefreshRate : refreshRate); + } + + boolean isVrrPossible() { + return getCurrentRefreshRate() > DEFAULT_REFRESH_RATE; + } + + boolean isVrrEnabled() { + return getMinRefreshRate() <= mMinRefreshRate; + } + + void setVrrEnabled(boolean enable) { + setMinRefreshRate(enable ? mMinRefreshRate : getCurrentRefreshRate()); + } +} diff --git a/src/com/android/settings/display/ResetAutoBrightnessAdjustmentPreferenceController.java b/src/com/android/settings/display/ResetAutoBrightnessAdjustmentPreferenceController.java new file mode 100644 index 00000000000..57c3eb164c4 --- /dev/null +++ b/src/com/android/settings/display/ResetAutoBrightnessAdjustmentPreferenceController.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 Project Kaleidoscope + * + * 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.display; + +import static android.provider.Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ; + +import android.content.Context; +import android.provider.Settings; +import android.widget.Toast; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.R; + +public class ResetAutoBrightnessAdjustmentPreferenceController extends + BasePreferenceController implements Preference.OnPreferenceClickListener { + + public ResetAutoBrightnessAdjustmentPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + @AvailabilityStatus + public int getAvailabilityStatus() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_automatic_brightness_available) + ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + screen.findPreference(getPreferenceKey()).setOnPreferenceClickListener(this); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + Settings.System.putFloat(mContext.getContentResolver(), SCREEN_AUTO_BRIGHTNESS_ADJ, 0f); + Toast.makeText(mContext, mContext.getString( + R.string.reset_auto_brightness_adjustment_done), + Toast.LENGTH_SHORT).show(); + return true; + } +} diff --git a/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java b/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java index 8fd996ea575..551d95b3b53 100644 --- a/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java +++ b/src/com/android/settings/display/SmartAutoRotatePreferenceFragment.java @@ -20,6 +20,7 @@ import android.app.settings.SettingsEnums; import android.content.Context; import android.os.Bundle; +import android.os.UserHandle; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; @@ -27,6 +28,8 @@ import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; +import androidx.preference.SwitchPreference; +import android.provider.Settings; import com.android.settings.R; import com.android.settings.SettingsActivity; @@ -56,6 +59,23 @@ public class SmartAutoRotatePreferenceFragment extends DashboardFragment { static final String AUTO_ROTATE_SWITCH_PREFERENCE_KEY = "auto_rotate_switch"; private static final String KEY_FOOTER_PREFERENCE = "auto_rotate_footer_preference"; + private static final String LOCKSCREEN_ROTATION = "lockscreen_rotation"; + private static final String ROTATION_0_PREF = "display_rotation_0"; + private static final String ROTATION_90_PREF = "display_rotation_90"; + private static final String ROTATION_180_PREF = "display_rotation_180"; + private static final String ROTATION_270_PREF = "display_rotation_270"; + + private SwitchPreference mLockScreenRotationPref; + private SwitchPreference mRotation0Pref; + private SwitchPreference mRotation90Pref; + private SwitchPreference mRotation180Pref; + private SwitchPreference mRotation270Pref; + + public static final int ROTATION_0_MODE = 1; + public static final int ROTATION_90_MODE = 2; + public static final int ROTATION_180_MODE = 4; + public static final int ROTATION_270_MODE = 8; + @Override protected int getPreferenceScreenResId() { return R.xml.auto_rotate_settings; @@ -86,6 +106,27 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, footerPreference.setVisible(isRotationResolverServiceAvailable(activity)); setupFooter(); } + + mLockScreenRotationPref = findPreference(LOCKSCREEN_ROTATION); + mRotation0Pref = findPreference(ROTATION_0_PREF); + mRotation90Pref = findPreference(ROTATION_90_PREF); + mRotation180Pref = findPreference(ROTATION_180_PREF); + mRotation270Pref = findPreference(ROTATION_270_PREF); + + int mode = Settings.System.getIntForUser(getContentResolver(), + Settings.System.ACCELEROMETER_ROTATION_ANGLES, + ROTATION_0_MODE|ROTATION_90_MODE|ROTATION_270_MODE, UserHandle.USER_CURRENT); + + boolean configEnableLockRotation = getResources(). + getBoolean(com.android.internal.R.bool.config_enableLockScreenRotation); + boolean lockScreenRotationEnabled = Settings.System.getInt(getContentResolver(), + Settings.System.LOCKSCREEN_ROTATION, configEnableLockRotation ? 1 : 0) != 0; + + mLockScreenRotationPref.setChecked(lockScreenRotationEnabled); + mRotation0Pref.setChecked((mode & ROTATION_0_MODE) != 0); + mRotation90Pref.setChecked((mode & ROTATION_90_MODE) != 0); + mRotation180Pref.setChecked((mode & ROTATION_180_MODE) != 0); + mRotation270Pref.setChecked((mode & ROTATION_270_MODE) != 0); return view; } @@ -138,6 +179,37 @@ void addHelpLink() { } } + @Override + public boolean onPreferenceTreeClick(Preference preference) { + if (preference == mRotation0Pref || + preference == mRotation90Pref || + preference == mRotation180Pref || + preference == mRotation270Pref) { + int mode = 0; + if (mRotation0Pref.isChecked()) + mode |= ROTATION_0_MODE; + if (mRotation90Pref.isChecked()) + mode |= ROTATION_90_MODE; + if (mRotation180Pref.isChecked()) + mode |= ROTATION_180_MODE; + if (mRotation270Pref.isChecked()) + mode |= ROTATION_270_MODE; + if (mode == 0) { + mode |= ROTATION_0_MODE; + mRotation0Pref.setChecked(true); + } + Settings.System.putIntForUser(getActivity().getApplicationContext().getContentResolver(), + Settings.System.ACCELEROMETER_ROTATION_ANGLES, mode, UserHandle.USER_CURRENT); + return true; + } else if (preference == mLockScreenRotationPref) { + Settings.System.putInt(getContentResolver(), + Settings.System.LOCKSCREEN_ROTATION, + mLockScreenRotationPref.isChecked() ? 1 : 0); + return true; + } + return super.onPreferenceTreeClick(preference); + } + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider(R.xml.auto_rotate_settings) { diff --git a/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorController.java b/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorController.java index e122ad06864..980b8063e95 100644 --- a/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorController.java +++ b/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorController.java @@ -20,7 +20,7 @@ import android.location.LocationManager; import android.os.PowerManager; -import androidx.preference.DropDownPreference; +import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @@ -29,7 +29,7 @@ import com.android.settings.display.TwilightLocationDialog; /** - * Controller for the dark ui option dropdown + * Controller for the dark ui option list */ public class DarkModeScheduleSelectorController extends BasePreferenceController implements Preference.OnPreferenceChangeListener { @@ -40,7 +40,7 @@ public class DarkModeScheduleSelectorController extends BasePreferenceController private final LocationManager mLocationManager; private final BedtimeSettings mBedtimeSettings; - private DropDownPreference mPreference; + private ListPreference mPreference; private int mCurrentMode; public DarkModeScheduleSelectorController(Context context, String key) { diff --git a/src/com/android/settings/flashlight/FlashlightHandleActivity.java b/src/com/android/settings/flashlight/FlashlightHandleActivity.java index 6ae07cdcea4..bb3ff2d0477 100644 --- a/src/com/android/settings/flashlight/FlashlightHandleActivity.java +++ b/src/com/android/settings/flashlight/FlashlightHandleActivity.java @@ -87,10 +87,7 @@ public List getRawDataToIndex(Context context, @Override public List getNonIndexableKeys(Context context) { List keys = super.getNonIndexableKeys(context); - if (!FlashlightSlice.isFlashlightAvailable(context)) { - Log.i(TAG, "Flashlight is unavailable"); - keys.add(DATA_KEY); - } + keys.add(DATA_KEY); return keys; } }; diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java index 7d15858f8b5..8480b16594f 100644 --- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java @@ -77,6 +77,7 @@ public int getAvailabilityStatus() { } private CharSequence generateLabel(BatteryInfo info) { + String temperature = info.batteryTemp + "\u2103"; if (Utils.containsIncompatibleChargers(mContext, TAG)) { return mContext.getString( com.android.settingslib.R.string.battery_info_status_not_charging); @@ -91,7 +92,7 @@ private CharSequence generateLabel(BatteryInfo info) { } if (info.remainingLabel == null || info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) { - return info.statusLabel; + return info.statusLabel + " \u2022 " + temperature; } if (info.pluggedStatus == BatteryManager.BATTERY_PLUGGED_WIRELESS) { final CharSequence wirelessChargingLabel = @@ -110,25 +111,25 @@ private CharSequence generateLabel(BatteryInfo info) { ? mContext.getString( R.string.battery_state_and_duration, info.statusLabel, - info.remainingLabel) + info.remainingLabel, temperature) : info.remainingLabel; } return mContext.getString( - R.string.battery_state_and_duration, info.statusLabel, info.remainingLabel); + R.string.battery_state_and_duration, info.statusLabel, info.remainingLabel, temperature); } else if (mPowerManager.isPowerSaveMode()) { // Power save mode is on final String powerSaverOn = mContext.getString(R.string.battery_tip_early_heads_up_done_title); return mContext.getString( - R.string.battery_state_and_duration, powerSaverOn, info.remainingLabel); + R.string.battery_state_and_duration, powerSaverOn, info.remainingLabel, temperature); } else if (mBatteryTip != null && mBatteryTip.getType() == BatteryTip.TipType.LOW_BATTERY) { // Low battery state final String lowBattery = mContext.getString(R.string.low_battery_summary); return mContext.getString( - R.string.battery_state_and_duration, lowBattery, info.remainingLabel); + R.string.battery_state_and_duration, lowBattery, info.remainingLabel, temperature); } else { // Discharging state - return info.remainingLabel; + return info.remainingLabel + " \u2022 " + temperature; } } @@ -155,9 +156,17 @@ public void quickUpdateHeaderPreference() { final int batteryLevel = Utils.getBatteryLevel(batteryBroadcast); final boolean discharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) == 0; + final int chargeCounterUah = + batteryBroadcast.getIntExtra(BatteryManager.EXTRA_CHARGE_COUNTER, -1); mBatteryUsageProgressBarPref.setUsageSummary(formatBatteryPercentageText(batteryLevel)); mBatteryUsageProgressBarPref.setPercent(batteryLevel, BATTERY_MAX_LEVEL); + + if (chargeCounterUah > 0) { + int chargeCounter = chargeCounterUah / 1_000; + mBatteryUsageProgressBarPref.setTotalSummary( + formatBatteryChargeCounterText(chargeCounter)); + } } /** Update summary when battery tips changed. */ @@ -172,4 +181,8 @@ public void updateHeaderByBatteryTips(BatteryTip batteryTip, BatteryInfo battery private CharSequence formatBatteryPercentageText(int batteryLevel) { return com.android.settings.Utils.formatPercentage(batteryLevel); } + + private CharSequence formatBatteryChargeCounterText(int chargeCounter) { + return mContext.getString(R.string.battery_charge_counter_summary, chargeCounter); + } } diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java index b54801a677a..ea5935c11df 100644 --- a/src/com/android/settings/fuelgauge/BatteryInfo.java +++ b/src/com/android/settings/fuelgauge/BatteryInfo.java @@ -55,6 +55,7 @@ public class BatteryInfo { public boolean discharging = true; public boolean isBatteryDefender; public boolean isFastCharging; + public String batteryTemp; public long remainingTimeUs = 0; public long averageTimeToDischarge = EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN; public String batteryPercentString; @@ -311,6 +312,7 @@ public static BatteryInfo getBatteryInfo( BatteryManager.EXTRA_CHARGING_STATUS, BatteryManager.CHARGING_POLICY_DEFAULT) == BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE; + info.batteryTemp = tenthsToFixedString(batteryBroadcast.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0)); info.statusLabel = Utils.getBatteryStatus(context, batteryBroadcast, isCompactStatus); info.batteryStatus = @@ -340,6 +342,16 @@ public static BatteryInfo getBatteryInfo( return info; } + /** + * Format a number of tenths-units as a decimal string without using a + * conversion to float. E.g. 347 -> "34.7", -99 -> "-9.9" + */ + private static final String tenthsToFixedString(int x) { + int tens = x / 10; + // use Math.abs to avoid "-9.-9" about -99 + return Integer.toString(tens) + "." + Math.abs(x - 10 * tens); + } + /** Returns a {@code BatteryInfo} with battery and charging relative information. */ @WorkerThread public static BatteryInfo getBatteryInfo( diff --git a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java index 4b5d9526f16..476cc8b081e 100644 --- a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java +++ b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java @@ -21,6 +21,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.settings.R; import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy; import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; @@ -42,7 +43,7 @@ public boolean isFirstUseDateAvailable(Context context, long firstUseDateMs) { @Override public boolean isBatteryInfoEnabled(Context context) { - return false; + return context.getResources().getBoolean(R.bool.config_show_battery_info); } @Override diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java index 9e08664c901..93423d6d782 100644 --- a/src/com/android/settings/fuelgauge/BatteryUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryUtils.java @@ -566,26 +566,9 @@ public long getAppLongVersionCode(String packageName) { return -1L; } - /** Whether the package is installed from Google Play Store or not */ - public static boolean isAppInstalledFromGooglePlayStore(Context context, String packageName) { - if (TextUtils.isEmpty(packageName)) { - return false; - } - InstallSourceInfo installSourceInfo; - try { - installSourceInfo = context.getPackageManager().getInstallSourceInfo(packageName); - } catch (PackageManager.NameNotFoundException e) { - return false; - } - return installSourceInfo != null - && GOOGLE_PLAY_STORE_PACKAGE.equals(installSourceInfo.getInitiatingPackageName()); - } - /** Gets the logging package name. */ public static String getLoggingPackageName(Context context, String originalPackingName) { - return BatteryUtils.isAppInstalledFromGooglePlayStore(context, originalPackingName) - ? originalPackingName - : PACKAGE_NAME_NONE; + return PACKAGE_NAME_NONE; } /** Gets the latest sticky battery intent from the Android system. */ diff --git a/src/com/android/settings/fuelgauge/FastChargingPreferenceController.java b/src/com/android/settings/fuelgauge/FastChargingPreferenceController.java new file mode 100644 index 00000000000..a4eea6e11b4 --- /dev/null +++ b/src/com/android/settings/fuelgauge/FastChargingPreferenceController.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2021 The LineageOS 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.fuelgauge; + +import android.content.Context; +import android.os.RemoteException; +import android.util.Log; + +import androidx.preference.Preference; +import androidx.preference.SwitchPreferenceCompat; + +import com.android.settings.core.BasePreferenceController; + +import vendor.lineage.fastcharge.V1_0.IFastCharge; + +import java.util.NoSuchElementException; + +/** + * Controller to change and update the fast charging toggle + */ +public class FastChargingPreferenceController extends BasePreferenceController + implements Preference.OnPreferenceChangeListener { + + private static final String KEY_FAST_CHARGING = "fast_charging"; + private static final String TAG = "FastChargingPreferenceController"; + + private IFastCharge mFastCharge = null; + + public FastChargingPreferenceController(Context context) { + super(context, KEY_FAST_CHARGING); + try { + mFastCharge = IFastCharge.getService(); + } catch (NoSuchElementException | RemoteException e) { + Log.e(TAG, "Failed to get IFastCharge interface", e); + } + } + + @Override + public int getAvailabilityStatus() { + return mFastCharge != null ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + boolean fastChargingEnabled = false; + + try { + fastChargingEnabled = mFastCharge.isEnabled(); + } catch (RemoteException e) { + Log.e(TAG, "isEnabled failed", e); + } + + ((SwitchPreferenceCompat) preference).setChecked(fastChargingEnabled); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean shouldEnableFastCharging = (Boolean) newValue; + + try { + mFastCharge.setEnabled(shouldEnableFastCharging); + updateState(preference); + } catch (RemoteException e) { + Log.e(TAG, "setEnabled failed", e); + } + + return false; + } +} diff --git a/src/com/android/settings/fuelgauge/batterydata/BatteryDataBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batterydata/BatteryDataBroadcastReceiver.java new file mode 100644 index 00000000000..3b8b12fdbf8 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterydata/BatteryDataBroadcastReceiver.java @@ -0,0 +1,41 @@ +// +// Copyright (C) 2022 The Project Mia +// +// SPDX-License-Identifier: Apache-2.0 +// + +package com.android.settings.fuelgauge.batterydata; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +public final class BatteryDataBroadcastReceiver extends BroadcastReceiver { + + private static final String TAG = "BatteryDataBroadcastReceiver"; + + public boolean mFetchBatteryUsageData; + + @Override + public void onReceive(Context context, Intent intent) { + String batteryData = intent.getAction(); + switch (batteryData) { + // Fetch device usage data + case "settings.intelligence.battery.action.FETCH_BATTERY_USAGE_DATA": + mFetchBatteryUsageData = true; + BatteryDataFetchService.enqueueWork(context); + break; + // Fetch bluetooth device usage data + case "settings.intelligence.battery.action.FETCH_BLUETOOTH_BATTERY_DATA": + try { + BluetoothBatteryDataFetch.returnBluetoothDevices(context, intent); + } catch (Exception e) { + Log.e(TAG, "returnBluetoothDevices() error: ", e); + } + break; + default: + break; + } + } +} diff --git a/src/com/android/settings/fuelgauge/batterydata/BatteryDataFetchService.java b/src/com/android/settings/fuelgauge/batterydata/BatteryDataFetchService.java new file mode 100644 index 00000000000..19360981e78 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterydata/BatteryDataFetchService.java @@ -0,0 +1,53 @@ +// +// Copyright (C) 2022 The Project Mia +// +// SPDX-License-Identifier: Apache-2.0 +// + +package com.android.settings.fuelgauge.batterydata; + +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.BatteryStatsManager; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.core.app.JobIntentService; + +import java.util.List; + +public class BatteryDataFetchService extends JobIntentService { + + private static final String TAG = "BatteryDataFetchService"; + private static final Intent JOB_INTENT = new Intent("action.LOAD_BATTERY_USAGE_DATA"); + + public static void enqueueWork(final Context context) { + AsyncTask.execute(() -> { + loadUsageDataSafely(context); + }); + } + + @Override + protected void onHandleWork(@NonNull Intent intent) { + loadUsageDataSafely(this); + } + + private static void loadUsageDataSafely(Context context) { + try { + loadUsageData(context); + } catch (RuntimeException e) { + Log.e(TAG, "Fail load usage data:" + e); + } + } + + private static void loadUsageData(Context context) { + BatteryUsageStats batteryUsageStats = context + .getSystemService(BatteryStatsManager.class) + .getBatteryUsageStats(new BatteryUsageStatsQuery.Builder() + .includeBatteryHistory() + .build()); + } +} diff --git a/src/com/android/settings/fuelgauge/batterydata/BluetoothBatteryDataFetch.java b/src/com/android/settings/fuelgauge/batterydata/BluetoothBatteryDataFetch.java new file mode 100644 index 00000000000..d437cf5fd80 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterydata/BluetoothBatteryDataFetch.java @@ -0,0 +1,171 @@ +// +// Copyright (C) 2022 The Project Mia +// +// SPDX-License-Identifier: Apache-2.0 +// + +package com.android.settings.fuelgauge.batterydata; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ResultReceiver; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public final class BluetoothBatteryDataFetch { + + private static final String TAG = "BluetoothBatteryDataFetch"; + + @VisibleForTesting + public static LocalBluetoothManager mLocalBluetoothManager; + private static Context context; + private static Intent intent; + + private static String emptyIfNull(String value) { + return value == null ? "" : value; + } + + public static ContentValues wrapBluetoothData( + Context context, CachedBluetoothDevice cachedBluetoothDevice, + boolean nonbuds) { + BluetoothDevice device = cachedBluetoothDevice.getDevice(); + + ContentValues contentValues = new ContentValues(); + contentValues.put("type", device.getType()); + contentValues.put("name", emptyIfNull(device.getName())); + contentValues.put("alias", emptyIfNull(device.getAlias())); + contentValues.put("address", emptyIfNull(device.getAddress())); + contentValues.put("batteryLevel", device.getBatteryLevel()); + + putStringMetadata(contentValues, "hardwareVersion", device.getMetadata( + BluetoothDevice.METADATA_HARDWARE_VERSION)); + putStringMetadata(contentValues, "batteryLevelRight", device.getMetadata( + BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY)); + putStringMetadata(contentValues, "batteryLevelLeft", device.getMetadata( + BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)); + putStringMetadata(contentValues, "batteryLevelCase", device.getMetadata( + BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY)); + putStringMetadata(contentValues, "batteryChargingRight", device.getMetadata( + BluetoothDevice.METADATA_UNTETHERED_RIGHT_CHARGING)); + putStringMetadata(contentValues, "batteryChargingLeft", device.getMetadata( + BluetoothDevice.METADATA_UNTETHERED_LEFT_CHARGING)); + putStringMetadata(contentValues, "batteryChargingCase", device.getMetadata( + BluetoothDevice.METADATA_UNTETHERED_CASE_CHARGING)); + putStringMetadata(contentValues, "batteryChargingMain", device.getMetadata( + BluetoothDevice.METADATA_MAIN_CHARGING)); + if (nonbuds) { + putStringMetadata(contentValues, "deviceIconMain", device.getMetadata( + BluetoothDevice.METADATA_MAIN_ICON)); + putStringMetadata(contentValues, "deviceIconCase", device.getMetadata( + BluetoothDevice.METADATA_UNTETHERED_CASE_ICON)); + putStringMetadata(contentValues, "deviceIconLeft", device.getMetadata( + BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON)); + putStringMetadata(contentValues, "deviceIconRight", device.getMetadata( + BluetoothDevice.METADATA_UNTETHERED_RIGHT_ICON)); + } + BluetoothClass bluetoothClass = device.getBluetoothClass(); + if (bluetoothClass != null) { + contentValues.put("bluetoothClass", marshall(bluetoothClass)); + } + return contentValues; + } + + private static byte[] marshall(Parcelable parcelable) { + Parcel obtain = Parcel.obtain(); + parcelable.writeToParcel(obtain, 0); + byte[] marshall = obtain.marshall(); + obtain.recycle(); + return marshall; + } + + private static void putStringMetadata( + ContentValues contentValues, String key, byte[] value) { + if (value == null || value.length == 0) { + return; + } + contentValues.put(key, new String(value)); + } + + public static void returnBluetoothDevices(Context context, Intent intent) { + AsyncTask.execute(() -> returnBluetoothDevicesInner(context, intent)); + } + + public static void returnBluetoothDevicesInner(Context context, Intent intent) { + ResultReceiver resultReceiver = intent.getParcelableExtra(Intent.EXTRA_RESULT_RECEIVER); + if (resultReceiver == null) { + Log.w(TAG, "No result receiver found from intent"); + return; + } + if (mLocalBluetoothManager == null) { + mLocalBluetoothManager = LocalBluetoothManager.getInstance(context, null); + } + BluetoothAdapter adapter = context.getSystemService(BluetoothManager.class).getAdapter(); + if (adapter == null || !adapter.isEnabled() || mLocalBluetoothManager == null) { + Log.w(TAG, "BluetoothAdapter not present or not enabled"); + resultReceiver.send(1, null); + return; + } + sendAndFilterBluetoothData(context, resultReceiver, mLocalBluetoothManager, + intent.getBooleanExtra("extra_fetch_icon", false)); + } + + public static void sendAndFilterBluetoothData(Context context, + ResultReceiver resultReceiver, + LocalBluetoothManager localBluetoothManager, + boolean cache) { + long start = System.currentTimeMillis(); + Collection cachedDevicesCopy = + localBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy(); + Log.d(TAG, "cachedDevices:" + cachedDevicesCopy); + if (cachedDevicesCopy == null || cachedDevicesCopy.isEmpty()) { + resultReceiver.send(0, Bundle.EMPTY); + return; + } + List connectedDevices = cachedDevicesCopy.stream() + .filter(CachedBluetoothDevice::isConnected) + .collect(Collectors.toList()); + Log.d(TAG, "Connected devices:" + connectedDevices); + if (connectedDevices.isEmpty()) { + resultReceiver.send(0, Bundle.EMPTY); + return; + } + ArrayList bluetoothWrapDataListKey = new ArrayList<>(); + ArrayList bluetoothParcelableList = new ArrayList<>(); + connectedDevices.forEach(cachedBluetoothDevice -> { + BluetoothDevice device = cachedBluetoothDevice.getDevice(); + bluetoothParcelableList.add(device); + try { + bluetoothWrapDataListKey.add( + wrapBluetoothData(context, cachedBluetoothDevice, cache)); + } catch (Exception e) { + Log.e(TAG, "Wrap bluetooth data failed: " + device, e); + } + }); + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList("bluetoothParcelableListKey", bluetoothParcelableList); + if (!bluetoothWrapDataListKey.isEmpty()) { + bundle.putParcelableArrayList("bluetoothWrapDataListKey", bluetoothWrapDataListKey); + } + resultReceiver.send(0, bundle); + Log.d(TAG, String.format("Send and filter bluetooth data size=%d in %d/ms", + bluetoothWrapDataListKey.size(), (System.currentTimeMillis() - start))); + } +} diff --git a/src/com/android/settings/gestures/BackGestureIndicatorView.java b/src/com/android/settings/gestures/BackGestureIndicatorView.java index c60afd003d3..06b455470c4 100644 --- a/src/com/android/settings/gestures/BackGestureIndicatorView.java +++ b/src/com/android/settings/gestures/BackGestureIndicatorView.java @@ -19,6 +19,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.PixelFormat; +import android.graphics.Point; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -37,6 +38,7 @@ public class BackGestureIndicatorView extends LinearLayout { private ImageView mRightIndicator; private BackGestureIndicatorDrawable mLeftDrawable; private BackGestureIndicatorDrawable mRightDrawable; + private int mHeightScale; public BackGestureIndicatorView(Context context) { super(context); @@ -87,6 +89,10 @@ public void setIndicatorWidth(int width, boolean leftIndicator) { indicator.setWidth(width); } + public void setIndicatorHeightScale(int heightScale) { + mHeightScale = heightScale; + } + public WindowManager.LayoutParams getLayoutParams( WindowManager.LayoutParams parentWindowAttributes) { int copiedFlags = (parentWindowAttributes.flags @@ -99,8 +105,18 @@ public WindowManager.LayoutParams getLayoutParams( | copiedFlags, PixelFormat.TRANSLUCENT); + setCurrentGestureHeight(lp); lp.setTitle("BackGestureIndicatorView"); lp.token = getContext().getActivityToken(); return lp; } + + private void setCurrentGestureHeight(WindowManager.LayoutParams lp) { + Point displaySize = new Point(); + getContext().getDisplay().getRealSize(displaySize); + lp.height = Math.round((float) displaySize.y - + ((float) displaySize.y) * ((float) mHeightScale / 6f)); + if (mHeightScale == 0) lp.y = 0; + else lp.y = displaySize.y - lp.height; + } } diff --git a/src/com/android/settings/gestures/DoubleTapAmbientSettings.java b/src/com/android/settings/gestures/DoubleTapAmbientSettings.java new file mode 100644 index 00000000000..1289fc3cd9e --- /dev/null +++ b/src/com/android/settings/gestures/DoubleTapAmbientSettings.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019-2020 The Evolution X 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.gestures; + +import android.os.Bundle; + +import androidx.preference.Preference; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +public class DoubleTapAmbientSettings extends SettingsPreferenceFragment implements + Preference.OnPreferenceChangeListener { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.double_tap_ambient_screen_settings); + + getActivity().getActionBar().setTitle(R.string.double_tap_title); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + return false; + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } +} diff --git a/src/com/android/settings/gestures/GestureNavigationLongPressController.java b/src/com/android/settings/gestures/GestureNavigationLongPressController.java new file mode 100644 index 00000000000..e0bf2bde7cc --- /dev/null +++ b/src/com/android/settings/gestures/GestureNavigationLongPressController.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 Yet Another AOSP 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.gestures; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.core.TogglePreferenceController; + +public class GestureNavigationLongPressController extends TogglePreferenceController { + + private static final String GSA_PACKAGE = "com.google.android.googlequicksearchbox"; + + public GestureNavigationLongPressController(Context context, String key) { + super(context, key); + } + + @Override + public boolean isChecked() { + return Settings.System.getInt(mContext.getContentResolver(), + Settings.System.NAVBAR_LONG_PRESS_GESTURE, 1) == 1; + } + + @Override + public boolean setChecked(boolean isChecked) { + return Settings.System.putInt(mContext.getContentResolver(), + Settings.System.NAVBAR_LONG_PRESS_GESTURE, isChecked ? 1 : 0); + } + + @Override + public int getAvailabilityStatus() { + PackageManager pm = mContext.getPackageManager(); + if (pm == null) { + return UNSUPPORTED_ON_DEVICE; + } + try { + ApplicationInfo ai = pm.getApplicationInfo(GSA_PACKAGE, 0); + if (ai.enabled && ai.isProduct()) { + return AVAILABLE; + } + } catch (PackageManager.NameNotFoundException e) { + return UNSUPPORTED_ON_DEVICE; + } + return UNSUPPORTED_ON_DEVICE; + } + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_system; + } +} diff --git a/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java b/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java index 546581bd128..1350f2eadd3 100644 --- a/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java +++ b/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java @@ -31,6 +31,8 @@ import com.android.settings.widget.SeekBarPreference; import com.android.settingslib.search.SearchIndexable; +import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; + /** * A fragment to include all the settings related to Gesture Navigation mode. */ @@ -44,12 +46,18 @@ public class GestureNavigationSettingsFragment extends DashboardFragment { private static final String LEFT_EDGE_SEEKBAR_KEY = "gesture_left_back_sensitivity"; private static final String RIGHT_EDGE_SEEKBAR_KEY = "gesture_right_back_sensitivity"; + private static final String KEY_BACK_HEIGHT = "gesture_back_height"; + + private static final String NAVIGATION_BAR_MISC = "navigation_bar_misc"; private WindowManager mWindowManager; private BackGestureIndicatorView mIndicatorView; private float[] mBackGestureInsetScales; private float mDefaultBackGestureInset; + private float[] mBackGestureHeightScales = { 0f, 1f, 2f, 3f, 4f, 5f }; + private int mCurrentRightWidth; + private int mCurrentLeftWidth; public GestureNavigationSettingsFragment() { super(); @@ -75,6 +83,14 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { initSeekBarPreference(LEFT_EDGE_SEEKBAR_KEY); initSeekBarPreference(RIGHT_EDGE_SEEKBAR_KEY); + initSeekBarPreference(KEY_BACK_HEIGHT); + + boolean isTaskbarEnabled = Settings.System.getInt(getContext().getContentResolver(), + Settings.System.ENABLE_TASKBAR, isLargeScreen(getContext()) ? 1 : 0) == 1; + if (isTaskbarEnabled) { + getPreferenceScreen().removePreference( + getPreferenceScreen().findPreference(NAVIGATION_BAR_MISC)); + } } @Override @@ -118,12 +134,30 @@ private void initSeekBarPreference(final String key) { pref.setContinuousUpdates(true); pref.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_TICKS); - final String settingsKey = key == LEFT_EDGE_SEEKBAR_KEY - ? Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT - : Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT; - final float initScale = Settings.Secure.getFloat( + final String settingsKey = key == LEFT_EDGE_SEEKBAR_KEY ? + Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT + : key == RIGHT_EDGE_SEEKBAR_KEY ? + Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT + : Settings.System.BACK_GESTURE_HEIGHT; + + + float initScale = Settings.Secure.getFloat( getContext().getContentResolver(), settingsKey, 1.0f); + // needed if we just change the height + float currentWidthScale = Settings.Secure.getFloat( + getContext().getContentResolver(), Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f); + mCurrentRightWidth = (int) (mDefaultBackGestureInset * currentWidthScale); + currentWidthScale = Settings.Secure.getFloat( + getContext().getContentResolver(), Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f); + mCurrentLeftWidth = (int) (mDefaultBackGestureInset * currentWidthScale); + + if (key == KEY_BACK_HEIGHT) { + mBackGestureInsetScales = mBackGestureHeightScales; + initScale = Settings.System.getInt( + getContext().getContentResolver(), settingsKey, 0); + } + // Find the closest value to initScale float minDistance = Float.MAX_VALUE; int minDistanceIndex = -1; @@ -137,15 +171,38 @@ private void initSeekBarPreference(final String key) { pref.setProgress(minDistanceIndex); pref.setOnPreferenceChangeListener((p, v) -> { - final int width = (int) (mDefaultBackGestureInset * mBackGestureInsetScales[(int) v]); - mIndicatorView.setIndicatorWidth(width, key == LEFT_EDGE_SEEKBAR_KEY); + if (key != KEY_BACK_HEIGHT) { + final int width = (int) (mDefaultBackGestureInset * mBackGestureInsetScales[(int) v]); + mIndicatorView.setIndicatorWidth(width, key == LEFT_EDGE_SEEKBAR_KEY); + if (key == LEFT_EDGE_SEEKBAR_KEY) { + mCurrentLeftWidth = width; + } else { + mCurrentRightWidth = width; + } + } else { + final int heightScale = (int) (mBackGestureInsetScales[(int) v]); + mIndicatorView.setIndicatorHeightScale(heightScale); + // dont use updateViewLayout else it will animate + mWindowManager.removeView(mIndicatorView); + mWindowManager.addView(mIndicatorView, mIndicatorView.getLayoutParams( + getActivity().getWindow().getAttributes())); + // peek the indicators + mIndicatorView.setIndicatorWidth(mCurrentRightWidth, false); + mIndicatorView.setIndicatorWidth(mCurrentLeftWidth, true); + } return true; }); pref.setOnPreferenceChangeStopListener((p, v) -> { - mIndicatorView.setIndicatorWidth(0, key == LEFT_EDGE_SEEKBAR_KEY); final float scale = mBackGestureInsetScales[(int) v]; - Settings.Secure.putFloat(getContext().getContentResolver(), settingsKey, scale); + if (key == KEY_BACK_HEIGHT) { + mIndicatorView.setIndicatorWidth(0, false); + mIndicatorView.setIndicatorWidth(0, true); + Settings.System.putInt(getContext().getContentResolver(), settingsKey, (int) scale); + } else { + mIndicatorView.setIndicatorWidth(0, key == LEFT_EDGE_SEEKBAR_KEY); + Settings.Secure.putFloat(getContext().getContentResolver(), settingsKey, scale); + } return true; }); } diff --git a/src/com/android/settings/gestures/GestureSettings.java b/src/com/android/settings/gestures/GestureSettings.java index 8532b162224..5d5d038bb65 100644 --- a/src/com/android/settings/gestures/GestureSettings.java +++ b/src/com/android/settings/gestures/GestureSettings.java @@ -55,6 +55,7 @@ public void onAttach(Context context) { super.onAttach(context); use(PickupGesturePreferenceController.class).setConfig(getConfig(context)); use(DoubleTapScreenPreferenceController.class).setConfig(getConfig(context)); + use(ScreenOffUdfpsPreferenceController.class).setConfig(getConfig(context)); } private AmbientDisplayConfiguration getConfig(Context context) { diff --git a/src/com/android/settings/gestures/GesturesSettingPreferenceController.java b/src/com/android/settings/gestures/GesturesSettingPreferenceController.java index e8c4b934663..d6e0077b0e9 100644 --- a/src/com/android/settings/gestures/GesturesSettingPreferenceController.java +++ b/src/com/android/settings/gestures/GesturesSettingPreferenceController.java @@ -68,6 +68,9 @@ private static List buildAllPreferenceControllers( controllers.add(new DoubleTapScreenPreferenceController(context, FAKE_PREF_KEY) .setConfig(ambientDisplayConfiguration)); controllers.add(new PreventRingingParentPreferenceController(context, FAKE_PREF_KEY)); + controllers.add(new SwipeToScreenshotPreferenceController(context, FAKE_PREF_KEY)); + controllers.add(new ScreenOffUdfpsPreferenceController(context, FAKE_PREF_KEY) + .setConfig(ambientDisplayConfiguration)); return controllers; } } diff --git a/src/com/android/settings/gestures/PowerMenuSettingsUtils.java b/src/com/android/settings/gestures/PowerMenuSettingsUtils.java index b1103811749..4262c0d8cd3 100644 --- a/src/com/android/settings/gestures/PowerMenuSettingsUtils.java +++ b/src/com/android/settings/gestures/PowerMenuSettingsUtils.java @@ -55,6 +55,7 @@ final class PowerMenuSettingsUtils { private static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; // a.k.a., Power Menu private static final int LONG_PRESS_POWER_ASSISTANT_VALUE = 5; // Settings.Secure.ASSISTANT + private static final int KEY_CHORD_POWER_VOLUME_UP_MUTE_TOGGLE = 1; private static final int KEY_CHORD_POWER_VOLUME_UP_GLOBAL_ACTIONS = 2; private static final Uri POWER_BUTTON_LONG_PRESS_URI = @@ -113,14 +114,11 @@ public static boolean setLongPressPowerForPowerMenu(Context context) { context.getContentResolver(), POWER_BUTTON_LONG_PRESS_SETTING, LONG_PRESS_POWER_GLOBAL_ACTIONS)) { - // We restore power + volume up buttons to the default action. - int keyChordDefaultValue = - context.getResources() - .getInteger(KEY_CHORD_POWER_VOLUME_UP_DEFAULT_VALUE_RESOURCE); + // We restore power + volume up buttons to the mute action. Settings.Global.putInt( context.getContentResolver(), KEY_CHORD_POWER_VOLUME_UP_SETTING, - keyChordDefaultValue); + KEY_CHORD_POWER_VOLUME_UP_MUTE_TOGGLE); return true; } return false; diff --git a/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java b/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java index d171677d72a..0f9d6f321a5 100644 --- a/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java +++ b/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java @@ -16,6 +16,11 @@ package com.android.settings.gestures; +import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_OFF; +import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_MUTE; +import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_NORMAL; +import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_VIBRATE; + import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -26,8 +31,8 @@ import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; -import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.core.AbstractPreferenceController; @@ -36,27 +41,32 @@ import com.android.settingslib.core.lifecycle.events.OnPause; import com.android.settingslib.core.lifecycle.events.OnResume; import com.android.settingslib.widget.SelectorWithWidgetPreference; +import com.android.settingslib.widget.LayoutPreference; +import com.android.settingslib.widget.MainSwitchPreference; + +import java.util.ArrayList; +import java.util.Arrays; public class PreventRingingGesturePreferenceController extends AbstractPreferenceController - implements SelectorWithWidgetPreference.OnClickListener, LifecycleObserver, + implements Preference.OnPreferenceChangeListener, LifecycleObserver, OnResume, OnPause, PreferenceControllerMixin { - @VisibleForTesting + static final String KEY_MASTER = "gesture_prevent_ringing_switch"; static final String KEY_VIBRATE = "prevent_ringing_option_vibrate"; - - @VisibleForTesting static final String KEY_MUTE = "prevent_ringing_option_mute"; + static final String KEY_NORMAL = "prevent_ringing_option_normal"; + + static final String KEY_CYCLE = "prevent_ringing_option_cycle"; private final String PREF_KEY_VIDEO = "gesture_prevent_ringing_video"; private final String KEY = "gesture_prevent_ringing_category"; private final Context mContext; - @VisibleForTesting PreferenceCategory mPreferenceCategory; - @VisibleForTesting - SelectorWithWidgetPreference mVibratePref; - @VisibleForTesting - SelectorWithWidgetPreference mMutePref; + MainSwitchPreference mMasterSwitch; + SwitchPreference mNormalPref; + SwitchPreference mVibratePref; + SwitchPreference mMutePref; private SettingObserver mSettingObserver; @@ -76,8 +86,10 @@ public void displayPreference(PreferenceScreen screen) { return; } mPreferenceCategory = screen.findPreference(getPreferenceKey()); - mVibratePref = makeRadioPreference(KEY_VIBRATE, R.string.prevent_ringing_option_vibrate); - mMutePref = makeRadioPreference(KEY_MUTE, R.string.prevent_ringing_option_mute); + mMasterSwitch = screen.findPreference(KEY_MASTER); + mNormalPref = makeSwitchPreference(KEY_NORMAL, R.string.prevent_ringing_option_normal); + mVibratePref = makeSwitchPreference(KEY_VIBRATE, R.string.prevent_ringing_option_vibrate); + mMutePref = makeSwitchPreference(KEY_MUTE, R.string.prevent_ringing_option_mute); if (mPreferenceCategory != null) { mSettingObserver = new SettingObserver(mPreferenceCategory); @@ -100,35 +112,66 @@ public String getVideoPrefKey() { } @Override - public void onRadioButtonClicked(SelectorWithWidgetPreference preference) { - int preventRingingSetting = keyToSetting(preference.getKey()); - if (preventRingingSetting != Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE)) { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.VOLUME_HUSH_GESTURE, preventRingingSetting); + public boolean onPreferenceChange(Preference preference, Object newValue) { + final boolean isAdd = (Boolean) newValue; + final String preventRingingSetting = keyToSetting(preference.getKey()); + String settingsValue = Settings.Secure.getString( + mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE); + if (settingsValue == null) settingsValue = DERP_VOLUME_HUSH_OFF; + ArrayList currentValue = new ArrayList(); + currentValue.addAll(Arrays.asList(settingsValue.split(",", 0))); + + if (isAdd) { + if (currentValue.get(0).equals(DERP_VOLUME_HUSH_OFF)) + currentValue.clear(); + if (!currentValue.contains(preventRingingSetting)) + currentValue.add(preventRingingSetting); + } else { + if (currentValue.size() == 1 || + preventRingingSetting.equals(DERP_VOLUME_HUSH_OFF)) { + currentValue.clear(); + currentValue.add(DERP_VOLUME_HUSH_OFF); + if (mMasterSwitch != null) mMasterSwitch.setChecked(false); + } else { + currentValue.remove(preventRingingSetting); + } + } + + String value = ""; + boolean first = true; + for (String str : currentValue) { + if (first) { + value += str; + first = false; + continue; + } + value += "," + str; } + Settings.Secure.putString(mContext.getContentResolver(), + Settings.Secure.VOLUME_HUSH_GESTURE, value); + return true; } @Override public void updateState(Preference preference) { - int preventRingingSetting = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE); - final boolean isVibrate = preventRingingSetting == Settings.Secure.VOLUME_HUSH_VIBRATE; - final boolean isMute = preventRingingSetting == Settings.Secure.VOLUME_HUSH_MUTE; - if (mVibratePref != null && mVibratePref.isChecked() != isVibrate) { + final String preventRingingSetting = Settings.Secure.getString( + mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE); + + final boolean enabled = preventRingingSetting != null && + !preventRingingSetting.equals(DERP_VOLUME_HUSH_OFF); + if (mVibratePref != null) mVibratePref.setEnabled(enabled); + if (mMutePref != null) mMutePref.setEnabled(enabled); + if (mNormalPref != null) mNormalPref.setEnabled(enabled); + + final boolean isVibrate = enabled && preventRingingSetting.contains(DERP_VOLUME_HUSH_VIBRATE); + final boolean isMute = enabled && preventRingingSetting.contains(DERP_VOLUME_HUSH_MUTE); + final boolean isNormal = enabled && preventRingingSetting.contains(DERP_VOLUME_HUSH_NORMAL); + if (mVibratePref != null && mVibratePref.isChecked() != isVibrate) mVibratePref.setChecked(isVibrate); - } - if (mMutePref != null && mMutePref.isChecked() != isMute) { + if (mMutePref != null && mMutePref.isChecked() != isMute) mMutePref.setChecked(isMute); - } - - if (preventRingingSetting == Settings.Secure.VOLUME_HUSH_OFF) { - mVibratePref.setEnabled(false); - mMutePref.setEnabled(false); - } else { - mVibratePref.setEnabled(true); - mMutePref.setEnabled(true); - } + if (mNormalPref != null && mNormalPref.isChecked() != isNormal) + mNormalPref.setChecked(isNormal); } @Override @@ -146,23 +189,24 @@ public void onPause() { } } - private int keyToSetting(String key) { + private String keyToSetting(String key) { switch (key) { case KEY_MUTE: - return Settings.Secure.VOLUME_HUSH_MUTE; + return DERP_VOLUME_HUSH_MUTE; case KEY_VIBRATE: - return Settings.Secure.VOLUME_HUSH_VIBRATE; + return DERP_VOLUME_HUSH_VIBRATE; + case KEY_NORMAL: + return DERP_VOLUME_HUSH_NORMAL; default: - return Settings.Secure.VOLUME_HUSH_OFF; + return DERP_VOLUME_HUSH_OFF; } } - private SelectorWithWidgetPreference makeRadioPreference(String key, int titleId) { - SelectorWithWidgetPreference pref = new SelectorWithWidgetPreference( - mPreferenceCategory.getContext()); + private SwitchPreference makeSwitchPreference(String key, int titleId) { + SwitchPreference pref = new SwitchPreference(mPreferenceCategory.getContext()); pref.setKey(key); pref.setTitle(titleId); - pref.setOnClickListener(this); + pref.setOnPreferenceChangeListener(this); mPreferenceCategory.addPreference(pref); return pref; } diff --git a/src/com/android/settings/gestures/PreventRingingParentPreferenceController.java b/src/com/android/settings/gestures/PreventRingingParentPreferenceController.java index 03b0259449c..924d6c2826c 100644 --- a/src/com/android/settings/gestures/PreventRingingParentPreferenceController.java +++ b/src/com/android/settings/gestures/PreventRingingParentPreferenceController.java @@ -17,8 +17,10 @@ package com.android.settings.gestures; import static android.provider.Settings.Secure.VOLUME_HUSH_GESTURE; -import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; -import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE; +import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_MUTE; +import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_NORMAL; +import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_OFF; +import static android.provider.Settings.Secure.DERP_VOLUME_HUSH_VIBRATE; import android.content.ContentResolver; import android.content.Context; @@ -39,6 +41,10 @@ import com.google.common.annotations.VisibleForTesting; +import java.lang.StringBuilder; +import java.util.ArrayList; +import java.util.Arrays; + /** The controller manages the behaviour of the Prevent Ringing gesture setting. */ public class PreventRingingParentPreferenceController extends TogglePreferenceController implements LifecycleObserver, OnStart, OnStop { @@ -68,53 +74,58 @@ public boolean isChecked() { return false; } - final int preventRinging = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.VOLUME_HUSH_GESTURE, - Settings.Secure.VOLUME_HUSH_VIBRATE); - return preventRinging != Settings.Secure.VOLUME_HUSH_OFF; + String preventRinging = Settings.Secure.getString( + mContext.getContentResolver(), VOLUME_HUSH_GESTURE); + if (preventRinging == null) preventRinging = DERP_VOLUME_HUSH_OFF; + return !preventRinging.equals(DERP_VOLUME_HUSH_OFF); } @Override public boolean setChecked(boolean isChecked) { - final int preventRingingSetting = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE); - final int newRingingSetting = preventRingingSetting == Settings.Secure.VOLUME_HUSH_OFF - ? Settings.Secure.VOLUME_HUSH_VIBRATE - : preventRingingSetting; - - return Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.VOLUME_HUSH_GESTURE, isChecked + String preventRingingSetting = Settings.Secure.getString( + mContext.getContentResolver(), VOLUME_HUSH_GESTURE); + if (preventRingingSetting == null) preventRingingSetting = DERP_VOLUME_HUSH_OFF; + + final String newRingingSetting = preventRingingSetting.equals(DERP_VOLUME_HUSH_OFF) + ? DERP_VOLUME_HUSH_VIBRATE : preventRingingSetting; + + return Settings.Secure.putString(mContext.getContentResolver(), + VOLUME_HUSH_GESTURE, isChecked ? newRingingSetting - : Settings.Secure.VOLUME_HUSH_OFF); + : DERP_VOLUME_HUSH_OFF); } @Override public void updateState(Preference preference) { super.updateState(preference); - final int value = Settings.Secure.getInt( - mContext.getContentResolver(), SECURE_KEY, VOLUME_HUSH_VIBRATE); - CharSequence summary; + String value = Settings.Secure.getString( + mContext.getContentResolver(), SECURE_KEY); + if (value == null) value = DERP_VOLUME_HUSH_OFF; + StringBuilder summary = new StringBuilder( + mContext.getString(R.string.switch_off_text)); if (isVolumePowerKeyChordSetToHush()) { - switch (value) { - case VOLUME_HUSH_VIBRATE: - summary = mContext.getText(R.string.prevent_ringing_option_vibrate_summary); - break; - case VOLUME_HUSH_MUTE: - summary = mContext.getText(R.string.prevent_ringing_option_mute_summary); - break; - // VOLUME_HUSH_OFF - default: - summary = mContext.getText(R.string.switch_off_text); + if (!value.equals(DERP_VOLUME_HUSH_OFF)) { + ArrayList values = + new ArrayList<>(Arrays.asList(value.split(",", 0))); + if (!values.isEmpty()) { + summary = new StringBuilder( + mContext.getString(R.string.switch_on_text) + + " (" + getStringForMode(values.remove(0))); + for (String str : values) + summary.append(", ").append(getStringForMode(str)); + summary.append(")"); + } } preference.setEnabled(true); mPreference.setSwitchEnabled(true); } else { - summary = mContext.getText(R.string.prevent_ringing_option_unavailable_lpp_summary); + summary = new StringBuilder(mContext.getString( + R.string.prevent_ringing_option_unavailable_lpp_summary)); preference.setEnabled(false); mPreference.setSwitchEnabled(false); } - preference.setSummary(summary); + preference.setSummary(summary.toString()); } @Override @@ -203,4 +214,15 @@ public void onChange(boolean selfChange, Uri uri) { } } } + + private String getStringForMode(String mode) { + switch (mode) { + case DERP_VOLUME_HUSH_VIBRATE: + return mContext.getText(R.string.prevent_ringing_option_vibrate).toString(); + case DERP_VOLUME_HUSH_MUTE: + return mContext.getText(R.string.prevent_ringing_option_mute).toString(); + } + // DERP_VOLUME_HUSH_NORMAL + return mContext.getText(R.string.prevent_ringing_option_normal).toString(); + } } diff --git a/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java b/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java index 9c0e0bffae7..1f42ca38542 100644 --- a/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java +++ b/src/com/android/settings/gestures/PreventRingingSwitchPreferenceController.java @@ -24,6 +24,7 @@ import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; @@ -34,10 +35,11 @@ public class PreventRingingSwitchPreferenceController extends AbstractPreference implements PreferenceControllerMixin, OnCheckedChangeListener { private static final String KEY = "gesture_prevent_ringing_switch"; + private static final String KEY_VIBRATE = "prevent_ringing_option_vibrate"; private final Context mContext; - @VisibleForTesting MainSwitchPreference mSwitch; + SwitchPreference mVibratePref; public PreventRingingSwitchPreferenceController(Context context) { super(context); @@ -53,17 +55,18 @@ public String getPreferenceKey() { public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); if (isAvailable()) { + mVibratePref = screen.findPreference(KEY_VIBRATE); Preference pref = screen.findPreference(getPreferenceKey()); if (pref != null) { pref.setOnPreferenceClickListener(preference -> { - int preventRinging = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.VOLUME_HUSH_GESTURE, - Settings.Secure.VOLUME_HUSH_VIBRATE); - boolean isChecked = preventRinging != Settings.Secure.VOLUME_HUSH_OFF; - Settings.Secure.putInt(mContext.getContentResolver(), + String preventRinging = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.VOLUME_HUSH_GESTURE); + boolean isChecked = preventRinging != null && + !preventRinging.equals(Settings.Secure.DERP_VOLUME_HUSH_OFF); + Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, isChecked - ? Settings.Secure.VOLUME_HUSH_OFF - : Settings.Secure.VOLUME_HUSH_VIBRATE); + ? Settings.Secure.DERP_VOLUME_HUSH_OFF + : Settings.Secure.DERP_VOLUME_HUSH_VIBRATE); return true; }); mSwitch = (MainSwitchPreference) pref; @@ -82,9 +85,10 @@ public void setChecked(boolean isChecked) { @Override public void updateState(Preference preference) { - int preventRingingSetting = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE); - setChecked(preventRingingSetting != Settings.Secure.VOLUME_HUSH_OFF); + String preventRingingSetting = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.VOLUME_HUSH_GESTURE); + setChecked(preventRingingSetting != null && + !preventRingingSetting.equals(Settings.Secure.DERP_VOLUME_HUSH_OFF)); } @Override @@ -95,15 +99,23 @@ public boolean isAvailable() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - final int preventRingingSetting = Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_VIBRATE); - final int newRingingSetting = preventRingingSetting == Settings.Secure.VOLUME_HUSH_OFF - ? Settings.Secure.VOLUME_HUSH_VIBRATE + final String preventRingingSetting = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.VOLUME_HUSH_GESTURE); + final String newRingingSetting = + preventRingingSetting == null || + preventRingingSetting.equals(Settings.Secure.DERP_VOLUME_HUSH_OFF) + ? Settings.Secure.DERP_VOLUME_HUSH_VIBRATE : preventRingingSetting; - Settings.Secure.putInt(mContext.getContentResolver(), + if ((preventRingingSetting == null + || preventRingingSetting.equals(Settings.Secure.DERP_VOLUME_HUSH_OFF)) + && mVibratePref != null) { + mVibratePref.setChecked(true); + } + + Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, isChecked ? newRingingSetting - : Settings.Secure.VOLUME_HUSH_OFF); + : Settings.Secure.DERP_VOLUME_HUSH_OFF); } } diff --git a/src/com/android/settings/gestures/ScreenOffUdfpsPreferenceController.java b/src/com/android/settings/gestures/ScreenOffUdfpsPreferenceController.java new file mode 100644 index 00000000000..d1202cb4cac --- /dev/null +++ b/src/com/android/settings/gestures/ScreenOffUdfpsPreferenceController.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.gestures; + +import android.annotation.UserIdInt; +import android.content.Context; +import android.content.SharedPreferences; +import android.hardware.display.AmbientDisplayConfiguration; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils; + +import androidx.annotation.VisibleForTesting; + +public class ScreenOffUdfpsPreferenceController extends GesturePreferenceController { + + private final int ON = 1; + private final int OFF = 0; + + private static final String PREF_KEY_VIDEO = "gesture_screen_off_udfps_video"; + + private static final String SECURE_KEY = "screen_off_udfps_enabled"; + + private AmbientDisplayConfiguration mAmbientConfig; + @UserIdInt + private final int mUserId; + + public ScreenOffUdfpsPreferenceController(Context context, String key) { + super(context, key); + mUserId = UserHandle.myUserId(); + } + + public ScreenOffUdfpsPreferenceController setConfig(AmbientDisplayConfiguration config) { + mAmbientConfig = config; + return this; + } + + private static boolean screenOffUdfpsAvailable(AmbientDisplayConfiguration config) { + return !TextUtils.isEmpty(config.udfpsLongPressSensorType()); + } + + public static boolean isSuggestionComplete(Context context, SharedPreferences prefs) { + return isSuggestionComplete(new AmbientDisplayConfiguration(context), prefs); + } + + @VisibleForTesting + static boolean isSuggestionComplete(AmbientDisplayConfiguration config, + SharedPreferences prefs) { + return !screenOffUdfpsAvailable(config) + || prefs.getBoolean(ScreenOffUdfpsSettings.PREF_KEY_SUGGESTION_COMPLETE, false); + } + + @Override + public int getAvailabilityStatus() { + // No hardware support for Screen-Off UDFPS + if (!screenOffUdfpsAvailable(getAmbientConfig())) { + return UNSUPPORTED_ON_DEVICE; + } + + return AVAILABLE; + } + + @Override + public boolean isSliceable() { + return TextUtils.equals(getPreferenceKey(), "gesture_screen_off_udfps"); + } + + @Override + public boolean isPublicSlice() { + return true; + } + + @Override + public boolean setChecked(boolean isChecked) { + return Settings.Secure.putInt(mContext.getContentResolver(), SECURE_KEY, + isChecked ? ON : OFF); + } + + @Override + protected String getVideoPrefKey() { + return PREF_KEY_VIDEO; + } + + @Override + public boolean isChecked() { + return getAmbientConfig().screenOffUdfpsEnabled(mUserId); + } + + private AmbientDisplayConfiguration getAmbientConfig() { + if (mAmbientConfig == null) { + mAmbientConfig = new AmbientDisplayConfiguration(mContext); + } + return mAmbientConfig; + } +} diff --git a/src/com/android/settings/gestures/ScreenOffUdfpsSettings.java b/src/com/android/settings/gestures/ScreenOffUdfpsSettings.java new file mode 100644 index 00000000000..763ffb69bf7 --- /dev/null +++ b/src/com/android/settings/gestures/ScreenOffUdfpsSettings.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.gestures; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.SharedPreferences; +import android.hardware.display.AmbientDisplayConfiguration; +import android.provider.SearchIndexableResource; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +import java.util.Arrays; +import java.util.List; + +@SearchIndexable +public class ScreenOffUdfpsSettings extends DashboardFragment { + + private static final String TAG = "ScreenOffUdfps"; + + public static final String PREF_KEY_SUGGESTION_COMPLETE = + "pref_screen_off_udfps_suggestion_complete"; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + SuggestionFeatureProvider suggestionFeatureProvider = + FeatureFactory.Companion.getFeatureFactory().getSuggestionFeatureProvider(); + SharedPreferences prefs = suggestionFeatureProvider.getSharedPrefs(context); + prefs.edit().putBoolean(PREF_KEY_SUGGESTION_COMPLETE, true).apply(); + + use(ScreenOffUdfpsPreferenceController.class) + .setConfig(new AmbientDisplayConfiguration(context)); + } + + + @Override + public int getMetricsCategory() { + return SettingsEnums.PAGE_UNKNOWN; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.screen_off_udfps_settings; + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.screen_off_udfps_settings); +} diff --git a/src/com/android/settings/gestures/SwipeToScreenshotGestureSettings.java b/src/com/android/settings/gestures/SwipeToScreenshotGestureSettings.java new file mode 100644 index 00000000000..ae0f4e3ae92 --- /dev/null +++ b/src/com/android/settings/gestures/SwipeToScreenshotGestureSettings.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.gestures; + +import android.content.Context; +import android.content.SharedPreferences; +import android.provider.SearchIndexableResource; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +import java.util.Arrays; +import java.util.List; + +@SearchIndexable +public class SwipeToScreenshotGestureSettings extends DashboardFragment { + + private static final String TAG = "SwipeToScreenshotGestureSettings"; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + } + + @Override + public int getMetricsCategory() { + return -1; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.swipe_to_screenshot_gesture_settings; + } + + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex( + Context context, boolean enabled) { + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.swipe_to_screenshot_gesture_settings; + return Arrays.asList(sir); + } + }; +} diff --git a/src/com/android/settings/gestures/SwipeToScreenshotPreferenceController.java b/src/com/android/settings/gestures/SwipeToScreenshotPreferenceController.java new file mode 100644 index 00000000000..121428eaaea --- /dev/null +++ b/src/com/android/settings/gestures/SwipeToScreenshotPreferenceController.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.gestures; + +import static android.provider.Settings.System.THREE_FINGER_GESTURE; + +import android.content.Context; +import android.content.SharedPreferences; +import android.provider.Settings; +import android.text.TextUtils; + +public class SwipeToScreenshotPreferenceController extends GesturePreferenceController { + + private final int ON = 1; + private final int OFF = 0; + + private static final String PREF_KEY_VIDEO = "swipe_to_screenshot_video"; + + public SwipeToScreenshotPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean isSliceable() { + return TextUtils.equals(getPreferenceKey(), "swipe_to_screenshot"); + } + + @Override + protected String getVideoPrefKey() { + return PREF_KEY_VIDEO; + } + + @Override + public boolean setChecked(boolean isChecked) { + return Settings.System.putInt(mContext.getContentResolver(), THREE_FINGER_GESTURE, + isChecked ? ON : OFF); + } + + @Override + public boolean isChecked() { + return Settings.System.getInt(mContext.getContentResolver(), THREE_FINGER_GESTURE, 0) != 0; + } +} diff --git a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java index cfaee006d39..48a0cf31cea 100644 --- a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java +++ b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java @@ -59,6 +59,8 @@ import com.android.settingslib.widget.IllustrationPreference; import com.android.settingslib.widget.SelectorWithWidgetPreference; +import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen; + import java.util.ArrayList; import java.util.List; @@ -209,6 +211,9 @@ protected List getCandidates() { final Context c = getContext(); List candidates = new ArrayList<>(); + boolean isTaskbarEnabled = Settings.System.getInt(getContext().getContentResolver(), + Settings.System.ENABLE_TASKBAR, isLargeScreen(getContext()) ? 1 : 0) == 1; + if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c, NAV_BAR_MODE_GESTURAL_OVERLAY)) { candidates.add(new CandidateInfoExtra( @@ -216,7 +221,7 @@ protected List getCandidates() { c.getText(R.string.edge_to_edge_navigation_summary), KEY_SYSTEM_NAV_GESTURAL, true /* enabled */)); } - if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c, + if (!isTaskbarEnabled && SystemNavigationPreferenceController.isOverlayPackageAvailable(c, NAV_BAR_MODE_2BUTTON_OVERLAY)) { candidates.add(new CandidateInfoExtra( c.getText(R.string.swipe_up_to_switch_apps_title), diff --git a/src/com/android/settings/gestures/SystemNavigationPreferenceController.java b/src/com/android/settings/gestures/SystemNavigationPreferenceController.java index ccdb2577ca2..87955aaf130 100644 --- a/src/com/android/settings/gestures/SystemNavigationPreferenceController.java +++ b/src/com/android/settings/gestures/SystemNavigationPreferenceController.java @@ -23,6 +23,10 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.view.Display; +import android.view.IWindowManager; +import android.view.WindowManagerGlobal; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; @@ -54,9 +58,19 @@ public CharSequence getSummary() { /** Returns {@code true} if gesture is available. */ public static boolean isGestureAvailable(Context context) { + boolean hasNavigationBar = false; + final boolean configEnabled = context.getResources().getBoolean( + com.android.internal.R.bool.config_swipe_up_gesture_setting_available); + + try { + IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService(); + hasNavigationBar = windowManager.hasNavigationBar(Display.DEFAULT_DISPLAY); + } catch (RemoteException ex) { + // no window manager? good luck with that + } // Skip if the swipe up settings are not available - if (!context.getResources().getBoolean( - com.android.internal.R.bool.config_swipe_up_gesture_setting_available)) { + // or if on-screen navbar is disabled (for devices with hardware keys) + if (!configEnabled || !hasNavigationBar) { return false; } diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java index 99441b0cc50..cbcdf296bf0 100644 --- a/src/com/android/settings/homepage/TopLevelSettings.java +++ b/src/com/android/settings/homepage/TopLevelSettings.java @@ -88,7 +88,7 @@ public TopLevelSettings(TopLevelHighlightMixin highlightMixin) { @Override protected int getPreferenceScreenResId() { - return Flags.homepageRevamp() ? R.xml.top_level_settings_v2 : R.xml.top_level_settings; + return Flags.homepageRevamp() ? R.xml.top_level_settings_v2 : R.xml.derp_top_level_settings; } @Override @@ -350,7 +350,9 @@ protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen @Override protected Preference createPreference(Tile tile) { - return new HomepagePreference(getPrefContext()); + Preference p = new HomepagePreference(getPrefContext()); + p.setLayoutResource(R.layout.derp_dashboard_preference_middle); + return p; } void reloadHighlightMenuKey() { @@ -394,7 +396,7 @@ default void init() { new BaseSearchIndexProvider( Flags.homepageRevamp() ? R.xml.top_level_settings_v2 - : R.xml.top_level_settings) { + : R.xml.derp_top_level_settings) { @Override protected boolean isPageSearchEnabled(Context context) { diff --git a/src/com/android/settings/homepage/contextualcards/CardContentProvider.java b/src/com/android/settings/homepage/contextualcards/CardContentProvider.java index 7ad550611c7..859c8112786 100644 --- a/src/com/android/settings/homepage/contextualcards/CardContentProvider.java +++ b/src/com/android/settings/homepage/contextualcards/CardContentProvider.java @@ -186,7 +186,7 @@ public int update(Uri uri, ContentValues values, String selection, String[] sele @VisibleForTesting void maybeEnableStrictMode() { - if (Build.IS_DEBUGGABLE && ThreadUtils.isMainThread()) { + if (Build.IS_ENG && ThreadUtils.isMainThread()) { enableStrictMode(); } } diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java index 5059d90a15e..5c5cdfed2ea 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java @@ -76,7 +76,7 @@ int resetDismissedTime(long threshold) { + CardDatabaseHelper.CardColumns.DISMISSED_TIMESTAMP + " IS NOT NULL"; final String[] selectionArgs = {String.valueOf(threshold)}; final int rowsUpdated = database.update(CARD_TABLE, values, selection, selectionArgs); - if (Build.IS_DEBUGGABLE) { + if (Build.IS_ENG) { Log.d(TAG, "Reset " + rowsUpdated + " records of dismissed time."); } return rowsUpdated; diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java b/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java index a66e066343a..5c0a33c7c05 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java @@ -45,7 +45,7 @@ public class ContextualCardsFragment extends InstrumentedFragment implements FocusRecyclerView.FocusListener { private static final String TAG = "ContextualCardsFragment"; - private static final boolean DEBUG = Build.IS_DEBUGGABLE; + private static final boolean DEBUG = Build.IS_ENG; @VisibleForTesting static boolean sRestartLoaderNeeded; diff --git a/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java b/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java index 974af9fef4e..5a909ad5655 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java +++ b/src/com/android/settings/homepage/contextualcards/slices/DarkThemeSlice.java @@ -49,7 +49,7 @@ public class DarkThemeSlice implements CustomSliceable { private static final String TAG = "DarkThemeSlice"; - private static final boolean DEBUG = Build.IS_DEBUGGABLE; + private static final boolean DEBUG = Build.IS_ENG; private static final int BATTERY_LEVEL_THRESHOLD = 50; private static final int DELAY_TIME_EXECUTING_DARK_THEME = 200; diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java index b348d3a536b..36b23de2402 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardController.java @@ -103,7 +103,7 @@ void showFeedbackDialog(ContextualCard card) { @VisibleForTesting boolean isFeedbackEnabled(String email) { - return !TextUtils.isEmpty(email) && Build.IS_DEBUGGABLE; + return !TextUtils.isEmpty(email) && Build.IS_ENG; } private String getSimpleCardName(ContextualCard card) { diff --git a/src/com/android/settings/inputmethod/KeyboardSettings.java b/src/com/android/settings/inputmethod/KeyboardSettings.java index 38f73438cf1..86c2c7f5b30 100644 --- a/src/com/android/settings/inputmethod/KeyboardSettings.java +++ b/src/com/android/settings/inputmethod/KeyboardSettings.java @@ -36,6 +36,8 @@ import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.search.SearchIndexable; +import com.android.internal.derp.hardware.LineageHardwareManager; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -47,6 +49,7 @@ public class KeyboardSettings extends DashboardFragment { private static final String KEY_KEYBOARDS_CATEGORY = "keyboards_category"; private static final String KEY_POINTER_CATEGORY = "pointer_category"; + private static final String KEY_TOUCH_HOVERING = "feature_touch_hovering"; @Override public int getMetricsCategory() { @@ -110,5 +113,15 @@ private static List buildPreferenceControllers( } public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider(R.xml.keyboard_settings); -} \ No newline at end of file + new BaseSearchIndexProvider(R.xml.keyboard_settings) { + @Override + public List getNonIndexableKeys(Context context) { + List keys = super.getNonIndexableKeys(context); + LineageHardwareManager hardware = LineageHardwareManager.getInstance(context); + if (!hardware.isSupported(LineageHardwareManager.FEATURE_TOUCH_HOVERING)) { + keys.add(KEY_TOUCH_HOVERING); + } + return keys; + } + }; +} diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java index 5ba1c848b89..1bd665698de 100644 --- a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java +++ b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java @@ -70,6 +70,7 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment private static final String KEYBOARD_OPTIONS_CATEGORY = "keyboard_options_category"; private static final String KEYBOARD_A11Y_CATEGORY = "keyboard_a11y_category"; + private static final String KEYBOARD_EXTRAS_CATEGORY = "keyboard_extras_category"; private static final String SHOW_VIRTUAL_KEYBOARD_SWITCH = "show_virtual_keyboard_switch"; private static final String ACCESSIBILITY_BOUNCE_KEYS = "accessibility_bounce_keys"; private static final String ACCESSIBILITY_SLOW_KEYS = "accessibility_slow_keys"; @@ -99,6 +100,8 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment private PreferenceCategory mKeyboardAssistanceCategory; @Nullable private PreferenceCategory mKeyboardA11yCategory = null; + @NonNull + private PreferenceCategory mKeyboardExtrasCategory = null; @Nullable private TwoStatePreference mShowVirtualKeyboardSwitch = null; @Nullable @@ -129,6 +132,8 @@ public void onCreatePreferences(Bundle bundle, String s) { mImm = Preconditions.checkNotNull(activity.getSystemService(InputMethodManager.class)); mKeyboardAssistanceCategory = Preconditions.checkNotNull( findPreference(KEYBOARD_OPTIONS_CATEGORY)); + mKeyboardExtrasCategory = Preconditions.checkNotNull( + findPreference(KEYBOARD_EXTRAS_CATEGORY)); mShowVirtualKeyboardSwitch = Objects.requireNonNull( mKeyboardAssistanceCategory.findPreference(SHOW_VIRTUAL_KEYBOARD_SWITCH)); @@ -311,6 +316,8 @@ private void updateHardKeyboards(@NonNull List newHardKe } mKeyboardAssistanceCategory.setOrder(1); preferenceScreen.addPreference(mKeyboardAssistanceCategory); + mKeyboardExtrasCategory.setOrder(99); + preferenceScreen.addPreference(mKeyboardExtrasCategory); if (mSupportsFirmwareUpdate) { mFeatureProvider.addFirmwareUpdateCategory(getPrefContext(), preferenceScreen); } diff --git a/src/com/android/settings/location/AgpsPreferenceController.java b/src/com/android/settings/location/AgpsPreferenceController.java new file mode 100644 index 00000000000..0b0bb73a6f0 --- /dev/null +++ b/src/com/android/settings/location/AgpsPreferenceController.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2011 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.location; + +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; + +import androidx.preference.SwitchPreferenceCompat; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +public class AgpsPreferenceController extends LocationBasePreferenceController { + private static final String KEY_ASSISTED_GPS = "assisted_gps"; + + private SwitchPreferenceCompat mAgpsPreference; + + public AgpsPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public String getPreferenceKey() { + return KEY_ASSISTED_GPS; + } + + @AvailabilityStatus + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mAgpsPreference = (SwitchPreferenceCompat) screen.findPreference(KEY_ASSISTED_GPS); + } + + @Override + public void updateState(Preference preference) { + if (mAgpsPreference != null) { + mAgpsPreference.setChecked(Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.ASSISTED_GPS_ENABLED, 1) == 1); + } + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (KEY_ASSISTED_GPS.equals(preference.getKey())) { + final ContentResolver cr = mContext.getContentResolver(); + final boolean switchState = mAgpsPreference.isChecked(); + Settings.Global.putInt(cr, Settings.Global.ASSISTED_GPS_ENABLED, + switchState ? 1 : 0); + return true; + } + return false; + } + + @Override + public void onLocationModeChanged(int mode, boolean restricted) {} +} diff --git a/src/com/android/settings/location/LocationSettings.java b/src/com/android/settings/location/LocationSettings.java index 87e8817808f..bcba10f62e6 100644 --- a/src/com/android/settings/location/LocationSettings.java +++ b/src/com/android/settings/location/LocationSettings.java @@ -120,6 +120,7 @@ public void onAttach(Context context) { use(LocationForWorkPreferenceController.class).init(this); use(LocationSettingsFooterPreferenceController.class).init(this); use(LocationForPrivateProfilePreferenceController.class).init(this); + use(AgpsPreferenceController.class).init(this); } @Override diff --git a/src/com/android/settings/media/AppVolumeSlice.java b/src/com/android/settings/media/AppVolumeSlice.java new file mode 100644 index 00000000000..e653ffc0fb6 --- /dev/null +++ b/src/com/android/settings/media/AppVolumeSlice.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2022 Project Kaleidoscope + * + * 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.media; + +import static android.app.slice.Slice.EXTRA_RANGE_VALUE; +import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; + +import static com.android.settings.slices.CustomSliceRegistry.APP_VOLUME_SLICE_URI; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.media.AudioManager; +import android.media.AppVolume; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import androidx.core.graphics.drawable.IconCompat; +import androidx.slice.Slice; +import androidx.slice.builders.ListBuilder; +import androidx.slice.builders.ListBuilder.InputRangeBuilder; +import androidx.slice.builders.SliceAction; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.slices.CustomSliceable; +import com.android.settings.slices.SliceBroadcastReceiver; + +import java.util.ArrayList; +import java.util.List; + +public class AppVolumeSlice implements CustomSliceable { + + private static final String TAG = "AppVolumeSlice"; + private static final String PACKAGE_NAME = "package_name"; + private static final String ACTION_LAUNCH_DIALOG = "action_launch_dialog"; + + private final Context mContext; + + private final AudioManager mAudioManager; + + public AppVolumeSlice(Context context) { + mContext = context; + mAudioManager = context.getSystemService(AudioManager.class); + } + + @Override + public void onNotifyChange(Intent intent) { + final int newPosition = intent.getIntExtra(EXTRA_RANGE_VALUE, -1); + final String packageName = intent.getStringExtra(PACKAGE_NAME); + if (!TextUtils.isEmpty(packageName)) { + mAudioManager.setAppVolume(packageName, newPosition / 100.0f); + return; + } + } + + @Override + public Slice getSlice() { + final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY) + .setAccentColor(COLOR_NOT_TINTED); + + // Only displaying active tracks + final List appVols = new ArrayList<>(); + for (AppVolume vol : mAudioManager.listAppVolumes()) { + if (vol.isActive()) { + appVols.add(vol); + } + } + if (appVols.isEmpty()) { + Log.d(TAG, "No active tracks"); + return listBuilder.build(); + } + + for (AppVolume vol : appVols) { + final CharSequence appName = Utils.getApplicationLabel( + mContext, vol.getPackageName()); + IconCompat icon = getApplicationIcon(vol.getPackageName()); + final SliceAction primarySliceAction = SliceAction.create( + getBroadcastIntent(mContext), icon, ListBuilder.ICON_IMAGE, appName); + listBuilder.addInputRange(new InputRangeBuilder() + .setTitleItem(icon, ListBuilder.ICON_IMAGE) + .setTitle(appName) + .setInputAction(getSliderInputAction(vol.getPackageName())) + .setMax(100) + .setValue((int)(vol.getVolume() * 100)) + .setPrimaryAction(primarySliceAction)); + } + return listBuilder.build(); + } + + private IconCompat getApplicationIcon(String packageName) { + PackageManager pm = mContext.getPackageManager(); + try { + ApplicationInfo ai = pm.getApplicationInfo(packageName, 0); + Resources resources = pm.getResourcesForApplication(ai); + IconCompat icon = IconCompat.createWithResource(resources, packageName, ai.icon); + return icon; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to get icon of " + packageName, e); + } + + final Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + return IconCompat.createWithBitmap(bitmap); + } + + + private PendingIntent getSliderInputAction(String packageName) { + final int requestCode = packageName.hashCode(); + final Intent intent = new Intent(getUri().toString()) + .setData(getUri()) + .putExtra(PACKAGE_NAME, packageName) + .setClass(mContext, SliceBroadcastReceiver.class); + return PendingIntent.getBroadcast(mContext, requestCode, intent, + PendingIntent.FLAG_MUTABLE); + } + + @Override + public Uri getUri() { + return APP_VOLUME_SLICE_URI; + } + + @Override + public Intent getIntent() { + return null; + } + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_sound; + } +} diff --git a/src/com/android/settings/network/PrivateDnsModeDialogPreference.java b/src/com/android/settings/network/PrivateDnsModeDialogPreference.java index 3b99777720a..a5317ff4909 100644 --- a/src/com/android/settings/network/PrivateDnsModeDialogPreference.java +++ b/src/com/android/settings/network/PrivateDnsModeDialogPreference.java @@ -72,9 +72,13 @@ public class PrivateDnsModeDialogPreference extends CustomDialogPreferenceCompat // DNS_MODE -> RadioButton id private static final Map PRIVATE_DNS_MAP; + // Only used in Settings, update on additions to ConnectivitySettingsUtils + private static final int PRIVATE_DNS_MODE_CLOUDFLARE = 4; + static { PRIVATE_DNS_MAP = new HashMap<>(); PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_OFF, R.id.private_dns_mode_off); + PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_CLOUDFLARE, R.id.private_dns_mode_cloudflare); PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_OPPORTUNISTIC, R.id.private_dns_mode_opportunistic); PRIVATE_DNS_MAP.put(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, R.id.private_dns_mode_provider); } @@ -144,6 +148,15 @@ protected void onBindDialogView(View view) { final ContentResolver contentResolver = context.getContentResolver(); mMode = ConnectivitySettingsManager.getPrivateDnsMode(context); + if (mMode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) { + final String privateDnsHostname = + ConnectivitySettingsManager.getPrivateDnsHostname(context); + final String cloudflareHostname = + context.getString(R.string.private_dns_hostname_cloudflare); + if (privateDnsHostname.equals(cloudflareHostname)) { + mMode = PRIVATE_DNS_MODE_CLOUDFLARE; + } + } mEditText = view.findViewById(R.id.private_dns_mode_provider_hostname); mEditText.addTextChangedListener(this); @@ -156,6 +169,9 @@ protected void onBindDialogView(View view) { // Initial radio button text final RadioButton offRadioButton = view.findViewById(R.id.private_dns_mode_off); offRadioButton.setText(com.android.settingslib.R.string.private_dns_mode_off); + final RadioButton cloudflareRadioButton = + view.findViewById(R.id.private_dns_mode_cloudflare); + cloudflareRadioButton.setText(R.string.private_dns_mode_cloudflare); final RadioButton opportunisticRadioButton = view.findViewById(R.id.private_dns_mode_opportunistic); opportunisticRadioButton.setText( @@ -182,15 +198,21 @@ protected void onBindDialogView(View view) { public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_POSITIVE) { final Context context = getContext(); + int modeToSet = mMode; if (mMode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) { // Only clickable if hostname is valid, so we could save it safely ConnectivitySettingsManager.setPrivateDnsHostname(context, mEditText.getText().toString()); + } else if (mMode == PRIVATE_DNS_MODE_CLOUDFLARE) { + final String cloudflareHostname = + context.getString(R.string.private_dns_hostname_cloudflare); + ConnectivitySettingsManager.setPrivateDnsHostname(context, cloudflareHostname); + modeToSet = PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; } FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().action(context, - SettingsEnums.ACTION_PRIVATE_DNS_MODE, mMode); - ConnectivitySettingsManager.setPrivateDnsMode(context, mMode); + SettingsEnums.ACTION_PRIVATE_DNS_MODE, modeToSet); + ConnectivitySettingsManager.setPrivateDnsMode(context, modeToSet); } } @@ -198,6 +220,8 @@ public void onClick(DialogInterface dialog, int which) { public void onCheckedChanged(RadioGroup group, int checkedId) { if (checkedId == R.id.private_dns_mode_off) { mMode = PRIVATE_DNS_MODE_OFF; + } else if (checkedId == R.id.private_dns_mode_cloudflare) { + mMode = PRIVATE_DNS_MODE_CLOUDFLARE; } else if (checkedId == R.id.private_dns_mode_opportunistic) { mMode = PRIVATE_DNS_MODE_OPPORTUNISTIC; } else if (checkedId == R.id.private_dns_mode_provider) { diff --git a/src/com/android/settings/network/PrivateDnsPreferenceController.java b/src/com/android/settings/network/PrivateDnsPreferenceController.java index 21e4926f490..17aaaeb869a 100644 --- a/src/com/android/settings/network/PrivateDnsPreferenceController.java +++ b/src/com/android/settings/network/PrivateDnsPreferenceController.java @@ -65,6 +65,9 @@ public class PrivateDnsPreferenceController extends BasePreferenceController Settings.Global.getUriFor(PRIVATE_DNS_SPECIFIER), }; + // Only used in Settings, update on additions to ConnectivitySettingsUtils + private static final int PRIVATE_DNS_MODE_CLOUDFLARE = 4; + private final Handler mHandler; private final ContentObserver mSettingsObserver; private final ConnectivityManager mConnectivityManager; @@ -129,15 +132,24 @@ public CharSequence getSummary() { switch (mode) { case PRIVATE_DNS_MODE_OFF: return res.getString(com.android.settingslib.R.string.private_dns_mode_off); + case PRIVATE_DNS_MODE_CLOUDFLARE: case PRIVATE_DNS_MODE_OPPORTUNISTIC: return dnsesResolved ? res.getString(R.string.private_dns_mode_on) : res.getString( com.android.settingslib.R.string.private_dns_mode_opportunistic); case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME: - return dnsesResolved - ? PrivateDnsModeDialogPreference.getHostnameFromSettings(cr) - : res.getString( - com.android.settingslib.R.string.private_dns_mode_provider_failure); + if (!dnsesResolved) { + return res.getString( + com.android.settingslib.R.string.private_dns_mode_provider_failure); + } + final String privateDnsHostname = + ConnectivitySettingsManager.getPrivateDnsHostname(mContext); + final String cloudflareHostname = + res.getString(R.string.private_dns_hostname_cloudflare); + if (privateDnsHostname.equals(cloudflareHostname)) { + return res.getString(R.string.private_dns_mode_cloudflare); + } + return PrivateDnsModeDialogPreference.getHostnameFromSettings(cr); } return ""; } diff --git a/src/com/android/settings/notification/ChargingVibroPreferenceController.java b/src/com/android/settings/notification/ChargingVibroPreferenceController.java new file mode 100644 index 00000000000..b1f9f165ce6 --- /dev/null +++ b/src/com/android/settings/notification/ChargingVibroPreferenceController.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 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.notification; + +import static com.android.settings.notification.SettingPref.TYPE_GLOBAL; + +import android.content.Context; + +import android.provider.Settings.Global; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settingslib.core.lifecycle.Lifecycle; + +public class ChargingVibroPreferenceController extends SettingPrefController { + + private static final String KEY_CHARGING_VIBRO = "charging_vibro"; + + public ChargingVibroPreferenceController(Context context, SettingsPreferenceFragment parent, + Lifecycle lifecycle) { + super(context, parent, lifecycle); + mPreference = new SettingPref( + TYPE_GLOBAL, KEY_CHARGING_VIBRO, Global.CHARGING_VIBRATION_ENABLED, DEFAULT_ON); + + } + +} diff --git a/src/com/android/settings/notification/ConfigureNotificationSettings.java b/src/com/android/settings/notification/ConfigureNotificationSettings.java index 19222612f3a..c0cd5f7cdc1 100644 --- a/src/com/android/settings/notification/ConfigureNotificationSettings.java +++ b/src/com/android/settings/notification/ConfigureNotificationSettings.java @@ -23,15 +23,23 @@ import android.app.Application; import android.app.settings.SettingsEnums; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.database.ContentObserver; import android.os.Bundle; +import android.os.Looper; +import android.os.Handler; import android.os.UserHandle; +import android.provider.Settings; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +import androidx.preference.PreferenceCategory; +import androidx.preference.Preference.OnPreferenceChangeListener; +import androidx.preference.SwitchPreferenceCompat; import com.android.settings.R; import com.android.settings.RingtonePreference; @@ -46,7 +54,7 @@ @SearchIndexable public class ConfigureNotificationSettings extends DashboardFragment implements - OnActivityResultListener { + OnActivityResultListener, OnPreferenceChangeListener { private static final String TAG = "ConfigNotiSettings"; @VisibleForTesting @@ -57,11 +65,17 @@ public class ConfigureNotificationSettings extends DashboardFragment implements private static final int REQUEST_CODE = 200; private static final String SELECTED_PREFERENCE_KEY = "selected_preference"; private static final String KEY_ADVANCED_CATEGORY = "configure_notifications_advanced"; + private static final String KEY_HEADS_UP = "heads_up_notifications_enabled"; private RingtonePreference mRequestPreference; private NotificationAssistantPreferenceController mNotificationAssistantPreferenceController; + private SwitchPreferenceCompat mHeadsUp; + + private final HeadsUpObserver mHeadsUpObserver = new HeadsUpObserver(); + private boolean mHeadsUpSelfChange = false; + @Override public int getMetricsCategory() { return SettingsEnums.CONFIGURE_NOTIFICATION; @@ -81,6 +95,21 @@ public void onCreate(Bundle icicle) { replaceEnterpriseStringSummary("lock_screen_work_redact", WORK_PROFILE_LOCK_SCREEN_REDACT_NOTIFICATION_SUMMARY, R.string.lock_screen_notifs_redact_work_summary); + + PreferenceScreen prefScreen = getPreferenceScreen(); + final ContentResolver resolver = getActivity().getContentResolver(); + + mHeadsUp = (SwitchPreferenceCompat) findPreference(KEY_HEADS_UP); + boolean enabled = Settings.Global.getInt(resolver, KEY_HEADS_UP, 1) == 1; + mHeadsUp.setChecked(enabled); + mHeadsUp.setOnPreferenceChangeListener(this); + mHeadsUpObserver.observe(); + } + + @Override + public void onDestroy() { + mHeadsUpObserver.stop(); + super.onDestroy(); } @Override @@ -159,6 +188,46 @@ public void onSaveInstanceState(Bundle outState) { } } + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + ContentResolver resolver = getActivity().getContentResolver(); + if (preference == mHeadsUp) { + boolean enabled = (Boolean) newValue; + mHeadsUpSelfChange = true; + Settings.Global.putInt(resolver, KEY_HEADS_UP, enabled ? 1 : 0); + return true; + } + return false; + } + + private class HeadsUpObserver extends ContentObserver { + HeadsUpObserver() { + super(new Handler(Looper.getMainLooper())); + } + + void observe() { + getActivity().getContentResolver().registerContentObserver( + Settings.Global.getUriFor(KEY_HEADS_UP), + false, this, UserHandle.USER_ALL); + } + + void stop() { + getActivity().getContentResolver().unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange) { + if (mHeadsUp == null || selfChange) return; + if (mHeadsUpSelfChange) { + mHeadsUpSelfChange = false; + return; + } + final boolean enabled = Settings.Global.getInt( + getActivity().getContentResolver(), KEY_HEADS_UP, 1) == 1; + mHeadsUp.setChecked(enabled); + } + } + /** * For Search. */ diff --git a/src/com/android/settings/notification/IncreasingRingPreferenceController.java b/src/com/android/settings/notification/IncreasingRingPreferenceController.java new file mode 100644 index 00000000000..2152451f8b3 --- /dev/null +++ b/src/com/android/settings/notification/IncreasingRingPreferenceController.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 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.notification; + +import android.content.Context; + +import com.android.settings.Utils; +import com.android.settings.core.BasePreferenceController; + +public class IncreasingRingPreferenceController extends BasePreferenceController { + private static final String KEY_INCREASING_RING = "increasing_ring"; + + public IncreasingRingPreferenceController(Context context) { + super(context, KEY_INCREASING_RING); + } + + @Override + public int getAvailabilityStatus() { + return Utils.isVoiceCapable(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } +} diff --git a/src/com/android/settings/notification/IncreasingRingVolumePreference.java b/src/com/android/settings/notification/IncreasingRingVolumePreference.java new file mode 100644 index 00000000000..0b2dd6cfe61 --- /dev/null +++ b/src/com/android/settings/notification/IncreasingRingVolumePreference.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2014 CyanogenMod 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.notification; + +import android.content.ContentResolver; +import android.content.Context; +import android.media.AudioAttributes; +import android.media.AudioManager; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.provider.Settings; +import android.text.format.Formatter; +import android.util.AttributeSet; +import android.util.Log; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.core.content.res.TypedArrayUtils; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.R; + +public class IncreasingRingVolumePreference extends Preference + implements Handler.Callback, SeekBar.OnSeekBarChangeListener { + private static final String TAG = "IncreasingRingMinVolumePreference"; + + public interface Callback { + void onSampleStarting(IncreasingRingVolumePreference pref); + } + + private SeekBar mStartVolumeSeekBar; + private SeekBar mRampUpTimeSeekBar; + private TextView mRampUpTimeValue; + + private Ringtone mRingtone; + private Callback mCallback; + + private Handler mHandler; + private final Handler mMainHandler = new Handler(this); + + private static final int MSG_START_SAMPLE = 1; + private static final int MSG_STOP_SAMPLE = 2; + private static final int MSG_INIT_SAMPLE = 3; + private static final int MSG_SET_VOLUME = 4; + private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000; + + public IncreasingRingVolumePreference(Context context) { + this(context, null); + } + + public IncreasingRingVolumePreference(Context context, AttributeSet attrs) { + this(context, attrs, TypedArrayUtils.getAttr(context, + androidx.preference.R.attr.preferenceStyle, + android.R.attr.preferenceStyle)); + } + + public IncreasingRingVolumePreference(Context context, AttributeSet attrs, + int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public IncreasingRingVolumePreference(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setLayoutResource(R.layout.preference_increasing_ring); + initHandler(); + } + + public void setCallback(Callback callback) { + mCallback = callback; + } + + public void onActivityResume() { + initHandler(); + } + + public void onActivityStop() { + if (mHandler != null) { + postStopSample(); + mHandler.getLooper().quitSafely(); + mHandler = null; + } + } + + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_START_SAMPLE: + onStartSample((float) msg.arg1 / 1000F); + break; + case MSG_STOP_SAMPLE: + onStopSample(); + break; + case MSG_INIT_SAMPLE: + onInitSample(); + break; + case MSG_SET_VOLUME: + onSetVolume((float) msg.arg1 / 1000F); + break; + } + return true; + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder) { + super.onBindViewHolder(holder); + + initHandler(); + + final SeekBar seekBar = (SeekBar) holder.findViewById(R.id.start_volume); + if (seekBar == mStartVolumeSeekBar) return; + + mStartVolumeSeekBar = seekBar; + mRampUpTimeSeekBar = (SeekBar) holder.findViewById(R.id.ramp_up_time); + mRampUpTimeValue = (TextView) holder.findViewById(R.id.ramp_up_time_value); + + final ContentResolver cr = getContext().getContentResolver(); + float startVolume = Settings.System.getFloat(cr, + Settings.System.INCREASING_RING_START_VOLUME, 0.1f); + int rampUpTime = Settings.System.getInt(cr, + Settings.System.INCREASING_RING_RAMP_UP_TIME, 10); + + mStartVolumeSeekBar.setProgress(Math.round(startVolume * 1000F)); + mStartVolumeSeekBar.setOnSeekBarChangeListener(this); + mRampUpTimeSeekBar.setOnSeekBarChangeListener(this); + mRampUpTimeSeekBar.setProgress((rampUpTime / 5) - 1); + mRampUpTimeValue.setText( + Formatter.formatShortElapsedTime(getContext(), rampUpTime * 1000)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (seekBar == mStartVolumeSeekBar) { + postStartSample(seekBar.getProgress()); + } + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) { + ContentResolver cr = getContext().getContentResolver(); + if (fromTouch && seekBar == mStartVolumeSeekBar) { + Settings.System.putFloat(cr, + Settings.System.INCREASING_RING_START_VOLUME, (float) progress / 1000F); + } else if (seekBar == mRampUpTimeSeekBar) { + int seconds = (progress + 1) * 5; + mRampUpTimeValue.setText( + Formatter.formatShortElapsedTime(getContext(), seconds * 1000)); + if (fromTouch) { + Settings.System.putInt(cr, + Settings.System.INCREASING_RING_RAMP_UP_TIME, seconds); + } + } + } + + private void initHandler() { + if (mHandler != null) return; + + HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler"); + thread.start(); + + mHandler = new Handler(thread.getLooper(), this); + mHandler.sendEmptyMessage(MSG_INIT_SAMPLE); + } + + private void onInitSample() { + mRingtone = RingtoneManager.getRingtone(getContext(), + Settings.System.DEFAULT_RINGTONE_URI); + if (mRingtone != null) { + mRingtone.setStreamType(AudioManager.STREAM_RING); + mRingtone.setAudioAttributes( + new AudioAttributes.Builder(mRingtone.getAudioAttributes()) + .setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY | + AudioAttributes.FLAG_BYPASS_MUTE) + .build()); + } + } + + private void postStartSample(int progress) { + boolean playing = isSamplePlaying(); + mHandler.removeMessages(MSG_START_SAMPLE); + mHandler.removeMessages(MSG_SET_VOLUME); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE, progress, 0), + playing ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0); + if (playing) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_VOLUME, progress, 0)); + } + } + + private void onStartSample(float volume) { + if (mRingtone == null) { + return; + } + if (!isSamplePlaying()) { + if (mCallback != null) { + mCallback.onSampleStarting(this); + } + try { + mRingtone.play(); + } catch (Throwable e) { + Log.w(TAG, "Error playing ringtone", e); + } + } + mRingtone.setVolume(volume); + } + + private void onSetVolume(float volume) { + if (mRingtone != null) { + mRingtone.setVolume(volume); + } + } + + private boolean isSamplePlaying() { + return mRingtone != null && mRingtone.isPlaying(); + } + + public void stopSample() { + if (mHandler != null) { + postStopSample(); + } + } + + private void postStopSample() { + // remove pending delayed start messages + mHandler.removeMessages(MSG_START_SAMPLE); + mHandler.removeMessages(MSG_STOP_SAMPLE); + mHandler.sendEmptyMessage(MSG_STOP_SAMPLE); + } + + private void onStopSample() { + if (mRingtone != null) { + mRingtone.stop(); + } + } +} diff --git a/src/com/android/settings/notification/IncreasingRingVolumePreferenceController.java b/src/com/android/settings/notification/IncreasingRingVolumePreferenceController.java new file mode 100644 index 00000000000..78da2485a27 --- /dev/null +++ b/src/com/android/settings/notification/IncreasingRingVolumePreferenceController.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2016 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.notification; + +import android.content.Context; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.Utils; +import com.android.settings.slices.SliceData; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.core.lifecycle.events.OnStop; + +/** + * Base class for preference controller that handles VolumeSeekBarPreference + */ +public class IncreasingRingVolumePreferenceController + extends AdjustVolumeRestrictedPreferenceController + implements LifecycleObserver, OnResume, OnStop { + + private static final String KEY_INCREASING_RING_VOLUME = "increasing_ring_volume"; + + private IncreasingRingVolumePreference mPreference; + private IncreasingRingVolumePreference.Callback mCallback; + private AudioHelper mHelper; + + public IncreasingRingVolumePreferenceController(Context context) { + super(context, KEY_INCREASING_RING_VOLUME); + mHelper = new AudioHelper(context); + } + + public void setCallback(IncreasingRingVolumePreference.Callback callback) { + mCallback = callback; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + if (isAvailable()) { + mPreference = screen.findPreference(getPreferenceKey()); + mPreference.setCallback(mCallback); + } + } + + @Override + public void onResume() { + if (mPreference != null) { + mPreference.onActivityResume(); + } + } + + @Override + public void onStop() { + if (mPreference != null) { + mPreference.onActivityStop(); + } + } + + @Override + public String getPreferenceKey() { + return KEY_INCREASING_RING_VOLUME; + } + + @Override + public int getSliderPosition() { + return 0; + } + + @Override + public boolean setSliderPosition(int position) { + return false; + } + + @Override + public int getMin() { + return 0; + } + + @Override + public int getMax() { + return 0; + } + + @Override + public int getAvailabilityStatus() { + return Utils.isVoiceCapable(mContext) && !mHelper.isSingleVolume() ? + AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public int getSliceType() { + return SliceData.SliceType.INTENT; + } +} diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index 0661de7a5e7..0d9a5f48213 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -93,6 +93,7 @@ public AppRow loadAppRow(Context context, PackageManager pm, ApplicationInfo app row.userId = UserHandle.getUserId(row.uid); row.blockedChannelCount = getBlockedChannelCount(row.pkg, row.uid); row.channelCount = getChannelCount(row.pkg, row.uid); + row.soundTimeout = getNotificationSoundTimeout(row.pkg, row.uid); recordAggregatedUsageEvents(context, row); return row; } @@ -664,6 +665,25 @@ void setNm(INotificationManager inm) { sINM = inm; } + public long getNotificationSoundTimeout(String pkg, int uid) { + try { + return sINM.getNotificationSoundTimeout(pkg, uid); + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return 0; + } + } + + public boolean setNotificationSoundTimeout(String pkg, int uid, long timeout) { + try { + sINM.setNotificationSoundTimeout(pkg, uid, timeout); + return true; + } catch (Exception e) { + Log.w(TAG, "Error calling NoMan", e); + return false; + } + } + /** * NotificationsSentState contains how often an app sends notifications and how recently it sent * one. @@ -697,6 +717,7 @@ public static class AppRow extends Row { public int userId; public int blockedChannelCount; public int channelCount; + public long soundTimeout; public Map sentByChannel; public NotificationsSentState sentByApp; } diff --git a/src/com/android/settings/notification/NotificationVibrationPatternPreferenceController.java b/src/com/android/settings/notification/NotificationVibrationPatternPreferenceController.java new file mode 100644 index 00000000000..8f60872043b --- /dev/null +++ b/src/com/android/settings/notification/NotificationVibrationPatternPreferenceController.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2022 Yet Another AOSP 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.notification; + +import android.content.Context; +import android.content.res.Resources; +import android.provider.Settings; + +/** + * This class allows choosing a vibration pattern for notifications + */ +public class NotificationVibrationPatternPreferenceController extends VibrationPatternPreferenceController { + + private static final String KEY_VIB_PATTERN = "notification_vibration_pattern"; + private static final String KEY_CUSTOM_VIB_CATEGORY = "custom_notification_vibration_pattern"; + private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; + private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps + + private final long[] mDefaultPattern; + private final int[] mDefaultPatternAmp; + + public NotificationVibrationPatternPreferenceController(Context context) { + super(context); + mDefaultPattern = getLongArray(context.getResources(), + com.android.internal.R.array.config_defaultNotificationVibePattern, + VIBRATE_PATTERN_MAXLEN, + DEFAULT_VIBRATE_PATTERN); + + // making a full amp array according to what we have in config + int[] ampArr = new int[mDefaultPattern.length]; + for (int i = 0; i < mDefaultPattern.length; i++) { + if (i % 2 == 0) ampArr[i] = 0; + else ampArr[i] = 255; + } + mDefaultPatternAmp = ampArr; + } + + @Override + public boolean isAvailable() { + return mVibrator.hasVibrator(); + } + + @Override + public String getPreferenceKey() { + return KEY_VIB_PATTERN; + } + + @Override + protected String getCustomPreferenceKey() { + return KEY_CUSTOM_VIB_CATEGORY; + } + + @Override + protected String getSettingsKey() { + return Settings.System.NOTIFICATION_VIBRATION_PATTERN; + } + + @Override + protected long[] getDefaultPattern() { + return mDefaultPattern; + } + + @Override + protected int[] getDefaultPatternAmp() { + return mDefaultPatternAmp; + } + + private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) { + int[] ar = resources.getIntArray(resId); + if (ar == null) return def; + final int len = ar.length > maxLength ? maxLength : ar.length; + long[] out = new long[len]; + for (int i = 0; i < len; i++) out[i] = ar[i]; + return out; + } +} diff --git a/src/com/android/settings/notification/PhoneRingtone2PreferenceController.java b/src/com/android/settings/notification/PhoneRingtone2PreferenceController.java new file mode 100644 index 00000000000..2acda015e5d --- /dev/null +++ b/src/com/android/settings/notification/PhoneRingtone2PreferenceController.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018 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.notification; + +import android.content.Context; +import android.media.RingtoneManager; +import android.telephony.TelephonyManager; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.DefaultRingtonePreference; +import com.android.settings.Utils; + +public class PhoneRingtone2PreferenceController extends RingtonePreferenceControllerBase { + + private static final int SLOT_ID = 1; + private static final String KEY_PHONE_RINGTONE2 = "ringtone2"; + + public PhoneRingtone2PreferenceController(Context context) { + super(context); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + DefaultRingtonePreference ringtonePreference = + (DefaultRingtonePreference) screen.findPreference(KEY_PHONE_RINGTONE2); + ringtonePreference.setSlotId(SLOT_ID); + ringtonePreference.setEnabled(hasCard()); + } + + @Override + public String getPreferenceKey() { + return KEY_PHONE_RINGTONE2; + } + + @Override + public boolean isAvailable() { + TelephonyManager telephonyManager = + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + return Utils.isVoiceCapable(mContext) && telephonyManager.isMultiSimEnabled(); + } + + @Override + public int getRingtoneType() { + return RingtoneManager.TYPE_RINGTONE; + } + + private boolean hasCard() { + TelephonyManager telephonyManager = + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + return telephonyManager.hasIccCard(SLOT_ID); + } +} diff --git a/src/com/android/settings/notification/PhoneRingtonePreferenceController.java b/src/com/android/settings/notification/PhoneRingtonePreferenceController.java index 7bd78feeb3c..1c70e1acf33 100644 --- a/src/com/android/settings/notification/PhoneRingtonePreferenceController.java +++ b/src/com/android/settings/notification/PhoneRingtonePreferenceController.java @@ -19,16 +19,39 @@ import android.content.Context; import android.media.RingtoneManager; +import android.telephony.TelephonyManager; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.DefaultRingtonePreference; +import com.android.settings.R; + import com.android.settings.Utils; public class PhoneRingtonePreferenceController extends RingtonePreferenceControllerBase { + private static final int SLOT_ID = 0; private static final String KEY_PHONE_RINGTONE = "phone_ringtone"; public PhoneRingtonePreferenceController(Context context) { super(context); } + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + TelephonyManager telephonyManager = + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + if (telephonyManager.isMultiSimEnabled()) { + // For Multi SIM device, shoud show "Phone ringtone - SIM 1" for slot1 ringtone setting. + DefaultRingtonePreference ringtonePreference = + (DefaultRingtonePreference) screen.findPreference(KEY_PHONE_RINGTONE); + ringtonePreference.setTitle(mContext.getString(R.string.ringtone1_title)); + ringtonePreference.setEnabled(hasCard()); + } + } + @Override public String getPreferenceKey() { return KEY_PHONE_RINGTONE; @@ -43,4 +66,10 @@ public boolean isAvailable() { public int getRingtoneType() { return RingtoneManager.TYPE_RINGTONE; } + + private boolean hasCard() { + TelephonyManager telephonyManager = + (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + return telephonyManager.hasIccCard(SLOT_ID); + } } diff --git a/src/com/android/settings/notification/RingtonePreferenceControllerBase.java b/src/com/android/settings/notification/RingtonePreferenceControllerBase.java index 29b9266335d..359ea71abdf 100644 --- a/src/com/android/settings/notification/RingtonePreferenceControllerBase.java +++ b/src/com/android/settings/notification/RingtonePreferenceControllerBase.java @@ -24,6 +24,7 @@ import androidx.preference.Preference; +import com.android.settings.RingtonePreference; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.utils.ThreadUtils; @@ -51,8 +52,8 @@ public void updateState(Preference preference) { } private void updateSummary(Preference preference) { - final Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri( - mContext, getRingtoneType()); + final Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUriBySlot(mContext, + getRingtoneType(), ((RingtonePreference)preference).getSlotId()); final CharSequence summary; try { diff --git a/src/com/android/settings/notification/SoundSettings.java b/src/com/android/settings/notification/SoundSettings.java index 4575708bbbd..5f76ab825f6 100644 --- a/src/com/android/settings/notification/SoundSettings.java +++ b/src/com/android/settings/notification/SoundSettings.java @@ -66,6 +66,8 @@ public class SoundSettings extends DashboardFragment implements OnActivityResult @VisibleForTesting final VolumePreferenceCallback mVolumeCallback = new VolumePreferenceCallback(); + private final IncreasingRingVolumePreferenceCallback mIncreasingRingVolumeCallback = + new IncreasingRingVolumePreferenceCallback(); @VisibleForTesting final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override @@ -73,6 +75,7 @@ public void handleMessage(Message msg) { switch (msg.what) { case STOP_SAMPLE: mVolumeCallback.stopSample(); + mIncreasingRingVolumeCallback.stopSample(); break; } } @@ -122,6 +125,7 @@ public int getHelpResource() { public void onPause() { super.onPause(); mVolumeCallback.stopSample(); + mIncreasingRingVolumeCallback.stopSample(); } @Override @@ -209,6 +213,11 @@ public void onAttach(Context context) { controller.setCallback(mVolumeCallback); getSettingsLifecycle().addObserver(controller); } + + IncreasingRingVolumePreferenceController irvpc = + use(IncreasingRingVolumePreferenceController.class); + irvpc.setCallback(mIncreasingRingVolumeCallback); + getLifecycle().addObserver(irvpc); } // === Volumes === @@ -218,6 +227,7 @@ final class VolumePreferenceCallback implements VolumeSeekBarPreference.Callback @Override public void onSampleStarting(SeekBarVolumizer sbv) { + mIncreasingRingVolumeCallback.stopSample(); if (mCurrent != null) { mHandler.removeMessages(STOP_SAMPLE); mHandler.sendEmptyMessageDelayed(STOP_SAMPLE, SAMPLE_CUTOFF); @@ -248,6 +258,26 @@ public void stopSample() { } } + final class IncreasingRingVolumePreferenceCallback implements + IncreasingRingVolumePreference.Callback { + private IncreasingRingVolumePreference mPlayingPref; + + @Override + public void onSampleStarting(IncreasingRingVolumePreference pref) { + mPlayingPref = pref; + mVolumeCallback.stopSample(); + mHandler.removeMessages(STOP_SAMPLE); + mHandler.sendEmptyMessageDelayed(STOP_SAMPLE, SAMPLE_CUTOFF); + } + + public void stopSample() { + if (mPlayingPref != null) { + mPlayingPref.stopSample(); + mPlayingPref = null; + } + } + }; + private static List buildPreferenceControllers(Context context, SoundSettings fragment, Lifecycle lifecycle) { final List controllers = new ArrayList<>(); @@ -256,8 +286,13 @@ private static List buildPreferenceControllers(Con // === Phone & notification ringtone === controllers.add(new PhoneRingtonePreferenceController(context)); + controllers.add(new PhoneRingtone2PreferenceController(context)); controllers.add(new AlarmRingtonePreferenceController(context)); controllers.add(new NotificationRingtonePreferenceController(context)); + controllers.add(new IncreasingRingPreferenceController(context)); + controllers.add(new IncreasingRingVolumePreferenceController(context)); + controllers.add(new VibrationPatternPreferenceController(context)); + controllers.add(new NotificationVibrationPatternPreferenceController(context)); // === Other Sound Settings === final DialPadTonePreferenceController dialPadTonePreferenceController = @@ -266,6 +301,8 @@ private static List buildPreferenceControllers(Con new ScreenLockSoundPreferenceController(context, fragment, lifecycle); final ChargingSoundPreferenceController chargingSoundPreferenceController = new ChargingSoundPreferenceController(context, fragment, lifecycle); + final ChargingVibroPreferenceController chargingVibroPreferenceController = + new ChargingVibroPreferenceController(context, fragment, lifecycle); final DockingSoundPreferenceController dockingSoundPreferenceController = new DockingSoundPreferenceController(context, fragment, lifecycle); final TouchSoundPreferenceController touchSoundPreferenceController = @@ -282,6 +319,7 @@ private static List buildPreferenceControllers(Con controllers.add(dialPadTonePreferenceController); controllers.add(screenLockSoundPreferenceController); controllers.add(chargingSoundPreferenceController); + controllers.add(chargingVibroPreferenceController); controllers.add(dockingSoundPreferenceController); controllers.add(touchSoundPreferenceController); controllers.add(vibrateIconPreferenceController); diff --git a/src/com/android/settings/notification/VibrateIconPreferenceController.java b/src/com/android/settings/notification/VibrateIconPreferenceController.java index d772b47d59e..e538dd12f0f 100644 --- a/src/com/android/settings/notification/VibrateIconPreferenceController.java +++ b/src/com/android/settings/notification/VibrateIconPreferenceController.java @@ -40,6 +40,6 @@ public VibrateIconPreferenceController(Context context, SettingsPreferenceFragme @Override public boolean isAvailable() { - return mHasVibrator; + return false; } } diff --git a/src/com/android/settings/notification/VibrationPatternPreferenceController.java b/src/com/android/settings/notification/VibrationPatternPreferenceController.java new file mode 100644 index 00000000000..d755c0ff2f5 --- /dev/null +++ b/src/com/android/settings/notification/VibrationPatternPreferenceController.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2020-2022 Yet Another AOSP 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.notification; + +import android.content.ContentResolver; +import android.content.Context; +import android.media.AudioAttributes; +import android.net.Uri; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.Settings; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settingslib.core.AbstractPreferenceController; + +/** + * This class allows choosing a vibration pattern while ringing + */ +public class VibrationPatternPreferenceController extends AbstractPreferenceController + implements Preference.OnPreferenceChangeListener { + + private static final String KEY_VIB_PATTERN = "vibration_pattern"; + private static final String KEY_CUSTOM_VIB_CATEGORY = "custom_vibration_pattern"; + + private ListPreference mVibPattern; + private Preference mCustomVibCategory; + + protected final Vibrator mVibrator; + + private static class VibrationEffectProxy { + public VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) { + return VibrationEffect.createWaveform(timings, amplitudes, repeat); + } + } + + private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) + .build(); + + private static final long[] SIMPLE_VIBRATION_PATTERN = { + 0, // No delay before starting + 1000, // How long to vibrate + 1000, // How long to wait before vibrating again + 1000, // How long to vibrate + 1000, // How long to wait before vibrating again + }; + + private static final long[] DZZZ_DA_VIBRATION_PATTERN = { + 0, // No delay before starting + 500, // How long to vibrate + 200, // Delay + 70, // How long to vibrate + 720, // How long to wait before vibrating again + }; + + private static final long[] MM_MM_MM_VIBRATION_PATTERN = { + 0, // No delay before starting + 300, // How long to vibrate + 400, // Delay + 300, // How long to vibrate + 400, // Delay + 300, // How long to vibrate + 1400, // How long to wait before vibrating again + }; + + private static final long[] DA_DA_DZZZ_VIBRATION_PATTERN = { + 0, // No delay before starting + 70, // How long to vibrate + 80, // Delay + 70, // How long to vibrate + 180, // Delay + 600, // How long to vibrate + 1050, // How long to wait before vibrating again + }; + + private static final long[] DA_DZZZ_DA_VIBRATION_PATTERN = { + 0, // No delay before starting + 80, // How long to vibrate + 200, // Delay + 600, // How long to vibrate + 150, // Delay + 20, // How long to vibrate + 1050, // How long to wait before vibrating again + }; + + private static final int[] SEVEN_ELEMENTS_VIBRATION_AMPLITUDE = { + 0, // No delay before starting + 255, // Vibrate full amplitude + 0, // No amplitude while waiting + 255, + 0, + 255, + 0, + }; + + private static final int[] FIVE_ELEMENTS_VIBRATION_AMPLITUDE = { + 0, // No delay before starting + 255, // Vibrate full amplitude + 0, // No amplitude while waiting + 255, + 0, + }; + + public VibrationPatternPreferenceController(Context context) { + super(context); + mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + } + + @Override + public boolean isAvailable() { + return Utils.isVoiceCapable(mContext) && mVibrator.hasVibrator(); + } + + @Override + public String getPreferenceKey() { + return KEY_VIB_PATTERN; + } + + protected String getCustomPreferenceKey() { + return KEY_CUSTOM_VIB_CATEGORY; + } + + protected String getSettingsKey() { + return Settings.System.RINGTONE_VIBRATION_PATTERN; + } + + protected long[] getDefaultPattern() { + return SIMPLE_VIBRATION_PATTERN; + } + + protected int[] getDefaultPatternAmp() { + return FIVE_ELEMENTS_VIBRATION_AMPLITUDE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mVibPattern = screen.findPreference(getPreferenceKey()); + int vibPattern = Settings.System.getInt( + mContext.getContentResolver(), getSettingsKey(), 0); + mVibPattern.setValueIndex(vibPattern); + mVibPattern.setSummary(mVibPattern.getEntries()[vibPattern]); + mVibPattern.setOnPreferenceChangeListener(this); + + mCustomVibCategory = screen.findPreference(getCustomPreferenceKey()); + mCustomVibCategory.setVisible(vibPattern == 5); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mVibPattern) { + int vibPattern = Integer.valueOf((String) newValue); + Settings.System.putInt( + mContext.getContentResolver(), getSettingsKey(), vibPattern); + mVibPattern.setSummary(mVibPattern.getEntries()[vibPattern]); + boolean isCustom = vibPattern == 5; + mCustomVibCategory.setVisible(isCustom); + if (!isCustom) previewPattern(); + return true; + } + return false; + } + + private void previewPattern() { + VibrationEffect effect; + VibrationEffectProxy vibrationEffectProxy = new VibrationEffectProxy(); + int vibPattern = Settings.System.getInt( + mContext.getContentResolver(), getSettingsKey(), 0); + switch (vibPattern) { + case 1: + effect = vibrationEffectProxy.createWaveform(DZZZ_DA_VIBRATION_PATTERN, + FIVE_ELEMENTS_VIBRATION_AMPLITUDE, -1); + break; + case 2: + effect = vibrationEffectProxy.createWaveform(MM_MM_MM_VIBRATION_PATTERN, + SEVEN_ELEMENTS_VIBRATION_AMPLITUDE, -1); + break; + case 3: + effect = vibrationEffectProxy.createWaveform(DA_DA_DZZZ_VIBRATION_PATTERN, + SEVEN_ELEMENTS_VIBRATION_AMPLITUDE, -1); + break; + case 4: + effect = vibrationEffectProxy.createWaveform(DA_DZZZ_DA_VIBRATION_PATTERN, + SEVEN_ELEMENTS_VIBRATION_AMPLITUDE, -1); + break; + default: + case 0: + effect = vibrationEffectProxy.createWaveform(getDefaultPattern(), + getDefaultPatternAmp(), -1); + break; + } + mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES); + } +} diff --git a/src/com/android/settings/notification/app/AppNotificationSettings.java b/src/com/android/settings/notification/app/AppNotificationSettings.java index 89756b7b839..de17e46f599 100644 --- a/src/com/android/settings/notification/app/AppNotificationSettings.java +++ b/src/com/android/settings/notification/app/AppNotificationSettings.java @@ -84,6 +84,7 @@ protected List createPreferenceControllers(Context mControllers.add(new BlockPreferenceController(context, mDependentFieldListener, mBackend)); mControllers.add(new FullScreenIntentPermissionPreferenceController(context, mBackend)); mControllers.add(new BadgePreferenceController(context, mBackend)); + mControllers.add(new SoundTimeoutPreferenceController(context, mBackend)); mControllers.add(new AllowSoundPreferenceController( context, mDependentFieldListener, mBackend)); mControllers.add(new ImportancePreferenceController( @@ -95,7 +96,7 @@ protected List createPreferenceControllers(Context mControllers.add(new SoundPreferenceController(context, this, mDependentFieldListener, mBackend)); mControllers.add(new LightsPreferenceController(context, mBackend)); - mControllers.add(new VibrationPreferenceController(context, mBackend)); + mControllers.add(new VibrationPreferenceController(context, mBackend, mDependentFieldListener)); mControllers.add(new VisibilityPreferenceController(context, new LockPatternUtils(context), mBackend)); mControllers.add(new DndPreferenceController(context, mBackend)); diff --git a/src/com/android/settings/notification/app/ChannelNotificationSettings.java b/src/com/android/settings/notification/app/ChannelNotificationSettings.java index a7aba5284d6..1afaa8a88ee 100644 --- a/src/com/android/settings/notification/app/ChannelNotificationSettings.java +++ b/src/com/android/settings/notification/app/ChannelNotificationSettings.java @@ -127,7 +127,8 @@ protected List createPreferenceControllers(Context context, mDependentFieldListener, mBackend)); mControllers.add(new SoundPreferenceController(context, this, mDependentFieldListener, mBackend)); - mControllers.add(new VibrationPreferenceController(context, mBackend)); + mControllers.add(new VibrationPreferenceController(context, mBackend, mDependentFieldListener)); + mControllers.add(new CustomVibrationPreferenceController(context, mBackend)); mControllers.add(new AppLinkPreferenceController(context)); mControllers.add(new VisibilityPreferenceController(context, new LockPatternUtils(context), mBackend)); diff --git a/src/com/android/settings/notification/app/ConversationNotificationSettings.java b/src/com/android/settings/notification/app/ConversationNotificationSettings.java index 02ea6c060ec..3987ba05110 100644 --- a/src/com/android/settings/notification/app/ConversationNotificationSettings.java +++ b/src/com/android/settings/notification/app/ConversationNotificationSettings.java @@ -90,7 +90,8 @@ protected List createPreferenceControllers(Context context, mDependentFieldListener, mBackend)); mControllers.add(new SoundPreferenceController(context, this, mDependentFieldListener, mBackend)); - mControllers.add(new VibrationPreferenceController(context, mBackend)); + mControllers.add(new VibrationPreferenceController(context, mBackend, mDependentFieldListener)); + mControllers.add(new CustomVibrationPreferenceController(context, mBackend)); mControllers.add(new VisibilityPreferenceController(context, new LockPatternUtils(context), mBackend)); mControllers.add(new LightsPreferenceController(context, mBackend)); diff --git a/src/com/android/settings/notification/app/CustomVibrationPreferenceController.java b/src/com/android/settings/notification/app/CustomVibrationPreferenceController.java new file mode 100644 index 00000000000..a04301f0ce0 --- /dev/null +++ b/src/com/android/settings/notification/app/CustomVibrationPreferenceController.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2022 Yet Another AOSP 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.notification.app; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.content.res.Resources; +import android.media.AudioAttributes; +import android.os.VibrationEffect; +import android.os.Vibrator; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.RestrictedSwitchPreference; +import org.derpfest.support.preferences.ProperSeekBarPreference; + +public class CustomVibrationPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + private static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250}; + private static final int[] VIBRATION_AMPLITUDE = {0, 255, 0, 255}; + private static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps + private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) + .build(); + + private static final String KEY = "custom_vibrate"; + private static final String KEY1 = "custom_vibrate1"; + private static final String KEY2 = "custom_vibrate2"; + private static final String KEY3 = "custom_vibrate3"; + private static final String CATEGORY_KEY = "custom_vibrate_seek_bars"; + private final Vibrator mVibrator; + private final long[] mDefaultPattern; + + private RestrictedSwitchPreference mPreference; + private ProperSeekBarPreference mSeekBar1; + private ProperSeekBarPreference mSeekBar2; + private ProperSeekBarPreference mSeekBar3; + private PreferenceCategory mBarsCategory; + + public CustomVibrationPreferenceController(Context context, NotificationBackend backend) { + super(context, backend); + mVibrator = context.getSystemService(Vibrator.class); + mDefaultPattern = getLongArray(context.getResources(), + com.android.internal.R.array.config_defaultNotificationVibePattern, + VIBRATE_PATTERN_MAXLEN, + DEFAULT_VIBRATE_PATTERN); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(KEY); + mPreference.setOnPreferenceChangeListener(this); + mSeekBar1 = screen.findPreference(KEY1); + mSeekBar1.setOnPreferenceChangeListener(this); + mSeekBar2 = screen.findPreference(KEY2); + mSeekBar2.setOnPreferenceChangeListener(this); + mSeekBar3 = screen.findPreference(KEY3); + mSeekBar3.setOnPreferenceChangeListener(this); + mBarsCategory = screen.findPreference(CATEGORY_KEY); + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable() || mChannel == null) { + if (mBarsCategory != null) + mBarsCategory.setVisible(false); + return false; + } + final boolean avail = checkCanBeVisible(NotificationManager.IMPORTANCE_DEFAULT) + && mChannel.shouldVibrate() + && mChannel.getVibrationPattern() == null + && !isDefaultChannel() + && mVibrator != null + && mVibrator.hasVibrator(); + if (mBarsCategory != null && !avail) + mBarsCategory.setVisible(false); + return avail; + } + + @Override + boolean isIncludedInFilter() { + return mPreferenceFilter.contains(NotificationChannel.EDIT_VIBRATION); + } + + public void updateState(Preference preference) { + if (mChannel == null) return; + if (mPreference == null) mPreference = (RestrictedSwitchPreference) preference; + mPreference.setDisabledByAdmin(mAdmin); + mPreference.setEnabled(!mPreference.isDisabledByAdmin()); + final boolean isChecked = mChannel.shouldVibrate() && + mChannel.getCustomVibrationPattern() != null; + mPreference.setChecked(isChecked); + mBarsCategory.setVisible(isChecked); + if (isChecked) getValues(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + int pref = 0; + if (preference == mPreference) { + Boolean value = (Boolean) newValue; + mBarsCategory.setVisible(value); + if (value) getValues(); + else setValues(false, 0, 0); + return true; + } else if (preference == mSeekBar1) { + pref = 1; + } else if (preference == mSeekBar2) { + pref = 2; + } else if (preference == mSeekBar3) { + pref = 3; + } + return setValues(mPreference.isChecked(), pref, + ((Integer) newValue).longValue()); + } + + private boolean setValues(boolean enabled, int pref, long val) { + mBarsCategory.setVisible(enabled); + if (enabled) { + final long val1 = pref == 1 ? val : mSeekBar1.getValue(); + final long val2 = pref == 2 ? val : mSeekBar2.getValue(); + final long val3 = pref == 3 ? val : mSeekBar3.getValue(); + if (val1 == 0 && val2 == 0 && val3 == 0) + return false; + final long[] pattern = {0, val1, val2, val3}; + mChannel.setCustomVibrationPattern(pattern); + previewPattern(pattern); + } else { + mChannel.setCustomVibrationPattern(null); + } + saveChannel(); + return true; + } + + private void getValues() { + long[] pattern = mChannel.getCustomVibrationPattern(); + if (pattern == null) pattern = mDefaultPattern; + if (pattern.length < 4) pattern = new long[] {0, 100, 150, 100}; + mSeekBar1.setValue(Math.round(pattern[1])); + mSeekBar2.setValue(Math.round(pattern[2])); + mSeekBar3.setValue(Math.round(pattern[3])); + } + + private void previewPattern(long[] pattern) { + VibrationEffect effect = VibrationEffect.createWaveform( + pattern, VIBRATION_AMPLITUDE, -1); + mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES); + } + + private static long[] getLongArray(Resources resources, int resId, int maxLength, long[] def) { + int[] ar = resources.getIntArray(resId); + if (ar == null) { + return def; + } + final int len = ar.length > maxLength ? maxLength : ar.length; + long[] out = new long[len]; + for (int i = 0; i < len; i++) { + out[i] = ar[i]; + } + return out; + } +} diff --git a/src/com/android/settings/notification/app/HighImportancePreferenceController.java b/src/com/android/settings/notification/app/HighImportancePreferenceController.java index d60668b9abe..4807d73a8fa 100644 --- a/src/com/android/settings/notification/app/HighImportancePreferenceController.java +++ b/src/com/android/settings/notification/app/HighImportancePreferenceController.java @@ -21,9 +21,11 @@ import android.app.NotificationChannel; import android.content.Context; +import android.provider.Settings; import androidx.preference.Preference; +import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.notification.NotificationBackend; import com.android.settingslib.RestrictedSwitchPreference; @@ -68,13 +70,22 @@ boolean isIncludedInFilter() { @Override public void updateState(Preference preference) { if (mAppRow != null && mChannel != null) { - preference.setEnabled(mAdmin == null && isChannelConfigurable(mChannel)); + preference.setEnabled(isGlobalHeadsUpEnabled() && mAdmin == null + && isChannelConfigurable(mChannel)); RestrictedSwitchPreference pref = (RestrictedSwitchPreference) preference; pref.setChecked(mChannel.getImportance() >= IMPORTANCE_HIGH); + refreshSummary(preference); } } + @Override + public CharSequence getSummary() { + return mContext.getString(isGlobalHeadsUpEnabled() + ? R.string.notification_channel_summary_high + : R.string.heads_up_off); + } + @Override public boolean onPreferenceChange(Preference preference, Object newValue) { if (mChannel != null) { @@ -87,4 +98,10 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } return true; } + + private boolean isGlobalHeadsUpEnabled() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, + Settings.Global.HEADS_UP_OFF) == Settings.Global.HEADS_UP_ON; + } } diff --git a/src/com/android/settings/notification/app/SoundTimeoutPreferenceController.java b/src/com/android/settings/notification/app/SoundTimeoutPreferenceController.java new file mode 100644 index 00000000000..1fb9a215a88 --- /dev/null +++ b/src/com/android/settings/notification/app/SoundTimeoutPreferenceController.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019 The LineageOS 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.notification.app; + +import android.content.Context; +import android.provider.Settings; + +import androidx.preference.Preference; + +import com.android.settings.RestrictedListPreference; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.notification.NotificationBackend; + +public class SoundTimeoutPreferenceController extends NotificationPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + private static final String TAG = "SoundTimeoutPreferenceController"; + private static final String KEY_SOUND_TIMEOUT = "sound_timeout"; + + public SoundTimeoutPreferenceController(Context context, + NotificationBackend backend) { + super(context, backend); + } + + @Override + public String getPreferenceKey() { + return KEY_SOUND_TIMEOUT; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + if (mAppRow == null) { + return false; + } + return true; + } + + @Override + boolean isIncludedInFilter() { + return false; + } + + public void updateState(Preference preference) { + if (mAppRow != null) { + RestrictedListPreference pref = (RestrictedListPreference) preference; + pref.setDisabledByAdmin(mAdmin); + + pref.setSummary("%s"); + pref.setValue(Long.toString(mAppRow.soundTimeout)); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mAppRow != null) { + mAppRow.soundTimeout = Long.valueOf((String) newValue); + mBackend.setNotificationSoundTimeout(mAppRow.pkg, mAppRow.uid, mAppRow.soundTimeout); + } + return true; + } + +} diff --git a/src/com/android/settings/notification/app/VibrationPreferenceController.java b/src/com/android/settings/notification/app/VibrationPreferenceController.java index 34d1a543b56..69664ca3169 100644 --- a/src/com/android/settings/notification/app/VibrationPreferenceController.java +++ b/src/com/android/settings/notification/app/VibrationPreferenceController.java @@ -32,10 +32,13 @@ public class VibrationPreferenceController extends NotificationPreferenceControl private static final String KEY_VIBRATE = "vibrate"; private final Vibrator mVibrator; + private NotificationSettings.DependentFieldListener mDependentFieldListener; - public VibrationPreferenceController(Context context, NotificationBackend backend) { + public VibrationPreferenceController(Context context, NotificationBackend backend, + NotificationSettings.DependentFieldListener dependentFieldListener) { super(context, backend); mVibrator = context.getSystemService(Vibrator.class); + mDependentFieldListener = dependentFieldListener; } @Override @@ -74,6 +77,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { final boolean vibrate = (Boolean) newValue; mChannel.enableVibration(vibrate); saveChannel(); + mDependentFieldListener.onFieldValueChanged(); } return true; } diff --git a/src/com/android/settings/notification/history/NotificationStation.java b/src/com/android/settings/notification/history/NotificationStation.java index e79a4ac58c6..ec6fc8e93ec 100644 --- a/src/com/android/settings/notification/history/NotificationStation.java +++ b/src/com/android/settings/notification/history/NotificationStation.java @@ -208,6 +208,9 @@ public void onActivityCreated(Bundle savedInstanceState) { public void onResume() { logd("onResume()"); super.onResume(); + // Set correct title on the action bar + getActivity().setTitle(R.string.notification_log_title); + try { mListener.registerAsSystemService(mContext, new ComponentName(mContext.getPackageName(), this.getClass().getCanonicalName()), ActivityManager.getCurrentUser()); diff --git a/src/com/android/settings/notification/zen/ZenModeEventRuleSettings.java b/src/com/android/settings/notification/zen/ZenModeEventRuleSettings.java index 9b0f3fc88a6..fa243e8cf7f 100644 --- a/src/com/android/settings/notification/zen/ZenModeEventRuleSettings.java +++ b/src/com/android/settings/notification/zen/ZenModeEventRuleSettings.java @@ -28,7 +28,7 @@ import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.EventInfo; -import androidx.preference.DropDownPreference; +import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceChangeListener; import androidx.preference.PreferenceScreen; @@ -50,8 +50,8 @@ public class ZenModeEventRuleSettings extends ZenModeRuleSettingsBase { public static final String ACTION = Settings.ACTION_ZEN_MODE_EVENT_RULE_SETTINGS; - private DropDownPreference mCalendar; - private DropDownPreference mReply; + private ListPreference mCalendar; + private ListPreference mReply; private EventInfo mEvent; @@ -128,7 +128,7 @@ protected void onCreateInternal() { mCreate = true; final PreferenceScreen root = getPreferenceScreen(); - mCalendar = (DropDownPreference) root.findPreference(KEY_CALENDAR); + mCalendar = (ListPreference) root.findPreference(KEY_CALENDAR); mCalendar.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { @@ -143,7 +143,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } }); - mReply = (DropDownPreference) root.findPreference(KEY_REPLY); + mReply = (ListPreference) root.findPreference(KEY_REPLY); mReply.setEntries(new CharSequence[] { getString(R.string.zen_mode_event_rule_reply_any_except_no), getString(R.string.zen_mode_event_rule_reply_yes_or_maybe), diff --git a/src/com/android/settings/panel/AppVolumePanel.java b/src/com/android/settings/panel/AppVolumePanel.java new file mode 100644 index 00000000000..876a15cf576 --- /dev/null +++ b/src/com/android/settings/panel/AppVolumePanel.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 Project Kaleidoscope + * + * 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.panel; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.slices.CustomSliceRegistry; + +import java.util.ArrayList; +import java.util.List; + +public class AppVolumePanel implements PanelContent { + + private final Context mContext; + + public static AppVolumePanel create(Context context) { + return new AppVolumePanel(context); + } + + private AppVolumePanel(Context context) { + mContext = context.getApplicationContext(); + } + + @Override + public CharSequence getTitle() { + return mContext.getText(R.string.app_volume); + } + + @Override + public List getSlices() { + final List uris = new ArrayList<>(); + uris.add(CustomSliceRegistry.APP_VOLUME_SLICE_URI); + return uris; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.PANEL_VOLUME; + } + + @Override + public Intent getSeeMoreIntent() { + return new Intent(Settings.ACTION_SOUND_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + +} diff --git a/src/com/android/settings/panel/PanelFeatureProviderImpl.java b/src/com/android/settings/panel/PanelFeatureProviderImpl.java index a0aeec60856..4d7acea49f3 100644 --- a/src/com/android/settings/panel/PanelFeatureProviderImpl.java +++ b/src/com/android/settings/panel/PanelFeatureProviderImpl.java @@ -87,6 +87,8 @@ public PanelContent getPanel(Context context, Bundle bundle) { return VolumePanel.create(context); } } + case Settings.Panel.ACTION_APP_VOLUME: + return AppVolumePanel.create(context); } throw new IllegalStateException("No matching panel for: " + panelType); diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java index a6453005ab9..da75b8136f3 100644 --- a/src/com/android/settings/password/ChooseLockPassword.java +++ b/src/com/android/settings/password/ChooseLockPassword.java @@ -1086,7 +1086,8 @@ private void startSaveAndFinish() { mUserId); mSaveAndFinishWorker.start(mLockPatternUtils, - mChosenPassword, mCurrentCredential, mUserId); + mChosenPassword, mCurrentCredential, mUserId, + mLockPatternUtils.getLockPatternSize(mUserId)); } @Override diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java index c331991a486..a3afe19670a 100644 --- a/src/com/android/settings/password/ChooseLockPattern.java +++ b/src/com/android/settings/password/ChooseLockPattern.java @@ -102,7 +102,8 @@ public static class IntentBuilder { private final Intent mIntent; public IntentBuilder(Context context) { - mIntent = new Intent(context, ChooseLockPattern.class); + mIntent = new Intent(context, ChooseLockPatternSize.class); + mIntent.putExtra("className", ChooseLockPattern.class.getName()); mIntent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, false); } @@ -211,19 +212,14 @@ public static class ChooseLockPatternFragment extends InstrumentedFragment protected FooterButton mSkipOrClearButton; protected FooterButton mNextButton; @VisibleForTesting protected LockscreenCredential mChosenPattern; + private byte mPatternSize = LockPatternUtils.PATTERN_SIZE_DEFAULT; private ColorStateList mDefaultHeaderColorList; private View mSudContent; /** * The patten used during the help screen to show how to draw a pattern. */ - private final List mAnimatePattern = - Collections.unmodifiableList(Lists.newArrayList( - LockPatternView.Cell.of(0, 0), - LockPatternView.Cell.of(0, 1), - LockPatternView.Cell.of(1, 1), - LockPatternView.Cell.of(2, 1) - )); + private List mAnimatePattern; @Override public void onActivityResult(int requestCode, int resultCode, @@ -268,12 +264,13 @@ public void onPatternCleared() { mLockPatternView.removeCallbacks(mClearPatternRunnable); } - public void onPatternDetected(List pattern) { + public void onPatternDetected(List pattern, + byte patternSize) { if (mUiStage == Stage.NeedToConfirm || mUiStage == Stage.ConfirmWrong) { if (mChosenPattern == null) throw new IllegalStateException( "null chosen pattern in stage 'need to confirm"); try (LockscreenCredential confirmPattern = - LockscreenCredential.createPattern(pattern)) { + LockscreenCredential.createPattern(pattern, patternSize)) { if (mChosenPattern.equals(confirmPattern)) { updateStage(Stage.ChoiceConfirmed); } else { @@ -284,7 +281,8 @@ public void onPatternDetected(List pattern) { if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) { updateStage(Stage.ChoiceTooShort); } else { - mChosenPattern = LockscreenCredential.createPattern(pattern); + mChosenPattern = LockscreenCredential.createPattern( + pattern, patternSize); updateStage(Stage.FirstChoiceValid); } } else { @@ -542,6 +540,16 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(), 0); + mPatternSize = getActivity().getIntent().getByteExtra("pattern_size", + LockPatternUtils.PATTERN_SIZE_DEFAULT); + LockPatternView.Cell.updateSize(mPatternSize); + mAnimatePattern = Collections.unmodifiableList(Lists.newArrayList( + LockPatternView.Cell.of(0, 0, mPatternSize), + LockPatternView.Cell.of(0, 1, mPatternSize), + LockPatternView.Cell.of(1, 1, mPatternSize), + LockPatternView.Cell.of(2, 1, mPatternSize) + )); + return layout; } @@ -555,6 +563,8 @@ public void onViewCreated(View view, Bundle savedInstanceState) { mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern); mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener); mLockPatternView.setFadePattern(false); + mLockPatternView.setLockPatternUtils(mLockPatternUtils); + mLockPatternView.setLockPatternSize(mPatternSize); mFooterText = (TextView) view.findViewById(R.id.footerText); @@ -601,6 +611,9 @@ public void onViewCreated(View view, Bundle savedInstanceState) { // restore from previous state mChosenPattern = savedInstanceState.getParcelable(KEY_PATTERN_CHOICE); mCurrentCredential = savedInstanceState.getParcelable(KEY_CURRENT_PATTERN); + mLockPatternView.setPattern(DisplayMode.Correct, + LockPatternUtils.byteArrayToPattern( + mChosenPattern.getCredential(), mPatternSize)); updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]); @@ -859,7 +872,7 @@ private void startSaveAndFinish() { } } mSaveAndFinishWorker.start(mLockPatternUtils, - mChosenPattern, mCurrentCredential, mUserId); + mChosenPattern, mCurrentCredential, mUserId, mPatternSize); } @Override diff --git a/src/com/android/settings/password/ChooseLockPatternSize.java b/src/com/android/settings/password/ChooseLockPatternSize.java new file mode 100644 index 00000000000..601e4576952 --- /dev/null +++ b/src/com/android/settings/password/ChooseLockPatternSize.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2012-2013 The CyanogenMod 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.password; + +import android.content.Intent; +import android.content.res.Resources.Theme; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +import androidx.preference.Preference; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockscreenCredential; +import com.android.settings.R; +import com.android.settings.SettingsActivity; +import com.android.settings.SetupWizardUtils; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.utils.SettingsDividerItemDecoration; + +import com.google.android.setupdesign.GlifPreferenceLayout; +import com.google.android.setupdesign.util.ThemeHelper; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + +public class ChooseLockPatternSize extends SettingsActivity { + + @Override + public Intent getIntent() { + Intent modIntent = new Intent(super.getIntent()); + modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockPatternSizeFragment.class.getName()); + return modIntent; + } + + @Override + protected boolean isValidFragment(String fragmentName) { + if (ChooseLockPatternSizeFragment.class.getName().equals(fragmentName)) return true; + return false; + } + + @Override + protected boolean isToolbarEnabled() { + return false; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + setTheme(SetupWizardUtils.getTheme(this, getIntent())); + ThemeHelper.trySetDynamicColor(this); + super.onCreate(savedInstanceState); + findViewById(R.id.content_parent).setFitsSystemWindows(false); + } + + public static class ChooseLockPatternSizeFragment extends SettingsPreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (!(getActivity() instanceof ChooseLockPatternSize)) { + throw new SecurityException("Fragment contained in wrong activity"); + } + addPreferencesFromResource(R.xml.security_settings_pattern_size); + } + + @Override + public boolean onPreferenceTreeClick(Preference preference) { + final String key = preference.getKey(); + + byte patternSize; + if ("lock_pattern_size_4".equals(key)) { + patternSize = 4; + } else if ("lock_pattern_size_5".equals(key)) { + patternSize = 5; + } else if ("lock_pattern_size_6".equals(key)) { + patternSize = 6; + } else { + patternSize = 3; + } + + Bundle extras = getActivity().getIntent().getExtras(); + Intent intent = new Intent(); + intent.setClassName(getActivity(), extras.getString("className")); + intent.putExtras(extras); + intent.putExtra("pattern_size", patternSize); + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + startActivity(intent); + + finish(); + return true; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + GlifPreferenceLayout layout = (GlifPreferenceLayout) view; + layout.setDividerItemDecoration(new SettingsDividerItemDecoration(getContext())); + + layout.setIcon(getContext().getDrawable(R.drawable.ic_lock)); + + if (getActivity() != null) { + getActivity().setTitle(R.string.lock_settings_picker_pattern_size_message); + } + + layout.setHeaderText(R.string.lock_settings_picker_pattern_size_message); + + // Remove the padding on the start of the header text. + if (ThemeHelper.shouldApplyMaterialYouStyle(getContext())) { + final LinearLayout headerLayout = layout.findManagedViewById( + com.google.android.setupdesign.R.id.sud_layout_header); + if (headerLayout != null) { + headerLayout.setPadding(0, layout.getPaddingTop(), 0, + layout.getPaddingBottom()); + } + } + + // Use the dividers in SetupWizardRecyclerLayout. Suppress the dividers in + // PreferenceFragment. + setDivider(null); + } + + @Override + public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, + Bundle savedInstanceState) { + GlifPreferenceLayout layout = (GlifPreferenceLayout) parent; + return layout.onCreateRecyclerView(inflater, parent, savedInstanceState); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } + } +} diff --git a/src/com/android/settings/password/ChooseLockSettingsHelper.java b/src/com/android/settings/password/ChooseLockSettingsHelper.java index 91875cc88d6..0f907cd0eb1 100644 --- a/src/com/android/settings/password/ChooseLockSettingsHelper.java +++ b/src/com/android/settings/password/ChooseLockSettingsHelper.java @@ -475,6 +475,9 @@ private boolean launchConfirmationActivity(int request, CharSequence title, Char intent.setClassName(SETTINGS_PACKAGE_NAME, activityClass.getName()); intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE, SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE); + if (userId == LockPatternUtils.USER_FRP) { + intent.putExtra("className", ConfirmLockPattern.class.getName()); + } Intent inIntent = mFragment != null ? mFragment.getActivity().getIntent() : mActivity.getIntent(); @@ -555,7 +558,9 @@ private Optional> determineAppropriateActivityClass(boolean returnCrede ? ConfirmLockPassword.InternalActivity.class : ConfirmLockPassword.class); case KeyguardManager.PATTERN: - return Optional.of(returnCredentials || forceVerifyPath + return Optional.of(userId == LockPatternUtils.USER_FRP + ? ChooseLockPatternSize.class + : returnCredentials || forceVerifyPath ? ConfirmLockPattern.InternalActivity.class : ConfirmLockPattern.class); } diff --git a/src/com/android/settings/password/ConfirmLockPassword.java b/src/com/android/settings/password/ConfirmLockPassword.java index b9b1810da25..35f6a82e3c0 100644 --- a/src/com/android/settings/password/ConfirmLockPassword.java +++ b/src/com/android/settings/password/ConfirmLockPassword.java @@ -628,7 +628,8 @@ public void onRemoteLockscreenValidationResult( mLockPatternUtils, mRemoteLockscreenValidationFragment.getLockscreenCredential(), /* currentCredential= */ null, - mEffectiveUserId); + mEffectiveUserId, + mLockPatternUtils.getLockPatternSize(mEffectiveUserId)); } else { mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(), /* timeoutMs= */ 0, mEffectiveUserId); diff --git a/src/com/android/settings/password/ConfirmLockPattern.java b/src/com/android/settings/password/ConfirmLockPattern.java index 3415478c912..06fdc496ff8 100644 --- a/src/com/android/settings/password/ConfirmLockPattern.java +++ b/src/com/android/settings/password/ConfirmLockPattern.java @@ -115,6 +115,7 @@ public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBa private DisappearAnimationUtils mDisappearAnimationUtils; private boolean mIsManagedProfile; + private byte mPatternSize; // required constructor for fragments public ConfirmLockPatternFragment() { @@ -141,6 +142,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(), 0); mIsManagedProfile = UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId); + mPatternSize = mLockPatternUtils.getLockPatternSize(mEffectiveUserId); // make it so unhandled touch events within the unlock screen go to the // lock pattern view. @@ -155,6 +157,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mDetailsText = intent.getCharSequenceExtra( ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT); mCheckBoxLabel = intent.getCharSequenceExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL); + mPatternSize = intent.getByteExtra("pattern_size", mPatternSize); } if (TextUtils.isEmpty(mHeaderText) && mIsManagedProfile) { mHeaderText = mDevicePolicyManager.getOrganizationNameForUser(mUserId); @@ -162,6 +165,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( mEffectiveUserId)); + mLockPatternView.setLockPatternSize(mPatternSize); mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener); mLockPatternView.setOnTouchListener((v, event) -> { if (event.getAction() == MotionEvent.ACTION_DOWN) { @@ -497,14 +501,15 @@ public void onPatternCellAdded(List pattern) { } - public void onPatternDetected(List pattern) { + public void onPatternDetected(List pattern, byte patternSize) { if (mPendingLockCheck != null || mDisappearing) { return; } mLockPatternView.setEnabled(false); - final LockscreenCredential credential = LockscreenCredential.createPattern(pattern); + final LockscreenCredential credential = LockscreenCredential.createPattern(pattern, + patternSize); if (mRemoteValidation) { validateGuess(credential); @@ -643,7 +648,8 @@ public void onRemoteLockscreenValidationResult( mLockPatternUtils, mRemoteLockscreenValidationFragment.getLockscreenCredential(), /* currentCredential= */ null, - mEffectiveUserId); + mEffectiveUserId, + mLockPatternUtils.getLockPatternSize(mEffectiveUserId)); } else { mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(), /* timeoutMs= */ 0, mEffectiveUserId); diff --git a/src/com/android/settings/password/SaveAndFinishWorker.java b/src/com/android/settings/password/SaveAndFinishWorker.java index 40054b77645..5592071e7a4 100644 --- a/src/com/android/settings/password/SaveAndFinishWorker.java +++ b/src/com/android/settings/password/SaveAndFinishWorker.java @@ -53,6 +53,7 @@ public class SaveAndFinishWorker extends Fragment { private LockscreenCredential mUnificationProfileCredential; private LockscreenCredential mChosenCredential; private LockscreenCredential mCurrentCredential; + private byte mPatternSize; private boolean mBlocking; @@ -76,9 +77,10 @@ public SaveAndFinishWorker setListener(Listener listener) { @VisibleForTesting void prepare(LockPatternUtils utils, LockscreenCredential chosenCredential, - LockscreenCredential currentCredential, int userId) { + LockscreenCredential currentCredential, int userId, byte patternSize) { mUtils = utils; mUserId = userId; + mPatternSize = patternSize; // This will be a no-op for non managed profiles. mWasSecureBefore = mUtils.isSecure(mUserId); mFinished = false; @@ -90,8 +92,8 @@ void prepare(LockPatternUtils utils, LockscreenCredential chosenCredential, } public void start(LockPatternUtils utils, LockscreenCredential chosenCredential, - LockscreenCredential currentCredential, int userId) { - prepare(utils, chosenCredential, currentCredential, userId); + LockscreenCredential currentCredential, int userId, byte patternSize) { + prepare(utils, chosenCredential, currentCredential, userId, patternSize); if (mBlocking) { finish(saveAndVerifyInBackground().second); } else { @@ -107,6 +109,7 @@ public void start(LockPatternUtils utils, LockscreenCredential chosenCredential, @VisibleForTesting Pair saveAndVerifyInBackground() { final int userId = mUserId; + mUtils.setLockPatternSize(mPatternSize, userId); try { if (!mUtils.setLockCredential(mChosenCredential, mCurrentCredential, userId)) { return Pair.create(false, null); diff --git a/src/com/android/settings/password/SetupChooseLockPattern.java b/src/com/android/settings/password/SetupChooseLockPattern.java index 55b3847125e..e0e598064ff 100644 --- a/src/com/android/settings/password/SetupChooseLockPattern.java +++ b/src/com/android/settings/password/SetupChooseLockPattern.java @@ -47,7 +47,8 @@ public class SetupChooseLockPattern extends ChooseLockPattern { public static Intent modifyIntentForSetup(Context context, Intent chooseLockPatternIntent) { - chooseLockPatternIntent.setClass(context, SetupChooseLockPattern.class); + chooseLockPatternIntent.setClass(context, ChooseLockPatternSize.class); + chooseLockPatternIntent.putExtra("className", SetupChooseLockPattern.class.getName()); return chooseLockPatternIntent; } diff --git a/src/com/android/settings/preference/SystemSettingMainSwitchPreference.java b/src/com/android/settings/preference/SystemSettingMainSwitchPreference.java new file mode 100644 index 00000000000..92a95bcf358 --- /dev/null +++ b/src/com/android/settings/preference/SystemSettingMainSwitchPreference.java @@ -0,0 +1,32 @@ +package com.android.settings.preference; + +import android.content.Context; +import android.util.AttributeSet; + +import org.derpfest.support.preferences.SystemSettingsStore; + +import com.android.settingslib.widget.MainSwitchPreference; + +public class SystemSettingMainSwitchPreference extends MainSwitchPreference { + + public SystemSettingMainSwitchPreference(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver())); + } + + public SystemSettingMainSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver())); + } + + public SystemSettingMainSwitchPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver())); + } + + public SystemSettingMainSwitchPreference(Context context) { + super(context); + setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver())); + } +} diff --git a/src/com/android/settings/preference/SystemSettingPrimarySwitchPreference.java b/src/com/android/settings/preference/SystemSettingPrimarySwitchPreference.java new file mode 100644 index 00000000000..092f418ba22 --- /dev/null +++ b/src/com/android/settings/preference/SystemSettingPrimarySwitchPreference.java @@ -0,0 +1,32 @@ +package com.android.settings.preference; + +import android.content.Context; +import android.util.AttributeSet; + +import org.derpfest.support.preferences.SystemSettingsStore; + +import com.android.settingslib.PrimarySwitchPreference; + +public class SystemSettingPrimarySwitchPreference extends PrimarySwitchPreference { + + public SystemSettingPrimarySwitchPreference(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver())); + } + + public SystemSettingPrimarySwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver())); + } + + public SystemSettingPrimarySwitchPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver())); + } + + public SystemSettingPrimarySwitchPreference(Context context) { + super(context); + setPreferenceDataStore(new SystemSettingsStore(context.getContentResolver())); + } +} diff --git a/src/com/android/settings/privatespace/HidePrivateSpaceController.java b/src/com/android/settings/privatespace/HidePrivateSpaceController.java index 81814adc9db..fca598d80a9 100644 --- a/src/com/android/settings/privatespace/HidePrivateSpaceController.java +++ b/src/com/android/settings/privatespace/HidePrivateSpaceController.java @@ -46,10 +46,7 @@ public HidePrivateSpaceController(Context context, String key) { @Override @AvailabilityStatus public int getAvailabilityStatus() { - return android.os.Flags.allowPrivateProfile() - && android.multiuser.Flags.enablePrivateSpaceFeatures() - ? AVAILABLE - : UNSUPPORTED_ON_DEVICE; + return UNSUPPORTED_ON_DEVICE; } @Override diff --git a/src/com/android/settings/security/LockscreenDashboardFragment.java b/src/com/android/settings/security/LockscreenDashboardFragment.java index 2e4a1f28917..0c79e0198b0 100644 --- a/src/com/android/settings/security/LockscreenDashboardFragment.java +++ b/src/com/android/settings/security/LockscreenDashboardFragment.java @@ -30,13 +30,17 @@ import android.provider.Settings; import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.display.AmbientDisplayAlwaysOnPreferenceController; import com.android.settings.display.AmbientDisplayNotificationsPreferenceController; +import com.android.settings.display.DozeOnChargePreferenceController; +import com.android.settings.display.AODSchedulePreferenceController; import com.android.settings.gestures.DoubleTapScreenPreferenceController; import com.android.settings.gestures.PickupGesturePreferenceController; +import com.android.settings.gestures.ScreenOffUdfpsPreferenceController; import com.android.settings.notification.LockScreenNotificationPreferenceController; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.security.screenlock.LockScreenPreferenceController; @@ -53,9 +57,6 @@ @SearchIndexable public class LockscreenDashboardFragment extends DashboardFragment implements OwnerInfoPreferenceController.OwnerInfoCallback { - - public static final String KEY_AMBIENT_DISPLAY_ALWAYS_ON = "ambient_display_always_on"; - private static final String TAG = "LockscreenDashboardFragment"; @VisibleForTesting @@ -70,7 +71,6 @@ public class LockscreenDashboardFragment extends DashboardFragment static final String KEY_ADD_USER_FROM_LOCK_SCREEN = "security_lockscreen_add_users_when_locked"; - private AmbientDisplayConfiguration mConfig; private OwnerInfoPreferenceController mOwnerInfoPreferenceController; @VisibleForTesting @@ -110,9 +110,12 @@ public int getHelpResource() { public void onAttach(Context context) { super.onAttach(context); use(AmbientDisplayAlwaysOnPreferenceController.class).setConfig(getConfig(context)); + use(DozeOnChargePreferenceController.class).setConfig(getConfig(context)); use(AmbientDisplayNotificationsPreferenceController.class).setConfig(getConfig(context)); use(DoubleTapScreenPreferenceController.class).setConfig(getConfig(context)); use(PickupGesturePreferenceController.class).setConfig(getConfig(context)); + use(AODSchedulePreferenceController.class).setConfig(getConfig(context)); + use(ScreenOffUdfpsPreferenceController.class).setConfig(getConfig(context)); mControlsContentObserver = new ContentObserver( new Handler(Looper.getMainLooper())) { @@ -149,6 +152,7 @@ protected List createPreferenceControllers(Context controllers.add(notificationController); mOwnerInfoPreferenceController = new OwnerInfoPreferenceController(context, this); controllers.add(mOwnerInfoPreferenceController); + controllers.add(new DozeOnChargePreferenceController(context, lifecycle)); return controllers; } @@ -177,6 +181,7 @@ public List createPreferenceControllers( controllers.add(new LockScreenNotificationPreferenceController(context)); controllers.add(new OwnerInfoPreferenceController( context, null /* fragment */)); + controllers.add(new DozeOnChargePreferenceController(context, null /* lifecycle */)); return controllers; } diff --git a/src/com/android/settings/security/TrustRestrictUsbPreferenceController.java b/src/com/android/settings/security/TrustRestrictUsbPreferenceController.java new file mode 100644 index 00000000000..5c88ec3ad91 --- /dev/null +++ b/src/com/android/settings/security/TrustRestrictUsbPreferenceController.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 LibreMoblieOS Foundation. + * + * 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.security; + +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.hardware.usb.IUsbManager; +import android.hardware.usb.UsbManager; +import com.android.settings.core.BasePreferenceController; + +import java.util.NoSuchElementException; + +public class TrustRestrictUsbPreferenceController extends BasePreferenceController { + + public static final String KEY = "trust_restrict_usb"; + + private Context mContext; + + private boolean mIsUsb1_3 = false; + + public TrustRestrictUsbPreferenceController(Context context, String key) { + super(context, key); + + mContext = context; + + IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService( + Context.USB_SERVICE)); + try { + int version = usbMgr.getUsbHalVersion(); + + if (version >= UsbManager.USB_HAL_V1_3) + mIsUsb1_3 = true; + } catch (RemoteException e) { + // ignore, the hal is not available + } + } + + public TrustRestrictUsbPreferenceController(Context context) { + this(context, KEY); + } + + @Override + public int getAvailabilityStatus() { + boolean exists = (mIsUsb1_3); + return (exists ? AVAILABLE : UNSUPPORTED_ON_DEVICE); + } + +} diff --git a/src/com/android/settings/security/screenlock/AbstractPatternSwitchPreferenceController.java b/src/com/android/settings/security/screenlock/AbstractPatternSwitchPreferenceController.java new file mode 100644 index 00000000000..e2102aade4e --- /dev/null +++ b/src/com/android/settings/security/screenlock/AbstractPatternSwitchPreferenceController.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 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.security.screenlock; + +import android.content.Context; + +import androidx.preference.Preference; +import androidx.preference.TwoStatePreference; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.core.AbstractPreferenceController; + +public abstract class AbstractPatternSwitchPreferenceController + extends AbstractPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + private final String mKey; + private final int mUserId; + private final LockPatternUtils mLockPatternUtils; + + public AbstractPatternSwitchPreferenceController(Context context, String key, + int userId, LockPatternUtils lockPatternUtils) { + super(context); + mKey = key; + mUserId = userId; + mLockPatternUtils = lockPatternUtils; + } + + @Override + public boolean isAvailable() { + return isPatternLock(); + } + + @Override + public String getPreferenceKey() { + return mKey; + } + + @Override + public void updateState(Preference preference) { + ((TwoStatePreference) preference).setChecked(isEnabled(mLockPatternUtils, mUserId)); + } + + private boolean isPatternLock() { + return mLockPatternUtils.getCredentialTypeForUser(mUserId) + == LockPatternUtils.CREDENTIAL_TYPE_PATTERN; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + setEnabled(mLockPatternUtils, mUserId, (Boolean) newValue); + return true; + } + + protected abstract boolean isEnabled(LockPatternUtils utils, int userId); + protected abstract void setEnabled(LockPatternUtils utils, int userId, boolean enabled); +} diff --git a/src/com/android/settings/security/screenlock/PatternDotsVisiblePreferenceController.java b/src/com/android/settings/security/screenlock/PatternDotsVisiblePreferenceController.java new file mode 100644 index 00000000000..8da044d449b --- /dev/null +++ b/src/com/android/settings/security/screenlock/PatternDotsVisiblePreferenceController.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 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.security.screenlock; + +import android.content.Context; + +import com.android.internal.widget.LockPatternUtils; + +public class PatternDotsVisiblePreferenceController + extends AbstractPatternSwitchPreferenceController { + private static final String PREF_KEY = "visibledots"; + + public PatternDotsVisiblePreferenceController(Context context, int userId, + LockPatternUtils lockPatternUtils) { + super(context, PREF_KEY, userId, lockPatternUtils); + } + + @Override + protected boolean isEnabled(LockPatternUtils utils, int userId) { + return utils.isVisibleDotsEnabled(userId); + } + + @Override + protected void setEnabled(LockPatternUtils utils, int userId, boolean enabled) { + utils.setVisibleDotsEnabled(enabled, userId); + } +} diff --git a/src/com/android/settings/security/screenlock/PatternErrorVisiblePreferenceController.java b/src/com/android/settings/security/screenlock/PatternErrorVisiblePreferenceController.java new file mode 100644 index 00000000000..b9a18c194fd --- /dev/null +++ b/src/com/android/settings/security/screenlock/PatternErrorVisiblePreferenceController.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 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.security.screenlock; + +import android.content.Context; + +import com.android.internal.widget.LockPatternUtils; + +public class PatternErrorVisiblePreferenceController + extends AbstractPatternSwitchPreferenceController { + private static final String PREF_KEY = "visible_error_pattern"; + + public PatternErrorVisiblePreferenceController(Context context, int userId, + LockPatternUtils lockPatternUtils) { + super(context, PREF_KEY, userId, lockPatternUtils); + } + + @Override + protected boolean isEnabled(LockPatternUtils utils, int userId) { + return utils.isShowErrorPath(userId); + } + + @Override + protected void setEnabled(LockPatternUtils utils, int userId, boolean enabled) { + utils.setShowErrorPath(enabled, userId); + } +} diff --git a/src/com/android/settings/security/screenlock/PatternVisiblePreferenceController.java b/src/com/android/settings/security/screenlock/PatternVisiblePreferenceController.java index ea3403bd947..2f8b641a87e 100644 --- a/src/com/android/settings/security/screenlock/PatternVisiblePreferenceController.java +++ b/src/com/android/settings/security/screenlock/PatternVisiblePreferenceController.java @@ -18,52 +18,23 @@ import android.content.Context; -import androidx.preference.Preference; -import androidx.preference.TwoStatePreference; - import com.android.internal.widget.LockPatternUtils; -import com.android.settings.core.PreferenceControllerMixin; -import com.android.settingslib.core.AbstractPreferenceController; - -public class PatternVisiblePreferenceController extends AbstractPreferenceController - implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { +public class PatternVisiblePreferenceController extends AbstractPatternSwitchPreferenceController { private static final String PREF_KEY = "visiblepattern"; - private final int mUserId; - private final LockPatternUtils mLockPatternUtils; - public PatternVisiblePreferenceController(Context context, int userId, LockPatternUtils lockPatternUtils) { - super(context); - mUserId = userId; - mLockPatternUtils = lockPatternUtils; - } - - @Override - public boolean isAvailable() { - return isPatternLock(); + super(context, PREF_KEY, userId, lockPatternUtils); } @Override - public String getPreferenceKey() { - return PREF_KEY; - } - - @Override - public void updateState(Preference preference) { - ((TwoStatePreference) preference).setChecked( - mLockPatternUtils.isVisiblePatternEnabled(mUserId)); - } - - private boolean isPatternLock() { - return mLockPatternUtils.getCredentialTypeForUser(mUserId) - == LockPatternUtils.CREDENTIAL_TYPE_PATTERN; + protected boolean isEnabled(LockPatternUtils utils, int userId) { + return utils.isVisiblePatternEnabled(userId); } @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - mLockPatternUtils.setVisiblePatternEnabled((Boolean) newValue, mUserId); - return true; + protected void setEnabled(LockPatternUtils utils, int userId, boolean enabled) { + utils.setVisiblePatternEnabled(enabled, userId); } } diff --git a/src/com/android/settings/security/screenlock/PinScramblePreferenceController.java b/src/com/android/settings/security/screenlock/PinScramblePreferenceController.java new file mode 100644 index 00000000000..5f21b35f070 --- /dev/null +++ b/src/com/android/settings/security/screenlock/PinScramblePreferenceController.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 The LineageOS 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.security.screenlock; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import androidx.preference.Preference; +import androidx.preference.TwoStatePreference; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.core.AbstractPreferenceController; + +import android.provider.Settings; + +public class PinScramblePreferenceController extends AbstractPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + static final String KEY_LOCKSCREEN_SCRAMBLE_PIN_LAYOUT = "lockscreen_scramble_pin_layout"; + + private final int mUserId; + private final LockPatternUtils mLockPatternUtils; + + public PinScramblePreferenceController(Context context, int userId, + LockPatternUtils lockPatternUtils) { + super(context); + mUserId = userId; + mLockPatternUtils = lockPatternUtils; + } + + @Override + public boolean isAvailable() { + return isPinLock(); + } + + @Override + public String getPreferenceKey() { + return KEY_LOCKSCREEN_SCRAMBLE_PIN_LAYOUT; + } + + @Override + public void updateState(Preference preference) { + ((TwoStatePreference) preference).setChecked(Settings.System.getInt( + mContext.getContentResolver(), + Settings.System.LOCKSCREEN_PIN_SCRAMBLE_LAYOUT, + 0) == 1); + } + + private boolean isPinLock() { + int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId); + boolean hasPin = quality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC || + quality == DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; + return mLockPatternUtils.isSecure(mUserId) && hasPin; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Settings.System.putInt( + mContext.getContentResolver(), + Settings.System.LOCKSCREEN_PIN_SCRAMBLE_LAYOUT, + (Boolean) newValue ? 1 : 0); + return true; + } +} diff --git a/src/com/android/settings/security/screenlock/ScreenLockSettings.java b/src/com/android/settings/security/screenlock/ScreenLockSettings.java index 1c66b711382..0d67ea338d0 100644 --- a/src/com/android/settings/security/screenlock/ScreenLockSettings.java +++ b/src/com/android/settings/security/screenlock/ScreenLockSettings.java @@ -82,12 +82,20 @@ private static List buildPreferenceControllers(Con context, MY_USER_ID, lockPatternUtils)); controllers.add(new PinPrivacyPreferenceController( context, MY_USER_ID, lockPatternUtils)); + controllers.add(new PatternErrorVisiblePreferenceController( + context, MY_USER_ID, lockPatternUtils)); + controllers.add(new PatternDotsVisiblePreferenceController( + context, MY_USER_ID, lockPatternUtils)); controllers.add(new PowerButtonInstantLockPreferenceController( context, MY_USER_ID, lockPatternUtils)); controllers.add(new LockAfterTimeoutPreferenceController( context, MY_USER_ID, lockPatternUtils)); controllers.add(new AutoPinConfirmPreferenceController( context, MY_USER_ID, lockPatternUtils, parent)); + controllers.add(new PinScramblePreferenceController( + context, MY_USER_ID, lockPatternUtils)); + controllers.add(new StatusBarPreferenceController( + context, MY_USER_ID, lockPatternUtils)); controllers.add(new OwnerInfoPreferenceController(context, parent)); return controllers; } diff --git a/src/com/android/settings/security/screenlock/StatusBarPreferenceController.java b/src/com/android/settings/security/screenlock/StatusBarPreferenceController.java new file mode 100644 index 00000000000..d119c36c46c --- /dev/null +++ b/src/com/android/settings/security/screenlock/StatusBarPreferenceController.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018 The LineageOS 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.security.screenlock; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; + +import androidx.preference.Preference; +import androidx.preference.TwoStatePreference; + +import com.android.internal.widget.LockPatternUtils; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settingslib.core.AbstractPreferenceController; + +import android.provider.Settings; + +public class StatusBarPreferenceController extends AbstractPreferenceController + implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + + static final String KEY_STATUS_BAR_SHOWN_ON_SECURE_KEYGUARD = + "status_bar_shown_on_secure_keyguard"; + + private final int mUserId; + private final LockPatternUtils mLockPatternUtils; + + public StatusBarPreferenceController(Context context, int userId, + LockPatternUtils lockPatternUtils) { + super(context); + mUserId = userId; + mLockPatternUtils = lockPatternUtils; + } + + @Override + public boolean isAvailable() { + if (!mLockPatternUtils.isSecure(mUserId)) { + return false; + } + switch (mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)) { + case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: + case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: + case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: + case DevicePolicyManager.PASSWORD_QUALITY_MANAGED: + return true; + default: + return false; + } + } + + @Override + public String getPreferenceKey() { + return KEY_STATUS_BAR_SHOWN_ON_SECURE_KEYGUARD; + } + + @Override + public void updateState(Preference preference) { + ((TwoStatePreference) preference).setChecked(Settings.Secure.getInt( + mContext.getContentResolver(), + Settings.Secure.QS_TILES_TOGGLEABLE_ON_LOCK_SCREEN, + 1) == 1); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + Settings.Secure.putInt( + mContext.getContentResolver(), + Settings.Secure.QS_TILES_TOGGLEABLE_ON_LOCK_SCREEN, + (Boolean) newValue ? 1 : 0); + return true; + } +} diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java index 8f301066e4a..e4bd841111a 100644 --- a/src/com/android/settings/slices/CustomSliceRegistry.java +++ b/src/com/android/settings/slices/CustomSliceRegistry.java @@ -35,6 +35,7 @@ import com.android.settings.homepage.contextualcards.slices.FaceSetupSlice; import com.android.settings.homepage.contextualcards.slices.LowStorageSlice; import com.android.settings.location.LocationSlice; +import com.android.settings.media.AppVolumeSlice; import com.android.settings.media.MediaOutputIndicatorSlice; import com.android.settings.media.RemoteMediaSlice; import com.android.settings.network.ProviderModelSlice; @@ -293,6 +294,16 @@ public class CustomSliceRegistry { .appendPath("always_on_display") .build(); + /** + * Backing Uri for the App Volume Slice. + */ + public static Uri APP_VOLUME_SLICE_URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath("app_volume") + .build(); + @VisibleForTesting static final Map> sUriToSlice; @@ -307,6 +318,7 @@ public class CustomSliceRegistry { sUriToSlice.put(ALWAYS_ON_SLICE_URI, AlwaysOnDisplaySlice.class); sUriToSlice.put(MEDIA_OUTPUT_INDICATOR_SLICE_URI, MediaOutputIndicatorSlice.class); sUriToSlice.put(REMOTE_MEDIA_SLICE_URI, RemoteMediaSlice.class); + sUriToSlice.put(APP_VOLUME_SLICE_URI, AppVolumeSlice.class); // Slices for contextual card. sUriToSlice.put(FACE_ENROLL_SLICE_URI, FaceSetupSlice.class); diff --git a/src/com/android/settings/slices/SlicesDatabaseHelper.java b/src/com/android/settings/slices/SlicesDatabaseHelper.java index cad045e549b..a5ecc345186 100644 --- a/src/com/android/settings/slices/SlicesDatabaseHelper.java +++ b/src/com/android/settings/slices/SlicesDatabaseHelper.java @@ -246,6 +246,6 @@ private boolean isLocaleIndexed() { @VisibleForTesting String getBuildTag() { - return Build.FINGERPRINT; + return Build.VERSION.INCREMENTAL; } -} \ No newline at end of file +} diff --git a/src/com/android/settings/sound/AdaptivePlaybackParentPreferenceController.java b/src/com/android/settings/sound/AdaptivePlaybackParentPreferenceController.java new file mode 100644 index 00000000000..61383fa3eb5 --- /dev/null +++ b/src/com/android/settings/sound/AdaptivePlaybackParentPreferenceController.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020-2021 Paranoid Android + * + * 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.sound; + +import static android.provider.Settings.System.ADAPTIVE_PLAYBACK_ENABLED; +import static android.provider.Settings.System.ADAPTIVE_PLAYBACK_TIMEOUT; + +import static com.android.settings.sound.AdaptivePlaybackSoundPreferenceController.ADAPTIVE_PLAYBACK_TIMEOUT_10_MIN; +import static com.android.settings.sound.AdaptivePlaybackSoundPreferenceController.ADAPTIVE_PLAYBACK_TIMEOUT_1_MIN; +import static com.android.settings.sound.AdaptivePlaybackSoundPreferenceController.ADAPTIVE_PLAYBACK_TIMEOUT_2_MIN; +import static com.android.settings.sound.AdaptivePlaybackSoundPreferenceController.ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS; +import static com.android.settings.sound.AdaptivePlaybackSoundPreferenceController.ADAPTIVE_PLAYBACK_TIMEOUT_5_MIN; +import static com.android.settings.sound.AdaptivePlaybackSoundPreferenceController.ADAPTIVE_PLAYBACK_TIMEOUT_NONE; + +import android.content.Context; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +public class AdaptivePlaybackParentPreferenceController extends BasePreferenceController { + + public AdaptivePlaybackParentPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public CharSequence getSummary() { + boolean enabled = Settings.System.getIntForUser( + mContext.getContentResolver(), ADAPTIVE_PLAYBACK_ENABLED, 0, + UserHandle.USER_CURRENT) != 0; + int timeout = Settings.System.getIntForUser( + mContext.getContentResolver(), ADAPTIVE_PLAYBACK_TIMEOUT, + ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS, + UserHandle.USER_CURRENT); + int summary = R.string.adaptive_playback_disabled_summary; + if (!enabled) { + return mContext.getText(summary); + } + switch (timeout) { + case ADAPTIVE_PLAYBACK_TIMEOUT_NONE -> + summary = R.string.adaptive_playback_timeout_none_summary; + case ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS -> + summary = R.string.adaptive_playback_timeout_30_secs_summary; + case ADAPTIVE_PLAYBACK_TIMEOUT_1_MIN -> + summary = R.string.adaptive_playback_timeout_1_min_summary; + case ADAPTIVE_PLAYBACK_TIMEOUT_2_MIN -> + summary = R.string.adaptive_playback_timeout_2_min_summary; + case ADAPTIVE_PLAYBACK_TIMEOUT_5_MIN -> + summary = R.string.adaptive_playback_timeout_5_min_summary; + case ADAPTIVE_PLAYBACK_TIMEOUT_10_MIN -> + summary = R.string.adaptive_playback_timeout_10_min_summary; + } + return mContext.getText(summary); + } +} diff --git a/src/com/android/settings/sound/AdaptivePlaybackSoundPreferenceController.java b/src/com/android/settings/sound/AdaptivePlaybackSoundPreferenceController.java new file mode 100644 index 00000000000..9cf0be44cf3 --- /dev/null +++ b/src/com/android/settings/sound/AdaptivePlaybackSoundPreferenceController.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2020-2021 Paranoid Android + * + * 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.sound; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settingslib.widget.SelectorWithWidgetPreference; + +public class AdaptivePlaybackSoundPreferenceController extends BasePreferenceController + implements SelectorWithWidgetPreference.OnClickListener, LifecycleObserver, OnStart, OnStop { + + private static final String KEY_NO_TIMEOUT = "adaptive_playback_timeout_none"; + private static final String KEY_30_SECS = "adaptive_playback_timeout_30_secs"; + private static final String KEY_1_MIN = "adaptive_playback_timeout_1_min"; + private static final String KEY_2_MIN = "adaptive_playback_timeout_2_min"; + private static final String KEY_5_MIN = "adaptive_playback_timeout_5_min"; + private static final String KEY_10_MIN = "adaptive_playback_timeout_10_min"; + + static final int ADAPTIVE_PLAYBACK_TIMEOUT_NONE = 0; + static final int ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS = 30000; + static final int ADAPTIVE_PLAYBACK_TIMEOUT_1_MIN = 60000; + static final int ADAPTIVE_PLAYBACK_TIMEOUT_2_MIN = 120000; + static final int ADAPTIVE_PLAYBACK_TIMEOUT_5_MIN = 300000; + static final int ADAPTIVE_PLAYBACK_TIMEOUT_10_MIN = 600000; + + private boolean mAdaptivePlaybackEnabled; + private int mAdaptivePlaybackTimeout; + + private PreferenceCategory mPreferenceCategory; + private SelectorWithWidgetPreference mTimeoutNonePref; + private SelectorWithWidgetPreference mTimeout30SecPref; + private SelectorWithWidgetPreference mTimeout1MinPref; + private SelectorWithWidgetPreference mTimeout2MinPref; + private SelectorWithWidgetPreference mTimeout5MinPref; + private SelectorWithWidgetPreference mTimeout10MinPref; + + private final SettingObserver mSettingObserver; + + public AdaptivePlaybackSoundPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + + mSettingObserver = new SettingObserver(new Handler(Looper.getMainLooper())); + mAdaptivePlaybackEnabled = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.ADAPTIVE_PLAYBACK_ENABLED, 0, UserHandle.USER_CURRENT) != 0; + mAdaptivePlaybackTimeout = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.ADAPTIVE_PLAYBACK_TIMEOUT, ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS, + UserHandle.USER_CURRENT); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreferenceCategory = screen.findPreference(getPreferenceKey()); + mTimeoutNonePref = makeRadioPreference(KEY_NO_TIMEOUT, + R.string.adaptive_playback_timeout_none); + mTimeout30SecPref = makeRadioPreference(KEY_30_SECS, + R.string.adaptive_playback_timeout_30_secs); + mTimeout1MinPref = makeRadioPreference(KEY_1_MIN, R.string.adaptive_playback_timeout_1_min); + mTimeout2MinPref = makeRadioPreference(KEY_2_MIN, R.string.adaptive_playback_timeout_2_min); + mTimeout5MinPref = makeRadioPreference(KEY_5_MIN, R.string.adaptive_playback_timeout_5_min); + mTimeout10MinPref = makeRadioPreference(KEY_10_MIN, + R.string.adaptive_playback_timeout_10_min); + updateState(null); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; + } + + @Override + public void onRadioButtonClicked(SelectorWithWidgetPreference preference) { + int adaptivePlaybackTimeout = keyToSetting(preference.getKey()); + if (adaptivePlaybackTimeout != Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.ADAPTIVE_PLAYBACK_TIMEOUT, ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS, + UserHandle.USER_CURRENT)) { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.ADAPTIVE_PLAYBACK_TIMEOUT, adaptivePlaybackTimeout, + UserHandle.USER_CURRENT); + } + } + + @Override + public void updateState(Preference preference) { + final boolean isTimeoutNone = mAdaptivePlaybackEnabled + && mAdaptivePlaybackTimeout == ADAPTIVE_PLAYBACK_TIMEOUT_NONE; + final boolean isTimeout30Sec = mAdaptivePlaybackEnabled + && mAdaptivePlaybackTimeout == ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS; + final boolean isTimeout1Min = mAdaptivePlaybackEnabled + && mAdaptivePlaybackTimeout == ADAPTIVE_PLAYBACK_TIMEOUT_1_MIN; + final boolean isTimeout2Min = mAdaptivePlaybackEnabled + && mAdaptivePlaybackTimeout == ADAPTIVE_PLAYBACK_TIMEOUT_2_MIN; + final boolean isTimeout5Min = mAdaptivePlaybackEnabled + && mAdaptivePlaybackTimeout == ADAPTIVE_PLAYBACK_TIMEOUT_5_MIN; + final boolean isTimeout10Min = mAdaptivePlaybackEnabled + && mAdaptivePlaybackTimeout == ADAPTIVE_PLAYBACK_TIMEOUT_10_MIN; + if (mTimeoutNonePref != null && mTimeoutNonePref.isChecked() != isTimeoutNone) { + mTimeoutNonePref.setChecked(isTimeoutNone); + } + if (mTimeout30SecPref != null && mTimeout30SecPref.isChecked() != isTimeout30Sec) { + mTimeout30SecPref.setChecked(isTimeout30Sec); + } + if (mTimeout1MinPref != null && mTimeout1MinPref.isChecked() != isTimeout1Min) { + mTimeout1MinPref.setChecked(isTimeout1Min); + } + if (mTimeout2MinPref != null && mTimeout2MinPref.isChecked() != isTimeout2Min) { + mTimeout2MinPref.setChecked(isTimeout2Min); + } + if (mTimeout5MinPref != null && mTimeout5MinPref.isChecked() != isTimeout5Min) { + mTimeout5MinPref.setChecked(isTimeout5Min); + } + if (mTimeout10MinPref != null && mTimeout10MinPref.isChecked() != isTimeout10Min) { + mTimeout10MinPref.setChecked(isTimeout10Min); + } + + if (mAdaptivePlaybackEnabled) { + mPreferenceCategory.setEnabled(true); + mTimeoutNonePref.setEnabled(true); + mTimeout30SecPref.setEnabled(true); + mTimeout1MinPref.setEnabled(true); + mTimeout2MinPref.setEnabled(true); + mTimeout5MinPref.setEnabled(true); + mTimeout10MinPref.setEnabled(true); + } else { + mPreferenceCategory.setEnabled(false); + mTimeoutNonePref.setEnabled(false); + mTimeout30SecPref.setEnabled(false); + mTimeout1MinPref.setEnabled(false); + mTimeout2MinPref.setEnabled(false); + mTimeout5MinPref.setEnabled(false); + mTimeout10MinPref.setEnabled(false); + } + } + + @Override + public void onStart() { + mSettingObserver.observe(); + } + + @Override + public void onStop() { + mContext.getContentResolver().unregisterContentObserver(mSettingObserver); + } + + private static int keyToSetting(String key) { + switch (key) { + case KEY_NO_TIMEOUT: + return ADAPTIVE_PLAYBACK_TIMEOUT_NONE; + case KEY_1_MIN: + return ADAPTIVE_PLAYBACK_TIMEOUT_1_MIN; + case KEY_2_MIN: + return ADAPTIVE_PLAYBACK_TIMEOUT_2_MIN; + case KEY_5_MIN: + return ADAPTIVE_PLAYBACK_TIMEOUT_5_MIN; + case KEY_10_MIN: + return ADAPTIVE_PLAYBACK_TIMEOUT_10_MIN; + default: + return ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS; + } + } + + private SelectorWithWidgetPreference makeRadioPreference(String key, int titleId) { + SelectorWithWidgetPreference pref = new SelectorWithWidgetPreference(mPreferenceCategory.getContext()); + pref.setKey(key); + pref.setTitle(titleId); + pref.setOnClickListener(this); + mPreferenceCategory.addPreference(pref); + return pref; + } + + private final class SettingObserver extends ContentObserver { + private final Uri ADAPTIVE_PLAYBACK = Settings.System.getUriFor( + Settings.System.ADAPTIVE_PLAYBACK_ENABLED); + private final Uri ADAPTIVE_PLAYBACK_TIMEOUT = Settings.System.getUriFor( + Settings.System.ADAPTIVE_PLAYBACK_TIMEOUT); + + public SettingObserver(Handler handler) { + super(handler); + } + + public void observe() { + final ContentResolver cr = mContext.getContentResolver(); + cr.registerContentObserver(ADAPTIVE_PLAYBACK, false, this, UserHandle.USER_ALL); + cr.registerContentObserver(ADAPTIVE_PLAYBACK_TIMEOUT, false, this, UserHandle.USER_ALL); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (ADAPTIVE_PLAYBACK.equals(uri) || ADAPTIVE_PLAYBACK_TIMEOUT.equals(uri)) { + mAdaptivePlaybackEnabled = Settings.System.getIntForUser( + mContext.getContentResolver(), Settings.System.ADAPTIVE_PLAYBACK_ENABLED, 0, + UserHandle.USER_CURRENT) != 0; + mAdaptivePlaybackTimeout = Settings.System.getIntForUser( + mContext.getContentResolver(), Settings.System.ADAPTIVE_PLAYBACK_TIMEOUT, + ADAPTIVE_PLAYBACK_TIMEOUT_30_SECS, UserHandle.USER_CURRENT); + updateState(null); + } + } + } +} diff --git a/src/com/android/settings/sound/AdaptivePlaybackSoundSettings.java b/src/com/android/settings/sound/AdaptivePlaybackSoundSettings.java new file mode 100644 index 00000000000..e74723e1a26 --- /dev/null +++ b/src/com/android/settings/sound/AdaptivePlaybackSoundSettings.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020-2021 Paranoid Android + * + * 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.sound; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; + +public class AdaptivePlaybackSoundSettings extends DashboardFragment { + + private static final String TAG = "AdaptivePlaybackSoundSettings"; + + @Override + public int getMetricsCategory() { + return -1; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.adaptive_playback_sound_settings; + } + + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.adaptive_playback_sound_settings); +} diff --git a/src/com/android/settings/sound/AdaptivePlaybackSwitchPreferenceController.java b/src/com/android/settings/sound/AdaptivePlaybackSwitchPreferenceController.java new file mode 100644 index 00000000000..5b11ae1e5cb --- /dev/null +++ b/src/com/android/settings/sound/AdaptivePlaybackSwitchPreferenceController.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2020-2022 Paranoid Android + * + * 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.sound; + +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.widget.SettingsMainSwitchPreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settingslib.widget.MainSwitchPreference; + +public class AdaptivePlaybackSwitchPreferenceController extends + SettingsMainSwitchPreferenceController implements LifecycleObserver, OnStart, OnStop { + + private MainSwitchPreference mPreference; + private final SettingsObserver mSettingsObserver; + + public AdaptivePlaybackSwitchPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mSettingsObserver = new SettingsObserver(new Handler(Looper.getMainLooper())); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean isChecked() { + return Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.ADAPTIVE_PLAYBACK_ENABLED, 0, UserHandle.USER_CURRENT) != 0; + } + + @Override + public boolean setChecked(boolean isChecked) { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.ADAPTIVE_PLAYBACK_ENABLED, isChecked ? 1 : 0, + UserHandle.USER_CURRENT); + return true; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public void onStart() { + mSettingsObserver.observe(); + } + + @Override + public void onStop() { + mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.ADAPTIVE_PLAYBACK_ENABLED, isChecked ? 1 : 0, + UserHandle.USER_CURRENT); + } + + @Override + public int getSliceHighlightMenuRes() { + return NO_RES; + } + + private final class SettingsObserver extends ContentObserver { + private final Uri ADAPTIVE_PLAYBACK = Settings.System.getUriFor( + Settings.System.ADAPTIVE_PLAYBACK_ENABLED); + + public SettingsObserver(Handler handler) { + super(handler); + } + + public void observe() { + mContext.getContentResolver().registerContentObserver(ADAPTIVE_PLAYBACK, false, this, + UserHandle.USER_ALL); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + if (ADAPTIVE_PLAYBACK.equals(uri)) { + mPreference.setChecked(isChecked()); + } + } + } +} diff --git a/src/com/android/settings/sound/CustomNotificationVibrationPreferenceController.java b/src/com/android/settings/sound/CustomNotificationVibrationPreferenceController.java new file mode 100644 index 00000000000..735aa24ff68 --- /dev/null +++ b/src/com/android/settings/sound/CustomNotificationVibrationPreferenceController.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 Yet Another AOSP 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.sound; + +import android.content.Context; +import android.provider.Settings; + +public class CustomNotificationVibrationPreferenceController extends CustomVibrationPreferenceController { + private static final String DEFAULT_SETTINGS_VALUE = "80,40,0"; + + public CustomNotificationVibrationPreferenceController(Context context) { + super(context); + } + + @Override + public boolean isAvailable() { + return mVibrator.hasVibrator(); + } + + @Override + protected String getSettingsKey() { + return Settings.System.CUSTOM_NOTIFICATION_VIBRATION_PATTERN; + } + + @Override + protected String getDefaultValue() { + return DEFAULT_SETTINGS_VALUE; + } + + @Override + protected long getDelay() { + return 120; + } +} diff --git a/src/com/android/settings/sound/CustomNotificationVibrationPreferenceFragment.java b/src/com/android/settings/sound/CustomNotificationVibrationPreferenceFragment.java new file mode 100644 index 00000000000..0b03893cd62 --- /dev/null +++ b/src/com/android/settings/sound/CustomNotificationVibrationPreferenceFragment.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 Yet Another AOSP 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.sound; + +import android.content.Context; + +import androidx.annotation.XmlRes; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.core.AbstractPreferenceController; + +import java.util.ArrayList; +import java.util.List; + +/** + * Settings for custom notification vibration pattern + */ +public class CustomNotificationVibrationPreferenceFragment extends DashboardFragment { + + private static final String TAG = "CustomNotificationVibrationPreferenceFragment"; + + @Override + public void addPreferencesFromResource(@XmlRes int preferencesResId) { + super.addPreferencesFromResource(preferencesResId); + getActivity().setTitle(getContext().getText( + R.string.notification_vibration_pattern_title)); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.custom_vibration_pattern; + } + + @Override + protected List createPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + controllers.add(new CustomNotificationVibrationPreferenceController(context)); + return controllers; + } +} diff --git a/src/com/android/settings/sound/CustomVibrationPreferenceController.java b/src/com/android/settings/sound/CustomVibrationPreferenceController.java new file mode 100644 index 00000000000..f64f38c6c75 --- /dev/null +++ b/src/com/android/settings/sound/CustomVibrationPreferenceController.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2020 Yet Another AOSP 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.sound; + +import android.content.ContentResolver; +import android.content.Context; +import android.media.AudioAttributes; +import android.net.Uri; +import android.os.VibrationEffect; +import android.os.Vibrator; +import android.provider.Settings; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.sound.CustomVibrationPreferenceController; +import com.android.settings.Utils; +import com.android.settingslib.core.AbstractPreferenceController; + +import org.derpfest.support.preferences.ProperSeekBarPreference; + +/** + * This class allows choosing a vibration pattern while ringing + */ +public class CustomVibrationPreferenceController extends AbstractPreferenceController + implements Preference.OnPreferenceChangeListener { + + private static final String KEY = "custom_vibration_pattern"; + private static final String KEY_CUSTOM_VIB1 = "custom_vibration_pattern1"; + private static final String KEY_CUSTOM_VIB2 = "custom_vibration_pattern2"; + private static final String KEY_CUSTOM_VIB3 = "custom_vibration_pattern3"; + private static final String DEFAULT_SETTINGS_VALUE = "0,800,800"; + + private ProperSeekBarPreference mCustomVib1; + private ProperSeekBarPreference mCustomVib2; + private ProperSeekBarPreference mCustomVib3; + + protected final Vibrator mVibrator; + + private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) + .build(); + + private static final int[] SEVEN_ELEMENTS_VIBRATION_AMPLITUDE = { + 0, // No delay before starting + 255, // Vibrate full amplitude + 0, // No amplitude while waiting + 255, + 0, + 255, + 0, + }; + + public CustomVibrationPreferenceController(Context context) { + super(context); + mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + } + + private static class VibrationEffectProxy { + public VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) { + return VibrationEffect.createWaveform(timings, amplitudes, repeat); + } + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mCustomVib1 = (ProperSeekBarPreference) screen.findPreference(KEY_CUSTOM_VIB1); + mCustomVib2 = (ProperSeekBarPreference) screen.findPreference(KEY_CUSTOM_VIB2); + mCustomVib3 = (ProperSeekBarPreference) screen.findPreference(KEY_CUSTOM_VIB3); + updateCustomVibPreferences(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mCustomVib1) { + updateCustomVib(0, (Integer) newValue); + return true; + } else if (preference == mCustomVib2) { + updateCustomVib(1, (Integer) newValue); + return true; + } else if (preference == mCustomVib3) { + updateCustomVib(2, (Integer) newValue); + return true; + } + return false; + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public boolean isAvailable() { + return Utils.isVoiceCapable(mContext) && mVibrator.hasVibrator(); + } + + protected String getSettingsKey() { + return Settings.System.CUSTOM_RINGTONE_VIBRATION_PATTERN; + } + + protected String getDefaultValue() { + return DEFAULT_SETTINGS_VALUE; + } + + protected long getDelay() { + return 400; + } + + private void updateCustomVibPreferences() { + String value = Settings.System.getString( + mContext.getContentResolver(), getSettingsKey()); + if (value != null) { + String[] customPattern = value.split(",", 3); + mCustomVib1.setValue(Integer.parseInt(customPattern[0])); + mCustomVib2.setValue(Integer.parseInt(customPattern[1])); + mCustomVib3.setValue(Integer.parseInt(customPattern[2])); + } else { // set default + String[] defaultPattern = getDefaultValue().split(",", 3); + mCustomVib1.setValue(Integer.parseInt(defaultPattern[0])); + mCustomVib2.setValue(Integer.parseInt(defaultPattern[1])); + mCustomVib3.setValue(Integer.parseInt(defaultPattern[2])); + Settings.System.putString( + mContext.getContentResolver(), getSettingsKey(), getDefaultValue()); + } + mCustomVib1.setOnPreferenceChangeListener(this); + mCustomVib2.setOnPreferenceChangeListener(this); + mCustomVib3.setOnPreferenceChangeListener(this); + } + + private void updateCustomVib(int index, int value) { + String[] customPattern = Settings.System.getString(mContext.getContentResolver(), + getSettingsKey()).split(",", 3); + customPattern[index] = String.valueOf(value); + Settings.System.putString(mContext.getContentResolver(), + getSettingsKey(), String.join( + ",", customPattern[0], customPattern[1], customPattern[2])); + previewPattern(); + } + + private void previewPattern() { + VibrationEffect effect; + VibrationEffectProxy vibrationEffectProxy = new VibrationEffectProxy(); + String[] customVib = Settings.System.getString( + mContext.getContentResolver(), + getSettingsKey()).split(",", 3); + long[] customVibPattern = { + 0, // No delay before starting + Long.parseLong(customVib[0]), // How long to vibrate + getDelay(), // Delay + Long.parseLong(customVib[1]), // How long to vibrate + getDelay(), // Delay + Long.parseLong(customVib[2]), // How long to vibrate + getDelay(), // How long to wait before vibrating again + }; + effect = vibrationEffectProxy.createWaveform(customVibPattern, + SEVEN_ELEMENTS_VIBRATION_AMPLITUDE, -1); + mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES); + } + +} diff --git a/src/com/android/settings/sound/CustomVibrationPreferenceFragment.java b/src/com/android/settings/sound/CustomVibrationPreferenceFragment.java new file mode 100644 index 00000000000..cd713a005af --- /dev/null +++ b/src/com/android/settings/sound/CustomVibrationPreferenceFragment.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 Yet Another AOSP 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.sound; + +import android.content.Context; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.core.AbstractPreferenceController; + +import java.util.ArrayList; +import java.util.List; + +/** + * Settings for custom ringtone vibration pattern + */ +public class CustomVibrationPreferenceFragment extends DashboardFragment { + + private static final String TAG = "CustomVibrationPreferenceFragment"; + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.custom_vibration_pattern; + } + + @Override + protected List createPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + controllers.add(new CustomVibrationPreferenceController(context)); + return controllers; + } +} diff --git a/src/com/android/settings/sound/HapticSettings.java b/src/com/android/settings/sound/HapticSettings.java new file mode 100644 index 00000000000..497936441a8 --- /dev/null +++ b/src/com/android/settings/sound/HapticSettings.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 The Nameless-AOSP 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.sound; + +import android.os.Bundle; + +import androidx.preference.Preference; + +import com.android.internal.logging.nano.MetricsProto; + +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; + +public class HapticSettings extends SettingsPreferenceFragment { + + @Override + public void onCreate(Bundle savedInstance) { + super.onCreate(savedInstance); + addPreferencesFromResource(R.xml.haptic_settings); + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DERP; + } +} diff --git a/src/com/android/settings/sound/PulseSettings.java b/src/com/android/settings/sound/PulseSettings.java new file mode 100644 index 00000000000..1f0662caab2 --- /dev/null +++ b/src/com/android/settings/sound/PulseSettings.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2020 The Dirty Unicorns 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.sound; + +import android.content.ContentResolver; +import android.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.os.UserHandle; +import android.provider.SearchIndexableResource; +import android.provider.Settings; + +import androidx.preference.PreferenceCategory; +import androidx.preference.ListPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import androidx.preference.Preference.OnPreferenceChangeListener; +import androidx.preference.SwitchPreferenceCompat; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.Indexable; +import org.derpfest.support.colorpicker.ColorPickerPreference; + +import java.util.Arrays; +import java.util.List; + +public class PulseSettings extends SettingsPreferenceFragment implements + Preference.OnPreferenceChangeListener { + private static final String TAG = PulseSettings.class.getSimpleName(); + private static final String NAVBAR_PULSE_ENABLED_KEY = "navbar_pulse_enabled"; + private static final String LOCKSCREEN_PULSE_ENABLED_KEY = "lockscreen_pulse_enabled"; + private static final String AMBIENT_PULSE_ENABLED_KEY = "ambient_pulse_enabled"; + private static final String PULSE_SMOOTHING_KEY = "pulse_smoothing_enabled"; + private static final String PULSE_COLOR_MODE_KEY = "pulse_color_mode"; + private static final String PULSE_COLOR_MODE_CHOOSER_KEY = "pulse_color_user"; + private static final String PULSE_COLOR_MODE_LAVA_SPEED_KEY = "pulse_lavalamp_speed"; + private static final String PULSE_RENDER_CATEGORY_SOLID = "pulse_2"; + private static final String PULSE_RENDER_CATEGORY_FADING = "pulse_fading_bars_category"; + private static final String PULSE_RENDER_MODE_KEY = "pulse_render_style"; + private static final String PULSE_CUSTOM_GRAVITY = "pulse_custom_gravity"; + private static final String VISUALIZER_CENTER_MIRRORED = "visualizer_center_mirrored"; + private static final String PULSE_VERTICAL_MIRROR = "pulse_vertical_mirror"; + private static final int RENDER_STYLE_FADING_BARS = 0; + private static final int RENDER_STYLE_SOLID_LINES = 1; + private static final int COLOR_TYPE_ACCENT = 0; + private static final int COLOR_TYPE_USER = 1; + private static final int COLOR_TYPE_LAVALAMP = 2; + private static final int COLOR_TYPE_AUTO = 3; + + private static final String PULSE_SETTINGS_FOOTER = "pulse_settings_footer"; + + private SwitchPreferenceCompat mNavbarPulse; + private SwitchPreferenceCompat mLockscreenPulse; + private SwitchPreferenceCompat mAmbientPulse; + private SwitchPreferenceCompat mPulseSmoothing; + private SwitchPreferenceCompat mPulseCenterMirrored; + private SwitchPreferenceCompat mPulseVerticalMirror; + private Preference mRenderMode; + private ListPreference mColorModePref; + private ColorPickerPreference mColorPickerPref; + private ListPreference mPulseGravity; + private Preference mLavaSpeedPref; + private Preference mFooterPref; + + private PreferenceCategory mFadingBarsCat; + private PreferenceCategory mSolidBarsCat; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.pulse_settings); + + ContentResolver resolver = getContext().getContentResolver(); + + mNavbarPulse = (SwitchPreferenceCompat) findPreference(NAVBAR_PULSE_ENABLED_KEY); + boolean navbarPulse = Settings.Secure.getIntForUser(resolver, + Settings.Secure.NAVBAR_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) != 0; + mNavbarPulse.setChecked(navbarPulse); + mNavbarPulse.setOnPreferenceChangeListener(this); + + mLockscreenPulse = (SwitchPreferenceCompat) findPreference(LOCKSCREEN_PULSE_ENABLED_KEY); + boolean lockscreenPulse = Settings.Secure.getIntForUser(resolver, + Settings.Secure.LOCKSCREEN_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) != 0; + mLockscreenPulse.setChecked(lockscreenPulse); + mLockscreenPulse.setOnPreferenceChangeListener(this); + + mAmbientPulse = (SwitchPreferenceCompat) findPreference(AMBIENT_PULSE_ENABLED_KEY); + boolean ambientPulse = Settings.Secure.getIntForUser(resolver, + Settings.Secure.AMBIENT_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) != 0; + mAmbientPulse.setChecked(ambientPulse); + mAmbientPulse.setOnPreferenceChangeListener(this); + + mColorModePref = (ListPreference) findPreference(PULSE_COLOR_MODE_KEY); + mColorPickerPref = (ColorPickerPreference) findPreference(PULSE_COLOR_MODE_CHOOSER_KEY); + mLavaSpeedPref = findPreference(PULSE_COLOR_MODE_LAVA_SPEED_KEY); + mColorModePref.setOnPreferenceChangeListener(this); + + mRenderMode = findPreference(PULSE_RENDER_MODE_KEY); + mRenderMode.setOnPreferenceChangeListener(this); + + mFadingBarsCat = (PreferenceCategory) findPreference( + PULSE_RENDER_CATEGORY_FADING); + mSolidBarsCat = (PreferenceCategory) findPreference( + PULSE_RENDER_CATEGORY_SOLID); + + mPulseSmoothing = (SwitchPreferenceCompat) findPreference(PULSE_SMOOTHING_KEY); + + mPulseCenterMirrored = (SwitchPreferenceCompat) findPreference(VISUALIZER_CENTER_MIRRORED); + + mPulseVerticalMirror = (SwitchPreferenceCompat) findPreference(PULSE_VERTICAL_MIRROR); + + mPulseGravity = (ListPreference) findPreference(PULSE_CUSTOM_GRAVITY); + + mFooterPref = findPreference(PULSE_SETTINGS_FOOTER); + mFooterPref.setTitle(R.string.pulse_help_policy_notice_summary); + + updateAllPrefs(); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + ContentResolver resolver = getContext().getContentResolver(); + if (preference == mNavbarPulse) { + boolean val = (Boolean) newValue; + Settings.Secure.putIntForUser(resolver, + Settings.Secure.NAVBAR_PULSE_ENABLED, val ? 1 : 0, UserHandle.USER_CURRENT); + updateAllPrefs(); + return true; + } else if (preference == mLockscreenPulse) { + boolean val = (Boolean) newValue; + Settings.Secure.putIntForUser(resolver, + Settings.Secure.LOCKSCREEN_PULSE_ENABLED, val ? 1 : 0, UserHandle.USER_CURRENT); + updateAllPrefs(); + return true; + } else if (preference == mAmbientPulse) { + boolean val = (Boolean) newValue; + Settings.Secure.putIntForUser(resolver, + Settings.Secure.AMBIENT_PULSE_ENABLED, val ? 1 : 0, UserHandle.USER_CURRENT); + updateAllPrefs(); + return true; + } else if (preference == mColorModePref) { + updateColorPrefs(Integer.valueOf(String.valueOf(newValue))); + return true; + } else if (preference == mRenderMode) { + updateRenderCategories(Integer.valueOf(String.valueOf(newValue))); + return true; + } + return false; + } + + private void updateAllPrefs() { + ContentResolver resolver = getContext().getContentResolver(); + + boolean navbarPulse = Settings.Secure.getIntForUser(resolver, + Settings.Secure.NAVBAR_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) != 0; + boolean lockscreenPulse = Settings.Secure.getIntForUser(resolver, + Settings.Secure.LOCKSCREEN_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) != 0; + boolean ambientPulse = Settings.Secure.getIntForUser(resolver, + Settings.Secure.AMBIENT_PULSE_ENABLED, 0, UserHandle.USER_CURRENT) != 0; + + mPulseSmoothing.setEnabled(navbarPulse || lockscreenPulse || ambientPulse); + + mPulseCenterMirrored.setEnabled(navbarPulse || lockscreenPulse || ambientPulse); + + mPulseVerticalMirror.setEnabled(navbarPulse || lockscreenPulse || ambientPulse); + + mPulseGravity.setEnabled(navbarPulse || lockscreenPulse || ambientPulse); + + mColorModePref.setEnabled(navbarPulse || lockscreenPulse || ambientPulse); + if (navbarPulse || lockscreenPulse) { + int colorMode = Settings.Secure.getIntForUser(resolver, + Settings.Secure.PULSE_COLOR_MODE, COLOR_TYPE_LAVALAMP, UserHandle.USER_CURRENT); + updateColorPrefs(colorMode); + } else { + mColorPickerPref.setEnabled(false); + mLavaSpeedPref.setEnabled(false); + } + + mRenderMode.setEnabled(navbarPulse || lockscreenPulse || ambientPulse); + if (navbarPulse || lockscreenPulse || ambientPulse) { + int renderMode = Settings.Secure.getIntForUser(resolver, + Settings.Secure.PULSE_RENDER_STYLE, RENDER_STYLE_SOLID_LINES, UserHandle.USER_CURRENT); + updateRenderCategories(renderMode); + } else { + mFadingBarsCat.setEnabled(false); + mSolidBarsCat.setEnabled(false); + } + + mFooterPref.setEnabled(navbarPulse || lockscreenPulse); + } + + private void updateColorPrefs(int val) { + switch (val) { + case COLOR_TYPE_ACCENT: + mColorPickerPref.setEnabled(false); + mLavaSpeedPref.setEnabled(false); + break; + case COLOR_TYPE_USER: + mColorPickerPref.setEnabled(true); + mLavaSpeedPref.setEnabled(false); + break; + case COLOR_TYPE_LAVALAMP: + mColorPickerPref.setEnabled(false); + mLavaSpeedPref.setEnabled(true); + break; + case COLOR_TYPE_AUTO: + mColorPickerPref.setEnabled(false); + mLavaSpeedPref.setEnabled(false); + break; + } + } + + private void updateRenderCategories(int mode) { + mFadingBarsCat.setEnabled(mode == RENDER_STYLE_FADING_BARS); + mSolidBarsCat.setEnabled(mode == RENDER_STYLE_SOLID_LINES); + } + + @Override + public int getMetricsCategory() { + return MetricsProto.MetricsEvent.DERP; + } + + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex( + Context context, boolean enabled) { + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.pulse_settings; + return Arrays.asList(sir); + } + + @Override + protected boolean isPageSearchEnabled(Context context) { + return true; + } + }; +} diff --git a/src/com/android/settings/sound/TouchHapticPreferenceController.java b/src/com/android/settings/sound/TouchHapticPreferenceController.java new file mode 100644 index 00000000000..95676eb2f7a --- /dev/null +++ b/src/com/android/settings/sound/TouchHapticPreferenceController.java @@ -0,0 +1,35 @@ +package com.android.settings.sound; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +import com.android.settings.core.BasePreferenceController; + +public class TouchHapticPreferenceController extends BasePreferenceController { + + public TouchHapticPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public int getSliceHighlightMenuRes() { + return 0; + } +} diff --git a/src/com/android/settings/system/AdditionalSystemUpdatePreferenceController.java b/src/com/android/settings/system/AdditionalSystemUpdatePreferenceController.java index 868f10f3b50..7dc9ffce2e1 100644 --- a/src/com/android/settings/system/AdditionalSystemUpdatePreferenceController.java +++ b/src/com/android/settings/system/AdditionalSystemUpdatePreferenceController.java @@ -29,9 +29,12 @@ public AdditionalSystemUpdatePreferenceController(Context context) { @Override public int getAvailabilityStatus() { + /* return mContext.getResources().getBoolean( com.android.settings.R.bool.config_additional_system_update_setting_enable) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + */ + return UNSUPPORTED_ON_DEVICE; } -} \ No newline at end of file +} diff --git a/src/com/android/settings/system/SystemDashboardFragment.java b/src/com/android/settings/system/SystemDashboardFragment.java index 678b675e6ad..1513f07583d 100644 --- a/src/com/android/settings/system/SystemDashboardFragment.java +++ b/src/com/android/settings/system/SystemDashboardFragment.java @@ -16,6 +16,7 @@ package com.android.settings.system; import android.app.settings.SettingsEnums; +import android.content.Context; import android.os.Bundle; import androidx.preference.Preference; @@ -26,6 +27,11 @@ import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.search.SearchIndexable; +import com.android.settings.deviceinfo.UpdatePreferenceController; +import com.android.settingslib.core.AbstractPreferenceController; + +import java.util.ArrayList; +import java.util.List; @SearchIndexable public class SystemDashboardFragment extends DashboardFragment { @@ -76,6 +82,16 @@ private int getVisiblePreferenceCount(PreferenceGroup group) { return visibleCount; } + protected List getPreferenceControllers(Context context) { + return buildPreferenceControllers(context); + } + + private static List buildPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + controllers.add(new UpdatePreferenceController(context)); + return controllers; + } + /** * For Search. */ diff --git a/src/com/android/settings/system/SystemUpdatePreferenceController.kt b/src/com/android/settings/system/SystemUpdatePreferenceController.kt index 87a402e2236..525b52681c3 100644 --- a/src/com/android/settings/system/SystemUpdatePreferenceController.kt +++ b/src/com/android/settings/system/SystemUpdatePreferenceController.kt @@ -40,9 +40,12 @@ open class SystemUpdatePreferenceController(context: Context, preferenceKey: Str private lateinit var preference: Preference override fun getAvailabilityStatus() = + /* if (mContext.resources.getBoolean(R.bool.config_show_system_update_settings) && userManager.isAdminUser ) AVAILABLE else UNSUPPORTED_ON_DEVICE + */ + UNSUPPORTED_ON_DEVICE override fun displayPreference(screen: PreferenceScreen) { super.displayPreference(screen) diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index bf21c9b913a..dcee497f7d7 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -1544,7 +1544,7 @@ private Drawable centerAndTint(Drawable icon) { int getRealUsersCount() { return (int) mUserManager.getUsers() .stream() - .filter(user -> !user.isGuest() && !user.isProfile()) + .filter(user -> !user.isGuest() && !user.isProfile() && !user.isParallel()) .count(); } diff --git a/src/com/android/settings/vpn2/AppManagementFragment.java b/src/com/android/settings/vpn2/AppManagementFragment.java index 00c8f5994ce..65ea4da5049 100644 --- a/src/com/android/settings/vpn2/AppManagementFragment.java +++ b/src/com/android/settings/vpn2/AppManagementFragment.java @@ -27,10 +27,12 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.UserInfo; import android.net.VpnManager; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.text.TextUtils; import android.util.Log; @@ -39,6 +41,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.preference.Preference; +import androidx.preference.SwitchPreferenceCompat; import com.android.internal.net.VpnConfig; import com.android.internal.util.ArrayUtils; @@ -63,6 +66,7 @@ public class AppManagementFragment extends SettingsPreferenceFragment private static final String ARG_PACKAGE_NAME = "package"; private static final String KEY_VERSION = "version"; + private static final String KEY_GLOBAL_VPN = "global_vpn"; private static final String KEY_ALWAYS_ON_VPN = "always_on_vpn"; private static final String KEY_LOCKDOWN_VPN = "lockdown_vpn"; private static final String KEY_FORGET_VPN = "forget_vpn"; @@ -80,6 +84,7 @@ public class AppManagementFragment extends SettingsPreferenceFragment // UI preference private Preference mPreferenceVersion; + private SwitchPreferenceCompat mPreferenceGlobal; private RestrictedSwitchPreference mPreferenceAlwaysOn; private RestrictedSwitchPreference mPreferenceLockdown; private RestrictedPreference mPreferenceForget; @@ -126,10 +131,16 @@ public void onCreate(Bundle savedState) { mFeatureProvider = FeatureFactory.getFeatureFactory().getAdvancedVpnFeatureProvider(); mPreferenceVersion = findPreference(KEY_VERSION); + mPreferenceGlobal = (SwitchPreferenceCompat) findPreference(KEY_GLOBAL_VPN); mPreferenceAlwaysOn = (RestrictedSwitchPreference) findPreference(KEY_ALWAYS_ON_VPN); mPreferenceLockdown = (RestrictedSwitchPreference) findPreference(KEY_LOCKDOWN_VPN); mPreferenceForget = (RestrictedPreference) findPreference(KEY_FORGET_VPN); + if (mUserId != UserHandle.USER_SYSTEM) { + removePreference(KEY_GLOBAL_VPN); + } + + mPreferenceGlobal.setOnPreferenceChangeListener(this); mPreferenceAlwaysOn.setOnPreferenceChangeListener(this); mPreferenceLockdown.setOnPreferenceChangeListener(this); mPreferenceForget.setOnPreferenceClickListener(this); @@ -163,6 +174,8 @@ public boolean onPreferenceClick(Preference preference) { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { switch (preference.getKey()) { + case KEY_GLOBAL_VPN: + return onGlobalVpnClick((Boolean) newValue); case KEY_ALWAYS_ON_VPN: return onAlwaysOnVpnClick((Boolean) newValue, mPreferenceLockdown.isChecked()); case KEY_LOCKDOWN_VPN: @@ -202,6 +215,11 @@ private boolean onAlwaysOnVpnClick(final boolean alwaysOnSetting, final boolean return setAlwaysOnVpnByUI(alwaysOnSetting, lockdown); } + private boolean onGlobalVpnClick(final boolean global) { + return Settings.Global.putString(getContext().getContentResolver(), + Settings.Global.GLOBAL_VPN_APP, global ? mPackageName : ""); + } + @Override public void onConfirmLockdown(Bundle options, boolean isEnabled, boolean isLockdown) { setAlwaysOnVpnByUI(isEnabled, isLockdown); @@ -235,7 +253,18 @@ private void updateUI() { final boolean alwaysOn = isVpnAlwaysOn(); final boolean lockdown = alwaysOn && VpnUtils.isAnyLockdownActive(getActivity()); - + final boolean anyVpnActive = isAnyVpnActive(); + final boolean globalVpn = isGlobalVpn(); + + mPreferenceGlobal.setEnabled(!anyVpnActive); + mPreferenceGlobal.setChecked(globalVpn); + if (globalVpn) { + mPreferenceGlobal.setSummary(R.string.global_vpn_summary_on); + } else if (anyVpnActive) { + mPreferenceGlobal.setSummary(R.string.global_vpn_summary_any_vpn_active); + } else { + mPreferenceGlobal.setSummary(R.string.global_vpn_summary); + } mPreferenceAlwaysOn.setChecked(alwaysOn); mPreferenceLockdown.setChecked(lockdown); updateRestrictedViews(); @@ -298,6 +327,11 @@ private boolean isVpnAlwaysOn() { return mPackageName.equals(getAlwaysOnVpnPackage()); } + private boolean isGlobalVpn() { + return mPackageName.equals(Settings.Global.getString( + getContext().getContentResolver(), Settings.Global.GLOBAL_VPN_APP)); + } + /** * @return false if the intent doesn't contain an existing package or can't retrieve activated * vpn info. @@ -352,6 +386,18 @@ private boolean isAnotherVpnActive() { return config != null && !TextUtils.equals(config.user, mPackageName); } + /** + * @return {@code true} if any VPN (VpnService or legacy) is connected or set as always-on. + */ + private boolean isAnyVpnActive() { + for (UserInfo userInfo : UserManager.get(getContext()).getUsers()) { + if (mVpnManager.getVpnConfig(userInfo.id) != null) { + return true; + } + } + return false; + } + public static class CannotConnectFragment extends InstrumentedDialogFragment { private static final String TAG = "CannotConnect"; private static final String ARG_VPN_LABEL = "label"; diff --git a/src/com/android/settings/wfd/WifiDisplaySettings.java b/src/com/android/settings/wfd/WifiDisplaySettings.java index 2ec69c43016..2575225e81c 100644 --- a/src/com/android/settings/wfd/WifiDisplaySettings.java +++ b/src/com/android/settings/wfd/WifiDisplaySettings.java @@ -348,22 +348,26 @@ public void onBindViewHolder(PreferenceViewHolder view) { super.onBindViewHolder(view); Button b = (Button) view.findViewById(R.id.left_button); - b.setText(R.string.wifi_display_pause); - b.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mDisplayManager.pauseWifiDisplay(); - } - }); + if (b != null) { + b.setText(R.string.wifi_display_pause); + b.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mDisplayManager.pauseWifiDisplay(); + } + }); + } b = (Button) view.findViewById(R.id.right_button); - b.setText(R.string.wifi_display_resume); - b.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mDisplayManager.resumeWifiDisplay(); - } - }); + if (b != null) { + b.setText(R.string.wifi_display_resume); + b.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mDisplayManager.resumeWifiDisplay(); + } + }); + } } }); mCertCategory.setLayoutResource(R.layout.two_buttons_panel); diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java index 39c77a17924..7a565d8480d 100644 --- a/src/com/android/settings/wifi/WifiConfigController.java +++ b/src/com/android/settings/wifi/WifiConfigController.java @@ -174,6 +174,7 @@ public class WifiConfigController implements TextWatcher, private String mMultipleCertSetString; private String mUseSystemCertsString; private String mDoNotProvideEapUserCertString; + private String mDoNotValidateEapServerString; private Spinner mSecuritySpinner; @VisibleForTesting Spinner mEapMethodSpinner; @@ -281,6 +282,8 @@ private void initWifiConfigController(AccessPoint accessPoint, int mode) { mUseSystemCertsString = mContext.getString(R.string.wifi_use_system_certs); mDoNotProvideEapUserCertString = mContext.getString(R.string.wifi_do_not_provide_eap_user_cert); + mDoNotValidateEapServerString = + mContext.getString(R.string.wifi_do_not_validate_eap_server); if (Flags.androidVWifiApi() && mAccessPointSecurity == WifiEntry.SECURITY_WEP) { LinearLayout wepWarningLayout = @@ -566,7 +569,8 @@ boolean isSubmittable() { // Disallow submit if the user has not selected a CA certificate for an EAP network // configuration. enabled = false; - } else if (mEapDomainView != null + } else if (!caCertSelection.equals(mDoNotValidateEapServerString) + && mEapDomainView != null && mView.findViewById(R.id.l_domain).getVisibility() != View.GONE && TextUtils.isEmpty(mEapDomainView.getText().toString())) { // Disallow submit if the user chooses to use a certificate for EAP server @@ -588,6 +592,7 @@ boolean isSubmittable() { } void showWarningMessagesIfAppropriate() { + mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.GONE); mView.findViewById(R.id.no_user_cert_warning).setVisibility(View.GONE); mView.findViewById(R.id.no_domain_warning).setVisibility(View.GONE); mView.findViewById(R.id.ssid_too_long_warning).setVisibility(View.GONE); @@ -600,7 +605,13 @@ void showWarningMessagesIfAppropriate() { } if (mEapCaCertSpinner != null && mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) { - if (mEapDomainView != null + String caCertSelection = (String) mEapCaCertSpinner.getSelectedItem(); + if (caCertSelection.equals(mDoNotValidateEapServerString)) { + // Display warning if user chooses not to validate the EAP server with a + // user-supplied CA certificate in an EAP network configuration. + mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.VISIBLE); + } else if (!caCertSelection.equals(mUnspecifiedCertString) + && mEapDomainView != null && mView.findViewById(R.id.l_domain).getVisibility() != View.GONE && TextUtils.isEmpty(mEapDomainView.getText().toString())) { // Display warning if user chooses to use a certificate without restricting the @@ -744,7 +755,8 @@ public WifiConfiguration getConfig() { config.enterpriseConfig.setCaCertificateAliases(null); config.enterpriseConfig.setCaPath(null); config.enterpriseConfig.setDomainSuffixMatch(mEapDomainView.getText().toString()); - if (caCert.equals(mUnspecifiedCertString)) { + if (caCert.equals(mUnspecifiedCertString) + || caCert.equals(mDoNotValidateEapServerString)) { // ca_cert already set to null, so do nothing. } else if (caCert.equals(mUseSystemCertsString)) { config.enterpriseConfig.setCaPath(SYSTEM_CA_STORE_PATH); @@ -778,7 +790,8 @@ public WifiConfiguration getConfig() { } // Only set OCSP option if there is a valid CA certificate. - if (caCert.equals(mUnspecifiedCertString)) { + if (caCert.equals(mUnspecifiedCertString) + || caCert.equals(mDoNotValidateEapServerString)) { config.enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_NONE); } else { config.enterpriseConfig.setOcsp(mEapOcspSpinner.getSelectedItemPosition()); @@ -1089,7 +1102,7 @@ private void showSecurityFields(boolean refreshEapMethods, boolean refreshCertif loadCertificates( mEapCaCertSpinner, androidKeystoreAliasLoader.getCaCertAliases(), - null /* noCertificateString */, + mDoNotValidateEapServerString /* noCertificateString */, false /* showMultipleCerts */, true /* showUsePreinstalledCertOption */); loadCertificates( @@ -1173,7 +1186,7 @@ private void showSecurityFields(boolean refreshEapMethods, boolean refreshCertif } else { String[] caCerts = enterpriseConfig.getCaCertificateAliases(); if (caCerts == null) { - setSelection(mEapCaCertSpinner, mUnspecifiedCertString); + setSelection(mEapCaCertSpinner, mDoNotValidateEapServerString); } else if (caCerts.length == 1) { setSelection(mEapCaCertSpinner, caCerts[0]); } else { @@ -1184,7 +1197,7 @@ private void showSecurityFields(boolean refreshEapMethods, boolean refreshCertif loadCertificates( mEapCaCertSpinner, androidKeystoreAliasLoader.getCaCertAliases(), - null /* noCertificateString */, + mDoNotValidateEapServerString /* noCertificateString */, true /* showMultipleCerts */, true /* showUsePreinstalledCertOption */); setSelection(mEapCaCertSpinner, mMultipleCertSetString); @@ -1317,7 +1330,8 @@ private void showEapFieldsByMethod(int eapMethod) { if (mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) { String eapCertSelection = (String) mEapCaCertSpinner.getSelectedItem(); - if (eapCertSelection.equals(mUnspecifiedCertString)) { + if (eapCertSelection.equals(mDoNotValidateEapServerString) + || eapCertSelection.equals(mUnspecifiedCertString)) { // Domain suffix matching is not relevant if the user hasn't chosen a CA // certificate yet, or chooses not to validate the EAP server. setDomainInvisible(); @@ -1578,8 +1592,7 @@ void loadCertificates( }).collect(Collectors.toList())); } - if (!TextUtils.isEmpty(noCertificateString) - && mAccessPointSecurity != AccessPoint.SECURITY_EAP_SUITE_B) { + if (mAccessPointSecurity != AccessPoint.SECURITY_EAP_SUITE_B) { certs.add(noCertificateString); } diff --git a/src/com/android/settings/wifi/WifiConfigController2.java b/src/com/android/settings/wifi/WifiConfigController2.java index 70e08eb9033..72374682647 100644 --- a/src/com/android/settings/wifi/WifiConfigController2.java +++ b/src/com/android/settings/wifi/WifiConfigController2.java @@ -178,6 +178,7 @@ public class WifiConfigController2 implements TextWatcher, private String mUseSystemCertsString; private String mTrustOnFirstUse; private String mDoNotProvideEapUserCertString; + private String mDoNotValidateEapServerString; @VisibleForTesting String mInstallCertsString; private Spinner mSecuritySpinner; @@ -290,6 +291,8 @@ private void initWifiConfigController2(WifiEntry wifiEntry) { mTrustOnFirstUse = mContext.getString(R.string.wifi_trust_on_first_use); mDoNotProvideEapUserCertString = mContext.getString(R.string.wifi_do_not_provide_eap_user_cert); + mDoNotValidateEapServerString = + mContext.getString(R.string.wifi_do_not_validate_eap_server); mInstallCertsString = mContext.getString(R.string.wifi_install_credentials); if (Flags.androidVWifiApi() && mWifiEntrySecurity == WifiEntry.SECURITY_WEP) { @@ -344,7 +347,7 @@ private void initWifiConfigController2(WifiEntry wifiEntry) { if (mPrivacySettingsSpinner != null) { final int prefMacValue = WifiPrivacyPreferenceController2 - .translateMacRandomizedValueToPrefValue(config.macRandomizationSetting); + .translateMacRandomizedValueToPrefValue(mWifiEntry.getPrivacy()); mPrivacySettingsSpinner.setSelection(prefMacValue); } @@ -565,7 +568,8 @@ boolean isSubmittable() { // Disallow submit if the user has not selected a CA certificate for an EAP network // configuration. enabled = false; - } else if (mEapDomainView != null + } else if (!caCertSelection.equals(mDoNotValidateEapServerString) + && mEapDomainView != null && mView.findViewById(R.id.l_domain).getVisibility() != View.GONE && TextUtils.isEmpty(mEapDomainView.getText().toString())) { // Disallow submit if the user chooses to use a certificate for EAP server @@ -587,6 +591,7 @@ boolean isSubmittable() { } void showWarningMessagesIfAppropriate() { + mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.GONE); mView.findViewById(R.id.no_user_cert_warning).setVisibility(View.GONE); mView.findViewById(R.id.no_domain_warning).setVisibility(View.GONE); mView.findViewById(R.id.ssid_too_long_warning).setVisibility(View.GONE); @@ -599,7 +604,13 @@ void showWarningMessagesIfAppropriate() { } if (mEapCaCertSpinner != null && mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) { - if (mEapDomainView != null + String caCertSelection = (String) mEapCaCertSpinner.getSelectedItem(); + if (caCertSelection.equals(mDoNotValidateEapServerString)) { + // Display warning if user chooses not to validate the EAP server with a + // user-supplied CA certificate in an EAP network configuration. + mView.findViewById(R.id.no_ca_cert_warning).setVisibility(View.VISIBLE); + } else if (!caCertSelection.equals(mUnspecifiedCertString) + && mEapDomainView != null && mView.findViewById(R.id.l_domain).getVisibility() != View.GONE && TextUtils.isEmpty(mEapDomainView.getText().toString())) { // Display warning if user chooses to use a certificate without restricting the @@ -756,7 +767,8 @@ public WifiConfiguration getConfig() { config.enterpriseConfig.setCaCertificateAliases(null); config.enterpriseConfig.setCaPath(null); config.enterpriseConfig.setDomainSuffixMatch(mEapDomainView.getText().toString()); - if (caCert.equals(mUnspecifiedCertString)) { + if (caCert.equals(mUnspecifiedCertString) + || caCert.equals(mDoNotValidateEapServerString)) { // ca_cert already set to null, so do nothing. } else if (mIsTrustOnFirstUseSupported && caCert.equals(mTrustOnFirstUse)) { config.enterpriseConfig.enableTrustOnFirstUse(true); @@ -791,7 +803,8 @@ public WifiConfiguration getConfig() { } // Only set certificate option if there is a valid CA certificate. - if (caCert.equals(mUnspecifiedCertString)) { + if (caCert.equals(mUnspecifiedCertString) + || caCert.equals(mDoNotValidateEapServerString)) { config.enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_NONE); config.enterpriseConfig.setMinimumTlsVersion(WifiEnterpriseConfig.TLS_V1_0); } else { @@ -1105,7 +1118,7 @@ protected void showSecurityFields(boolean refreshEapMethods, boolean refreshCert loadCertificates( mEapCaCertSpinner, mAndroidKeystoreAliasLoader.getCaCertAliases(), - null /* noCertificateString */, + mDoNotValidateEapServerString /* noCertificateString */, false /* showMultipleCerts */, true /* showUsePreinstalledCertOption */); loadCertificates( @@ -1189,7 +1202,7 @@ protected void showSecurityFields(boolean refreshEapMethods, boolean refreshCert && enterpriseConfig.isTrustOnFirstUseEnabled()) { setSelection(mEapCaCertSpinner, mTrustOnFirstUse); } else { - setSelection(mEapCaCertSpinner, mUnspecifiedCertString); + setSelection(mEapCaCertSpinner, mDoNotValidateEapServerString); } } else if (caCerts.length == 1) { setSelection(mEapCaCertSpinner, caCerts[0]); @@ -1198,7 +1211,7 @@ protected void showSecurityFields(boolean refreshEapMethods, boolean refreshCert loadCertificates( mEapCaCertSpinner, mAndroidKeystoreAliasLoader.getCaCertAliases(), - null /* noCertificateString */, + mDoNotValidateEapServerString /* noCertificateString */, true /* showMultipleCerts */, true /* showUsePreinstalledCertOption */); setSelection(mEapCaCertSpinner, mMultipleCertSetString); @@ -1338,7 +1351,8 @@ private void showEapFieldsByMethod(int eapMethod) { if (mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) { String eapCertSelection = (String) mEapCaCertSpinner.getSelectedItem(); - if (eapCertSelection.equals(mUnspecifiedCertString) + if (eapCertSelection.equals(mDoNotValidateEapServerString) + || eapCertSelection.equals(mUnspecifiedCertString) || (mIsTrustOnFirstUseSupported && eapCertSelection.equals(mTrustOnFirstUse))) { setMinTlsVerInvisible(); @@ -1614,8 +1628,7 @@ void loadCertificates( }).collect(Collectors.toList())); } - if (!TextUtils.isEmpty(noCertificateString) - && mWifiEntrySecurity != WifiEntry.SECURITY_EAP_SUITE_B) { + if (mWifiEntrySecurity != WifiEntry.SECURITY_EAP_SUITE_B) { certs.add(noCertificateString); } diff --git a/src/com/android/settings/wifi/WifiTimeoutPreferenceController.java b/src/com/android/settings/wifi/WifiTimeoutPreferenceController.java new file mode 100644 index 00000000000..8e42ae56fe3 --- /dev/null +++ b/src/com/android/settings/wifi/WifiTimeoutPreferenceController.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2020 The Calyx Institute + * + * 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.wifi; + +import android.content.Context; +import android.os.UserManager; +import android.net.wifi.WifiManager; +import android.provider.Settings; +import android.util.Log; + +import androidx.preference.ListPreference; +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.PreferenceControllerMixin; + +public class WifiTimeoutPreferenceController extends BasePreferenceController implements + PreferenceControllerMixin, Preference.OnPreferenceChangeListener { + private static final String TAG = "WifiTimeoutPrefCtrl"; + + public static final int FALLBACK_WIFI_TIMEOUT_VALUE = 0; + + private final String mWifiTimeoutKey; + + protected WifiManager mWifiManager; + + public WifiTimeoutPreferenceController(Context context, String key) { + super(context, key); + mWifiTimeoutKey = key; + + mWifiManager = context.getSystemService(WifiManager.class); + if (mWifiManager == null) { + Log.e(TAG, "Wifi is not supported on this device"); + return; + } + } + + @Override + public int getAvailabilityStatus() { + if (mWifiManager != null) { + return UserManager.get(mContext).isAdminUser() ? AVAILABLE : DISABLED_FOR_USER; + } + return UNSUPPORTED_ON_DEVICE; + } + + @Override + public String getPreferenceKey() { + return mWifiTimeoutKey; + } + + @Override + public void updateState(Preference preference) { + final ListPreference timeoutListPreference = (ListPreference) preference; + final long currentTimeout = Settings.Global.getLong(mContext.getContentResolver(), + Settings.Global.WIFI_OFF_TIMEOUT, FALLBACK_WIFI_TIMEOUT_VALUE); + timeoutListPreference.setValue(String.valueOf(currentTimeout)); + updateTimeoutPreferenceDescription(timeoutListPreference, + Long.parseLong(timeoutListPreference.getValue())); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + try { + long value = Long.parseLong((String) newValue); + Settings.Global.putLong(mContext.getContentResolver(), + Settings.Global.WIFI_OFF_TIMEOUT, value); + updateTimeoutPreferenceDescription((ListPreference) preference, value); + } catch (NumberFormatException e) { + Log.e(TAG, "could not persist wifi timeout setting", e); + } + return true; + } + + public static CharSequence getTimeoutDescription( + long currentTimeout, CharSequence[] entries, CharSequence[] values) { + if (currentTimeout < 0 || entries == null || values == null + || values.length != entries.length) { + return null; + } + + for (int i = 0; i < values.length; i++) { + long timeout = Long.parseLong(values[i].toString()); + if (currentTimeout == timeout) { + return entries[i]; + } + } + return null; + } + + private void updateTimeoutPreferenceDescription(ListPreference preference, + long currentTimeout) { + final CharSequence[] entries = preference.getEntries(); + final CharSequence[] values = preference.getEntryValues(); + final CharSequence timeoutDescription = getTimeoutDescription( + currentTimeout, entries, values); + String summary = ""; + if (timeoutDescription != null) { + if (currentTimeout != 0) + summary = mContext.getString(R.string.wifi_timeout_summary, timeoutDescription); + else + summary = mContext.getString(R.string.wifi_timeout_summary2); + } + preference.setSummary(summary); + } +} diff --git a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java index 3d437e22e7d..f81fe50db13 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java +++ b/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java @@ -17,7 +17,9 @@ package com.android.settings.wifi.dpp; import android.app.settings.SettingsEnums; +import android.content.ClipboardManager; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -37,6 +39,7 @@ import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.Nullable; @@ -134,6 +137,18 @@ public void onViewCreated(View view, Bundle savedInstanceState) { } else { passwordView.setText(getString(R.string.wifi_dpp_wifi_password, password)); } + + passwordView.setOnClickListener(v -> { + Toast.makeText(getContext(), R.string.longpress_to_clipboard, Toast.LENGTH_SHORT).show(); + }); + + passwordView.setOnLongClickListener(v -> { + ClipboardManager cm = (ClipboardManager) getContext() + .getSystemService(Context.CLIPBOARD_SERVICE); + cm.setText(password); + Toast.makeText(getContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show(); + return true; + }); } final Intent intent = new Intent().setComponent(getNearbySharingComponent()); diff --git a/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java b/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java index 5d73fa464f1..2a5e8c6bace 100644 --- a/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java +++ b/src/com/android/settings/wifi/p2p/WifiP2pPreferenceController.java @@ -15,6 +15,8 @@ */ package com.android.settings.wifi.p2p; +import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -92,8 +94,7 @@ public void onPause() { @Override public boolean isAvailable() { - // Always show preference. - return true; + return mContext.getPackageManager().hasSystemFeature(FEATURE_WIFI_DIRECT); } @Override public String getPreferenceKey() { diff --git a/src/org/derpfest/settings/ambient/AmbientBatterySettings.java b/src/org/derpfest/settings/ambient/AmbientBatterySettings.java new file mode 100644 index 00000000000..c5827c8613b --- /dev/null +++ b/src/org/derpfest/settings/ambient/AmbientBatterySettings.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The PixelDust Project + * SPDX-License-Identifier: Apache-2.0 + * + */ + +package org.derpfest.settings.ambient; + +import android.os.Bundle; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +@SearchIndexable +public class AmbientBatterySettings extends SettingsPreferenceFragment { + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.ambient_battery_settings); + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.ambient_battery_settings); +} diff --git a/src/org/derpfest/settings/themes/LockClockFontsPicker.java b/src/org/derpfest/settings/themes/LockClockFontsPicker.java new file mode 100644 index 00000000000..95871a1013a --- /dev/null +++ b/src/org/derpfest/settings/themes/LockClockFontsPicker.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2021 AospExtended ROM 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 org.derpfest.settings.themes; + +import static android.os.UserHandle.USER_SYSTEM; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.content.pm.PackageManager; +import android.graphics.Typeface; +import android.graphics.drawable.AnimationDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.provider.SearchIndexableResource; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Gravity; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.FrameLayout; +import android.widget.TextView; +import android.text.TextUtils; +import androidx.preference.PreferenceViewHolder; +import android.view.ViewGroup.LayoutParams; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView.ViewHolder; +import androidx.recyclerview.widget.RecyclerView; +import androidx.preference.Preference; +import androidx.preference.Preference.OnPreferenceChangeListener; +import androidx.preference.PreferenceScreen; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.Indexable; +import com.android.settings.SettingsPreferenceFragment; + +import com.bumptech.glide.Glide; + +import com.android.internal.util.derp.ThemeUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Arrays; + +import org.json.JSONObject; +import org.json.JSONException; + +public class LockClockFontsPicker extends SettingsPreferenceFragment { + + private RecyclerView mRecyclerView; + private ThemeUtils mThemeUtils; + private String mCategory = "android.theme.customization.lockscreen_clock_font"; + + private List mPkgs; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getActivity().setTitle(R.string.theme_customization_lock_clock_title); + + mThemeUtils = new ThemeUtils(getActivity()); + mPkgs = mThemeUtils.getOverlayPackagesForCategory(mCategory, "android"); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate( + R.layout.item_view, container, false); + + mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view); + GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), 1); + mRecyclerView.setLayoutManager(gridLayoutManager); + Adapter mAdapter = new Adapter(getActivity()); + mRecyclerView.setAdapter(mAdapter); + + return view; + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.DERP; + } + + @Override + public void onResume() { + super.onResume(); + } + + public class Adapter extends RecyclerView.Adapter { + Context context; + String mSelectedPkg; + String mAppliedPkg; + + public Adapter(Context context) { + this.context = context; + } + + @Override + public CustomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.lock_clock_fonts_option, parent, false); + CustomViewHolder vh = new CustomViewHolder(v); + return vh; + } + + @Override + public void onBindViewHolder(CustomViewHolder holder, final int position) { + String pkg = mPkgs.get(position); + String label = getLabel(holder.itemView.getContext(), pkg); + + String currentPackageName = mThemeUtils.getOverlayInfos(mCategory).stream() + .filter(info -> info.isEnabled()) + .map(info -> info.packageName) + .findFirst() + .orElse("android"); + + holder.title.setTextSize(28); + holder.title.setTypeface(getTypeface(holder.title.getContext(), pkg)); + holder.name.setVisibility(View.VISIBLE); + holder.name.setText("android".equals(pkg) ? "Default" : label); + + if (currentPackageName.equals(pkg)) { + mAppliedPkg = pkg; + if (mSelectedPkg == null) { + mSelectedPkg = pkg; + } + } + + holder.itemView.setActivated(pkg == mSelectedPkg); + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + updateActivatedStatus(mSelectedPkg, false); + updateActivatedStatus(pkg, true); + mSelectedPkg = pkg; + enableOverlays(position); + } + }); + } + + @Override + public int getItemCount() { + return mPkgs.size(); + } + + public class CustomViewHolder extends RecyclerView.ViewHolder { + TextView name; + TextView title; + public CustomViewHolder(View itemView) { + super(itemView); + title = (TextView) itemView.findViewById(R.id.option_title); + name = (TextView) itemView.findViewById(R.id.option_label); + } + } + + private void updateActivatedStatus(String pkg, boolean isActivated) { + int index = mPkgs.indexOf(pkg); + if (index < 0) { + return; + } + RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(index); + if (holder != null && holder.itemView != null) { + holder.itemView.setActivated(isActivated); + } + } + } + + public Typeface getTypeface(Context context, String pkg) { + try { + PackageManager pm = context.getPackageManager(); + Resources res = pkg.equals("android") ? Resources.getSystem() + : pm.getResourcesForApplication(pkg); + return Typeface.create(res.getString( + res.getIdentifier("config_clockFontFamily", + "string", pkg)), Typeface.NORMAL); + } + catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + public String getLabel(Context context, String pkg) { + PackageManager pm = context.getPackageManager(); + try { + return pm.getApplicationInfo(pkg, 0) + .loadLabel(pm).toString(); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return pkg; + } + + public void enableOverlays(int position) { + mThemeUtils.setOverlayEnabled(mCategory, mPkgs.get(position), "android"); + } +} diff --git a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderControllerTest.java deleted file mode 100644 index 4fb78fbee48..00000000000 --- a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsHeaderControllerTest.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2018 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.connecteddevice.usb; - -import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; -import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; - -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.hardware.usb.UsbManager; - -import androidx.fragment.app.FragmentActivity; -import androidx.lifecycle.LifecycleOwner; -import androidx.preference.PreferenceManager; -import androidx.preference.PreferenceScreen; - -import com.android.settings.R; -import com.android.settings.testutils.shadow.ShadowEntityHeaderController; -import com.android.settings.widget.EntityHeaderController; -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.testutils.DrawableTestHelper; -import com.android.settingslib.widget.LayoutPreference; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.ArgumentMatcher; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Config(shadows = { - ShadowEntityHeaderController.class, - com.android.settings.testutils.shadow.ShadowFragment.class, -}) -public class UsbDetailsHeaderControllerTest { - - private UsbDetailsHeaderController mDetailsHeaderController; - private Context mContext; - private Lifecycle mLifecycle; - private LifecycleOwner mLifecycleOwner; - private LayoutPreference mPreference; - private PreferenceManager mPreferenceManager; - private PreferenceScreen mScreen; - - @Mock - private UsbBackend mUsbBackend; - @Mock - private UsbDetailsFragment mFragment; - @Mock - private FragmentActivity mActivity; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private EntityHeaderController mHeaderController; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mContext = RuntimeEnvironment.application; - mLifecycleOwner = () -> mLifecycle; - mLifecycle = new Lifecycle(mLifecycleOwner); - mPreferenceManager = new PreferenceManager(mContext); - mScreen = mPreferenceManager.createPreferenceScreen(mContext); - - when(mFragment.getActivity()).thenReturn(mActivity); - when(mActivity.getApplicationContext()).thenReturn(mContext); - when(mFragment.getContext()).thenReturn(mContext); - when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager); - when(mFragment.getPreferenceScreen()).thenReturn(mScreen); - - ShadowEntityHeaderController.setUseMock(mHeaderController); - mDetailsHeaderController = new UsbDetailsHeaderController(mContext, mFragment, mUsbBackend); - mPreference = new LayoutPreference( - mContext, com.android.settingslib.widget.preference.layout.R.layout.settings_entity_header); - mPreference.setKey(mDetailsHeaderController.getPreferenceKey()); - mScreen.addPreference(mPreference); - } - - @After - public void tearDown() { - ShadowEntityHeaderController.reset(); - } - - @Test - public void displayRefresh_charging_shouldSetHeader() { - mDetailsHeaderController.displayPreference(mScreen); - mDetailsHeaderController.refresh(true, UsbManager.FUNCTION_NONE, POWER_ROLE_SINK, - DATA_ROLE_DEVICE); - verify(mHeaderController).setLabel(mContext.getString(R.string.usb_pref)); - verify(mHeaderController).setIcon(argThat((ArgumentMatcher) t -> { - DrawableTestHelper.assertDrawableResId(t, R.drawable.ic_usb); - return true; - })); - verify(mHeaderController).done(true); - } -} diff --git a/tests/robotests/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorControllerTest.java b/tests/robotests/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorControllerTest.java index d95635e6437..136a9eda5a6 100644 --- a/tests/robotests/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorControllerTest.java +++ b/tests/robotests/src/com/android/settings/display/darkmode/DarkModeScheduleSelectorControllerTest.java @@ -34,7 +34,7 @@ import android.location.LocationManager; import android.os.PowerManager; -import androidx.preference.DropDownPreference; +import androidx.preference.ListPreference; import androidx.preference.PreferenceScreen; import androidx.test.core.app.ApplicationProvider; @@ -53,7 +53,7 @@ public class DarkModeScheduleSelectorControllerTest { private DarkModeScheduleSelectorController mController; private String mPreferenceKey = "key"; @Mock - private DropDownPreference mPreference; + private ListPreference mPreference; @Mock private PreferenceScreen mScreen; private Context mContext; @@ -94,7 +94,7 @@ public void setUp() { .thenReturn("custom_bedtime"); when(mResources.getStringArray(R.array.dark_ui_scheduler_with_bedtime_preference_titles)) .thenReturn(new String[]{"never", "auto", "custom", "custom_bedtime"}); - mPreference = spy(new DropDownPreference(mContext)); + mPreference = spy(new ListPreference(mContext)); mPreference.setEntryValues(new CharSequence[]{"never", "auto", "custom"}); doNothing().when(mPreference).setValueIndex(anyInt()); when(mLocationManager.isLocationEnabled()).thenReturn(true); diff --git a/tests/robotests/src/com/android/settings/gestures/PreventRingingGesturePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/gestures/PreventRingingGesturePreferenceControllerTest.java index f537ccb729e..90539b4f1e3 100644 --- a/tests/robotests/src/com/android/settings/gestures/PreventRingingGesturePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/gestures/PreventRingingGesturePreferenceControllerTest.java @@ -57,9 +57,6 @@ public void setUp() { when(mResources.getBoolean(com.android.internal.R.bool.config_volumeHushGestureEnabled)) .thenReturn(true); mController = new PreventRingingGesturePreferenceController(mContext, null); - mController.mPreferenceCategory = new PreferenceCategory(mContext); - mController.mVibratePref = new SelectorWithWidgetPreference(mContext); - mController.mMutePref = new SelectorWithWidgetPreference(mContext); } @Test @@ -77,74 +74,4 @@ public void testIsAvailable_configIsFalse_shouldReturnFalse() { assertThat(mController.isAvailable()).isFalse(); } - - @Test - public void testUpdateState_mute() { - Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, - Settings.Secure.VOLUME_HUSH_MUTE); - mController.updateState(mPreference); - assertThat(mController.mVibratePref.isEnabled()).isTrue(); - assertThat(mController.mMutePref.isEnabled()).isTrue(); - assertThat(mController.mVibratePref.isChecked()).isFalse(); - assertThat(mController.mMutePref.isChecked()).isTrue(); - } - - @Test - public void testUpdateState_vibrate() { - Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, - Settings.Secure.VOLUME_HUSH_VIBRATE); - mController.updateState(mPreference); - assertThat(mController.mVibratePref.isEnabled()).isTrue(); - assertThat(mController.mMutePref.isEnabled()).isTrue(); - assertThat(mController.mVibratePref.isChecked()).isTrue(); - assertThat(mController.mMutePref.isChecked()).isFalse(); - } - - @Test - public void testUpdateState_off() { - Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, - Settings.Secure.VOLUME_HUSH_OFF); - mController.updateState(mPreference); - assertThat(mController.mVibratePref.isEnabled()).isFalse(); - assertThat(mController.mMutePref.isEnabled()).isFalse(); - assertThat(mController.mVibratePref.isChecked()).isFalse(); - assertThat(mController.mMutePref.isChecked()).isFalse(); - } - - @Test - public void testUpdateState_other() { - Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, - 7); - mController.updateState(mPreference); - assertThat(mController.mVibratePref.isChecked()).isFalse(); - assertThat(mController.mMutePref.isChecked()).isFalse(); - } - - @Test - public void testRadioButtonClicked_mute() { - SelectorWithWidgetPreference rbPref = new SelectorWithWidgetPreference(mContext); - rbPref.setKey(PreventRingingGesturePreferenceController.KEY_MUTE); - - Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, - Settings.Secure.VOLUME_HUSH_OFF); - mController.onRadioButtonClicked(rbPref); - - assertThat(Settings.Secure.VOLUME_HUSH_MUTE).isEqualTo( - Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_OFF)); - } - - @Test - public void testRadioButtonClicked_vibrate() { - SelectorWithWidgetPreference rbPref = new SelectorWithWidgetPreference(mContext); - rbPref.setKey(PreventRingingGesturePreferenceController.KEY_VIBRATE); - - Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.VOLUME_HUSH_GESTURE, - Settings.Secure.VOLUME_HUSH_OFF); - mController.onRadioButtonClicked(rbPref); - - assertThat(Settings.Secure.VOLUME_HUSH_VIBRATE).isEqualTo( - Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.VOLUME_HUSH_GESTURE, Settings.Secure.VOLUME_HUSH_OFF)); - } } diff --git a/tests/robotests/src/com/android/settings/notification/PhoneRingtone2PreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/PhoneRingtone2PreferenceControllerTest.java new file mode 100644 index 00000000000..87fa6a5038c --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/PhoneRingtone2PreferenceControllerTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2018 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.notification; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.media.RingtoneManager; +import android.telephony.TelephonyManager; + +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.DefaultRingtonePreference; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +public class PhoneRingtone2PreferenceControllerTest { + + @Mock + private TelephonyManager mTelephonyManager; + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private DefaultRingtonePreference mPreference; + + private Context mContext; + private PhoneRingtone2PreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowContext = ShadowApplication.getInstance(); + shadowContext.setSystemService(Context.TELEPHONY_SERVICE, mTelephonyManager); + mContext = RuntimeEnvironment.application; + mController = new PhoneRingtone2PreferenceController(mContext); + } + + @Test + public void displayPreference_shouldSetSlotId() { + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())) + .thenReturn(mPreference); + mController.displayPreference(mPreferenceScreen); + + verify(mPreference).setSlotId(1); + } + + @Test + public void isAvailable_notVoiceCapable_shouldReturnFalse() { + when(mTelephonyManager.isVoiceCapable()).thenReturn(false); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_notMultiSimEnabled_shouldReturnFalse() { + when(mTelephonyManager.isMultiSimEnabled()).thenReturn(false); + + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void isAvailable_VoiceCapable_and_MultiSimEnabled_shouldReturnTrue() { + when(mTelephonyManager.isVoiceCapable()).thenReturn(true); + when(mTelephonyManager.isMultiSimEnabled()).thenReturn(true); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void getRingtoneType_shouldReturnRingtone() { + assertThat(mController.getRingtoneType()).isEqualTo(RingtoneManager.TYPE_RINGTONE); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/PhoneRingtonePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/PhoneRingtonePreferenceControllerTest.java index 24e845851a4..3b9894203e6 100644 --- a/tests/robotests/src/com/android/settings/notification/PhoneRingtonePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/PhoneRingtonePreferenceControllerTest.java @@ -18,12 +18,17 @@ import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; import android.media.RingtoneManager; import android.telephony.TelephonyManager; +import androidx.preference.PreferenceScreen; + +import com.android.settings.DefaultRingtonePreference; +import com.android.settings.R; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,6 +43,10 @@ public class PhoneRingtonePreferenceControllerTest { @Mock private TelephonyManager mTelephonyManager; + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private DefaultRingtonePreference mPreference; private Context mContext; private PhoneRingtonePreferenceController mController; @@ -51,6 +60,16 @@ public void setUp() { mController = new PhoneRingtonePreferenceController(mContext); } + @Test + public void displayPreference_shouldUpdateTitle_for_MultiSimDevice() { + when(mTelephonyManager.isMultiSimEnabled()).thenReturn(true); + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())) + .thenReturn(mPreference); + mController.displayPreference(mPreferenceScreen); + + verify(mPreference).setTitle(mContext.getString(R.string.ringtone1_title)); + + @Test public void isAvailable_notVoiceCapable_shouldReturnFalse() { when(mTelephonyManager.isVoiceCapable()).thenReturn(false); diff --git a/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java index c63fa60437b..58b3ea918bb 100644 --- a/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java +++ b/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java @@ -22,7 +22,7 @@ import android.content.Context; -import androidx.preference.DropDownPreference; +import androidx.preference.ListPreference; import com.android.settings.R; import com.android.wifitrackerlib.WifiEntry; @@ -47,7 +47,7 @@ public class WifiMeteredPreferenceController2Test { private WifiMeteredPreferenceController2 mPreferenceController; private Context mContext; - private DropDownPreference mDropDownPreference; + private ListPreference mListPreference; @Before public void setUp() { @@ -56,35 +56,35 @@ public void setUp() { mPreferenceController = spy( new WifiMeteredPreferenceController2(mContext, mWifiEntry)); - mDropDownPreference = new DropDownPreference(mContext); - mDropDownPreference.setEntries(R.array.wifi_metered_entries); - mDropDownPreference.setEntryValues(R.array.wifi_metered_values); + mListPreference = new ListPreference(mContext); + mListPreference.setEntries(R.array.wifi_metered_entries); + mListPreference.setEntryValues(R.array.wifi_metered_values); } @Test public void testUpdateState_wifiMetered_setCorrectValue() { doReturn(METERED_OVERRIDE_METERED).when(mPreferenceController).getMeteredOverride(); - mPreferenceController.updateState(mDropDownPreference); + mPreferenceController.updateState(mListPreference); - assertThat(mDropDownPreference.getEntry()).isEqualTo("Treat as metered"); + assertThat(mListPreference.getEntry()).isEqualTo("Treat as metered"); } @Test public void testUpdateState_wifiNotMetered_setCorrectValue() { doReturn(METERED_OVERRIDE_NOT_METERED).when(mPreferenceController).getMeteredOverride(); - mPreferenceController.updateState(mDropDownPreference); + mPreferenceController.updateState(mListPreference); - assertThat(mDropDownPreference.getEntry()).isEqualTo("Treat as unmetered"); + assertThat(mListPreference.getEntry()).isEqualTo("Treat as unmetered"); } @Test public void testUpdateState_wifiAuto_setCorrectValue() { doReturn(METERED_OVERRIDE_NONE).when(mPreferenceController).getMeteredOverride(); - mPreferenceController.updateState(mDropDownPreference); + mPreferenceController.updateState(mListPreference); - assertThat(mDropDownPreference.getEntry()).isEqualTo("Detect automatically"); + assertThat(mListPreference.getEntry()).isEqualTo("Detect automatically"); } } diff --git a/tests/uitests/src/com/android/settings/ui/SoundSettingsTest.java b/tests/uitests/src/com/android/settings/ui/SoundSettingsTest.java index f981f7fe73e..72d4a1140d7 100644 --- a/tests/uitests/src/com/android/settings/ui/SoundSettingsTest.java +++ b/tests/uitests/src/com/android/settings/ui/SoundSettingsTest.java @@ -21,6 +21,7 @@ import android.provider.Settings; import android.system.helpers.SettingsHelper; import android.system.helpers.SettingsHelper.SettingsType; +import android.telephony.TelephonyManager; import android.test.InstrumentationTestCase; import androidx.test.filters.MediumTest; @@ -42,6 +43,7 @@ public class SoundSettingsTest extends InstrumentationTestCase { private UiDevice mDevice; private ContentResolver mResolver; private SettingsHelper mHelper; + private TelephonyManager mTelephonyManager; private final Map ringtoneSounds = Map.of( @@ -100,6 +102,8 @@ public void setUp() throws Exception { mDevice.setOrientationNatural(); mResolver = getInstrumentation().getContext().getContentResolver(); mHelper = new SettingsHelper(); + mTelephonyManager = (TelephonyManager) getInstrumentation().getContext() + .getSystemService(Context.TELEPHONY_SERVICE); } @Override @@ -174,26 +178,49 @@ private void launchSoundSettings() throws Exception { public void testPhoneRingtoneNone() throws Exception { launchSoundSettings(); mHelper.clickSetting("Phone ringtone"); - verifyRingtone(new RingtoneSetting("None", "null"), - Settings.System.RINGTONE); + if (mTelephonyManager.isMultiSimEnabled()) { + mHelper.clickSetting("Phone ringtone - SIM 1"); + verifyRingtone(new RingtoneSetting("None", "null"), Settings.System.RINGTONE); + mHelper.clickSetting("Phone ringtone - SIM 2"); + verifyRingtone(new RingtoneSetting("None", "null"), Settings.System.RINGTONE2); + } else { + mHelper.clickSetting("Phone ringtone"); + verifyRingtone(new RingtoneSetting("None", "null"), Settings.System.RINGTONE); + } } @MediumTest @Suppress public void testPhoneRingtoneHangouts() throws Exception { launchSoundSettings(); - mHelper.clickSetting("Phone ringtone"); - verifyRingtone(new RingtoneSetting("Hangouts Call", "31"), Settings.System.RINGTONE); + if (mTelephonyManager.isMultiSimEnabled()) { + mHelper.clickSetting("Phone ringtone - SIM 1"); + verifyRingtone(new RingtoneSetting("Hangouts Call", "31"), Settings.System.RINGTONE); + mHelper.clickSetting("Phone ringtone - SIM 2"); + verifyRingtone(new RingtoneSetting("Hangouts Call", "31"), Settings.System.RINGTONE2); + } else { + mHelper.clickSetting("Phone ringtone"); + verifyRingtone(new RingtoneSetting("Hangouts Call", "31"), Settings.System.RINGTONE); + } } @MediumTest public void testPhoneRingtone() throws Exception { launchSoundSettings(); - mHelper.clickSetting("Phone ringtone"); String ringtone = ringtoneSounds.get(mDevice.getProductName()).toString(); String ringtoneSettingValue = ringtoneCodes.get(mDevice.getProductName()).toString(); - verifyRingtone(new RingtoneSetting(ringtone, ringtoneSettingValue), - Settings.System.RINGTONE); + if (mTelephonyManager.isMultiSimEnabled()) { + mHelper.clickSetting("Phone ringtone - SIM 1"); + verifyRingtone(new RingtoneSetting(ringtone, ringtoneSettingValue), + Settings.System.RINGTONE); + mHelper.clickSetting("Phone ringtone - SIM 2"); + verifyRingtone(new RingtoneSetting(ringtone, ringtoneSettingValue), + Settings.System.RINGTONE2); + } else { + mHelper.clickSetting("Phone ringtone"); + verifyRingtone(new RingtoneSetting(ringtone, ringtoneSettingValue), + Settings.System.RINGTONE); + } } @MediumTest diff --git a/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java b/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java index 9a5399c5f07..5f02b04f7e0 100644 --- a/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/deviceinfo/PhoneNumberPreferenceControllerTest.java @@ -37,6 +37,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.settings.core.BasePreferenceController; import com.android.settings.testutils.ResourcesUtils; import org.junit.Before; @@ -88,9 +89,26 @@ public void setup() { mCategory.setKey(categoryKey); mScreen.addPreference(mCategory); + doReturn(mSubscriptionInfo).when(mController).getSubscriptionInfo(anyInt()); doReturn(mSecondPreference).when(mController).createNewPreference(mContext); } + @Test + public void getAvailabilityStatus_isVoiceCapable_shouldBeAVAILABLE() { + when(mTelephonyManager.isVoiceCapable()).thenReturn(true); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.AVAILABLE); + } + + @Test + public void getAvailabilityStatus_isNotVoiceCapable_shouldBeUNSUPPORTED_ON_DEVICE() { + when(mTelephonyManager.isVoiceCapable()).thenReturn(false); + + assertThat(mController.getAvailabilityStatus()).isEqualTo( + BasePreferenceController.UNSUPPORTED_ON_DEVICE); + } + @Test public void displayPreference_multiSim_shouldAddSecondPreference() { when(mTelephonyManager.getPhoneCount()).thenReturn(2); @@ -105,7 +123,6 @@ public void displayPreference_multiSim_shouldAddSecondPreference() { @Test public void updateState_singleSim_shouldUpdateTitleAndPhoneNumber() { final String phoneNumber = "1111111111"; - doReturn(mSubscriptionInfo).when(mController).getSubscriptionInfo(anyInt()); doReturn(phoneNumber).when(mController).getFormattedPhoneNumber(mSubscriptionInfo); when(mTelephonyManager.getPhoneCount()).thenReturn(1); mController.displayPreference(mScreen); @@ -119,7 +136,6 @@ public void updateState_singleSim_shouldUpdateTitleAndPhoneNumber() { @Test public void updateState_multiSim_shouldUpdateTitleAndPhoneNumberOfMultiplePreferences() { final String phoneNumber = "1111111111"; - doReturn(mSubscriptionInfo).when(mController).getSubscriptionInfo(anyInt()); doReturn(phoneNumber).when(mController).getFormattedPhoneNumber(mSubscriptionInfo); when(mTelephonyManager.getPhoneCount()).thenReturn(2); mController.displayPreference(mScreen); @@ -137,11 +153,11 @@ public void updateState_multiSim_shouldUpdateTitleAndPhoneNumberOfMultiplePrefer @Test public void getSummary_cannotGetActiveSubscriptionInfo_shouldShowUnknown() { when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(null); - mController.displayPreference(mScreen); - mController.updateState(mPreference); + CharSequence primaryNumber = mController.getSummary(); - verify(mPreference).setSummary(ResourcesUtils.getResourcesString( + assertThat(primaryNumber).isNotNull(); + assertThat(primaryNumber).isEqualTo(ResourcesUtils.getResourcesString( mContext, "device_info_default")); } @@ -150,10 +166,9 @@ public void getSummary_getEmptySubscriptionInfo_shouldShowUnknown() { List infos = new ArrayList<>(); when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(infos); - mController.displayPreference(mScreen); - mController.updateState(mPreference); + CharSequence primaryNumber = mController.getSummary(); - verify(mPreference).setSummary(ResourcesUtils.getResourcesString( + assertThat(primaryNumber).isEqualTo(ResourcesUtils.getResourcesString( mContext, "device_info_default")); } }