Merge "Fix restriction to configure Calls & SMS" into main
diff --git a/Android.bp b/Android.bp
index 5a1224c..2699c38 100644
--- a/Android.bp
+++ b/Android.bp
@@ -76,16 +76,20 @@
"android.hardware.dumpstate-V1.0-java",
"android.hardware.dumpstate-V1.1-java",
"android.nfc.flags-aconfig-java",
+ "android.view.accessibility.flags-aconfig-java",
+ "com_android_server_accessibility_flags_lib",
"net-utils-framework-common",
"notification_flags_lib",
"securebox",
// Settings dependencies
"FingerprintManagerInteractor",
+ "MediaDrmSettingsFlagsLib",
"Settings-change-ids",
"SettingsLib",
"SettingsLibActivityEmbedding",
"aconfig_settings_flags_lib",
+ "accessibility_settings_flags_lib",
"app-usage-event-protos-lite",
"battery-event-protos-lite",
"battery-usage-slot-protos-lite",
@@ -99,7 +103,6 @@
"settings-logtags",
"settings-telephony-protos-lite",
"statslog-settings",
- "com_android_server_accessibility_flags_lib",
],
plugins: ["androidx.room_room-compiler-plugin"],
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a08bda3..75c6fbb 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4943,6 +4943,16 @@
</activity>
<activity
+ android:name="com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode.QrCodeScanModeActivity"
+ android:permission="android.permission.BLUETOOTH_CONNECT"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.settings.BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+
+ <activity
android:name=".spa.SpaActivity"
android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize"
android:knownActivityEmbeddingCerts="@array/config_known_host_certs"
diff --git a/aconfig/Android.bp b/aconfig/Android.bp
index eb9a6b4..de6d0af 100644
--- a/aconfig/Android.bp
+++ b/aconfig/Android.bp
@@ -25,3 +25,25 @@
name: "factory_reset_flags_lib",
aconfig_declarations: "factory_reset_flags",
}
+
+aconfig_declarations {
+ name: "media_drm_flags",
+ package: "com.android.settings.media_drm",
+ srcs: ["media_drm/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "MediaDrmSettingsFlagsLib",
+ aconfig_declarations: "media_drm_flags",
+}
+
+aconfig_declarations {
+ name: "accessibility_flags",
+ package: "com.android.settings.accessibility",
+ srcs: ["accessibility/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "accessibility_settings_flags_lib",
+ aconfig_declarations: "accessibility_flags",
+}
diff --git a/aconfig/accessibility/OWNERS b/aconfig/accessibility/OWNERS
new file mode 100644
index 0000000..7a76c21
--- /dev/null
+++ b/aconfig/accessibility/OWNERS
@@ -0,0 +1 @@
+include /src/com/android/settings/accessibility/OWNERS
diff --git a/aconfig/accessibility/accessibility_flags.aconfig b/aconfig/accessibility/accessibility_flags.aconfig
new file mode 100644
index 0000000..7f1f909
--- /dev/null
+++ b/aconfig/accessibility/accessibility_flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.settings.accessibility"
+
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+
+flag {
+ name: "remove_qs_tooltip_in_suw"
+ namespace: "accessibility"
+ description: "Don't show quick settings tooltip in SUW, since the user can't use quick settings there."
+ bug: "294560581"
+}
\ No newline at end of file
diff --git a/aconfig/media_drm/settings_mediadrm_flag_declarations.aconfig b/aconfig/media_drm/settings_mediadrm_flag_declarations.aconfig
new file mode 100644
index 0000000..06d75f1
--- /dev/null
+++ b/aconfig/media_drm/settings_mediadrm_flag_declarations.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.settings.media_drm"
+
+flag {
+ name: "force_l3_enabled"
+ namespace: "media_drm"
+ description: "Feature flag of forcing L3"
+ bug: "301669353"
+}
\ No newline at end of file
diff --git a/aconfig/settings_accessibility_flag_declarations.aconfig b/aconfig/settings_accessibility_flag_declarations_legacy.aconfig
similarity index 86%
rename from aconfig/settings_accessibility_flag_declarations.aconfig
rename to aconfig/settings_accessibility_flag_declarations_legacy.aconfig
index 246f983..acdce96 100644
--- a/aconfig/settings_accessibility_flag_declarations.aconfig
+++ b/aconfig/settings_accessibility_flag_declarations_legacy.aconfig
@@ -1,5 +1,8 @@
package: "com.android.settings.flags"
+# NOTE: Don't add new accessibility flags here, since the package name doesn't follow
+# the best practice for setting's feature flag go/settings-trunk-stable
+
# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
# NOTE: All Settings flags share the same Flags class, so prefix our
diff --git a/res/layout/content_protection_preference_fragment.xml b/res/layout/content_protection_preference_fragment.xml
index 4c7352e..8bf6582 100644
--- a/res/layout/content_protection_preference_fragment.xml
+++ b/res/layout/content_protection_preference_fragment.xml
@@ -17,6 +17,7 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:key="content_protection_preference_subpage"
android:title="@string/content_protection_preference_title">
<com.android.settingslib.widget.TopIntroPreference
diff --git a/res/layout/dialog_audio_sharing_join.xml b/res/layout/dialog_audio_sharing_join.xml
new file mode 100644
index 0000000..42d964a
--- /dev/null
+++ b/res/layout/dialog_audio_sharing_join.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="24dp"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/share_audio_subtitle1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="center"
+ android:layout_gravity="center"/>
+
+ <TextView
+ android:id="@+id/share_audio_subtitle2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="center"
+ android:layout_gravity="center"/>
+
+ <Button
+ android:id="@+id/share_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text=""/>
+
+ <Button
+ android:id="@+id/cancel_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/cancel"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/vpn_dialog.xml b/res/layout/vpn_dialog.xml
index 892a176..062772e 100644
--- a/res/layout/vpn_dialog.xml
+++ b/res/layout/vpn_dialog.xml
@@ -66,25 +66,6 @@
<EditText style="@style/vpn_value"
android:id="@+id/server"/>
- <CheckBox style="@style/vpn_value"
- android:id="@+id/mppe"
- android:text="@string/vpn_mppe"
- android:visibility="gone"/>
-
- <LinearLayout android:id="@+id/l2tp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:visibility="gone">
- <TextView style="@style/vpn_label"
- android:text="@string/vpn_l2tp_secret"
- android:labelFor="@+id/l2tp_secret"/>
- <EditText style="@style/vpn_value"
- android:id="@+id/l2tp_secret"
- android:password="true"
- android:hint="@string/vpn_not_used"/>
- </LinearLayout>
-
<LinearLayout android:id="@+id/options_ipsec_identity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -154,31 +135,6 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
- <LinearLayout android:id="@+id/network_options"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
- <TextView style="@style/vpn_label"
- android:text="@string/vpn_search_domains"
- android:labelFor="@+id/search_domains"/>
- <EditText style="@style/vpn_value"
- android:id="@+id/search_domains"
- android:hint="@string/vpn_not_used"/>
-
- <TextView style="@style/vpn_label"
- android:text="@string/vpn_dns_servers"
- android:labelFor="@+id/dns_servers"/>
- <EditText style="@style/vpn_value"
- android:id="@+id/dns_servers"
- android:hint="@string/vpn_not_used"/>
-
- <TextView style="@style/vpn_label"
- android:text="@string/vpn_routes"
- android:labelFor="@+id/routes"/>
- <EditText style="@style/vpn_value"
- android:id="@+id/routes"
- android:hint="@string/vpn_not_used"/>
- </LinearLayout>
<TextView android:id="@+id/vpn_proxy_settings_title"
style="@style/vpn_label"
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 7283be2..f84afec 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -872,15 +872,9 @@
<item>1</item>
</string-array>
- <!-- Match this with the constants in VpnProfile. --> <skip />
+ <!-- Match this with the array VPN_TYPES in ConfigDialog. --> <skip />
<!-- Short names for each VPN type, not really translatable. [CHAR LIMIT=20] -->
<string-array name="vpn_types" translatable="false">
- <item>PPTP</item>
- <item>L2TP/IPSec PSK</item>
- <item>L2TP/IPSec RSA</item>
- <item>IPSec Xauth PSK</item>
- <item>IPSec Xauth RSA</item>
- <item>IPSec Hybrid RSA</item>
<item>IKEv2/IPSec MSCHAPv2</item>
<item>IKEv2/IPSec PSK</item>
<item>IKEv2/IPSec RSA</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 40c0605..e4e5750 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -296,13 +296,15 @@
<string name="calls_and_alarms_device_title">Calls and alarms</string>
<!-- Title for audio streams preference category [CHAR LIMIT=none]-->
- <string name="audio_sharing_streams_category_title">Connect to a LE audio stream</string>
+ <string name="audio_streams_category_title">Connect to a LE audio stream</string>
<!-- Title for audio streams preference [CHAR LIMIT=none]-->
- <string name="audio_sharing_streams_pref_title">Nearby audio streams</string>
+ <string name="audio_streams_pref_title">Nearby audio streams</string>
<!-- Title for audio streams page [CHAR LIMIT=none]-->
- <string name="audio_sharing_streams_title">Audio streams</string>
+ <string name="audio_streams_title">Audio streams</string>
<!-- Summary for QR code scanning in audio streams page [CHAR LIMIT=none]-->
- <string name="audio_sharing_streams_qr_code_summary">Connect to an audio stream using QR code</string>
+ <string name="audio_streams_qr_code_summary">Connect to an audio stream using QR code</string>
+ <!--Text that appears when scanning for nearby audio streams is finished and no streams were found [CHAR LIMIT=40]-->
+ <string name="audio_streams_empty">No nearby audio streams were found.</string>
<!-- Date & time settings screen title -->
<string name="date_and_time">Date & time</string>
@@ -1206,8 +1208,22 @@
<string name="private_space_title">Private Space</string>
<!-- Summary for the Private Space page. [CHAR LIMIT=NONE] -->
<string name="private_space_summary">Hide apps in a private folder</string>
+ <!-- Description for the Private Space page. [CHAR LIMIT=NONE] -->
+ <string name="private_space_description">Hide apps in a private folder that only you can access</string>
<!-- Title for the Private Space one lock preference. [CHAR LIMIT=60] -->
- <string name="private_space_one_lock_title">Unlock using screen lock</string>
+ <string name="private_space_lock_title">Private Space lock</string>
+ <!-- Description for the Private Space one lock preference page. [CHAR LIMIT=NONE] -->
+ <string name="private_space_one_lock_summary">You can unlock Private Space the same way you unlock your device, or choose a different lock</string>
+ <!-- Title for the Private Space one lock preference. [CHAR LIMIT=60] -->
+ <string name="private_space_screen_lock_title">Use device screen lock</string>
+ <!-- Title for the Face and Fingerprint preference. [CHAR LIMIT=60] -->
+ <string name="private_space_biometric_title">Face & Fingerprint Unlock</string>
+ <!-- Summary for the Face and Fingerprint preference when no biometric is set. [CHAR LIMIT=60] -->
+ <string name="private_space_biometric_summary">Tap to set up</string>
+ <!-- Summary for one lock when device screen lock is used as private profile lock. [CHAR LIMIT=60] -->
+ <string name="private_space_screen_lock_summary">Same as device screen lock</string>
+ <!-- Dialog message to choose a new lock for Private Space. [CHAR LIMIT=50] -->
+ <string name="private_space_new_lock_title">Choose a new lock for Private Space?</string>
<!-- Title for the preference to hide Private Space. [CHAR LIMIT=60] -->
<string name="private_space_hide_title">Hide when locked</string>
<!-- Title for the hide Private Space setting. [CHAR LIMIT=60] -->
@@ -1727,7 +1743,7 @@
<string name="bluetooth_device_context_disconnect">Disconnect</string>
<!-- Bluetooth settings. Context menu item for a device. Action will first pair, and then connect to all profiles on the device. -->
<string name="bluetooth_device_context_pair_connect">Pair & connect</string>
- <!-- Bluetooth settings. Text displayed when Bluetooth is off and device list is empty [CHAR LIMIT=50]-->
+ <!-- Bluetooth settings. Text displayed when Bluetooth is off and device list is empty [CHAR LIMIT=NONE]-->
<string name="bluetooth_empty_list_bluetooth_off">When Bluetooth is turned on, your device can communicate with other nearby Bluetooth devices</string>
<!-- Bluetooth settings. Text displayed when Bluetooth is off and bluetooth scanning is turned on [CHAR LIMIT=NONE] -->
<string name="bluetooth_scanning_on_info_message">When Bluetooth is turned on, your device can communicate with other nearby Bluetooth devices.\n\nTo improve device experience, apps and services can still scan for nearby devices at any time, even when Bluetooth is off. This can be used, for example, to improve location-based features and services. You can change this in Bluetooth scanning settings.</string>
@@ -6424,10 +6440,6 @@
<string name="vpn_type">Type</string>
<!-- Input label for the server address of a VPN profile. [CHAR LIMIT=40] -->
<string name="vpn_server">Server address</string>
- <!-- Checkbox label to enable PPP encryption for a VPN profile. [CHAR LIMIT=40] -->
- <string name="vpn_mppe">PPP encryption (MPPE)</string>
- <!-- Input label for the L2TP secret of a VPN profile. [CHAR LIMIT=40] -->
- <string name="vpn_l2tp_secret">L2TP secret</string>
<!-- Input label for the IPSec identifier of a VPN profile. [CHAR LIMIT=40] -->
<string name="vpn_ipsec_identifier">IPSec identifier</string>
<!-- Input label for the IPSec pre-shared key of a VPN profile. [CHAR LIMIT=40] -->
@@ -6440,12 +6452,6 @@
<string name="vpn_ipsec_server_cert">IPSec server certificate</string>
<!-- Checkbox label to show advanced options of a VPN profile. [CHAR LIMIT=40] -->
<string name="vpn_show_options">Show advanced options</string>
- <!-- Input label for the DNS search domains of a VPN profile. [CHAR LIMIT=40] -->
- <string name="vpn_search_domains">DNS search domains</string>
- <!-- Input label for the DNS servers of a VPN profile. [CHAR LIMIT=40] -->
- <string name="vpn_dns_servers">DNS servers (e.g. 8.8.8.8)</string>
- <!-- Input label for the forwarding routes of a VPN profile. [CHAR LIMIT=40] -->
- <string name="vpn_routes">Forwarding routes (e.g. 10.0.0.0/8)</string>
<!-- Input label for the username of a VPN profile. [CHAR LIMIT=40] -->
<string name="vpn_username">Username</string>
<!-- Input label for the password of a VPN profile. [CHAR LIMIT=40] -->
@@ -6459,22 +6465,6 @@
<!-- Option to use the server certificate received from the VPN server. [CHAR LIMIT=40] -->
<string name="vpn_no_server_cert">(received from server)</string>
<!-- Error message displayed below the always-on VPN checkbox when the checkbox is disabled:
- the selected VPN type doesn't support always-on. [CHAR LIMIT=120] -->
- <string name="vpn_always_on_invalid_reason_type">This VPN type can\'t stay connected at all
- times</string>
- <!-- Error message displayed below the always-on VPN checkbox when the checkbox is disabled:
- the server address is not in numeric form (e.g. 8.8.8.8). [CHAR LIMIT=120] -->
- <string name="vpn_always_on_invalid_reason_server">Always-on VPN only supports numeric server
- addresses</string>
- <!-- Error message displayed below the always-on VPN checkbox when the checkbox is disabled:
- no DNS is found. [CHAR LIMIT=120] -->
- <string name="vpn_always_on_invalid_reason_no_dns">A DNS server must be specified for always-on
- VPN</string>
- <!-- Error message displayed below the always-on VPN checkbox when the checkbox is disabled:
- DNS server addresses are not in numeric form (e.g. 8.8.8.8). [CHAR LIMIT=120] -->
- <string name="vpn_always_on_invalid_reason_dns">DNS server addresses must be numeric for
- always-on VPN</string>
- <!-- Error message displayed below the always-on VPN checkbox when the checkbox is disabled:
generic error. [CHAR LIMIT=120] -->
<string name="vpn_always_on_invalid_reason_other">The information entered doesn\'t support
always-on VPN</string>
diff --git a/res/xml/bluetooth_audio_sharing.xml b/res/xml/bluetooth_audio_sharing.xml
index ca7137a..681c768 100644
--- a/res/xml/bluetooth_audio_sharing.xml
+++ b/res/xml/bluetooth_audio_sharing.xml
@@ -34,13 +34,13 @@
<PreferenceCategory
android:key="audio_streams_settings_category"
- android:title="@string/audio_sharing_streams_category_title"
- settings:controller="com.android.settings.connecteddevice.audiosharing.AudioStreamsCategoryController" >
+ android:title="@string/audio_streams_category_title"
+ settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsCategoryController">
<Preference
android:key="audio_streams_settings"
- android:fragment="com.android.settings.connecteddevice.audiosharing.AudioStreamsDashboardFragment"
- android:title="@string/audio_sharing_streams_pref_title"
+ android:fragment="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsDashboardFragment"
+ android:title="@string/audio_streams_pref_title"
android:icon="@drawable/ic_chevron_right_24dp" />
</PreferenceCategory>
diff --git a/res/xml/bluetooth_audio_streams.xml b/res/xml/bluetooth_audio_streams.xml
index 9d05a06..ce7374b 100644
--- a/res/xml/bluetooth_audio_streams.xml
+++ b/res/xml/bluetooth_audio_streams.xml
@@ -17,12 +17,17 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
- android:title="@string/audio_sharing_streams_title">
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/audio_streams_title">
<Preference
android:key="audio_streams_scan_qr_code"
android:title="@string/bluetooth_find_broadcast_button_scan"
android:icon="@drawable/ic_add_24dp"
- android:summary="@string/audio_sharing_streams_qr_code_summary"/>
+ android:summary="@string/audio_streams_qr_code_summary" />
+
+ <com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryPreference
+ android:key="audio_streams_nearby_category"
+ android:title="@string/audio_streams_pref_title" />
</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index f890984..f0a2881 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -619,6 +619,11 @@
android:title="@string/transcode_settings_title"
android:fragment="com.android.settings.development.transcode.TranscodeSettingsFragment" />
+ <Preference
+ android:key="widevine_settings"
+ android:title="@string/widevine_settings_title"
+ android:fragment="com.android.settings.development.widevine.WidevineSettingsFragment" />
+
</PreferenceCategory>
<PreferenceCategory
diff --git a/res/xml/private_space_settings.xml b/res/xml/private_space_settings.xml
index eb9fdae..48835fc 100644
--- a/res/xml/private_space_settings.xml
+++ b/res/xml/private_space_settings.xml
@@ -22,13 +22,25 @@
android:title="@string/private_space_title"
settings:searchable="false">
+ <com.android.settingslib.widget.IllustrationPreference
+ android:key="privatespace_hide_video"
+ settings:searchable="false"
+ settings:lottie_rawRes="@drawable/privatespace_placeholder_image"/>
+
+ <Preference
+ android:key="private_space_description"
+ android:summary="@string/private_space_description"
+ android:selectable="false"
+ settings:searchable="false" />
+
<PreferenceCategory
android:title="@string/security_header">
- <SwitchPreferenceCompat
+ <Preference
android:key="private_space_use_one_lock"
- android:title="@string/private_space_one_lock_title"
- settings:controller="com.android.settings.privatespace.UseOneLockController"
+ android:title="@string/private_space_lock_title"
+ android:fragment="com.android.settings.privatespace.onelock.UseOneLockSettingsFragment"
+ settings:controller="com.android.settings.privatespace.onelock.UseOneLockController"
settings:searchable="false" />
<Preference
@@ -51,4 +63,4 @@
</PreferenceCategory>
-</PreferenceScreen>
\ No newline at end of file
+</PreferenceScreen>
diff --git a/res/xml/privatespace_one_lock.xml b/res/xml/privatespace_one_lock.xml
new file mode 100644
index 0000000..e078c17
--- /dev/null
+++ b/res/xml/privatespace_one_lock.xml
@@ -0,0 +1,44 @@
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/private_space_lock_title"
+ settings:searchable="false" >
+
+ <com.android.settingslib.widget.TopIntroPreference
+ android:title="@string/private_space_one_lock_summary"
+ settings:searchable="false" />
+
+ <com.android.settingslib.widget.MainSwitchPreference
+ android:key="private_lock_unification"
+ android:title="@string/private_space_screen_lock_title"
+ settings:searchable="false" />
+
+ <Preference
+ android:key="change_private_space_lock"
+ android:title="@string/private_space_lock_title"
+ android:summary="@string/unlock_set_unlock_mode_pattern"
+ settings:searchable="false" />
+
+ <Preference
+ android:key="private_space_biometrics"
+ android:title="@string/private_space_biometric_title"
+ android:summary="@string/private_space_biometric_summary"
+ android:fragment="com.android.settings.privatespace.onelock.FaceFingerprintUnlockFragment"
+ settings:searchable="false" />
+
+</PreferenceScreen>
diff --git a/res/xml/widevine_settings.xml b/res/xml/widevine_settings.xml
new file mode 100644
index 0000000..1c118f0
--- /dev/null
+++ b/res/xml/widevine_settings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/widevine_settings_title"
+ settings:searchable="false">
+
+ <SwitchPreference
+ android:key="force_l3_fallback"
+ android:title="@string/force_l3_fallback_title"
+ android:summary="@string/force_l3_fallback_summary"
+ settings:controller="com.android.settings.development.widevine.ForceL3FallbackPreferenceController" />
+</PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/MainClear.java b/src/com/android/settings/MainClear.java
index 0aba5ca..7b96d42 100644
--- a/src/com/android/settings/MainClear.java
+++ b/src/com/android/settings/MainClear.java
@@ -26,11 +26,13 @@
import android.accounts.AuthenticatorDescription;
import android.app.ActionBar;
import android.app.Activity;
+import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -43,6 +45,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.image.DynamicSystemManager;
import android.provider.Settings;
import android.telephony.euicc.EuiccManager;
import android.text.TextUtils;
@@ -266,6 +269,19 @@
return;
}
+ final DynamicSystemManager dsuManager = (DynamicSystemManager)
+ getActivity().getSystemService(Context.DYNAMIC_SYSTEM_SERVICE);
+ if (dsuManager.isInUse()) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setTitle(R.string.dsu_is_running);
+ builder.setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {}
+ });
+ AlertDialog dsuAlertdialog = builder.create();
+ dsuAlertdialog.show();
+ return;
+ }
+
if (runKeyguardConfirmation(KEYGUARD_REQUEST)) {
return;
}
diff --git a/src/com/android/settings/accessibility/AccessibilityServiceWarning.java b/src/com/android/settings/accessibility/AccessibilityServiceWarning.java
index e8ed85c..9022ebf 100644
--- a/src/com/android/settings/accessibility/AccessibilityServiceWarning.java
+++ b/src/com/android/settings/accessibility/AccessibilityServiceWarning.java
@@ -67,7 +67,11 @@
void uninstallPackage();
}
- /** Returns a {@link Dialog} to be shown to confirm that they want to enable a service. */
+ /**
+ * Returns a {@link Dialog} to be shown to confirm that they want to enable a service.
+ * @deprecated Use {@link com.android.internal.accessibility.dialog.AccessibilityServiceWarning}
+ */
+ @Deprecated
public static Dialog createCapabilitiesDialog(@NonNull Context context,
@NonNull AccessibilityServiceInfo info, @NonNull View.OnClickListener listener,
@NonNull UninstallActionPerformer performer) {
diff --git a/src/com/android/settings/accessibility/InvisibleToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/InvisibleToggleAccessibilityServicePreferenceFragment.java
index 0c1876f..1ecb94a 100644
--- a/src/com/android/settings/accessibility/InvisibleToggleAccessibilityServicePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/InvisibleToggleAccessibilityServicePreferenceFragment.java
@@ -64,9 +64,24 @@
@Override
void onDialogButtonFromShortcutToggleClicked(View view) {
super.onDialogButtonFromShortcutToggleClicked(view);
- if (view.getId() == R.id.permission_enable_allow_button) {
- AccessibilityUtils.setAccessibilityServiceState(getContext(), mComponentName,
- true);
+ if (!android.view.accessibility.Flags.deduplicateAccessibilityWarningDialog()) {
+ if (view.getId() == R.id.permission_enable_allow_button) {
+ AccessibilityUtils.setAccessibilityServiceState(getContext(), mComponentName,
+ true);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Enables accessibility service when user clicks permission allow button.
+ */
+ @Override
+ void onAllowButtonFromShortcutToggleClicked() {
+ super.onAllowButtonFromShortcutToggleClicked();
+ if (android.view.accessibility.Flags.deduplicateAccessibilityWarningDialog()) {
+ AccessibilityUtils.setAccessibilityServiceState(getContext(), mComponentName, true);
}
}
diff --git a/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java b/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
index 6bd8747..f268a40 100644
--- a/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
+++ b/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java
@@ -16,6 +16,7 @@
package com.android.settings.accessibility;
+import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
@@ -33,6 +34,8 @@
import com.android.settingslib.core.lifecycle.events.OnDestroy;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;
+import com.google.android.setupcompat.util.WizardManagerHelper;
+
import java.util.Optional;
/**
@@ -207,6 +210,13 @@
return;
}
+ if (Flags.removeQsTooltipInSuw()
+ && mContext instanceof Activity
+ && WizardManagerHelper.isAnySetupWizard(((Activity) mContext).getIntent())) {
+ // Don't show QuickSettingsTooltip in Setup Wizard
+ return;
+ }
+
if (!mNeedsQSTooltipReshow && AccessibilityQuickSettingUtils.hasValueInSharedPreferences(
mContext, tileComponentName)) {
// Returns if quick settings tooltip only show once.
diff --git a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
index 6847a6d..213f108 100644
--- a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
@@ -157,28 +157,55 @@
if (info == null) {
return null;
}
- mWarningDialog = AccessibilityServiceWarning
- .createCapabilitiesDialog(getPrefContext(), info,
- this::onDialogButtonFromEnableToggleClicked,
- this::onDialogButtonFromUninstallClicked);
+ if (android.view.accessibility.Flags.deduplicateAccessibilityWarningDialog()) {
+ mWarningDialog =
+ com.android.internal.accessibility.dialog.AccessibilityServiceWarning
+ .createAccessibilityServiceWarningDialog(getPrefContext(), info,
+ v -> onAllowButtonFromEnableToggleClicked(),
+ v -> onDenyButtonFromEnableToggleClicked(),
+ v -> onDialogButtonFromUninstallClicked());
+ } else {
+ mWarningDialog = AccessibilityServiceWarning
+ .createCapabilitiesDialog(getPrefContext(), info,
+ this::onDialogButtonFromEnableToggleClicked,
+ this::onDialogButtonFromUninstallClicked);
+ }
return mWarningDialog;
case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT_TOGGLE:
if (info == null) {
return null;
}
- mWarningDialog = AccessibilityServiceWarning
- .createCapabilitiesDialog(getPrefContext(), info,
- this::onDialogButtonFromShortcutToggleClicked,
- this::onDialogButtonFromUninstallClicked);
+ if (android.view.accessibility.Flags.deduplicateAccessibilityWarningDialog()) {
+ mWarningDialog =
+ com.android.internal.accessibility.dialog.AccessibilityServiceWarning
+ .createAccessibilityServiceWarningDialog(getPrefContext(), info,
+ v -> onAllowButtonFromShortcutToggleClicked(),
+ v -> onDenyButtonFromShortcutToggleClicked(),
+ v -> onDialogButtonFromUninstallClicked());
+ } else {
+ mWarningDialog = AccessibilityServiceWarning
+ .createCapabilitiesDialog(getPrefContext(), info,
+ this::onDialogButtonFromShortcutToggleClicked,
+ this::onDialogButtonFromUninstallClicked);
+ }
return mWarningDialog;
case DialogEnums.ENABLE_WARNING_FROM_SHORTCUT:
if (info == null) {
return null;
}
- mWarningDialog = AccessibilityServiceWarning
- .createCapabilitiesDialog(getPrefContext(), info,
- this::onDialogButtonFromShortcutClicked,
- this::onDialogButtonFromUninstallClicked);
+ if (android.view.accessibility.Flags.deduplicateAccessibilityWarningDialog()) {
+ mWarningDialog =
+ com.android.internal.accessibility.dialog.AccessibilityServiceWarning
+ .createAccessibilityServiceWarningDialog(getPrefContext(), info,
+ v -> onAllowButtonFromShortcutClicked(),
+ v -> onDenyButtonFromShortcutClicked(),
+ v -> onDialogButtonFromUninstallClicked());
+ } else {
+ mWarningDialog = AccessibilityServiceWarning
+ .createCapabilitiesDialog(getPrefContext(), info,
+ this::onDialogButtonFromShortcutClicked,
+ this::onDialogButtonFromUninstallClicked);
+ }
return mWarningDialog;
case DialogEnums.DISABLE_WARNING_FROM_TOGGLE:
if (info == null) {
@@ -459,7 +486,7 @@
}
}
- private void onAllowButtonFromShortcutToggleClicked() {
+ void onAllowButtonFromShortcutToggleClicked() {
mShortcutPreference.setChecked(true);
final int shortcutTypes = retrieveUserShortcutType(getPrefContext(),
diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
index 427cad9..c76bb8b 100644
--- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java
@@ -905,6 +905,14 @@
return;
}
+ Activity activity = getActivity();
+ if (com.android.settings.accessibility.Flags.removeQsTooltipInSuw()
+ && activity != null
+ && WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
+ // Don't show QuickSettingsTooltip in Setup Wizard
+ return;
+ }
+
if (!mNeedsQSTooltipReshow && AccessibilityQuickSettingUtils.hasValueInSharedPreferences(
getContext(), tileComponentName)) {
// Returns if quick settings tooltip only show once.
diff --git a/src/com/android/settings/applications/AppStorageSettings.java b/src/com/android/settings/applications/AppStorageSettings.java
index 807f043..e45657f 100644
--- a/src/com/android/settings/applications/AppStorageSettings.java
+++ b/src/com/android/settings/applications/AppStorageSettings.java
@@ -53,6 +53,7 @@
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.deviceinfo.StorageWizardMoveConfirm;
+import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState.Callbacks;
@@ -359,6 +360,8 @@
mButtonsPref.setButton1Enabled(false);
// Invoke uninstall or clear user data based on sysPackage
String packageName = mAppEntry.info.packageName;
+ DynamicDenylistManager.getInstance(getContext())
+ .resetDenylistIfNeeded(packageName, /* force= */ false);
Log.i(TAG, "Clearing user data for package : " + packageName);
if (mClearDataObserver == null) {
mClearDataObserver = new ClearUserDataObserver();
diff --git a/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java b/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java
index 6da3e52..b2b7512 100644
--- a/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java
+++ b/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java
@@ -39,6 +39,7 @@
import com.android.settings.R;
import com.android.settings.fuelgauge.BatteryOptimizeUtils;
+import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager;
import java.util.Arrays;
import java.util.List;
@@ -155,6 +156,8 @@
}
mAom.resetAllModes();
BatteryOptimizeUtils.resetAppOptimizationMode(mContext, mIPm, mAom);
+ DynamicDenylistManager.getInstance(mContext)
+ .resetDenylistIfNeeded(/* packageName= */ null, /* force= */ true);
final int[] restrictedUids = mNpm.getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND);
final int currentUserId = ActivityManager.getCurrentUser();
for (int uid : restrictedUids) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
new file mode 100644
index 0000000..9ebe26d
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.flags.Flags;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+public abstract class AudioSharingBasePreferenceController extends BasePreferenceController {
+ private final LocalBluetoothManager mBtManager;
+ protected final LocalBluetoothLeBroadcast mBroadcast;
+ protected Preference mPreference;
+
+ public AudioSharingBasePreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mBtManager = Utils.getLocalBtManager(context);
+ mBroadcast =
+ mBtManager == null
+ ? null
+ : mBtManager.getProfileManager().getLeAudioBroadcastProfile();
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mBtManager != null && Flags.enableLeAudioSharing()
+ ? AVAILABLE
+ : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ updateVisibility(isBroadcasting());
+ }
+
+ /**
+ * Update the visibility of the preference.
+ *
+ * @param isVisible the latest visibility state for the preference.
+ */
+ public void updateVisibility(boolean isVisible) {
+ mPreference.setVisible(isVisible);
+ }
+
+ private boolean isBroadcasting() {
+ return mBroadcast != null && mBroadcast.isEnabled(null);
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
index b3b7a2c..40207be 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
@@ -25,11 +25,14 @@
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.SettingsMainSwitchBar;
-public class AudioSharingDashboardFragment extends DashboardFragment {
+public class AudioSharingDashboardFragment extends DashboardFragment
+ implements AudioSharingSwitchBarController.OnSwitchBarChangedListener {
private static final String TAG = "AudioSharingDashboardFrag";
SettingsMainSwitchBar mMainSwitchBar;
private AudioSharingSwitchBarController mSwitchBarController;
+ private CallsAndAlarmsPreferenceController mCallsAndAlarmsPreferenceController;
+ private AudioSharingNamePreferenceController mAudioSharingNamePreferenceController;
public AudioSharingDashboardFragment() {
super();
@@ -63,7 +66,9 @@
@Override
public void onAttach(Context context) {
super.onAttach(context);
- use(CallsAndAlarmsPreferenceController.class).init(this);
+ mCallsAndAlarmsPreferenceController = use(CallsAndAlarmsPreferenceController.class);
+ mCallsAndAlarmsPreferenceController.init(this);
+ mAudioSharingNamePreferenceController = use(AudioSharingNamePreferenceController.class);
}
@Override
@@ -74,9 +79,19 @@
final SettingsActivity activity = (SettingsActivity) getActivity();
mMainSwitchBar = activity.getSwitchBar();
mMainSwitchBar.setTitle(getText(R.string.audio_sharing_switch_title));
- mSwitchBarController = new AudioSharingSwitchBarController(activity, mMainSwitchBar);
+ mSwitchBarController = new AudioSharingSwitchBarController(activity, mMainSwitchBar, this);
mSwitchBarController.init(this);
getSettingsLifecycle().addObserver(mSwitchBarController);
mMainSwitchBar.show();
}
+
+ @Override
+ public void onSwitchBarChanged(boolean newState) {
+ updateVisibilityForAttachedPreferences(newState);
+ }
+
+ private void updateVisibilityForAttachedPreferences(boolean isVisible) {
+ mCallsAndAlarmsPreferenceController.updateVisibility(isVisible);
+ mAudioSharingNamePreferenceController.updateVisibility(isVisible);
+ }
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
index dd5eb62..0d2b53a 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
@@ -17,7 +17,6 @@
package com.android.settings.connecteddevice.audiosharing;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcast;
import android.bluetooth.BluetoothLeBroadcastAssistant;
@@ -43,16 +42,12 @@
import com.android.settings.flags.Flags;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LeAudioProfile;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.google.common.collect.ImmutableList;
-
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
@@ -74,24 +69,54 @@
private Preference mAudioSharingSettingsPreference;
private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
private DashboardFragment mFragment;
+ private List<BluetoothDevice> mTargetSinks = new ArrayList<>();
private final BluetoothLeBroadcast.Callback mBroadcastCallback =
new BluetoothLeBroadcast.Callback() {
@Override
- public void onBroadcastStarted(int reason, int broadcastId) {}
+ public void onBroadcastStarted(int reason, int broadcastId) {
+ Log.d(
+ TAG,
+ "onBroadcastStarted(), reason = "
+ + reason
+ + ", broadcastId = "
+ + broadcastId);
+ }
@Override
- public void onBroadcastStartFailed(int reason) {}
+ public void onBroadcastStartFailed(int reason) {
+ Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason);
+ // TODO: handle broadcast start fail
+ }
@Override
public void onBroadcastMetadataChanged(
- int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {}
+ int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {
+ Log.d(
+ TAG,
+ "onBroadcastMetadataChanged(), broadcastId = "
+ + broadcastId
+ + ", metadata = "
+ + metadata);
+ addSourceToTargetDevices(mTargetSinks);
+ mTargetSinks = new ArrayList<>();
+ }
@Override
- public void onBroadcastStopped(int reason, int broadcastId) {}
+ public void onBroadcastStopped(int reason, int broadcastId) {
+ Log.d(
+ TAG,
+ "onBroadcastStopped(), reason = "
+ + reason
+ + ", broadcastId = "
+ + broadcastId);
+ }
@Override
- public void onBroadcastStopFailed(int reason) {}
+ public void onBroadcastStopFailed(int reason) {
+ Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason);
+ // TODO: handle broadcast stop fail
+ }
@Override
public void onBroadcastUpdated(int reason, int broadcastId) {}
@@ -321,7 +346,7 @@
Log.d(
TAG,
"Ignore onProfileConnectionStateChanged, not the le profile for le audio"
- + " device");
+ + " device");
return;
}
boolean isFirstConnectedProfile = isFirstConnectedProfile(cachedDevice, bluetoothProfile);
@@ -330,7 +355,7 @@
Log.d(
TAG,
"Ignore onProfileConnectionStateChanged, not the first connected profile for"
- + " non le audio device");
+ + " non le audio device");
return;
}
if (!isLeAudioSupported) {
@@ -347,20 +372,21 @@
}
// Do nothing for ineligible (non LE audio) remote device when no sharing session.
} else {
+ Map<Integer, List<CachedBluetoothDevice>> groupedDevices =
+ AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager);
// Handle connected eligible (LE audio) remote device
if (isBroadcasting()) {
// Show audio sharing switch or join dialog according to device count in the sharing
// session.
- Map<Integer, List<CachedBluetoothDevice>> groupedDevices =
- fetchConnectedDevicesByGroupId();
- ArrayList<AudioSharingDeviceItem> deviceItems =
- buildDeviceItemsInSharingSession(groupedDevices);
- // Show switch audio sharing dialog when the third eligible (LE audio) remote device
+ ArrayList<AudioSharingDeviceItem> deviceItemsInSharingSession =
+ AudioSharingUtils.buildOrderedDeviceItemsInSharingSession(
+ groupedDevices, mLocalBtManager);
+ // Show audio sharing switch dialog when the third eligible (LE audio) remote device
// connected during a sharing session.
- if (deviceItems.size() >= 2) {
+ if (deviceItemsInSharingSession.size() >= 2) {
AudioSharingDisconnectDialogFragment.show(
mFragment,
- deviceItems,
+ deviceItemsInSharingSession,
cachedDevice.getName(),
(AudioSharingDeviceItem item) -> {
// Remove all sources from the device user clicked
@@ -379,11 +405,49 @@
/* isGroupOp= */ true);
});
} else {
- // TODO: show dialog to add device to sharing session.
+ // Show audio sharing join dialog when the first or second eligible (LE audio)
+ // remote device connected during a sharing session.
+ AudioSharingJoinDialogFragment.show(
+ mFragment,
+ deviceItemsInSharingSession,
+ cachedDevice.getName(),
+ () -> {
+ // Add current broadcast to the latest connected device
+ mAssistant.addSource(
+ cachedDevice.getDevice(),
+ mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
+ /* isGroupOp= */ true);
+ });
}
} else {
- // Show audio sharing join dialog when no sharing session.
- // TODO: show dialog to add device to sharing session.
+ ArrayList<AudioSharingDeviceItem> deviceItems = new ArrayList<>();
+ for (List<CachedBluetoothDevice> devices : groupedDevices.values()) {
+ // Use random device in the group within the sharing session to
+ // represent the group.
+ CachedBluetoothDevice device = devices.get(0);
+ if (device.getGroupId() == cachedDevice.getGroupId()) {
+ continue;
+ }
+ deviceItems.add(AudioSharingUtils.buildAudioSharingDeviceItem(device));
+ }
+ // Show audio sharing join dialog when the second eligible (LE audio) remote device
+ // connect and no sharing session.
+ if (deviceItems.size() == 1) {
+ AudioSharingJoinDialogFragment.show(
+ mFragment,
+ deviceItems,
+ cachedDevice.getName(),
+ () -> {
+ mTargetSinks = new ArrayList<>();
+ for (List<CachedBluetoothDevice> devices :
+ groupedDevices.values()) {
+ for (CachedBluetoothDevice device : devices) {
+ mTargetSinks.add(device.getDevice());
+ }
+ }
+ mBroadcast.startBroadcast("test", null);
+ });
+ }
}
}
}
@@ -425,49 +489,25 @@
return mBroadcast != null && mBroadcast.isEnabled(null);
}
- private Map<Integer, List<CachedBluetoothDevice>> fetchConnectedDevicesByGroupId() {
- // TODO: filter out devices with le audio disabled.
- List<BluetoothDevice> connectedDevices =
- mAssistant == null ? ImmutableList.of() : mAssistant.getConnectedDevices();
- Map<Integer, List<CachedBluetoothDevice>> groupedDevices = new HashMap<>();
- CachedBluetoothDeviceManager cacheManager = mLocalBtManager.getCachedDeviceManager();
- for (BluetoothDevice device : connectedDevices) {
- CachedBluetoothDevice cachedDevice = cacheManager.findDevice(device);
- if (cachedDevice == null) {
- Log.d(TAG, "Skip device due to not being cached: " + device.getAnonymizedAddress());
- continue;
- }
- int groupId = cachedDevice.getGroupId();
- if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- Log.d(
- TAG,
- "Skip device due to no valid group id: " + device.getAnonymizedAddress());
- continue;
- }
- if (!groupedDevices.containsKey(groupId)) {
- groupedDevices.put(groupId, new ArrayList<>());
- }
- groupedDevices.get(groupId).add(cachedDevice);
+ private void addSourceToTargetDevices(List<BluetoothDevice> sinks) {
+ if (sinks.isEmpty() || mBroadcast == null || mAssistant == null) {
+ Log.d(TAG, "Skip adding source to target.");
+ return;
}
- return groupedDevices;
- }
-
- private ArrayList<AudioSharingDeviceItem> buildDeviceItemsInSharingSession(
- Map<Integer, List<CachedBluetoothDevice>> groupedDevices) {
- ArrayList<AudioSharingDeviceItem> deviceItems = new ArrayList<>();
- for (List<CachedBluetoothDevice> devices : groupedDevices.values()) {
- for (CachedBluetoothDevice device : devices) {
- List<BluetoothLeBroadcastReceiveState> sourceList =
- mAssistant.getAllSources(device.getDevice());
- if (!sourceList.isEmpty()) {
- // Use random device in the group within the sharing session to
- // represent the group.
- deviceItems.add(
- new AudioSharingDeviceItem(device.getName(), device.getGroupId()));
- break;
- }
- }
+ BluetoothLeBroadcastMetadata broadcastMetadata =
+ mBroadcast.getLatestBluetoothLeBroadcastMetadata();
+ if (broadcastMetadata == null) {
+ Log.e(TAG, "Error: There is no broadcastMetadata.");
+ return;
}
- return deviceItems;
+ for (BluetoothDevice sink : sinks) {
+ Log.d(
+ TAG,
+ "Add broadcast with broadcastId: "
+ + broadcastMetadata.getBroadcastId()
+ + "to the device: "
+ + sink.getAnonymizedAddress());
+ mAssistant.addSource(sink, broadcastMetadata, /* isGroupOp= */ false);
+ }
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java
new file mode 100644
index 0000000..b9646ac
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingJoinDialogFragment.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing;
+
+import android.app.Dialog;
+import android.app.settings.SettingsEnums;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.flags.Flags;
+
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+public class AudioSharingJoinDialogFragment extends InstrumentedDialogFragment {
+ private static final String TAG = "AudioSharingJoinDialog";
+ private static final String BUNDLE_KEY_DEVICE_ITEMS = "bundle_key_device_items";
+ private static final String BUNDLE_KEY_NEW_DEVICE_NAME = "bundle_key_new_device_name";
+
+ // The host creates an instance of this dialog fragment must implement this interface to receive
+ // event callbacks.
+ public interface DialogEventListener {
+ /** Called when users click the share audio button in the dialog. */
+ void onShareClick();
+ }
+
+ private static DialogEventListener sListener;
+ private View mRootView;
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.DIALOG_START_AUDIO_SHARING;
+ }
+
+ /**
+ * Display the {@link AudioSharingJoinDialogFragment} dialog.
+ *
+ * @param host The Fragment this dialog will be hosted.
+ * @param deviceItems The existing connected device items eligible for audio sharing.
+ * @param newDeviceName The name of the latest connected device triggered this dialog.
+ * @param listener The callback to handle the user action on this dialog.
+ */
+ public static void show(
+ Fragment host,
+ ArrayList<AudioSharingDeviceItem> deviceItems,
+ String newDeviceName,
+ DialogEventListener listener) {
+ if (!Flags.enableLeAudioSharing()) return;
+ final FragmentManager manager = host.getChildFragmentManager();
+ sListener = listener;
+ if (manager.findFragmentByTag(TAG) == null) {
+ final Bundle bundle = new Bundle();
+ bundle.putParcelableArrayList(BUNDLE_KEY_DEVICE_ITEMS, deviceItems);
+ bundle.putString(BUNDLE_KEY_NEW_DEVICE_NAME, newDeviceName);
+ final AudioSharingJoinDialogFragment dialog = new AudioSharingJoinDialogFragment();
+ dialog.setArguments(bundle);
+ dialog.show(manager, TAG);
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Bundle arguments = requireArguments();
+ ArrayList<AudioSharingDeviceItem> deviceItems =
+ arguments.getParcelableArrayList(BUNDLE_KEY_DEVICE_ITEMS);
+ String newDeviceName = arguments.getString(BUNDLE_KEY_NEW_DEVICE_NAME);
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(getActivity())
+ .setTitle("Share audio?")
+ .setCancelable(false);
+ mRootView =
+ LayoutInflater.from(builder.getContext())
+ .inflate(R.layout.dialog_audio_sharing_join, null /* parent */);
+ TextView subtitle1 = mRootView.findViewById(R.id.share_audio_subtitle1);
+ TextView subtitle2 = mRootView.findViewById(R.id.share_audio_subtitle2);
+ if (deviceItems.isEmpty()) {
+ subtitle1.setText(newDeviceName);
+ } else {
+ subtitle1.setText(
+ String.format(
+ Locale.US,
+ "%s and %s",
+ deviceItems.stream()
+ .map(AudioSharingDeviceItem::getName)
+ .collect(Collectors.joining(", ")),
+ newDeviceName));
+ }
+ subtitle2.setText(
+ "Connected eligible headphones will hear videos ad music playing on this phone");
+ Button shareBtn = mRootView.findViewById(R.id.share_btn);
+ Button cancelBtn = mRootView.findViewById(R.id.cancel_btn);
+ shareBtn.setOnClickListener(
+ v -> {
+ sListener.onShareClick();
+ dismiss();
+ });
+ shareBtn.setText("Share audio");
+ cancelBtn.setOnClickListener(v -> dismiss());
+ Dialog dialog = builder.setView(mRootView).create();
+ dialog.setCanceledOnTouchOutside(false);
+ return dialog;
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
index b36ea54..81465ed 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
@@ -24,6 +24,7 @@
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
+import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsQrCodeFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.widget.ValidatedEditTextPreference;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
index 18c9bfd..8336691 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
@@ -22,13 +22,10 @@
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
-import androidx.preference.PreferenceScreen;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.flags.Flags;
import com.android.settings.widget.ValidatedEditTextPreference;
-public class AudioSharingNamePreferenceController extends BasePreferenceController
+public class AudioSharingNamePreferenceController extends AudioSharingBasePreferenceController
implements ValidatedEditTextPreference.Validator,
Preference.OnPreferenceChangeListener,
DefaultLifecycleObserver {
@@ -37,8 +34,6 @@
private static final String PREF_KEY = "audio_sharing_stream_name";
- protected Preference mPreference;
-
private AudioSharingNameTextValidator mAudioSharingNameTextValidator;
public AudioSharingNamePreferenceController(Context context) {
@@ -47,11 +42,6 @@
}
@Override
- public int getAvailabilityStatus() {
- return Flags.enableLeAudioSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
- }
-
- @Override
public String getPreferenceKey() {
return PREF_KEY;
}
@@ -63,12 +53,6 @@
}
@Override
- public void displayPreference(PreferenceScreen screen) {
- super.displayPreference(screen);
- mPreference = screen.findPreference(getPreferenceKey());
- }
-
- @Override
public boolean isTextValid(String value) {
return mAudioSharingNameTextValidator.isTextValid(value);
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index 83367ae..8b82fe9 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -16,13 +16,11 @@
package com.android.settings.connecteddevice.audiosharing;
-import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcast;
import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;
import android.widget.CompoundButton;
@@ -37,17 +35,14 @@
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.flags.Flags;
import com.android.settings.widget.SettingsMainSwitchBar;
+import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.utils.ThreadUtils;
-import com.google.common.collect.ImmutableList;
-
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -58,11 +53,17 @@
implements DefaultLifecycleObserver, OnCheckedChangeListener {
private static final String TAG = "AudioSharingSwitchBarCtl";
private static final String PREF_KEY = "audio_sharing_main_switch";
+
+ interface OnSwitchBarChangedListener {
+ void onSwitchBarChanged(boolean newState);
+ }
+
private final SettingsMainSwitchBar mSwitchBar;
private final LocalBluetoothManager mBtManager;
private final LocalBluetoothLeBroadcast mBroadcast;
private final LocalBluetoothLeBroadcastAssistant mAssistant;
private final Executor mExecutor;
+ private final OnSwitchBarChangedListener mListener;
private DashboardFragment mFragment;
private List<BluetoothDevice> mTargetSinks = new ArrayList<>();
@@ -196,9 +197,11 @@
BluetoothLeBroadcastReceiveState state) {}
};
- AudioSharingSwitchBarController(Context context, SettingsMainSwitchBar switchBar) {
+ AudioSharingSwitchBarController(
+ Context context, SettingsMainSwitchBar switchBar, OnSwitchBarChangedListener listener) {
super(context, PREF_KEY);
mSwitchBar = switchBar;
+ mListener = listener;
mBtManager = Utils.getLocalBtManager(context);
mBroadcast = mBtManager.getProfileManager().getLeAudioBroadcastProfile();
mAssistant = mBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
@@ -265,18 +268,17 @@
mSwitchBar.setEnabled(true);
return;
}
- Map<Integer, List<CachedBluetoothDevice>> groupedDevices = fetchConnectedDevicesByGroupId();
+ Map<Integer, List<CachedBluetoothDevice>> groupedDevices =
+ AudioSharingUtils.fetchConnectedDevicesByGroupId(mBtManager);
ArrayList<AudioSharingDeviceItem> deviceItems = new ArrayList<>();
Optional<Integer> activeGroupId = Optional.empty();
for (List<CachedBluetoothDevice> devices : groupedDevices.values()) {
// Use random device in the group to represent the group.
CachedBluetoothDevice device = devices.get(0);
- // TODO: add BluetoothUtils.isActiveLeAudioDevice to avoid directly using isActiveDevice
- if (device.isActiveDevice(BluetoothProfile.LE_AUDIO)) {
+ if (BluetoothUtils.isActiveLeAudioDevice(device)) {
activeGroupId = Optional.of(device.getGroupId());
} else {
- AudioSharingDeviceItem item =
- new AudioSharingDeviceItem(device.getName(), device.getGroupId());
+ AudioSharingDeviceItem item = AudioSharingUtils.buildAudioSharingDeviceItem(device);
deviceItems.add(item);
}
}
@@ -326,8 +328,12 @@
private void updateSwitch() {
ThreadUtils.postOnMainThread(
() -> {
- mSwitchBar.setChecked(isBroadcasting());
+ boolean isBroadcasting = isBroadcasting();
+ if (mSwitchBar.isChecked() != isBroadcasting) {
+ mSwitchBar.setChecked(isBroadcasting);
+ }
mSwitchBar.setEnabled(true);
+ mListener.onSwitchBarChanged(isBroadcasting);
});
}
@@ -335,31 +341,6 @@
return mBroadcast != null && mBroadcast.isEnabled(null);
}
- private Map<Integer, List<CachedBluetoothDevice>> fetchConnectedDevicesByGroupId() {
- // TODO: filter out devices with le audio disabled.
- List<BluetoothDevice> connectedDevices =
- mAssistant == null ? ImmutableList.of() : mAssistant.getConnectedDevices();
- Map<Integer, List<CachedBluetoothDevice>> groupedDevices = new HashMap<>();
- CachedBluetoothDeviceManager cacheManager = mBtManager.getCachedDeviceManager();
- for (BluetoothDevice device : connectedDevices) {
- CachedBluetoothDevice cachedDevice = cacheManager.findDevice(device);
- if (cachedDevice == null) {
- Log.d(TAG, "Skip device due to not being cached: " + device.getAnonymizedAddress());
- continue;
- }
- int groupId = cachedDevice.getGroupId();
- if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- Log.d(TAG, "Skip device due to no valid group id");
- continue;
- }
- if (!groupedDevices.containsKey(groupId)) {
- groupedDevices.put(groupId, new ArrayList<>());
- }
- groupedDevices.get(groupId).add(cachedDevice);
- }
- return groupedDevices;
- }
-
private void addSourceToTargetDevices(List<BluetoothDevice> sinks) {
if (sinks.isEmpty() || mBroadcast == null || mAssistant == null) {
Log.d(TAG, "Skip adding source to target.");
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
new file mode 100644
index 0000000..4ece70e
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing;
+
+import android.bluetooth.BluetoothCsipSetCoordinator;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.util.Log;
+
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class AudioSharingUtils {
+ private static final String TAG = "AudioSharingUtils";
+
+ /**
+ * Fetch {@link CachedBluetoothDevice}s connected to the broadcast assistant. The devices are
+ * grouped by CSIP group id.
+ *
+ * @param localBtManager The BT manager to provide BT functions.
+ * @return A map of connected devices grouped by CSIP group id.
+ */
+ public static Map<Integer, List<CachedBluetoothDevice>> fetchConnectedDevicesByGroupId(
+ LocalBluetoothManager localBtManager) {
+ Map<Integer, List<CachedBluetoothDevice>> groupedDevices = new HashMap<>();
+ LocalBluetoothLeBroadcastAssistant assistant =
+ localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) return groupedDevices;
+ // TODO: filter out devices with le audio disabled.
+ List<BluetoothDevice> connectedDevices = assistant.getConnectedDevices();
+ CachedBluetoothDeviceManager cacheManager = localBtManager.getCachedDeviceManager();
+ for (BluetoothDevice device : connectedDevices) {
+ CachedBluetoothDevice cachedDevice = cacheManager.findDevice(device);
+ if (cachedDevice == null) {
+ Log.d(TAG, "Skip device due to not being cached: " + device.getAnonymizedAddress());
+ continue;
+ }
+ int groupId = cachedDevice.getGroupId();
+ if (groupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
+ Log.d(
+ TAG,
+ "Skip device due to no valid group id: " + device.getAnonymizedAddress());
+ continue;
+ }
+ if (!groupedDevices.containsKey(groupId)) {
+ groupedDevices.put(groupId, new ArrayList<>());
+ }
+ groupedDevices.get(groupId).add(cachedDevice);
+ }
+ return groupedDevices;
+ }
+
+ /**
+ * Fetch a list of {@link AudioSharingDeviceItem}s in the audio sharing session.
+ *
+ * @param groupedConnectedDevices devices connected to broadcast assistant grouped by CSIP group
+ * id.
+ * @param localBtManager The BT manager to provide BT functions.
+ * @return A list of connected devices in the audio sharing session.
+ */
+ public static ArrayList<AudioSharingDeviceItem> buildOrderedDeviceItemsInSharingSession(
+ Map<Integer, List<CachedBluetoothDevice>> groupedConnectedDevices,
+ LocalBluetoothManager localBtManager) {
+ ArrayList<AudioSharingDeviceItem> deviceItems = new ArrayList<>();
+ LocalBluetoothLeBroadcastAssistant assistant =
+ localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) return deviceItems;
+ CachedBluetoothDevice activeDevice = null;
+ List<CachedBluetoothDevice> inactiveDevices = new ArrayList<>();
+ for (List<CachedBluetoothDevice> devices : groupedConnectedDevices.values()) {
+ for (CachedBluetoothDevice device : devices) {
+ List<BluetoothLeBroadcastReceiveState> sourceList =
+ assistant.getAllSources(device.getDevice());
+ if (!sourceList.isEmpty()) {
+ // Use random device in the group within the sharing session to
+ // represent the group.
+ if (BluetoothUtils.isActiveLeAudioDevice(device)) {
+ activeDevice = device;
+ } else {
+ inactiveDevices.add(device);
+ }
+ break;
+ }
+ }
+ }
+ if (activeDevice != null) {
+ deviceItems.add(buildAudioSharingDeviceItem(activeDevice));
+ }
+ inactiveDevices.stream()
+ .sorted(CachedBluetoothDevice::compareTo)
+ .forEach(
+ device -> {
+ deviceItems.add(buildAudioSharingDeviceItem(device));
+ });
+ return deviceItems;
+ }
+
+ /** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */
+ public static AudioSharingDeviceItem buildAudioSharingDeviceItem(
+ CachedBluetoothDevice cachedDevice) {
+ return new AudioSharingDeviceItem(cachedDevice.getName(), cachedDevice.getGroupId());
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
index 480b257..44e75ec 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java
@@ -19,21 +19,16 @@
import android.content.Context;
import android.util.Log;
-import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
-import com.android.settings.core.BasePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.flags.Flags;
/** PreferenceController to control the dialog to choose the active device for calls and alarms */
-public class CallsAndAlarmsPreferenceController extends BasePreferenceController {
+public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferenceController {
private static final String TAG = "CallsAndAlarmsPreferenceController";
private static final String PREF_KEY = "calls_and_alarms";
-
- private Preference mPreference;
private DashboardFragment mFragment;
public CallsAndAlarmsPreferenceController(Context context) {
@@ -41,11 +36,6 @@
}
@Override
- public int getAvailabilityStatus() {
- return Flags.enableLeAudioSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
- }
-
- @Override
public String getPreferenceKey() {
return PREF_KEY;
}
@@ -53,7 +43,6 @@
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
- mPreference = screen.findPreference(getPreferenceKey());
mPreference.setOnPreferenceClickListener(
preference -> {
if (mFragment != null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioStreamsCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
similarity index 88%
rename from src/com/android/settings/connecteddevice/audiosharing/AudioStreamsCategoryController.java
rename to src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
index e25a6ab..84a7be9 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioStreamsCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settings.connecteddevice.audiosharing;
+package com.android.settings.connecteddevice.audiosharing.audiostreams;
import android.content.Context;
@@ -29,6 +29,8 @@
@Override
public int getAvailabilityStatus() {
- return Flags.enableLeAudioQrCodePrivateBroadcastSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ return Flags.enableLeAudioQrCodePrivateBroadcastSharing()
+ ? AVAILABLE
+ : UNSUPPORTED_ON_DEVICE;
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioStreamsDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
similarity index 95%
rename from src/com/android/settings/connecteddevice/audiosharing/AudioStreamsDashboardFragment.java
rename to src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
index 40a8b29..562427f 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioStreamsDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDashboardFragment.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settings.connecteddevice.audiosharing;
+package com.android.settings.connecteddevice.audiosharing.audiostreams;
import android.content.Context;
import android.os.Bundle;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryPreference.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryPreference.java
new file mode 100644
index 0000000..d259900
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryPreference.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing.audiostreams;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.settings.ProgressCategory;
+import com.android.settings.R;
+
+public class AudioStreamsProgressCategoryPreference extends ProgressCategory {
+
+ public AudioStreamsProgressCategoryPreference(Context context) {
+ super(context);
+ init();
+ }
+
+ public AudioStreamsProgressCategoryPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public AudioStreamsProgressCategoryPreference(
+ Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ public AudioStreamsProgressCategoryPreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init();
+ }
+
+ private void init() {
+ setEmptyTextRes(R.string.audio_streams_empty);
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioStreamsQrCodeFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java
similarity index 85%
rename from src/com/android/settings/connecteddevice/audiosharing/AudioStreamsQrCodeFragment.java
rename to src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java
index edf2bd3..42b38ee 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioStreamsQrCodeFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settings.connecteddevice.audiosharing;
+package com.android.settings.connecteddevice.audiosharing.audiostreams;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.graphics.Bitmap;
@@ -49,8 +49,11 @@
public final View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.xml.bluetooth_audio_streams_qr_code, container, false);
- getQrCodeBitmap().ifPresent(
- bm -> ((ImageView) view.requireViewById(R.id.qrcode_view)).setImageBitmap(bm));
+ getQrCodeBitmap()
+ .ifPresent(
+ bm ->
+ ((ImageView) view.requireViewById(R.id.qrcode_view))
+ .setImageBitmap(bm));
return view;
}
@@ -66,10 +69,12 @@
Bitmap bitmap = QrCodeGenerator.encodeQrCode(broadcastMetadata, qrcodeSize);
return Optional.of(bitmap);
} catch (WriterException e) {
- Log.d(TAG, "onCreateView: broadcastMetadata "
- + broadcastMetadata
- + " qrCode generation exception "
- + e);
+ Log.d(
+ TAG,
+ "onCreateView: broadcastMetadata "
+ + broadcastMetadata
+ + " qrCode generation exception "
+ + e);
}
return Optional.empty();
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java
new file mode 100644
index 0000000..d6d0634
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeActivity.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode;
+
+import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_DEVICE_SINK;
+import static com.android.settingslib.bluetooth.BluetoothBroadcastUtils.EXTRA_BLUETOOTH_SINK_IS_GROUP;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.fragment.app.FragmentTransaction;
+
+import com.android.settings.R;
+import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+
+/**
+ * Finding a broadcast through QR code.
+ *
+ * <p>To use intent action {@link
+ * BluetoothBroadcastUtils#ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER}, specify the bluetooth device
+ * sink of the broadcast to be provisioned in {@link
+ * BluetoothBroadcastUtils#EXTRA_BLUETOOTH_DEVICE_SINK} and check the operation for all coordinated
+ * set members throughout one session or not by {@link
+ * BluetoothBroadcastUtils#EXTRA_BLUETOOTH_SINK_IS_GROUP}.
+ */
+public class QrCodeScanModeActivity extends QrCodeScanModeBaseActivity {
+ private static final boolean DEBUG = BluetoothUtils.D;
+ private static final String TAG = "QrCodeScanModeActivity";
+
+ private boolean mIsGroupOp;
+ private BluetoothDevice mSink;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void handleIntent(Intent intent) {
+ String action = intent != null ? intent.getAction() : null;
+ if (DEBUG) {
+ Log.d(TAG, "handleIntent(), action = " + action);
+ }
+
+ if (action == null) {
+ finish();
+ return;
+ }
+
+ switch (action) {
+ case BluetoothBroadcastUtils.ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER:
+ showQrCodeScannerFragment(intent);
+ break;
+ default:
+ if (DEBUG) {
+ Log.e(TAG, "Launch with an invalid action");
+ }
+ finish();
+ }
+ }
+
+ protected void showQrCodeScannerFragment(Intent intent) {
+ if (intent == null) {
+ if (DEBUG) {
+ Log.d(TAG, "intent is null, can not get bluetooth information from intent.");
+ }
+ return;
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "showQrCodeScannerFragment");
+ }
+
+ mSink = intent.getParcelableExtra(EXTRA_BLUETOOTH_DEVICE_SINK);
+ mIsGroupOp = intent.getBooleanExtra(EXTRA_BLUETOOTH_SINK_IS_GROUP, false);
+ if (DEBUG) {
+ Log.d(TAG, "get extra from intent");
+ }
+
+ QrCodeScanModeFragment fragment =
+ (QrCodeScanModeFragment)
+ mFragmentManager.findFragmentByTag(
+ BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
+
+ if (fragment == null) {
+ fragment = new QrCodeScanModeFragment();
+ } else {
+ if (fragment.isVisible()) {
+ return;
+ }
+
+ // When the fragment in back stack but not on top of the stack, we can simply pop
+ // stack because current fragment transactions are arranged in an order
+ mFragmentManager.popBackStackImmediate();
+ return;
+ }
+ final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
+
+ fragmentTransaction.replace(
+ R.id.fragment_container,
+ fragment,
+ BluetoothBroadcastUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
+ fragmentTransaction.commit();
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeBaseActivity.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeBaseActivity.java
new file mode 100644
index 0000000..637014a
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeBaseActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.SystemProperties;
+
+import androidx.fragment.app.FragmentManager;
+
+import com.android.settings.R;
+import com.android.settingslib.core.lifecycle.ObservableActivity;
+
+import com.google.android.setupdesign.util.ThemeHelper;
+import com.google.android.setupdesign.util.ThemeResolver;
+
+public abstract class QrCodeScanModeBaseActivity extends ObservableActivity {
+
+ private static final String THEME_KEY = "setupwizard.theme";
+ private static final String THEME_DEFAULT_VALUE = "SudThemeGlifV3_DayNight";
+ protected FragmentManager mFragmentManager;
+
+ protected abstract void handleIntent(Intent intent);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ int defaultTheme =
+ ThemeHelper.isSetupWizardDayNightEnabled(this)
+ ? com.google.android.setupdesign.R.style.SudThemeGlifV3_DayNight
+ : com.google.android.setupdesign.R.style.SudThemeGlifV3_Light;
+ ThemeResolver themeResolver =
+ new ThemeResolver.Builder(ThemeResolver.getDefault())
+ .setDefaultTheme(defaultTheme)
+ .setUseDayNight(true)
+ .build();
+ setTheme(
+ themeResolver.resolve(
+ SystemProperties.get(THEME_KEY, THEME_DEFAULT_VALUE),
+ /* suppressDayNight= */ !ThemeHelper.isSetupWizardDayNightEnabled(this)));
+
+ setContentView(R.layout.qrcode_scan_mode_activity);
+ mFragmentManager = getSupportFragmentManager();
+
+ if (savedInstanceState == null) {
+ handleIntent(getIntent());
+ }
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeFragment.java
new file mode 100644
index 0000000..2b52039
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeFragment.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode;
+
+import android.app.Activity;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Matrix;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.util.Log;
+import android.util.Size;
+import android.view.LayoutInflater;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
+import com.android.settings.R;
+import com.android.settings.core.InstrumentedFragment;
+import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.qrcode.QrCamera;
+
+import java.time.Duration;
+
+public class QrCodeScanModeFragment extends InstrumentedFragment
+ implements TextureView.SurfaceTextureListener, QrCamera.ScannerCallback {
+ private static final boolean DEBUG = BluetoothUtils.D;
+ private static final String TAG = "QrCodeScanModeFragment";
+
+ /** Message sent to hide error message */
+ private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1;
+
+ /** Message sent to show error message */
+ private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2;
+
+ /** Message sent to broadcast QR code */
+ private static final int MESSAGE_SCAN_BROADCAST_SUCCESS = 3;
+
+ private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000;
+ private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000;
+
+ private static final Duration VIBRATE_DURATION_QR_CODE_RECOGNITION = Duration.ofMillis(3);
+
+ public static final String KEY_BROADCAST_METADATA = "key_broadcast_metadata";
+
+ private int mCornerRadius;
+ private String mBroadcastMetadata;
+ private Context mContext;
+ private QrCamera mCamera;
+ private TextureView mTextureView;
+ private TextView mSummary;
+ private TextView mErrorMessage;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mContext = getContext();
+ }
+
+ @Override
+ public final View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(
+ R.layout.qrcode_scanner_fragment, container, /* attachToRoot */ false);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ mTextureView = view.findViewById(R.id.preview_view);
+ mCornerRadius =
+ mContext.getResources().getDimensionPixelSize(R.dimen.qrcode_preview_radius);
+ mTextureView.setSurfaceTextureListener(this);
+ mTextureView.setOutlineProvider(
+ new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(
+ 0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
+ }
+ });
+ mTextureView.setClipToOutline(true);
+ mErrorMessage = view.findViewById(R.id.error_message);
+ }
+
+ private void initCamera(SurfaceTexture surface) {
+ // Check if the camera has already created.
+ if (mCamera == null) {
+ mCamera = new QrCamera(mContext, this);
+ mCamera.start(surface);
+ }
+ }
+
+ private void destroyCamera() {
+ if (mCamera != null) {
+ mCamera.stop();
+ mCamera = null;
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
+ initCamera(surface);
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(
+ @NonNull SurfaceTexture surface, int width, int height) {}
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
+ destroyCamera();
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {}
+
+ @Override
+ public void handleSuccessfulResult(String qrCode) {
+ if (DEBUG) {
+ Log.d(TAG, "handleSuccessfulResult(), get the qr code string.");
+ }
+ mBroadcastMetadata = qrCode;
+ handleBtLeAudioScanner();
+ }
+
+ @Override
+ public void handleCameraFailure() {
+ destroyCamera();
+ }
+
+ @Override
+ public Size getViewSize() {
+ return new Size(mTextureView.getWidth(), mTextureView.getHeight());
+ }
+
+ @Override
+ public Rect getFramePosition(Size previewSize, int cameraOrientation) {
+ return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight());
+ }
+
+ @Override
+ public void setTransform(Matrix transform) {
+ mTextureView.setTransform(transform);
+ }
+
+ @Override
+ public boolean isValid(String qrCode) {
+ if (qrCode.startsWith(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA)) {
+ return true;
+ } else {
+ showErrorMessage(R.string.bt_le_audio_qr_code_is_not_valid_format);
+ return false;
+ }
+ }
+
+ protected boolean isDecodeTaskAlive() {
+ return mCamera != null && mCamera.isDecodeTaskAlive();
+ }
+
+ private final Handler mHandler =
+ new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_HIDE_ERROR_MESSAGE:
+ mErrorMessage.setVisibility(View.INVISIBLE);
+ break;
+
+ case MESSAGE_SHOW_ERROR_MESSAGE:
+ final String errorMessage = (String) msg.obj;
+
+ mErrorMessage.setVisibility(View.VISIBLE);
+ mErrorMessage.setText(errorMessage);
+ mErrorMessage.sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+
+ // Cancel any pending messages to hide error view and requeue the
+ // message so
+ // user has time to see error
+ removeMessages(MESSAGE_HIDE_ERROR_MESSAGE);
+ sendEmptyMessageDelayed(
+ MESSAGE_HIDE_ERROR_MESSAGE, SHOW_ERROR_MESSAGE_INTERVAL);
+ break;
+
+ case MESSAGE_SCAN_BROADCAST_SUCCESS:
+ Log.d(TAG, "scan success");
+ final Intent resultIntent = new Intent();
+ resultIntent.putExtra(KEY_BROADCAST_METADATA, mBroadcastMetadata);
+ getActivity().setResult(Activity.RESULT_OK, resultIntent);
+ notifyUserForQrCodeRecognition();
+ break;
+ default:
+ }
+ }
+ };
+
+ private void notifyUserForQrCodeRecognition() {
+ if (mCamera != null) {
+ mCamera.stop();
+ }
+
+ mErrorMessage.setVisibility(View.INVISIBLE);
+
+ triggerVibrationForQrCodeRecognition(getContext());
+
+ getActivity().finish();
+ }
+
+ private static void triggerVibrationForQrCodeRecognition(Context context) {
+ Vibrator vibrator = context.getSystemService(Vibrator.class);
+ if (vibrator == null) {
+ return;
+ }
+ vibrator.vibrate(
+ VibrationEffect.createOneShot(
+ VIBRATE_DURATION_QR_CODE_RECOGNITION.toMillis(),
+ VibrationEffect.DEFAULT_AMPLITUDE));
+ }
+
+ private void showErrorMessage(@StringRes int messageResId) {
+ final Message message =
+ mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE, getString(messageResId));
+ message.sendToTarget();
+ }
+
+ private void handleBtLeAudioScanner() {
+ Message message = mHandler.obtainMessage(MESSAGE_SCAN_BROADCAST_SUCCESS);
+ mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL);
+ }
+
+ private void updateSummary() {
+ mSummary.setText(getString(R.string.bt_le_audio_scan_qr_code_scanner));
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.LE_AUDIO_BROADCAST_SCAN_QR_CODE;
+ }
+}
diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java
index 38f09f4..fb28d68 100644
--- a/src/com/android/settings/datausage/AppDataUsage.java
+++ b/src/com/android/settings/datausage/AppDataUsage.java
@@ -44,6 +44,7 @@
import com.android.settings.datausage.lib.AppDataUsageDetailsRepository;
import com.android.settings.datausage.lib.NetworkTemplates;
import com.android.settings.datausage.lib.NetworkUsageDetailsData;
+import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager;
import com.android.settings.network.SubscriptionUtil;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.AppItem;
@@ -325,7 +326,8 @@
private boolean getAppRestrictBackground() {
final int uid = mAppItem.key;
final int uidPolicy = services.mPolicyManager.getUidPolicy(uid);
- return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
+ return (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0
+ && DynamicDenylistManager.getInstance(mContext).isInManualDenylist(uid);
}
private boolean getUnrestrictData() {
diff --git a/src/com/android/settings/datausage/DataSaverBackend.java b/src/com/android/settings/datausage/DataSaverBackend.java
index b4b6b8c..6e99453 100644
--- a/src/com/android/settings/datausage/DataSaverBackend.java
+++ b/src/com/android/settings/datausage/DataSaverBackend.java
@@ -23,6 +23,7 @@
import android.net.NetworkPolicyManager;
import android.util.SparseIntArray;
+import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.utils.ThreadUtils;
@@ -39,6 +40,7 @@
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final NetworkPolicyManager mPolicyManager;
+ private final DynamicDenylistManager mDynamicDenylistManager;
private final ArrayList<Listener> mListeners = new ArrayList<>();
private SparseIntArray mUidPolicies = new SparseIntArray();
private boolean mAllowlistInitialized;
@@ -50,6 +52,7 @@
mContext = context.getApplicationContext();
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
mPolicyManager = NetworkPolicyManager.from(mContext);
+ mDynamicDenylistManager = DynamicDenylistManager.getInstance(mContext);
}
public void addListener(Listener listener) {
@@ -83,7 +86,7 @@
public void setIsAllowlisted(int uid, String packageName, boolean allowlisted) {
final int policy = allowlisted ? POLICY_ALLOW_METERED_BACKGROUND : POLICY_NONE;
- mPolicyManager.setUidPolicy(uid, policy);
+ mDynamicDenylistManager.setUidPolicyLocked(uid, policy);
mUidPolicies.put(uid, policy);
if (allowlisted) {
mMetricsFeatureProvider.action(
@@ -113,7 +116,7 @@
public void setIsDenylisted(int uid, String packageName, boolean denylisted) {
final int policy = denylisted ? POLICY_REJECT_METERED_BACKGROUND : POLICY_NONE;
- mPolicyManager.setUidPolicy(uid, policy);
+ mDynamicDenylistManager.setUidPolicyLocked(uid, policy);
mUidPolicies.put(uid, policy);
if (denylisted) {
mMetricsFeatureProvider.action(
@@ -123,7 +126,8 @@
public boolean isDenylisted(int uid) {
loadDenylist();
- return mUidPolicies.get(uid, POLICY_NONE) == POLICY_REJECT_METERED_BACKGROUND;
+ return mUidPolicies.get(uid, POLICY_NONE) == POLICY_REJECT_METERED_BACKGROUND
+ && mDynamicDenylistManager.isInManualDenylist(uid);
}
private void loadDenylist() {
diff --git a/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java b/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java
index 482858f..e75ab1a 100644
--- a/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java
+++ b/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverController.java
@@ -52,9 +52,6 @@
private boolean mShouldToggleSwitchBackOnRebootDialogDismiss;
@VisibleForTesting
- static final String PROPERTY_RO_GFX_ANGLE_SUPPORTED = "ro.gfx.angle.supported";
-
- @VisibleForTesting
static final String PROPERTY_PERSISTENT_GRAPHICS_EGL = "persist.graphics.egl";
@VisibleForTesting
@@ -97,11 +94,6 @@
return mSystemProperties.getBoolean(PROPERTY_DEBUG_ANGLE_DEVELOPER_OPTION, false);
}
- private boolean isAngleSupported() {
- return TextUtils.equals(
- mSystemProperties.get(PROPERTY_RO_GFX_ANGLE_SUPPORTED, ""), "true");
- }
-
@VisibleForTesting
GraphicsDriverEnableAngleAsSystemDriverController(
Context context, DevelopmentSettingsDashboardFragment fragment, Injector injector) {
@@ -145,10 +137,6 @@
/** Return the default value of "persist.graphics.egl" */
public boolean isDefaultValue() {
- if (!isAngleSupported()) {
- return true;
- }
-
final String currentGlesDriver =
mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, "");
// default value of "persist.graphics.egl" is ""
@@ -158,17 +146,11 @@
@Override
public void updateState(Preference preference) {
super.updateState(preference);
- if (isAngleSupported()) {
- // set switch on if "persist.graphics.egl" is "angle" and angle is built in /vendor
- // set switch off otherwise.
- final String currentGlesDriver =
- mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, "");
- final boolean isAngle = TextUtils.equals(ANGLE_DRIVER_SUFFIX, currentGlesDriver);
- ((TwoStatePreference) mPreference).setChecked(isAngle);
- } else {
- mPreference.setEnabled(false);
- ((TwoStatePreference) mPreference).setChecked(false);
- }
+ // set switch on if "persist.graphics.egl" is "angle".
+ final String currentGlesDriver =
+ mSystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL, "");
+ final boolean isAngle = TextUtils.equals(ANGLE_DRIVER_SUFFIX, currentGlesDriver);
+ ((TwoStatePreference) mPreference).setChecked(isAngle);
// Disable the developer option toggle UI if ANGLE is disabled, this means next time the
// debug property needs to be set to true again to enable ANGLE. If ANGLE is enabled, don't
@@ -182,12 +164,10 @@
protected void onDeveloperOptionsSwitchDisabled() {
// 1) disable the switch
super.onDeveloperOptionsSwitchDisabled();
- if (isAngleSupported()) {
- // 2) set the persist.graphics.egl empty string
- GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(false);
- // 3) reset the switch
- ((TwoStatePreference) mPreference).setChecked(false);
- }
+ // 2) set the persist.graphics.egl empty string
+ GraphicsEnvironment.getInstance().toggleAngleAsSystemDriver(false);
+ // 3) reset the switch
+ ((TwoStatePreference) mPreference).setChecked(false);
}
void toggleSwitchBack() {
diff --git a/src/com/android/settings/development/widevine/ForceL3FallbackPreferenceController.java b/src/com/android/settings/development/widevine/ForceL3FallbackPreferenceController.java
new file mode 100644
index 0000000..78468c1
--- /dev/null
+++ b/src/com/android/settings/development/widevine/ForceL3FallbackPreferenceController.java
@@ -0,0 +1,80 @@
+/*
+* Copyright (C) 2023 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.android.settings.development.widevine;
+
+import android.content.Context;
+import android.sysprop.WidevineProperties;
+import android.util.Log;
+
+import androidx.preference.Preference;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.core.TogglePreferenceController;
+import com.android.settingslib.development.DevelopmentSettingsEnabler;
+import com.android.settings.media_drm.Flags;
+
+/**
+ * The controller (in the Media Widevine settings) enforces L3 security level
+* of Widevine CDM.
+*/
+public class ForceL3FallbackPreferenceController extends TogglePreferenceController {
+ private static final String TAG = "ForceL3FallbackPreferenceController";
+
+ public ForceL3FallbackPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public boolean isChecked() {
+ return WidevineProperties.forcel3_enabled().orElse(false);
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ WidevineProperties.forcel3_enabled(isChecked);
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ if (Flags.forceL3Enabled()) {
+ preference.setEnabled(true);
+ Log.i(TAG, "forceL3Enabled is on");
+ } else {
+ preference.setEnabled(false);
+ // In case of flag rollback, the controller should be unchecked.
+ WidevineProperties.forcel3_enabled(false);
+ Log.i(TAG, "forceL3Enabled is off");
+ }
+ super.updateState(preference);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) {
+ return AVAILABLE;
+ } else {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ return R.string.menu_key_system;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/development/widevine/WidevineSettingsFragment.java b/src/com/android/settings/development/widevine/WidevineSettingsFragment.java
new file mode 100644
index 0000000..058aa40
--- /dev/null
+++ b/src/com/android/settings/development/widevine/WidevineSettingsFragment.java
@@ -0,0 +1,58 @@
+/*
+* Copyright (C) 2023 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.android.settings.development.widevine;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.development.DevelopmentSettingsEnabler;
+import com.android.settingslib.search.SearchIndexable;
+
+/**
+ * Fragment for native widevine settings in Developer options.
+*/
+@SearchIndexable
+public class WidevineSettingsFragment extends DashboardFragment {
+ private static final String TAG = "WidevineSettings";
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.widevine_settings;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.WIDEVINE_SETTINGS;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.widevine_settings) {
+
+ @Override
+ protected boolean isPageSearchEnabled(Context context) {
+ return DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context);
+ }
+ };
+}
\ No newline at end of file
diff --git a/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java b/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java
index 5d9d047..dd49c8b 100644
--- a/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java
+++ b/src/com/android/settings/fuelgauge/BatterySettingsMigrateChecker.java
@@ -26,6 +26,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.settings.fuelgauge.batterysaver.BatterySaverScheduleRadioButtonsController;
+import com.android.settings.fuelgauge.datasaver.DynamicDenylistManager;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
import java.util.List;
@@ -50,6 +51,8 @@
context = context.getApplicationContext();
verifySaverConfiguration(context);
verifyBatteryOptimizeModes(context);
+ // Initialize and sync settings into SharedPreferences for migration.
+ DynamicDenylistManager.getInstance(context);
}
/** Avoid users set important apps into the unexpected battery optimize modes */
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
index a8be398..bad1b76 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffEntry.java
@@ -27,10 +27,12 @@
import android.util.Log;
import android.util.Pair;
+import androidx.annotation.GuardedBy;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.fuelgauge.batteryusage.BatteryEntry.NameAndIcon;
import com.android.settingslib.utils.StringUtil;
import java.util.Comparator;
@@ -40,16 +42,23 @@
/** A container class to carry battery data in a specific time slot. */
public class BatteryDiffEntry {
private static final String TAG = "BatteryDiffEntry";
+ private static final Object sResourceCacheLock = new Object();
+ private static final Object sPackageNameAndUidCacheLock = new Object();
+ private static final Object sValidForRestrictionLock = new Object();
static Locale sCurrentLocale = null;
+
// Caches app label and icon to improve loading performance.
- static final Map<String, BatteryEntry.NameAndIcon> sResourceCache = new ArrayMap<>();
+ @GuardedBy("sResourceCacheLock")
+ static final Map<String, NameAndIcon> sResourceCache = new ArrayMap<>();
// Caches package name and uid to improve loading performance.
+ @GuardedBy("sPackageNameAndUidCacheLock")
static final Map<String, Integer> sPackageNameAndUidCache = new ArrayMap<>();
// Whether a specific item is valid to launch restriction page?
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ @GuardedBy("sValidForRestrictionLock")
static final Map<String, Boolean> sValidForRestriction = new ArrayMap<>();
/** A comparator for {@link BatteryDiffEntry} based on the sorting key. */
@@ -304,12 +313,16 @@
}
private int getPackageUid(String packageName) {
- if (sPackageNameAndUidCache.containsKey(packageName)) {
- return sPackageNameAndUidCache.get(packageName);
+ synchronized (sPackageNameAndUidCacheLock) {
+ if (sPackageNameAndUidCache.containsKey(packageName)) {
+ return sPackageNameAndUidCache.get(packageName);
+ }
}
int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName);
- sPackageNameAndUidCache.put(packageName, uid);
+ synchronized (sPackageNameAndUidCacheLock) {
+ sPackageNameAndUidCache.put(packageName, uid);
+ }
return uid;
}
@@ -318,13 +331,16 @@
return;
}
// Checks whether we have cached data or not first before fetching.
- final BatteryEntry.NameAndIcon nameAndIcon = getCache();
+ final NameAndIcon nameAndIcon = getCache();
if (nameAndIcon != null) {
mAppLabel = nameAndIcon.mName;
mAppIcon = nameAndIcon.mIcon;
mAppIconId = nameAndIcon.mIconId;
}
- final Boolean validForRestriction = sValidForRestriction.get(getKey());
+ Boolean validForRestriction = null;
+ synchronized (sValidForRestrictionLock) {
+ validForRestriction = sValidForRestriction.get(getKey());
+ }
if (validForRestriction != null) {
mValidForRestriction = validForRestriction;
}
@@ -336,33 +352,34 @@
// Configures whether we can launch restriction page or not.
updateRestrictionFlagState();
- sValidForRestriction.put(getKey(), Boolean.valueOf(mValidForRestriction));
+ synchronized (sValidForRestrictionLock) {
+ sValidForRestriction.put(getKey(), Boolean.valueOf(mValidForRestriction));
+ }
if (getKey() != null && SPECIAL_ENTRY_MAP.containsKey(getKey())) {
Pair<Integer, Integer> pair = SPECIAL_ENTRY_MAP.get(getKey());
mAppLabel = mContext.getString(pair.first);
mAppIconId = pair.second;
mAppIcon = mContext.getDrawable(mAppIconId);
- sResourceCache.put(
- getKey(), new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, mAppIconId));
+ putResourceCache(getKey(), new NameAndIcon(mAppLabel, mAppIcon, mAppIconId));
return;
}
// Loads application icon and label based on consumer type.
switch (mConsumerType) {
case ConvertUtils.CONSUMER_TYPE_USER_BATTERY:
- final BatteryEntry.NameAndIcon nameAndIconForUser =
+ final NameAndIcon nameAndIconForUser =
BatteryEntry.getNameAndIconFromUserId(mContext, (int) mUserId);
if (nameAndIconForUser != null) {
mAppIcon = nameAndIconForUser.mIcon;
mAppLabel = nameAndIconForUser.mName;
- sResourceCache.put(
+ putResourceCache(
getKey(),
- new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, /* iconId= */ 0));
+ new NameAndIcon(mAppLabel, mAppIcon, /* iconId= */ 0));
}
break;
case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY:
- final BatteryEntry.NameAndIcon nameAndIconForSystem =
+ final NameAndIcon nameAndIconForSystem =
BatteryEntry.getNameAndIconFromPowerComponent(mContext, mComponentId);
if (nameAndIconForSystem != null) {
mAppLabel = nameAndIconForSystem.mName;
@@ -370,9 +387,8 @@
mAppIconId = nameAndIconForSystem.mIconId;
mAppIcon = mContext.getDrawable(nameAndIconForSystem.mIconId);
}
- sResourceCache.put(
- getKey(),
- new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, mAppIconId));
+ putResourceCache(
+ getKey(), new NameAndIcon(mAppLabel, mAppIcon, mAppIconId));
}
break;
case ConvertUtils.CONSUMER_TYPE_UID_BATTERY:
@@ -384,9 +400,9 @@
// Adds badge icon into app icon for work profile.
mAppIcon = getBadgeIconForUser(mAppIcon);
if (mAppLabel != null || mAppIcon != null) {
- sResourceCache.put(
+ putResourceCache(
getKey(),
- new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, /* iconId= */ 0));
+ new NameAndIcon(mAppLabel, mAppIcon, /* iconId= */ 0));
}
break;
}
@@ -429,7 +445,7 @@
}
}
- private BatteryEntry.NameAndIcon getCache() {
+ private NameAndIcon getCache() {
final Locale locale = Locale.getDefault();
if (sCurrentLocale != locale) {
Log.d(
@@ -440,7 +456,9 @@
sCurrentLocale = locale;
clearCache();
}
- return sResourceCache.get(getKey());
+ synchronized (sResourceCacheLock) {
+ return sResourceCache.get(getKey());
+ }
}
private void loadNameAndIconForUid() {
@@ -469,13 +487,13 @@
final String[] packages = packageManager.getPackagesForUid(uid);
// Loads special defined application label and icon if available.
if (packages == null || packages.length == 0) {
- final BatteryEntry.NameAndIcon nameAndIcon =
+ final NameAndIcon nameAndIcon =
BatteryEntry.getNameAndIconFromUid(mContext, mAppLabel, uid);
mAppLabel = nameAndIcon.mName;
mAppIcon = nameAndIcon.mIcon;
}
- final BatteryEntry.NameAndIcon nameAndIcon =
+ final NameAndIcon nameAndIcon =
BatteryEntry.loadNameAndIcon(
mContext, uid, /* batteryEntry= */ null, packageName, mAppLabel, mAppIcon);
// Clears BatteryEntry internal cache since we will have another one.
@@ -544,9 +562,21 @@
/** Clears all cache data. */
public static void clearCache() {
- sResourceCache.clear();
- sValidForRestriction.clear();
- sPackageNameAndUidCache.clear();
+ synchronized (sResourceCacheLock) {
+ sResourceCache.clear();
+ }
+ synchronized (sValidForRestrictionLock) {
+ sValidForRestriction.clear();
+ }
+ synchronized (sPackageNameAndUidCacheLock) {
+ sPackageNameAndUidCache.clear();
+ }
+ }
+
+ private static void putResourceCache(String key, NameAndIcon nameAndIcon) {
+ synchronized (sResourceCacheLock) {
+ sResourceCache.put(key, nameAndIcon);
+ }
}
private Drawable getBadgeIconForUser(Drawable icon) {
diff --git a/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java b/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java
index be72e56..7eae7eb 100644
--- a/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java
+++ b/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManager.java
@@ -16,12 +16,24 @@
package com.android.settings.fuelgauge.datasaver;
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
+
+import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
+import static com.android.settings.fuelgauge.BatteryUtils.UID_ZERO;
+
import android.content.Context;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.net.NetworkPolicyManager;
+import android.util.ArraySet;
+import android.util.Log;
import androidx.annotation.VisibleForTesting;
+import java.util.List;
+import java.util.Set;
+
/** A class to dynamically manage per apps {@link NetworkPolicyManager} POLICY_ flags. */
public final class DynamicDenylistManager {
@@ -29,47 +41,157 @@
private static final String PREF_KEY_MANUAL_DENY = "manual_denylist_preference";
private static final String PREF_KEY_DYNAMIC_DENY = "dynamic_denylist_preference";
+ private static DynamicDenylistManager sInstance;
+
private final Context mContext;
private final NetworkPolicyManager mNetworkPolicyManager;
+ private final Object mLock = new Object();
- private static DynamicDenylistManager sInstance;
+ @VisibleForTesting
+ static final String PREF_KEY_MANUAL_DENYLIST_SYNCED = "manual_denylist_synced";
/** @return a DynamicDenylistManager object */
public static DynamicDenylistManager getInstance(Context context) {
synchronized (DynamicDenylistManager.class) {
if (sInstance == null) {
- sInstance = new DynamicDenylistManager(context);
+ sInstance = new DynamicDenylistManager(
+ context, NetworkPolicyManager.from(context));
}
return sInstance;
}
}
- DynamicDenylistManager(Context context) {
+ @VisibleForTesting
+ DynamicDenylistManager(Context context, NetworkPolicyManager networkPolicyManager) {
mContext = context.getApplicationContext();
- mNetworkPolicyManager = NetworkPolicyManager.from(mContext);
+ mNetworkPolicyManager = networkPolicyManager;
+ syncPolicyIfNeeded();
}
- /** Update the target uid policy in {@link #getManualDenylistPref()}. */
- public void updateManualDenylist(String uid, int policy) {
- if (policy != NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND) {
- getManualDenylistPref().edit().remove(uid).apply();
- } else {
- getManualDenylistPref().edit().putInt(uid, policy).apply();
+ /** Sync the policy from {@link NetworkPolicyManager} if needed. */
+ private void syncPolicyIfNeeded() {
+ if (getManualDenylistPref().contains(PREF_KEY_MANUAL_DENYLIST_SYNCED)) {
+ Log.i(TAG, "syncPolicyIfNeeded() ignore synced manual denylist");
+ return;
+ }
+
+ final SharedPreferences.Editor editor = getManualDenylistPref().edit();
+ final int[] existedUids = mNetworkPolicyManager
+ .getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND);
+ if (existedUids != null && existedUids.length != 0) {
+ for (int uid : existedUids) {
+ editor.putInt(String.valueOf(uid), POLICY_REJECT_METERED_BACKGROUND);
+ }
+ }
+ editor.putInt(PREF_KEY_MANUAL_DENYLIST_SYNCED, POLICY_NONE).apply();
+ }
+
+ /** Set policy flags for specific UID. */
+ public void setUidPolicyLocked(int uid, int policy) {
+ synchronized (mLock) {
+ mNetworkPolicyManager.setUidPolicy(uid, policy);
+ }
+ updateDenylistPref(uid, policy);
+ }
+
+ /** Suggest a list of package to set as POLICY_REJECT. */
+ public void setDenylist(List<String> packageNameList) {
+ final Set<Integer> denylistTargetUids = new ArraySet<>(packageNameList.size());
+ for (String packageName : packageNameList) {
+ try {
+ final int uid = mContext.getPackageManager().getPackageUid(packageName, 0);
+ if (uid == UID_ZERO) {
+ continue;
+ }
+ denylistTargetUids.add(uid);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unknown package name: " + packageName, e);
+ }
+ }
+
+ final Set<Integer> manualDenylistUids = getDenylistAllUids(getManualDenylistPref());
+ denylistTargetUids.removeAll(manualDenylistUids);
+
+ final Set<Integer> lastDynamicDenylistUids = getDenylistAllUids(getDynamicDenylistPref());
+ if (lastDynamicDenylistUids.equals(denylistTargetUids)) {
+ Log.i(TAG, "setDenylist() ignore the same denylist with size: "
+ + lastDynamicDenylistUids.size());
+ return;
+ }
+
+ // Store target denied uids into DynamicDenylistPref.
+ final SharedPreferences.Editor editor = getDynamicDenylistPref().edit();
+ editor.clear();
+ denylistTargetUids.forEach(
+ uid -> editor.putInt(String.valueOf(uid), POLICY_REJECT_METERED_BACKGROUND));
+ editor.apply();
+
+ // Set new added UIDs into REJECT policy.
+ synchronized (mLock) {
+ for (int uid : denylistTargetUids) {
+ if (!lastDynamicDenylistUids.contains(uid)) {
+ mNetworkPolicyManager.setUidPolicy(uid, POLICY_REJECT_METERED_BACKGROUND);
+ }
+ }
+ }
+ // Unset removed UIDs back to NONE policy.
+ synchronized (mLock) {
+ for (int uid : lastDynamicDenylistUids) {
+ if (!denylistTargetUids.contains(uid)) {
+ mNetworkPolicyManager.setUidPolicy(uid, POLICY_NONE);
+ }
+ }
}
}
/** Return true if the target uid is in {@link #getManualDenylistPref()}. */
- public boolean isInManualDenylist(String uid) {
- return getManualDenylistPref().contains(uid);
+ public boolean isInManualDenylist(int uid) {
+ return getManualDenylistPref().contains(String.valueOf(uid));
}
- /** Clear all data in {@link #getManualDenylistPref()} */
- public void clearManualDenylistPref() {
+ /** Reset the UIDs in the denylist if needed. */
+ public void resetDenylistIfNeeded(String packageName, boolean force) {
+ if (!force && !SETTINGS_PACKAGE_NAME.equals(packageName)) {
+ return;
+ }
+ synchronized (mLock) {
+ for (int uid : mNetworkPolicyManager
+ .getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND)) {
+ if (!getDenylistAllUids(getManualDenylistPref()).contains(uid)) {
+ mNetworkPolicyManager.setUidPolicy(uid, POLICY_NONE);
+ }
+ }
+ }
+ clearSharedPreferences();
+ }
+
+ private Set<Integer> getDenylistAllUids(SharedPreferences sharedPreferences) {
+ final ArraySet<Integer> uids = new ArraySet<>();
+ for (String key : sharedPreferences.getAll().keySet()) {
+ if (PREF_KEY_MANUAL_DENYLIST_SYNCED.equals(key)) {
+ continue;
+ }
+ try {
+ uids.add(Integer.parseInt(key));
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "getDenylistAllUids() unexpected format for " + key);
+ }
+ }
+ return uids;
+ }
+
+ void updateDenylistPref(int uid, int policy) {
+ final String uidString = String.valueOf(uid);
+ if (policy != POLICY_REJECT_METERED_BACKGROUND) {
+ getManualDenylistPref().edit().remove(uidString).apply();
+ } else {
+ getManualDenylistPref().edit().putInt(uidString, policy).apply();
+ }
+ getDynamicDenylistPref().edit().remove(uidString).apply();
+ }
+
+ void clearSharedPreferences() {
getManualDenylistPref().edit().clear().apply();
- }
-
- /** Clear all data in {@link #getDynamicDenylistPref()} */
- public void clearDynamicDenylistPref() {
getDynamicDenylistPref().edit().clear().apply();
}
diff --git a/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java b/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java
index 8795bf9..59a5fb0 100644
--- a/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java
+++ b/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java
@@ -43,6 +43,7 @@
public class LocaleLinearLayoutManager extends LinearLayoutManager {
private final LocaleDragAndDropAdapter mAdapter;
private final Context mContext;
+ private LocaleListEditor mLocaleListEditor;
private final AccessibilityNodeInfoCompat.AccessibilityActionCompat mActionMoveUp;
private final AccessibilityNodeInfoCompat.AccessibilityActionCompat mActionMoveDown;
@@ -147,8 +148,12 @@
}
if (result) {
- mAdapter.doTheUpdate();
+ mLocaleListEditor.showConfirmDialog(false, mAdapter.getFeedItemList().get(0));
}
return result;
}
+
+ public void setLocaleListEditor(LocaleListEditor localeListEditor) {
+ mLocaleListEditor = localeListEditor;
+ }
}
diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java
index 28f066a..59a39c8 100644
--- a/src/com/android/settings/localepicker/LocaleListEditor.java
+++ b/src/com/android/settings/localepicker/LocaleListEditor.java
@@ -472,6 +472,7 @@
private void configureDragAndDrop(LayoutPreference layout) {
final RecyclerView list = layout.findViewById(R.id.dragList);
final LocaleLinearLayoutManager llm = new LocaleLinearLayoutManager(getContext(), mAdapter);
+ llm.setLocaleListEditor(this);
llm.setAutoMeasureEnabled(true);
list.setLayoutManager(llm);
list.setHasFixedSize(true);
@@ -505,7 +506,7 @@
return false;
}
- private void showConfirmDialog(boolean isFirstRemoved, LocaleStore.LocaleInfo localeInfo) {
+ public void showConfirmDialog(boolean isFirstRemoved, LocaleStore.LocaleInfo localeInfo) {
Locale currentSystemLocale = LocalePicker.getLocales().get(0);
if (!localeInfo.getLocale().equals(currentSystemLocale)) {
final LocaleDialogFragment localeDialogFragment =
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
index 70d4d7d..a0db4ce 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
@@ -31,6 +31,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.UserProperties;
import android.content.res.Configuration;
import android.graphics.Color;
import android.hardware.biometrics.BiometricConstants;
@@ -61,11 +62,6 @@
public class ConfirmDeviceCredentialActivity extends FragmentActivity {
public static final String TAG = ConfirmDeviceCredentialActivity.class.getSimpleName();
- // The normal flow that apps go through
- private static final int CREDENTIAL_NORMAL = 1;
- // Unlocks the managed profile when the primary profile is unlocked
- private static final int CREDENTIAL_MANAGED = 2;
-
private static final String TAG_BIOMETRIC_FRAGMENT = "fragment";
public static class InternalActivity extends ConfirmDeviceCredentialActivity {
@@ -84,7 +80,9 @@
private String mTitle;
private CharSequence mDetails;
private int mUserId;
- private int mCredentialMode;
+ // Used to force the verification path required to unlock profile that shares credentials with
+ // with parent
+ private boolean mForceVerifyPath = false;
private boolean mGoingToBackground;
private boolean mWaitingForBiometricCallback;
@@ -189,7 +187,9 @@
}
final int effectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
final boolean isEffectiveUserManagedProfile =
- UserManager.get(this).isManagedProfile(effectiveUserId);
+ mUserManager.isManagedProfile(effectiveUserId);
+ final UserProperties userProperties =
+ mUserManager.getUserProperties(UserHandle.of(mUserId));
// if the client app did not hand in a title and we are about to show the work challenge,
// check whether there is a policy setting the organization name and use that as title
if ((mTitle == null) && isEffectiveUserManagedProfile) {
@@ -278,7 +278,19 @@
.setForceVerifyPath(true)
.show();
} else if (isEffectiveUserManagedProfile && isInternalActivity()) {
- mCredentialMode = CREDENTIAL_MANAGED;
+ // When the mForceVerifyPath is set to true, we launch the real confirm credential
+ // activity with an explicit but fake challenge value (0L). This will result in
+ // ConfirmLockPassword calling verifyTiedProfileChallenge() (if it's a profile with
+ // unified challenge), due to the difference between
+ // ConfirmLockPassword.startVerifyPassword() and
+ // ConfirmLockPassword.startCheckPassword(). Calling verifyTiedProfileChallenge() here
+ // is necessary when this is part of the turning on work profile flow, because it forces
+ // unlocking the work profile even before the profile is running.
+ // TODO: Remove the duplication of checkPassword and verifyPassword in
+ // ConfirmLockPassword,
+ // LockPatternChecker and LockPatternUtils. verifyPassword should be the only API to
+ // use, which optionally accepts a challenge.
+ mForceVerifyPath = true;
if (isBiometricAllowed(effectiveUserId, mUserId)) {
showBiometricPrompt(promptInfo);
launchedBiometric = true;
@@ -286,8 +298,19 @@
showConfirmCredentials();
launchedCDC = true;
}
+ } else if (android.os.Flags.allowPrivateProfile()
+ && userProperties != null
+ && userProperties.isAuthAlwaysRequiredToDisableQuietMode()
+ && isInternalActivity()) {
+ // Force verification path is required to be invoked as we might need to verify the tied
+ // profile challenge if the profile is using the unified challenge mode. This would
+ // result in ConfirmLockPassword.startVerifyPassword/
+ // ConfirmLockPattern.startVerifyPattern being called instead of the
+ // startCheckPassword/startCheckPattern
+ mForceVerifyPath = userProperties.isCredentialShareableWithParent();
+ showConfirmCredentials();
+ launchedCDC = true;
} else {
- mCredentialMode = CREDENTIAL_NORMAL;
if (isBiometricAllowed(effectiveUserId, mUserId)) {
// Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to
// onAuthenticationError and do the right thing automatically.
@@ -313,11 +336,8 @@
private String getTitleFromCredentialType(@LockPatternUtils.CredentialType int credentialType,
boolean isEffectiveUserManagedProfile) {
- int overrideStringId;
- int defaultStringId;
switch (credentialType) {
case LockPatternUtils.CREDENTIAL_TYPE_PIN:
-
if (isEffectiveUserManagedProfile) {
return mDevicePolicyManager.getResources().getString(
CONFIRM_WORK_PROFILE_PIN_HEADER,
@@ -410,29 +430,15 @@
* Shows ConfirmDeviceCredentials for normal apps.
*/
private void showConfirmCredentials() {
- boolean launched = false;
- ChooseLockSettingsHelper.Builder builder = new ChooseLockSettingsHelper.Builder(this)
+ boolean launched = new ChooseLockSettingsHelper.Builder(this)
.setHeader(mTitle)
.setDescription(mDetails)
.setExternal(true)
.setUserId(mUserId)
- .setTaskOverlay(mTaskOverlay);
- // The only difference between CREDENTIAL_MANAGED and CREDENTIAL_NORMAL is that for
- // CREDENTIAL_MANAGED, we launch the real confirm credential activity with an explicit
- // but fake challenge value (0L). This will result in ConfirmLockPassword calling
- // verifyTiedProfileChallenge() (if it's a profile with unified challenge), due to the
- // difference between ConfirmLockPassword.startVerifyPassword() and
- // ConfirmLockPassword.startCheckPassword(). Calling verifyTiedProfileChallenge() here is
- // necessary when this is part of the turning on work profile flow, because it forces
- // unlocking the work profile even before the profile is running.
- // TODO: Remove the duplication of checkPassword and verifyPassword in ConfirmLockPassword,
- // LockPatternChecker and LockPatternUtils. verifyPassword should be the only API to use,
- // which optionally accepts a challenge.
- if (mCredentialMode == CREDENTIAL_MANAGED) {
- launched = builder.setForceVerifyPath(true).show();
- } else if (mCredentialMode == CREDENTIAL_NORMAL) {
- launched = builder.show();
- }
+ .setTaskOverlay(mTaskOverlay)
+ .setForceVerifyPath(mForceVerifyPath)
+ .show();
+
if (!launched) {
Log.d(TAG, "No pin/pattern/pass set");
setResult(Activity.RESULT_OK);
diff --git a/src/com/android/settings/privatespace/HidePrivateSpaceController.java b/src/com/android/settings/privatespace/HidePrivateSpaceController.java
index b972a3f..8a0f167 100644
--- a/src/com/android/settings/privatespace/HidePrivateSpaceController.java
+++ b/src/com/android/settings/privatespace/HidePrivateSpaceController.java
@@ -16,10 +16,10 @@
package com.android.settings.privatespace;
-import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT;
+import static com.android.settings.privatespace.PrivateSpaceMaintainer.HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL;
+import static com.android.settings.privatespace.PrivateSpaceMaintainer.HIDE_PRIVATE_SPACE_ENTRY_POINT_ENABLED_VAL;
import android.content.Context;
-import android.provider.Settings;
import com.android.settings.core.TogglePreferenceController;
@@ -28,11 +28,11 @@
* in All Apps.
*/
public class HidePrivateSpaceController extends TogglePreferenceController {
- private static final int DISABLED_VALUE = 0;
- private static final int ENABLED_VALUE = 1;
+ private final PrivateSpaceMaintainer mPrivateSpaceMaintainer;
public HidePrivateSpaceController(Context context, String key) {
super(context, key);
+ mPrivateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(context);
}
@Override
@@ -43,14 +43,15 @@
@Override
public boolean isChecked() {
- return Settings.Secure.getInt(mContext.getContentResolver(),
- HIDE_PRIVATESPACE_ENTRY_POINT, DISABLED_VALUE) != DISABLED_VALUE;
+ return mPrivateSpaceMaintainer.getHidePrivateSpaceEntryPointSetting()
+ != HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL;
}
@Override
public boolean setChecked(boolean isChecked) {
- Settings.Secure.putInt(mContext.getContentResolver(), HIDE_PRIVATESPACE_ENTRY_POINT,
- isChecked ? ENABLED_VALUE : DISABLED_VALUE);
+ mPrivateSpaceMaintainer.setHidePrivateSpaceEntryPointSetting(
+ isChecked ? HIDE_PRIVATE_SPACE_ENTRY_POINT_ENABLED_VAL
+ : HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL);
return true;
}
diff --git a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
index e6094ce..341110b 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
@@ -17,6 +17,7 @@
package com.android.settings.privatespace;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
+import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT;
import android.app.ActivityManager;
import android.app.IActivityManager;
@@ -27,6 +28,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.Log;
@@ -40,6 +42,7 @@
/** A class to help with the creation / deletion of Private Space */
public class PrivateSpaceMaintainer {
private static final String TAG = "PrivateSpaceMaintainer";
+
@GuardedBy("this")
private static PrivateSpaceMaintainer sPrivateSpaceMaintainer;
@@ -49,6 +52,10 @@
private UserHandle mUserHandle;
private final KeyguardManager mKeyguardManager;
+ /** This is the default value for the hide private space entry point settings. */
+ public static final int HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL = 0;
+ public static final int HIDE_PRIVATE_SPACE_ENTRY_POINT_ENABLED_VAL = 1;
+
public enum ErrorDeletingPrivateSpace {
DELETE_PS_ERROR_NONE,
DELETE_PS_ERROR_NO_PRIVATE_SPACE,
@@ -91,6 +98,7 @@
}
Log.i(TAG, "Private space created with id: " + mUserHandle.getIdentifier());
+ resetPrivateSpaceSettings();
}
return true;
}
@@ -197,4 +205,21 @@
return doesPrivateSpaceExist()
&& mKeyguardManager.isDeviceSecure(mUserHandle.getIdentifier());
}
+
+ /** Sets the setting to show PS entry point to the provided value. */
+ public void setHidePrivateSpaceEntryPointSetting(int value) {
+ Settings.Secure.putInt(mContext.getContentResolver(), HIDE_PRIVATESPACE_ENTRY_POINT, value);
+ }
+
+ /** @return the setting to show PS entry point. */
+ public int getHidePrivateSpaceEntryPointSetting() {
+ return Settings.Secure.getInt(
+ mContext.getContentResolver(),
+ HIDE_PRIVATESPACE_ENTRY_POINT,
+ HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL);
+ }
+
+ private void resetPrivateSpaceSettings() {
+ setHidePrivateSpaceEntryPointSetting(HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL);
+ }
}
diff --git a/src/com/android/settings/privatespace/UseOneLockController.java b/src/com/android/settings/privatespace/UseOneLockController.java
deleted file mode 100644
index a94db57..0000000
--- a/src/com/android/settings/privatespace/UseOneLockController.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.privatespace;
-
-import android.content.Context;
-
-import com.android.settings.core.TogglePreferenceController;
-
-/** Represents the preference controller for using the same lock as the screen lock */
-public class UseOneLockController extends TogglePreferenceController {
- public UseOneLockController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AVAILABLE;
- }
-
- @Override
- public boolean isChecked() {
- // TODO(b/293569406) Need to save this to a persistent store, maybe like SettingsProvider
- return false;
- }
-
- @Override
- public boolean setChecked(boolean isChecked) {
- // TODO(b/293569406) Need to save this to a persistent store, maybe like SettingsProvider
- return true;
- }
-
- @Override
- public int getSliceHighlightMenuRes() {
- return 0;
- }
-}
diff --git a/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java b/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java
new file mode 100644
index 0000000..e976261
--- /dev/null
+++ b/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.privatespace.onelock;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/** Represents the preference controller to enroll biometrics for private space lock. */
+public class FaceFingerprintUnlockController extends AbstractPreferenceController {
+ private static final String KEY_SET_UNSET_FACE_FINGERPRINT = "private_space_biometrics";
+
+ public FaceFingerprintUnlockController(Context context, SettingsPreferenceFragment host) {
+ super(context);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return false;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_SET_UNSET_FACE_FINGERPRINT;
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ return TextUtils.equals(preference.getKey(), getPreferenceKey());
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ //TODO(b/308862923) : Add condition to check and enable when separate private lock is set.
+ preference.setSummary(mContext.getString(R.string.lock_settings_profile_unified_summary));
+ preference.setEnabled(false);
+ }
+}
diff --git a/src/com/android/settings/privatespace/onelock/PrivateSpaceLockController.java b/src/com/android/settings/privatespace/onelock/PrivateSpaceLockController.java
new file mode 100644
index 0000000..2783c1c
--- /dev/null
+++ b/src/com/android/settings/privatespace/onelock/PrivateSpaceLockController.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.privatespace.onelock;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
+import static com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.preference.Preference;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.password.ChooseLockGeneric;
+import com.android.settings.privatespace.PrivateSpaceMaintainer;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.transition.SettingsTransitionHelper;
+
+
+/** Represents the preference controller for changing private space lock. */
+public class PrivateSpaceLockController extends AbstractPreferenceController {
+ private static final String TAG = "PrivateSpaceLockContr";
+ private static final String KEY_CHANGE_PROFILE_LOCK =
+ "change_private_space_lock";
+
+ private final SettingsPreferenceFragment mHost;
+ private final UserManager mUserManager;
+ private final LockPatternUtils mLockPatternUtils;
+ private final int mProfileUserId;
+
+ public PrivateSpaceLockController(Context context, SettingsPreferenceFragment host) {
+ super(context);
+ mUserManager = context.getSystemService(UserManager.class);
+ mLockPatternUtils = FeatureFactory.getFeatureFactory()
+ .getSecurityFeatureProvider()
+ .getLockPatternUtils(context);
+ mHost = host;
+ UserHandle privateProfileHandle = PrivateSpaceMaintainer.getInstance(context)
+ .getPrivateProfileHandle();
+ if (privateProfileHandle != null) {
+ mProfileUserId = privateProfileHandle.getIdentifier();
+ } else {
+ mProfileUserId = -1;
+ Log.e(TAG, "Private profile user handle is not expected to be null.");
+ }
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_CHANGE_PROFILE_LOCK;
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
+ return false;
+ }
+ //Checks if the profile is in quiet mode and show a dialog to unpause the profile.
+ if (Utils.startQuietModeDialogIfNecessary(mContext, mUserManager,
+ mProfileUserId)) {
+ return false;
+ }
+ final Bundle extras = new Bundle();
+ extras.putInt(Intent.EXTRA_USER_ID, mProfileUserId);
+ extras.putBoolean(HIDE_INSECURE_OPTIONS, true);
+ new SubSettingLauncher(mContext)
+ .setDestination(ChooseLockGeneric.ChooseLockGenericFragment.class.getName())
+ .setSourceMetricsCategory(mHost.getMetricsCategory())
+ .setArguments(extras)
+ .setExtras(extras)
+ .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE)
+ .launch();
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(mProfileUserId)) {
+ preference.setSummary(
+ mContext.getString(getCredentialTypeResId(mProfileUserId)));
+ preference.setEnabled(true);
+ } else {
+ preference.setSummary(mContext.getString(
+ R.string.lock_settings_profile_unified_summary));
+ preference.setEnabled(false);
+ }
+ }
+
+ private int getCredentialTypeResId(int userId) {
+ int credentialType = mLockPatternUtils.getCredentialTypeForUser(userId);
+ switch (credentialType) {
+ case CREDENTIAL_TYPE_PATTERN :
+ return R.string.unlock_set_unlock_mode_pattern;
+ case CREDENTIAL_TYPE_PIN:
+ return R.string.unlock_set_unlock_mode_pin;
+ case CREDENTIAL_TYPE_PASSWORD:
+ return R.string.unlock_set_unlock_mode_password;
+ default:
+ // This is returned for CREDENTIAL_TYPE_NONE
+ return R.string.unlock_set_unlock_mode_off;
+ }
+ }
+}
diff --git a/src/com/android/settings/privatespace/onelock/UseOneLockController.java b/src/com/android/settings/privatespace/onelock/UseOneLockController.java
new file mode 100644
index 0000000..5c461e0
--- /dev/null
+++ b/src/com/android/settings/privatespace/onelock/UseOneLockController.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.privatespace.onelock;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.privatespace.PrivateSpaceMaintainer;
+
+/** Represents the preference controller for using the same lock as the screen lock */
+public class UseOneLockController extends BasePreferenceController {
+ private static final String TAG = "UseOneLockController";
+ private final LockPatternUtils mLockPatternUtils;
+ private final PrivateSpaceMaintainer mPrivateSpaceMaintainer;
+
+ public UseOneLockController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mPrivateSpaceMaintainer = PrivateSpaceMaintainer.getInstance(mContext);
+ mLockPatternUtils = FeatureFactory.getFeatureFactory()
+ .getSecurityFeatureProvider()
+ .getLockPatternUtils(context);
+ }
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public int getSliceHighlightMenuRes() {
+ return 0;
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ UserHandle privateProfileHandle = mPrivateSpaceMaintainer.getPrivateProfileHandle();
+ if (privateProfileHandle != null) {
+ int privateUserId = privateProfileHandle.getIdentifier();
+ if (mLockPatternUtils.isSeparateProfileChallengeEnabled(privateUserId)) {
+ return mContext.getString(getCredentialTypeResId(privateUserId));
+ }
+ } else {
+ Log.w(TAG, "Did not find Private Space.");
+ }
+ return mContext.getString(R.string.private_space_screen_lock_summary);
+ }
+
+ private int getCredentialTypeResId(int userId) {
+ int credentialType = mLockPatternUtils.getCredentialTypeForUser(userId);
+ switch (credentialType) {
+ case CREDENTIAL_TYPE_PATTERN:
+ return R.string.unlock_set_unlock_mode_pattern;
+ case CREDENTIAL_TYPE_PIN:
+ return R.string.unlock_set_unlock_mode_pin;
+ case CREDENTIAL_TYPE_PASSWORD:
+ return R.string.unlock_set_unlock_mode_password;
+ default:
+ // This is returned for CREDENTIAL_TYPE_NONE
+ return R.string.unlock_set_unlock_mode_off;
+ }
+ }
+}
diff --git a/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java b/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java
new file mode 100644
index 0000000..218b870
--- /dev/null
+++ b/src/com/android/settings/privatespace/onelock/UseOneLockControllerSwitch.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.privatespace.onelock;
+
+import static com.android.settings.privatespace.PrivateSpaceSetupActivity.EXTRA_ACTION_TYPE;
+import static com.android.settings.privatespace.PrivateSpaceSetupActivity.SET_LOCK_ACTION;
+import static com.android.settings.privatespace.onelock.UseOneLockSettingsFragment.UNIFY_PRIVATE_LOCK_WITH_DEVICE_REQUEST;
+import static com.android.settings.privatespace.onelock.UseOneLockSettingsFragment.UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.password.ChooseLockGeneric;
+import com.android.settings.password.ChooseLockSettingsHelper;
+import com.android.settings.privatespace.PrivateProfileContextHelperActivity;
+import com.android.settings.privatespace.PrivateSpaceMaintainer;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.transition.SettingsTransitionHelper;
+import com.android.settingslib.widget.MainSwitchPreference;
+
+/** Represents the preference controller for using the same lock as the screen lock */
+public class UseOneLockControllerSwitch extends AbstractPreferenceController
+ implements Preference.OnPreferenceChangeListener {
+ private static final String TAG = "UseOneLockSwitch";
+ private static final String KEY_UNIFICATION = "private_lock_unification";
+ private final String mPreferenceKey;
+ private final SettingsPreferenceFragment mHost;
+ private final LockPatternUtils mLockPatternUtils;
+ private final UserManager mUserManager;
+ private final int mProfileUserId;
+ private final UserHandle mUserHandle;
+ private LockscreenCredential mCurrentDevicePassword;
+ private LockscreenCredential mCurrentProfilePassword;
+ private MainSwitchPreference mUnifyProfile;
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mUnifyProfile = screen.findPreference(mPreferenceKey);
+ }
+ public UseOneLockControllerSwitch(Context context, SettingsPreferenceFragment host) {
+ this(context, host, KEY_UNIFICATION);
+ }
+
+ public UseOneLockControllerSwitch(Context context, SettingsPreferenceFragment host,
+ String key) {
+ super(context);
+ mHost = host;
+ mUserManager = context.getSystemService(UserManager.class);
+ mLockPatternUtils = FeatureFactory.getFeatureFactory().getSecurityFeatureProvider()
+ .getLockPatternUtils(context);
+ mUserHandle = PrivateSpaceMaintainer.getInstance(context).getPrivateProfileHandle();
+ mProfileUserId = mUserHandle != null ? mUserHandle.getIdentifier() : -1;
+ mCurrentDevicePassword = LockscreenCredential.createNone();
+ mCurrentProfilePassword = LockscreenCredential.createNone();
+ this.mPreferenceKey = key;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return mPreferenceKey;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object value) {
+ //Checks if the profile is in quiet mode and show a dialog to unpause the profile.
+ if (Utils.startQuietModeDialogIfNecessary(mContext, mUserManager, mProfileUserId)) {
+ return false;
+ }
+ final boolean useOneLock = (Boolean) value;
+ if (useOneLock) {
+ startUnification();
+ } else {
+ showAlertDialog();
+ }
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ if (mUnifyProfile != null) {
+ final boolean separate =
+ mLockPatternUtils.isSeparateProfileChallengeEnabled(mProfileUserId);
+ mUnifyProfile.setChecked(!separate);
+ }
+ }
+
+ /** Method to handle onActivityResult */
+ public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST
+ && resultCode == Activity.RESULT_OK) {
+ mCurrentDevicePassword =
+ data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
+ separateLocks();
+ return true;
+ } else if (requestCode == UNIFY_PRIVATE_LOCK_WITH_DEVICE_REQUEST
+ && resultCode == Activity.RESULT_OK) {
+ mCurrentProfilePassword =
+ data.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
+ unifyLocks();
+ return true;
+ }
+ return false;
+ }
+
+ private void separateLocks() {
+ final Bundle extras = new Bundle();
+ extras.putInt(Intent.EXTRA_USER_ID, mProfileUserId);
+ extras.putParcelable(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, mCurrentDevicePassword);
+ new SubSettingLauncher(mContext)
+ .setDestination(ChooseLockGeneric.ChooseLockGenericFragment.class.getName())
+ .setSourceMetricsCategory(mHost.getMetricsCategory())
+ .setArguments(extras)
+ .setTransitionType(SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE)
+ .launch();
+ }
+
+ /** Unify primary and profile locks. */
+ public void startUnification() {
+ // Confirm profile lock
+ final ChooseLockSettingsHelper.Builder builder =
+ new ChooseLockSettingsHelper.Builder(mHost.getActivity(), mHost);
+ final boolean launched = builder.setRequestCode(UNIFY_PRIVATE_LOCK_WITH_DEVICE_REQUEST)
+ .setReturnCredentials(true)
+ .setUserId(mProfileUserId)
+ .show();
+ if (!launched) {
+ // If profile has no lock, go straight to unification.
+ unifyLocks();
+ }
+ }
+
+ private void unifyLocks() {
+ unifyKeepingDeviceLock();
+ if (mCurrentDevicePassword != null) {
+ mCurrentDevicePassword.zeroize();
+ mCurrentDevicePassword = null;
+ }
+ if (mCurrentProfilePassword != null) {
+ mCurrentProfilePassword.zeroize();
+ mCurrentProfilePassword = null;
+ }
+ }
+
+ private void unifyKeepingDeviceLock() {
+ mLockPatternUtils.setSeparateProfileChallengeEnabled(mProfileUserId, false,
+ mCurrentProfilePassword);
+ }
+
+ private void showAlertDialog() {
+ if (mUserHandle == null) {
+ Log.e(TAG, "Private profile user handle is not expected to be null");
+ mUnifyProfile.setChecked(true);
+ return;
+ }
+ new AlertDialog.Builder(mContext)
+ .setMessage(R.string.private_space_new_lock_title)
+ .setPositiveButton(
+ R.string.privatespace_set_lock_label,
+ (dialog, which) -> {
+ Intent intent = new Intent(mContext,
+ PrivateProfileContextHelperActivity.class);
+ intent.putExtra(EXTRA_ACTION_TYPE, SET_LOCK_ACTION);
+ ((Activity) mContext).startActivityForResultAsUser(intent,
+ UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST,
+ /*Options*/ null, mUserHandle);
+ })
+ .setNegativeButton(R.string.privatespace_cancel_label,
+ (DialogInterface dialog, int which) -> {
+ mUnifyProfile.setChecked(true);
+ dialog.dismiss();
+ })
+ .setOnCancelListener(
+ (DialogInterface dialog) -> {
+ mUnifyProfile.setChecked(true);
+ dialog.dismiss();
+ })
+ .show();
+ }
+}
diff --git a/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java b/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java
new file mode 100644
index 0000000..36f8448
--- /dev/null
+++ b/src/com/android/settings/privatespace/onelock/UseOneLockSettingsFragment.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.privatespace.onelock;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.annotation.Nullable;
+
+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;
+
+public class UseOneLockSettingsFragment extends DashboardFragment {
+ private static final String TAG = "UseOneLockSettings";
+ public static final int UNIFY_PRIVATE_LOCK_WITH_DEVICE_REQUEST = 1;
+ public static final int UNUNIFY_PRIVATE_LOCK_FROM_DEVICE_REQUEST = 2;
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.PRIVATE_SPACE_SETTINGS;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.privatespace_one_lock;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+ final List<AbstractPreferenceController> controllers = new ArrayList<>();
+ controllers.add(new UseOneLockControllerSwitch(context, this));
+ controllers.add(new PrivateSpaceLockController(context, this));
+ controllers.add(new FaceFingerprintUnlockController(context, this));
+ return controllers;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ if (use(UseOneLockControllerSwitch.class)
+ .handleActivityResult(requestCode, resultCode, data)) {
+ return;
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+}
diff --git a/src/com/android/settings/security/ContentProtectionPreferenceFragment.java b/src/com/android/settings/security/ContentProtectionPreferenceFragment.java
index 5c72b4d..c65fd96 100644
--- a/src/com/android/settings/security/ContentProtectionPreferenceFragment.java
+++ b/src/com/android/settings/security/ContentProtectionPreferenceFragment.java
@@ -19,13 +19,10 @@
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
-import android.os.UserManager;
import androidx.annotation.VisibleForTesting;
-import androidx.preference.SwitchPreference;
import com.android.settings.R;
-import com.android.settings.Utils;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable;
@@ -36,8 +33,10 @@
// Required by @SearchIndexable to make the fragment and preferences to be indexed.
// Do not rename.
- public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider(R.layout.content_protection_preference_fragment);
+ @VisibleForTesting
+ public static final ContentProtectionSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new ContentProtectionSearchIndexProvider(
+ R.layout.content_protection_preference_fragment);
@Override
public void onAttach(Context context) {
@@ -63,4 +62,17 @@
protected String getLogTag() {
return TAG;
}
+
+ public static class ContentProtectionSearchIndexProvider extends BaseSearchIndexProvider {
+
+ public ContentProtectionSearchIndexProvider(int xmlRes) {
+ super(xmlRes);
+ }
+
+ @Override
+ @VisibleForTesting
+ public boolean isPageSearchEnabled(Context context) {
+ return ContentProtectionPreferenceUtils.isAvailable(context);
+ }
+ }
}
diff --git a/src/com/android/settings/security/ContentProtectionPreferenceUtils.java b/src/com/android/settings/security/ContentProtectionPreferenceUtils.java
new file mode 100644
index 0000000..d84d7c5
--- /dev/null
+++ b/src/com/android/settings/security/ContentProtectionPreferenceUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.security;
+
+import static com.android.internal.R.string.config_defaultContentProtectionService;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.provider.DeviceConfig;
+import android.view.contentcapture.ContentCaptureManager;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
+
+/** Util class for content protection preference. */
+public class ContentProtectionPreferenceUtils {
+
+ /**
+ * Whether or not the content protection setting page is available.
+ */
+ public static boolean isAvailable(@NonNull Context context) {
+ if (!settingUiEnabled() || getContentProtectionServiceComponentName(context) == null) {
+ return false;
+ }
+ return true;
+ }
+
+ private static String getContentProtectionServiceFlatComponentName(@NonNull Context context) {
+ return context.getString(config_defaultContentProtectionService);
+ }
+
+ @Nullable
+ private static ComponentName getContentProtectionServiceComponentName(@NonNull Context context) {
+ String flatComponentName = getContentProtectionServiceFlatComponentName(context);
+ if (flatComponentName == null) {
+ return null;
+ }
+ return ComponentName.unflattenFromString(flatComponentName);
+ }
+
+ /**
+ * Whether or not the content protection UI is enabled.
+ */
+ private static boolean settingUiEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+ ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER);
+ }
+}
diff --git a/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java b/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java
index b656093..c874a5e 100644
--- a/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java
+++ b/src/com/android/settings/security/ContentProtectionTogglePreferenceController.java
@@ -62,7 +62,6 @@
@Override
public boolean setChecked(boolean isChecked) {
- mSwitchBar.setChecked(isChecked);
Settings.Global.putInt(
mContentResolver, KEY_CONTENT_PROTECTION_PREFERENCE, isChecked ? 1 : -1);
return true;
diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java
index 64699ff..29d136f 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -1724,6 +1724,9 @@
public List<SearchIndexableRaw> getRawDataToIndex(Context context,
boolean enabled) {
final List<SearchIndexableRaw> rawData = new ArrayList<>();
+ if (!UserManager.supportsMultipleUsers()) {
+ return rawData;
+ }
SearchIndexableRaw allowMultipleUsersResult = new SearchIndexableRaw(context);
diff --git a/src/com/android/settings/vpn2/ConfigDialog.java b/src/com/android/settings/vpn2/ConfigDialog.java
index 036487d..1c001cb 100644
--- a/src/com/android/settings/vpn2/ConfigDialog.java
+++ b/src/com/android/settings/vpn2/ConfigDialog.java
@@ -16,8 +16,6 @@
package com.android.settings.vpn2;
-import static com.android.internal.net.VpnProfile.isLegacyType;
-
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
@@ -43,9 +41,6 @@
import com.android.settings.R;
import com.android.settings.utils.AndroidKeystoreAliasLoader;
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -60,13 +55,18 @@
View.OnClickListener, AdapterView.OnItemSelectedListener,
CompoundButton.OnCheckedChangeListener {
private static final String TAG = "ConfigDialog";
+ // Vpn profile constants to match with R.array.vpn_types.
+ private static final List<Integer> VPN_TYPES = List.of(
+ VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS,
+ VpnProfile.TYPE_IKEV2_IPSEC_PSK,
+ VpnProfile.TYPE_IKEV2_IPSEC_RSA
+ );
+
private final DialogInterface.OnClickListener mListener;
private final VpnProfile mProfile;
private boolean mEditing;
private boolean mExists;
- private List<String> mTotalTypes;
- private List<String> mAllowedTypes;
private View mView;
@@ -75,14 +75,9 @@
private TextView mServer;
private TextView mUsername;
private TextView mPassword;
- private TextView mSearchDomains;
- private TextView mDnsServers;
- private TextView mRoutes;
private Spinner mProxySettings;
private TextView mProxyHost;
private TextView mProxyPort;
- private CheckBox mMppe;
- private TextView mL2tpSecret;
private TextView mIpsecIdentifier;
private TextView mIpsecSecret;
private Spinner mIpsecUserCert;
@@ -116,14 +111,9 @@
mServer = (TextView) mView.findViewById(R.id.server);
mUsername = (TextView) mView.findViewById(R.id.username);
mPassword = (TextView) mView.findViewById(R.id.password);
- mSearchDomains = (TextView) mView.findViewById(R.id.search_domains);
- mDnsServers = (TextView) mView.findViewById(R.id.dns_servers);
- mRoutes = (TextView) mView.findViewById(R.id.routes);
mProxySettings = (Spinner) mView.findViewById(R.id.vpn_proxy_settings);
mProxyHost = (TextView) mView.findViewById(R.id.vpn_proxy_host);
mProxyPort = (TextView) mView.findViewById(R.id.vpn_proxy_port);
- mMppe = (CheckBox) mView.findViewById(R.id.mppe);
- mL2tpSecret = (TextView) mView.findViewById(R.id.l2tp_secret);
mIpsecIdentifier = (TextView) mView.findViewById(R.id.ipsec_identifier);
mIpsecSecret = (TextView) mView.findViewById(R.id.ipsec_secret);
mIpsecUserCert = (Spinner) mView.findViewById(R.id.ipsec_user_cert);
@@ -137,29 +127,17 @@
// Second, copy values from the profile.
mName.setText(mProfile.name);
setTypesByFeature(mType);
- // Not all types will be available to the user. Find the index corresponding to the
- // string of the profile's type.
- if (mAllowedTypes != null && mTotalTypes != null) {
- mType.setSelection(mAllowedTypes.indexOf(mTotalTypes.get(mProfile.type)));
- } else {
- Log.w(TAG, "Allowed or Total vpn types not initialized when setting initial selection");
- }
+ mType.setSelection(convertVpnProfileConstantToTypeIndex(mProfile.type));
mServer.setText(mProfile.server);
if (mProfile.saveLogin) {
mUsername.setText(mProfile.username);
mPassword.setText(mProfile.password);
}
- mSearchDomains.setText(mProfile.searchDomains);
- mDnsServers.setText(mProfile.dnsServers);
- mRoutes.setText(mProfile.routes);
if (mProfile.proxy != null) {
mProxyHost.setText(mProfile.proxy.getHost());
int port = mProfile.proxy.getPort();
mProxyPort.setText(port == 0 ? "" : Integer.toString(port));
}
- mMppe.setChecked(mProfile.mppe);
- mL2tpSecret.setText(mProfile.l2tpSecret);
- mL2tpSecret.setTextAppearance(android.R.style.TextAppearance_DeviceDefault_Medium);
mIpsecIdentifier.setText(mProfile.ipsecIdentifier);
mIpsecSecret.setText(mProfile.ipsecSecret);
final AndroidKeystoreAliasLoader androidKeystoreAliasLoader =
@@ -185,8 +163,6 @@
mServer.addTextChangedListener(this);
mUsername.addTextChangedListener(this);
mPassword.addTextChangedListener(this);
- mDnsServers.addTextChangedListener(this);
- mRoutes.addTextChangedListener(this);
mProxySettings.setOnItemSelectedListener(this);
mProxyHost.addTextChangedListener(this);
mProxyPort.addTextChangedListener(this);
@@ -217,12 +193,6 @@
// Create a button to forget the profile if it has already been saved..
setButton(DialogInterface.BUTTON_NEUTRAL,
context.getString(R.string.vpn_forget), mListener);
-
- // Display warning subtitle if the existing VPN is an insecure type...
- if (VpnProfile.isLegacyType(mProfile.type)) {
- TextView subtitle = mView.findViewById(R.id.dialog_alert_subtitle);
- subtitle.setVisibility(View.VISIBLE);
- }
}
// Create a button to save the profile.
@@ -285,10 +255,7 @@
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (parent == mType) {
- // Because the spinner may not display all available types,
- // convert the selected position into the actual vpn profile type integer.
- final int profileType = convertAllowedIndexToProfileType(position);
- changeType(profileType);
+ changeType(VPN_TYPES.get(position));
} else if (parent == mProxySettings) {
updateProxyFieldsVisibility(position);
}
@@ -330,17 +297,7 @@
} else {
mAlwaysOnVpn.setChecked(false);
mAlwaysOnVpn.setEnabled(false);
- if (!profile.isTypeValidForLockdown()) {
- mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_type);
- } else if (isLegacyType(profile.type) && !profile.isServerAddressNumeric()) {
- mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_server);
- } else if (isLegacyType(profile.type) && !profile.hasDns()) {
- mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_no_dns);
- } else if (isLegacyType(profile.type) && !profile.areDnsAddressesNumeric()) {
- mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_dns);
- } else {
- mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_other);
- }
+ mAlwaysOnInvalidReason.setText(R.string.vpn_always_on_invalid_reason_other);
mAlwaysOnInvalidReason.setVisibility(View.VISIBLE);
}
@@ -370,21 +327,14 @@
}
private boolean isAdvancedOptionsEnabled() {
- return mSearchDomains.getText().length() > 0 || mDnsServers.getText().length() > 0 ||
- mRoutes.getText().length() > 0 || mProxyHost.getText().length() > 0
- || mProxyPort.getText().length() > 0;
+ return mProxyHost.getText().length() > 0 || mProxyPort.getText().length() > 0;
}
private void configureAdvancedOptionsVisibility() {
if (mShowOptions.isChecked() || isAdvancedOptionsEnabled()) {
mView.findViewById(R.id.options).setVisibility(View.VISIBLE);
mShowOptions.setVisibility(View.GONE);
-
- // Configure networking option visibility
// TODO(b/149070123): Add ability for platform VPNs to support DNS & routes
- final int visibility =
- isLegacyType(getSelectedVpnType()) ? View.VISIBLE : View.GONE;
- mView.findViewById(R.id.network_options).setVisibility(visibility);
} else {
mView.findViewById(R.id.options).setVisibility(View.GONE);
mShowOptions.setVisibility(View.VISIBLE);
@@ -393,8 +343,6 @@
private void changeType(int type) {
// First, hide everything.
- mMppe.setVisibility(View.GONE);
- mView.findViewById(R.id.l2tp).setVisibility(View.GONE);
mView.findViewById(R.id.ipsec_psk).setVisibility(View.GONE);
mView.findViewById(R.id.ipsec_user).setVisibility(View.GONE);
mView.findViewById(R.id.ipsec_peer).setVisibility(View.GONE);
@@ -403,34 +351,18 @@
setUsernamePasswordVisibility(type);
// Always enable identity for IKEv2/IPsec profiles.
- if (!isLegacyType(type)) {
- mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.VISIBLE);
- }
+ mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.VISIBLE);
// Then, unhide type-specific fields.
switch (type) {
- case VpnProfile.TYPE_PPTP:
- mMppe.setVisibility(View.VISIBLE);
- break;
-
- case VpnProfile.TYPE_L2TP_IPSEC_PSK:
- mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
- // fall through
- case VpnProfile.TYPE_IKEV2_IPSEC_PSK: // fall through
- case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
mView.findViewById(R.id.ipsec_psk).setVisibility(View.VISIBLE);
mView.findViewById(R.id.options_ipsec_identity).setVisibility(View.VISIBLE);
break;
-
- case VpnProfile.TYPE_L2TP_IPSEC_RSA:
- mView.findViewById(R.id.l2tp).setVisibility(View.VISIBLE);
- // fall through
- case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
- case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+ case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
mView.findViewById(R.id.ipsec_user).setVisibility(View.VISIBLE);
// fall through
- case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: // fall through
- case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+ case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
mView.findViewById(R.id.ipsec_peer).setVisibility(View.VISIBLE);
break;
}
@@ -443,7 +375,8 @@
return false;
}
- final int type = getSelectedVpnType();
+ final int position = mType.getSelectedItemPosition();
+ final int type = VPN_TYPES.get(position);
if (!editing && requiresUsernamePassword(type)) {
return mUsername.getText().length() != 0 && mPassword.getText().length() != 0;
}
@@ -451,15 +384,8 @@
return false;
}
- // TODO(b/149070123): Add ability for platform VPNs to support DNS & routes
- if (isLegacyType(mProfile.type)
- && (!validateAddresses(mDnsServers.getText().toString(), false)
- || !validateAddresses(mRoutes.getText().toString(), true))) {
- return false;
- }
-
// All IKEv2 methods require an identifier
- if (!isLegacyType(mProfile.type) && mIpsecIdentifier.getText().length() == 0) {
+ if (mIpsecIdentifier.getText().length() == 0) {
return false;
}
@@ -468,56 +394,23 @@
}
switch (type) {
- case VpnProfile.TYPE_PPTP: // fall through
- case VpnProfile.TYPE_IPSEC_HYBRID_RSA: // fall through
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
return true;
- case VpnProfile.TYPE_IKEV2_IPSEC_PSK: // fall through
- case VpnProfile.TYPE_L2TP_IPSEC_PSK: // fall through
- case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+ case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
return mIpsecSecret.getText().length() != 0;
- case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
- case VpnProfile.TYPE_L2TP_IPSEC_RSA: // fall through
- case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+ case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
return mIpsecUserCert.getSelectedItemPosition() != 0;
}
return false;
}
- private boolean validateAddresses(String addresses, boolean cidr) {
- try {
- for (String address : addresses.split(" ")) {
- if (address.isEmpty()) {
- continue;
- }
- // Legacy VPN currently only supports IPv4.
- int prefixLength = 32;
- if (cidr) {
- String[] parts = address.split("/", 2);
- address = parts[0];
- prefixLength = Integer.parseInt(parts[1]);
- }
- byte[] bytes = InetAddress.parseNumericAddress(address).getAddress();
- int integer = (bytes[3] & 0xFF) | (bytes[2] & 0xFF) << 8 |
- (bytes[1] & 0xFF) << 16 | (bytes[0] & 0xFF) << 24;
- if (bytes.length != 4 || prefixLength < 0 || prefixLength > 32 ||
- (prefixLength < 32 && (integer << prefixLength) != 0)) {
- return false;
- }
- }
- } catch (Exception e) {
- return false;
- }
- return true;
- }
-
private void setTypesByFeature(Spinner typeSpinner) {
String[] types = getContext().getResources().getStringArray(R.array.vpn_types);
- mTotalTypes = new ArrayList<>(Arrays.asList(types));
- mAllowedTypes = new ArrayList<>(Arrays.asList(types));
-
+ if (types.length != VPN_TYPES.size()) {
+ Log.wtf(TAG, "VPN_TYPES array length does not match string array");
+ }
// Although FEATURE_IPSEC_TUNNELS should always be present in android S and beyond,
// keep this check here just to be safe.
if (!getContext().getPackageManager().hasSystemFeature(
@@ -532,17 +425,6 @@
mProfile.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
}
- // Remove all types which are legacy types from the typesList
- if (!VpnProfile.isLegacyType(mProfile.type)) {
- for (int i = mAllowedTypes.size() - 1; i >= 0; i--) {
- // This must be removed from back to front in order to ensure index consistency
- if (VpnProfile.isLegacyType(i)) {
- mAllowedTypes.remove(i);
- }
- }
-
- types = mAllowedTypes.toArray(new String[0]);
- }
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(
getContext(), android.R.layout.simple_spinner_item, types);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
@@ -606,20 +488,14 @@
// First, save common fields.
VpnProfile profile = new VpnProfile(mProfile.key);
profile.name = mName.getText().toString();
- profile.type = getSelectedVpnType();
+ final int position = mType.getSelectedItemPosition();
+ profile.type = VPN_TYPES.get(position);
profile.server = mServer.getText().toString().trim();
profile.username = mUsername.getText().toString();
profile.password = mPassword.getText().toString();
// Save fields based on VPN type.
- if (isLegacyType(profile.type)) {
- // TODO(b/149070123): Add ability for platform VPNs to support DNS & routes
- profile.searchDomains = mSearchDomains.getText().toString().trim();
- profile.dnsServers = mDnsServers.getText().toString().trim();
- profile.routes = mRoutes.getText().toString().trim();
- } else {
- profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
- }
+ profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
if (hasProxy()) {
String proxyHost = mProxyHost.getText().toString().trim();
@@ -640,34 +516,17 @@
}
// Then, save type-specific fields.
switch (profile.type) {
- case VpnProfile.TYPE_PPTP:
- profile.mppe = mMppe.isChecked();
- break;
-
- case VpnProfile.TYPE_L2TP_IPSEC_PSK:
- profile.l2tpSecret = mL2tpSecret.getText().toString();
- // fall through
- case VpnProfile.TYPE_IKEV2_IPSEC_PSK: // fall through
- case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
- profile.ipsecIdentifier = mIpsecIdentifier.getText().toString();
+ case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
profile.ipsecSecret = mIpsecSecret.getText().toString();
break;
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
if (mIpsecUserCert.getSelectedItemPosition() != 0) {
- profile.ipsecSecret = (String) mIpsecUserCert.getSelectedItem();
- }
- // fall through
- case VpnProfile.TYPE_L2TP_IPSEC_RSA:
- profile.l2tpSecret = mL2tpSecret.getText().toString();
- // fall through
- case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
- if (mIpsecUserCert.getSelectedItemPosition() != 0) {
profile.ipsecUserCert = (String) mIpsecUserCert.getSelectedItem();
+ profile.ipsecSecret = profile.ipsecUserCert;
}
// fall through
- case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: // fall through
- case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+ case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
if (mIpsecCaCert.getSelectedItemPosition() != 0) {
profile.ipsecCaCert = (String) mIpsecCaCert.getSelectedItem();
}
@@ -692,19 +551,13 @@
return ProxyUtils.validate(host, port, "") == ProxyUtils.PROXY_VALID;
}
- private int getSelectedVpnType() {
- return convertAllowedIndexToProfileType(mType.getSelectedItemPosition());
- }
-
- private int convertAllowedIndexToProfileType(int allowedSelectedPosition) {
- if (mAllowedTypes != null && mTotalTypes != null) {
- final String typeString = mAllowedTypes.get(allowedSelectedPosition);
- final int profileType = mTotalTypes.indexOf(typeString);
- return profileType;
- } else {
- Log.w(TAG, "Allowed or Total vpn types not initialized when converting protileType");
- return allowedSelectedPosition;
+ private int convertVpnProfileConstantToTypeIndex(int vpnType) {
+ final int typeIndex = VPN_TYPES.indexOf(vpnType);
+ if (typeIndex == -1) {
+ // Existing legacy profile type
+ Log.wtf(TAG, "Invalid existing profile type");
+ return 0;
}
+ return typeIndex;
}
-
}
diff --git a/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java
index 87cd544..14306e0 100644
--- a/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java
@@ -26,31 +26,39 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Activity;
import android.content.ComponentName;
-import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.LayoutInflater;
import android.widget.PopupWindow;
import android.widget.SeekBar;
+import androidx.fragment.app.testing.EmptyFragmentActivity;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder;
import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.testutils.shadow.ShadowFragment;
-import com.android.settings.testutils.shadow.ShadowInteractionJankMonitor;
import com.android.settings.widget.LabeledSeekBarPreference;
+import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor;
+
+import com.google.android.setupcompat.util.WizardManagerHelper;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
@@ -64,10 +72,16 @@
@LooperMode(LooperMode.Mode.LEGACY)
@Config(shadows = {ShadowInteractionJankMonitor.class})
public class PreviewSizeSeekBarControllerTest {
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Rule
+ public ActivityScenarioRule<EmptyFragmentActivity> rule =
+ new ActivityScenarioRule<>(EmptyFragmentActivity.class);
private static final String FONT_SIZE_KEY = "font_size";
private static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow";
- @Spy
- private final Context mContext = ApplicationProvider.getApplicationContext();
+ private Activity mContext;
private PreviewSizeSeekBarController mSeekBarController;
private FontSizeData mFontSizeData;
private LabeledSeekBarPreference mSeekBarPreference;
@@ -91,7 +105,9 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ ShadowInteractionJankMonitor.reset();
+ rule.getScenario().onActivity(activity -> mContext = activity);
mContext.setTheme(androidx.appcompat.R.style.Theme_AppCompat);
mFragment = spy(new TestFragment());
when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager);
@@ -197,6 +213,24 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_REMOVE_QS_TOOLTIP_IN_SUW)
+ public void onProgressChanged_inSuw_toolTipShouldNotShown() {
+ Intent intent = mContext.getIntent();
+ intent.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true);
+ mContext.setIntent(intent);
+ mSeekBarController.displayPreference(mPreferenceScreen);
+
+ // Simulate changing the progress for the first time
+ int newProgress = (mSeekBarPreference.getProgress() != 0) ? 0 : mSeekBarPreference.getMax();
+ mSeekBarPreference.setProgress(newProgress);
+ mSeekBarPreference.onProgressChanged(new SeekBar(mContext),
+ newProgress,
+ /* fromUser= */ false);
+
+ assertThat(getLatestPopupWindow()).isNull();
+ }
+
+ @Test
public void onProgressChanged_tooltipViewHasBeenShown_notShowTooltipView() {
mSeekBarController.displayPreference(mPreferenceScreen);
// Simulate changing the progress for the first time
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
index 66211a2..2c59c26 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java
@@ -60,6 +60,8 @@
import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settingslib.widget.TopIntroPreference;
+import com.google.android.setupcompat.util.WizardManagerHelper;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -302,6 +304,20 @@
}
@Test
+ @RequiresFlagsEnabled(com.android.settings.accessibility.Flags.FLAG_REMOVE_QS_TOOLTIP_IN_SUW)
+ @Config(shadows = ShadowFragment.class)
+ public void onPreferenceToggledOnEnabledService_inSuw_toolTipViewShouldNotShow() {
+ Intent suwIntent = new Intent();
+ suwIntent.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true);
+ when(mActivity.getIntent()).thenReturn(suwIntent);
+
+ mFragment.onPreferenceToggled(
+ ToggleFeaturePreferenceFragment.KEY_USE_SERVICE_PREFERENCE, /* enabled= */ true);
+
+ assertThat(getLatestPopupWindow()).isNull();
+ }
+
+ @Test
@Config(shadows = ShadowFragment.class)
public void onPreferenceToggledOnEnabledService_tooltipViewShown_notShowTooltipView() {
mFragment.onPreferenceToggled(
diff --git a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
index 7b7c7a6..1d841fa 100644
--- a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
+++ b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
@@ -239,6 +239,7 @@
ReflectionHelpers.setField(mFragment, "mUnrestrictedData", unrestrictedDataPref);
ReflectionHelpers.setField(mFragment, "mDataSaverBackend", dataSaverBackend);
ReflectionHelpers.setField(mFragment.services, "mPolicyManager", networkPolicyManager);
+ ReflectionHelpers.setField(mFragment, "mContext", RuntimeEnvironment.application);
when(mFragment.getListView()).thenReturn(mock(RecyclerView.class));
ShadowRestrictedLockUtilsInternal.setRestricted(true);
diff --git a/tests/robotests/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerTest.java b/tests/robotests/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerTest.java
index 686df7a..e623eb8 100644
--- a/tests/robotests/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerTest.java
+++ b/tests/robotests/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerTest.java
@@ -19,7 +19,6 @@
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController.ANGLE_DRIVER_SUFFIX;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController.PROPERTY_DEBUG_ANGLE_DEVELOPER_OPTION;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController.PROPERTY_PERSISTENT_GRAPHICS_EGL;
-import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController.PROPERTY_RO_GFX_ANGLE_SUPPORTED;
import static com.google.common.truth.Truth.assertThat;
@@ -83,7 +82,6 @@
@Test
public void onPreferenceChange_switchOn_shouldEnableAngleAsSystemDriver() {
- ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "true");
// since GraphicsEnvironment is mocked in Robolectric test environment,
// we will override the system property persist.graphics.egl as if it is changed by
// mGraphicsEnvironment.toggleAngleAsSystemDriver(true).
@@ -100,7 +98,6 @@
@Test
public void onPreferenceChange_switchOff_shouldDisableAngleAsSystemDriver() {
- ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "true");
// since GraphicsEnvironment is mocked in Robolectric test environment,
// we will override the system property persist.graphics.egl as if it is changed by
// mGraphicsEnvironment.toggleAngleAsSystemDriver(false).
@@ -116,30 +113,14 @@
}
@Test
- public void updateState_angleNotSupported_preferenceShouldNotBeChecked() {
- ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "");
- mController.updateState(mPreference);
- verify(mPreference).setChecked(false);
- }
-
- @Test
- public void updateState_angleNotSupported_preferenceShouldNotBeEnabled() {
- ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "");
- mController.updateState(mPreference);
- verify(mPreference).setEnabled(false);
- }
-
- @Test
- public void updateState_angleSupported_angleUsed_preferenceShouldBeChecked() {
- ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "true");
+ public void updateState_angleUsed_preferenceShouldBeChecked() {
ShadowSystemProperties.override(PROPERTY_PERSISTENT_GRAPHICS_EGL, ANGLE_DRIVER_SUFFIX);
mController.updateState(mPreference);
verify(mPreference).setChecked(true);
}
@Test
- public void updateState_angleSupported_angleNotUsed_preferenceShouldNotBeChecked() {
- ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "true");
+ public void updateState_angleNotUsed_preferenceShouldNotBeChecked() {
ShadowSystemProperties.override(PROPERTY_PERSISTENT_GRAPHICS_EGL, "");
mController.updateState(mPreference);
verify(mPreference).setChecked(false);
@@ -147,7 +128,6 @@
@Test
public void onDeveloperOptionSwitchDisabled_shouldDisableAngleAsSystemDriver() {
- ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "true");
mController.onDeveloperOptionsSwitchDisabled();
final String systemEGLDriver = SystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL);
assertThat(systemEGLDriver).isEqualTo("");
@@ -155,14 +135,12 @@
@Test
public void onDeveloperOptionSwitchDisabled_preferenceShouldNotBeChecked() {
- ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "true");
mController.onDeveloperOptionsSwitchDisabled();
verify(mPreference).setChecked(false);
}
@Test
public void onDeveloperOptionsSwitchDisabled_preferenceShouldNotBeEnabled() {
- ShadowSystemProperties.override(PROPERTY_RO_GFX_ANGLE_SUPPORTED, "true");
mController.onDeveloperOptionsSwitchDisabled();
verify(mPreference).setEnabled(false);
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManagerTest.java
index cdf1514..bfa7cfa 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManagerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/datasaver/DynamicDenylistManagerTest.java
@@ -19,138 +19,373 @@
import static android.net.NetworkPolicyManager.POLICY_NONE;
import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
+import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
+import static com.android.settings.fuelgauge.datasaver.DynamicDenylistManager.PREF_KEY_MANUAL_DENYLIST_SYNCED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.net.NetworkPolicyManager;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import java.util.Collections;
+import java.util.List;
+
@RunWith(RobolectricTestRunner.class)
public class DynamicDenylistManagerTest {
- private static final String FAKE_UID_1 = "package_uid_1";
- private static final String FAKE_UID_2 = "package_uid_2";
+ private static final int[] EMPTY_ARRAY = new int[]{};
+ private static final String FAKE_UID_1 = "1001";
+ private static final String FAKE_UID_2 = "1002";
+ private static final int FAKE_UID_1_INT = Integer.parseInt(FAKE_UID_1);
+ private static final int FAKE_UID_2_INT = Integer.parseInt(FAKE_UID_2);
private SharedPreferences mManualDenyListPref;
private SharedPreferences mDynamicDenyListPref;
private DynamicDenylistManager mDynamicDenylistManager;
- private Context mContext;
+
+ @Mock
+ private NetworkPolicyManager mNetworkPolicyManager;
+ @Mock
+ private PackageManager mPackageManager;
@Before
public void setUp() {
- mContext = RuntimeEnvironment.application.getApplicationContext();
- mDynamicDenylistManager = new DynamicDenylistManager(mContext);
- mManualDenyListPref = mDynamicDenylistManager.getManualDenylistPref();
- mDynamicDenyListPref = mDynamicDenylistManager.getDynamicDenylistPref();
+ MockitoAnnotations.initMocks(this);
}
@After
public void tearDown() {
- mDynamicDenylistManager.clearManualDenylistPref();
- mDynamicDenylistManager.clearDynamicDenylistPref();
+ mDynamicDenylistManager.clearSharedPreferences();
}
@Test
- public void getManualDenylistPref_isEmpty() {
- assertThat(mManualDenyListPref.getAll()).isEmpty();
+ public void init_withoutExistedRejectPolicy_createWithExpectedValue() {
+ initDynamicDenylistManager(EMPTY_ARRAY);
+
+ assertThat(mManualDenyListPref.getAll()).hasSize(1);
+ assertTrue(mManualDenyListPref.contains(PREF_KEY_MANUAL_DENYLIST_SYNCED));
}
@Test
- public void getDynamicDenylistPref_isEmpty() {
- assertThat(mDynamicDenyListPref.getAll()).isEmpty();
+ public void init_withExistedRejectPolicy_createWithExpectedValue() {
+ initDynamicDenylistManager(new int[]{FAKE_UID_1_INT, FAKE_UID_2_INT});
+
+ assertThat(mManualDenyListPref.getAll()).hasSize(3);
+ assertTrue(mManualDenyListPref.contains(PREF_KEY_MANUAL_DENYLIST_SYNCED));
+ assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+ assertTrue(mManualDenyListPref.contains(FAKE_UID_2));
}
@Test
public void getManualDenylistPref_initiated_containsExpectedValue() {
- mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+ initDynamicDenylistManager(EMPTY_ARRAY);
- assertThat(mManualDenyListPref.getAll().size()).isEqualTo(1);
+ setupPreference(mManualDenyListPref, FAKE_UID_1);
+
assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
}
@Test
public void getDynamicDenylistPref_initiated_containsExpectedValue() {
- mDynamicDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+ initDynamicDenylistManager(EMPTY_ARRAY);
- assertThat(mDynamicDenyListPref.getAll()).hasSize(1);
+ setupPreference(mDynamicDenyListPref, FAKE_UID_1);
+
assertTrue(mDynamicDenyListPref.contains(FAKE_UID_1));
}
@Test
public void updateManualDenylist_policyReject_addsUid() {
- mDynamicDenylistManager.updateManualDenylist(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND);
+ initDynamicDenylistManager(EMPTY_ARRAY);
- assertThat(mManualDenyListPref.getAll()).hasSize(1);
+ mDynamicDenylistManager.updateDenylistPref(FAKE_UID_1_INT,
+ POLICY_REJECT_METERED_BACKGROUND);
+
assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
}
@Test
public void updateManualDenylist_policyNone_removesUid() {
- mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+ initDynamicDenylistManager(EMPTY_ARRAY);
+ setupPreference(mManualDenyListPref, FAKE_UID_1);
assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
- mDynamicDenylistManager.updateManualDenylist(FAKE_UID_1, POLICY_NONE);
+ mDynamicDenylistManager.updateDenylistPref(FAKE_UID_1_INT, POLICY_NONE);
- assertThat(mManualDenyListPref.getAll()).isEmpty();
+ assertFalse(mManualDenyListPref.contains(FAKE_UID_1));
}
@Test
public void updateManualDenylist_samePolicy_doNothing() {
- mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+ initDynamicDenylistManager(EMPTY_ARRAY);
+ setupPreference(mManualDenyListPref, FAKE_UID_1);
assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+ assertThat(mManualDenyListPref.getAll()).hasSize(2);
- mDynamicDenylistManager.updateManualDenylist(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND);
+ mDynamicDenylistManager.updateDenylistPref(FAKE_UID_1_INT,
+ POLICY_REJECT_METERED_BACKGROUND);
- assertThat(mManualDenyListPref.getAll()).hasSize(1);
+ assertThat(mManualDenyListPref.getAll()).hasSize(2);
}
@Test
- public void isManualDenylist_returnsFalse() {
- assertFalse(mDynamicDenylistManager.isInManualDenylist(FAKE_UID_1));
+ public void setUidPolicyLocked_invokeSetUidPolicy() {
+ initDynamicDenylistManager(EMPTY_ARRAY);
+
+ mDynamicDenylistManager.setUidPolicyLocked(FAKE_UID_1_INT,
+ POLICY_REJECT_METERED_BACKGROUND);
+
+ assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+ verify(mNetworkPolicyManager).setUidPolicy(eq(FAKE_UID_1_INT),
+ eq(POLICY_REJECT_METERED_BACKGROUND));
}
@Test
- public void isManualDenylist_incorrectUid_returnsFalse() {
+ public void setDenylist_emptyListAndNoData_doNothing() {
+ initDynamicDenylistManager(EMPTY_ARRAY);
+
+ mDynamicDenylistManager.setDenylist(Collections.emptyList());
+
+ verify(mNetworkPolicyManager, never()).setUidPolicy(anyInt(), anyInt());
+ }
+
+ @Test
+ public void setDenylist_uidDeniedAlready_doNothing()
+ throws PackageManager.NameNotFoundException {
+ when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(FAKE_UID_1_INT);
+ initDynamicDenylistManager(new int[]{FAKE_UID_1_INT});
+
+ mDynamicDenylistManager.setDenylist(List.of(FAKE_UID_1));
+
+ verify(mNetworkPolicyManager, never()).setUidPolicy(anyInt(), anyInt());
+ }
+
+ @Test
+ public void setDenylist_sameList_doNothing() throws PackageManager.NameNotFoundException {
+ when(mPackageManager.getPackageUid(eq(FAKE_UID_1), eq(0))).thenReturn(FAKE_UID_1_INT);
+ when(mPackageManager.getPackageUid(eq(FAKE_UID_2), eq(0))).thenReturn(FAKE_UID_2_INT);
+ initDynamicDenylistManager(EMPTY_ARRAY);
+ setupPreference(mDynamicDenyListPref, FAKE_UID_2, FAKE_UID_1);
+
+ mDynamicDenylistManager.setDenylist(List.of(FAKE_UID_1, FAKE_UID_2));
+
+ verify(mNetworkPolicyManager, never()).setUidPolicy(anyInt(), anyInt());
+ }
+
+ @Test
+ public void setDenylist_newListWithOldData_modifyPolicyNoneAndReject()
+ throws PackageManager.NameNotFoundException {
+ when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(
+ Integer.parseInt(FAKE_UID_1));
+ initDynamicDenylistManager(EMPTY_ARRAY);
+ setupPreference(mDynamicDenyListPref, FAKE_UID_2);
+
+ mDynamicDenylistManager.setDenylist(List.of(FAKE_UID_1));
+
+ verify(mNetworkPolicyManager).setUidPolicy(FAKE_UID_2_INT, POLICY_NONE);
+ verify(mNetworkPolicyManager).setUidPolicy(FAKE_UID_1_INT,
+ POLICY_REJECT_METERED_BACKGROUND);
+ assertThat(mDynamicDenyListPref.getAll()).hasSize(1);
+ assertTrue(mDynamicDenyListPref.contains(FAKE_UID_1));
+ }
+
+ @Test
+ public void setDenylist_newListWithoutOldData_modifyPolicyReject()
+ throws PackageManager.NameNotFoundException {
+ when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(
+ Integer.parseInt(FAKE_UID_1));
+ initDynamicDenylistManager(EMPTY_ARRAY);
+
+ mDynamicDenylistManager.setDenylist(List.of(FAKE_UID_1));
+
+ verify(mNetworkPolicyManager, never()).setUidPolicy(anyInt(), eq(POLICY_NONE));
+ verify(mNetworkPolicyManager).setUidPolicy(FAKE_UID_1_INT,
+ POLICY_REJECT_METERED_BACKGROUND);
+ assertThat(mDynamicDenyListPref.getAll()).hasSize(1);
+ assertTrue(mDynamicDenyListPref.contains(FAKE_UID_1));
+ }
+
+ @Test
+ public void setDenylist_emptyListWithOldData_modifyPolicyNone() {
+ initDynamicDenylistManager(EMPTY_ARRAY);
+ setupPreference(mDynamicDenyListPref, FAKE_UID_2);
+
+ mDynamicDenylistManager.setDenylist(Collections.emptyList());
+
+ verify(mNetworkPolicyManager).setUidPolicy(FAKE_UID_2_INT, POLICY_NONE);
+ verify(mNetworkPolicyManager, never()).setUidPolicy(anyInt(),
+ eq(POLICY_REJECT_METERED_BACKGROUND));
+ assertThat(mDynamicDenyListPref.getAll()).isEmpty();
+ }
+
+ @Test
+ public void isInManualDenylist_returnsFalse() {
+ initDynamicDenylistManager(EMPTY_ARRAY);
+
+ assertFalse(mDynamicDenylistManager.isInManualDenylist(FAKE_UID_1_INT));
+ }
+
+ @Test
+ public void isInManualDenylist_incorrectUid_returnsFalse() {
+ initDynamicDenylistManager(EMPTY_ARRAY);
+
mManualDenyListPref.edit().putInt(FAKE_UID_2, POLICY_REJECT_METERED_BACKGROUND).apply();
- assertFalse(mDynamicDenylistManager.isInManualDenylist(FAKE_UID_1));
+ assertFalse(mDynamicDenylistManager.isInManualDenylist(FAKE_UID_1_INT));
}
@Test
- public void isManualDenylist_initiated_returnsTrue() {
+ public void isInManualDenylist_initiated_returnsTrue() {
+ initDynamicDenylistManager(EMPTY_ARRAY);
+
mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
- assertTrue(mDynamicDenylistManager.isInManualDenylist(FAKE_UID_1));
+ assertTrue(mDynamicDenylistManager.isInManualDenylist(FAKE_UID_1_INT));
}
@Test
- public void clearManualDenylistPref_isEmpty() {
- mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+ public void resetDenylistIfNeeded_nullPackageName_doNothing() {
+ initDynamicDenylistManager(new int[0], new int[]{FAKE_UID_1_INT, FAKE_UID_2_INT});
+
+ mDynamicDenylistManager.resetDenylistIfNeeded(null, false);
+
assertThat(mManualDenyListPref.getAll()).hasSize(1);
- assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+ verify(mNetworkPolicyManager, never()).setUidPolicy(anyInt(), eq(POLICY_NONE));
+ }
- mDynamicDenylistManager.clearManualDenylistPref();
+ @Test
+ public void resetDenylistIfNeeded_invalidPackageName_doNothing() {
+ initDynamicDenylistManager(new int[0], new int[]{FAKE_UID_1_INT, FAKE_UID_2_INT});
+
+ mDynamicDenylistManager.resetDenylistIfNeeded("invalid_package_name", false);
+
+ assertThat(mManualDenyListPref.getAll()).hasSize(1);
+ verify(mNetworkPolicyManager, never()).setUidPolicy(anyInt(), eq(POLICY_NONE));
+ }
+
+ @Test
+ public void resetDenylistIfNeeded_denylistUnchanged_doNothingWithPolicy() {
+ initDynamicDenylistManager(new int[]{FAKE_UID_1_INT, FAKE_UID_2_INT});
+
+ mDynamicDenylistManager.resetDenylistIfNeeded(SETTINGS_PACKAGE_NAME, false);
+
+ verify(mNetworkPolicyManager, never()).setUidPolicy(anyInt(), eq(POLICY_NONE));
+ }
+
+ @Test
+ public void resetDenylistIfNeeded_denylistChanged_resetAndClear() {
+ initDynamicDenylistManager(new int[0], new int[]{FAKE_UID_1_INT, FAKE_UID_2_INT});
+
+ mDynamicDenylistManager.resetDenylistIfNeeded(SETTINGS_PACKAGE_NAME, false);
+
+ assertThat(mManualDenyListPref.getAll()).isEmpty();
+ verify(mNetworkPolicyManager, times(2)).setUidPolicy(anyInt(), eq(POLICY_NONE));
+ }
+
+ @Test
+ public void resetDenylistIfNeeded_forceResetWithNullPackageName_resetAndClear() {
+ initDynamicDenylistManager(new int[0], new int[]{FAKE_UID_2_INT});
+
+ mDynamicDenylistManager.resetDenylistIfNeeded(null, true);
+
+ assertThat(mManualDenyListPref.getAll()).isEmpty();
+ verify(mNetworkPolicyManager).setUidPolicy(eq(FAKE_UID_2_INT), eq(POLICY_NONE));
+ }
+
+ @Test// 4
+ public void resetDenylistIfNeeded_forceResetWithInvalidPackageName_resetAndClear() {
+ initDynamicDenylistManager(new int[0], new int[]{FAKE_UID_1_INT, FAKE_UID_2_INT});
+
+ mDynamicDenylistManager.resetDenylistIfNeeded("invalid_package_name", true);
+
+ assertThat(mManualDenyListPref.getAll()).isEmpty();
+ verify(mNetworkPolicyManager, times(2)).setUidPolicy(anyInt(), eq(POLICY_NONE));
+ }
+
+ @Test
+ public void resetDenylistIfNeeded_forceResetButDenylistUnchanged_doNothingWithPolicy() {
+ initDynamicDenylistManager(new int[]{FAKE_UID_1_INT});
+
+ mDynamicDenylistManager.resetDenylistIfNeeded(SETTINGS_PACKAGE_NAME, true);
+
+ assertThat(mManualDenyListPref.getAll()).isEmpty();
+ verify(mNetworkPolicyManager, never()).setUidPolicy(anyInt(), eq(POLICY_NONE));
+ }
+
+ @Test
+ public void resetDenylistIfNeeded_forceResetWithDenylistChanged_resetAndClear() {
+ initDynamicDenylistManager(new int[0], new int[]{FAKE_UID_1_INT, FAKE_UID_2_INT});
+
+ mDynamicDenylistManager.resetDenylistIfNeeded(SETTINGS_PACKAGE_NAME, true);
+
+ assertThat(mManualDenyListPref.getAll()).isEmpty();
+ verify(mNetworkPolicyManager, times(2)).setUidPolicy(anyInt(), eq(POLICY_NONE));
+ }
+
+ @Test
+ public void clearSharedPreferences_manualDenyListPrefIsEmpty() {
+ initDynamicDenylistManager(EMPTY_ARRAY);
+ mManualDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
+ assertThat(mManualDenyListPref.getAll()).hasSize(2);
+ assertTrue(mManualDenyListPref.contains(FAKE_UID_1));
+ assertTrue(mManualDenyListPref.contains(PREF_KEY_MANUAL_DENYLIST_SYNCED));
+
+ mDynamicDenylistManager.clearSharedPreferences();
assertThat(mManualDenyListPref.getAll()).isEmpty();
}
@Test
- public void clearDynamicDenylistPref_isEmpty() {
+ public void clearSharedPreferences_dynamicDenyListPrefIsEmpty() {
+ initDynamicDenylistManager(EMPTY_ARRAY);
mDynamicDenyListPref.edit().putInt(FAKE_UID_1, POLICY_REJECT_METERED_BACKGROUND).apply();
assertThat(mDynamicDenyListPref.getAll()).hasSize(1);
assertTrue(mDynamicDenyListPref.contains(FAKE_UID_1));
- mDynamicDenylistManager.clearDynamicDenylistPref();
+ mDynamicDenylistManager.clearSharedPreferences();
assertThat(mDynamicDenyListPref.getAll()).isEmpty();
}
+
+ private void initDynamicDenylistManager(int[] preload) {
+ initDynamicDenylistManager(preload, preload);
+ }
+ private void initDynamicDenylistManager(int[] preload1, int[] preload2) {
+ final Context context = spy(RuntimeEnvironment.application.getApplicationContext());
+ when(context.getApplicationContext()).thenReturn(context);
+ when(context.getPackageManager()).thenReturn(mPackageManager);
+ when(mNetworkPolicyManager.getUidsWithPolicy(anyInt()))
+ .thenReturn(preload1).thenReturn(preload2);
+ mDynamicDenylistManager = new DynamicDenylistManager(context, mNetworkPolicyManager);
+ mManualDenyListPref = mDynamicDenylistManager.getManualDenylistPref();
+ mDynamicDenyListPref = mDynamicDenylistManager.getDynamicDenylistPref();
+ }
+
+ private void setupPreference(SharedPreferences sharedPreferences, String... uids) {
+ for (String uid : uids) {
+ sharedPreferences.edit().putInt(uid, POLICY_REJECT_METERED_BACKGROUND).apply();
+ }
+ }
}
diff --git a/tests/robotests/src/com/android/settings/security/ContentProtectionPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/security/ContentProtectionPreferenceFragmentTest.java
index 2bd91a6..d394582 100644
--- a/tests/robotests/src/com/android/settings/security/ContentProtectionPreferenceFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/security/ContentProtectionPreferenceFragmentTest.java
@@ -18,22 +18,28 @@
import static android.app.settings.SettingsEnums.CONTENT_PROTECTION_PREFERENCE;
+import static com.android.internal.R.string.config_defaultContentProtectionService;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
+import android.content.ComponentName;
import android.content.Context;
+import android.provider.DeviceConfig;
import android.provider.SearchIndexableResource;
+import android.view.contentcapture.ContentCaptureManager;
import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.testutils.XmlTestUtils;
import com.android.settings.testutils.shadow.ShadowDashboardFragment;
+import com.android.settings.testutils.shadow.ShadowDeviceConfig;
import com.android.settings.testutils.shadow.ShadowUtils;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -49,8 +55,15 @@
shadows = {
ShadowDashboardFragment.class,
ShadowUtils.class,
+ ShadowDeviceConfig.class,
})
public class ContentProtectionPreferenceFragmentTest {
+ private static final String PACKAGE_NAME = "com.test.package";
+
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName(PACKAGE_NAME, "TestClass");
+
+ private String mConfigDefaultContentProtectionService = COMPONENT_NAME.flattenToString();
private ContentProtectionPreferenceFragment mFragment;
private Context mContext;
private PreferenceScreen mScreen;
@@ -67,6 +80,11 @@
doReturn(mScreen).when(mFragment).getPreferenceScreen();
}
+ @After
+ public void tearDown() {
+ ShadowDeviceConfig.reset();
+ }
+
@Test
public void getMetricsCategory() {
assertThat(mFragment.getMetricsCategory()).isEqualTo(CONTENT_PROTECTION_PREFERENCE);
@@ -79,7 +97,40 @@
}
@Test
- public void getNonIndexableKeys_existInXmlLayout() {
+ public void getNonIndexableKeys_uiEnabled_existInXmlLayout() {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+ "true",
+ /* makeDefault= */ false);
+ doReturn(mConfigDefaultContentProtectionService)
+ .when(mContext)
+ .getString(config_defaultContentProtectionService);
+
+ final List<String> nonIndexableKeys =
+ ContentProtectionPreferenceFragment.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(
+ mContext);
+ final List<String> allKeys =
+ XmlTestUtils.getKeysFromPreferenceXml(
+ mContext, R.layout.content_protection_preference_fragment);
+ final List<String> nonIndexableKeysExpected =
+ List.of(
+ "content_protection_preference_top_intro",
+ "content_protection_preference_subpage_illustration",
+ "content_protection_preference_user_consent_work_profile_switch");
+
+ assertThat(allKeys).containsAtLeastElementsIn(nonIndexableKeys);
+ assertThat(nonIndexableKeys).isEqualTo(nonIndexableKeysExpected);
+ }
+
+ @Test
+ public void getNonIndexableKeys_uiDisabled_notExisted() {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+ "false",
+ /* makeDefault= */ false);
+
final List<String> nonIndexableKeys =
ContentProtectionPreferenceFragment.SEARCH_INDEX_DATA_PROVIDER.getNonIndexableKeys(
mContext);
@@ -87,7 +138,7 @@
XmlTestUtils.getKeysFromPreferenceXml(
mContext, R.layout.content_protection_preference_fragment);
- assertThat(allKeys).containsAtLeastElementsIn(nonIndexableKeys);
+ assertThat(nonIndexableKeys).isEqualTo(allKeys);
}
@Test
@@ -100,4 +151,29 @@
assertThat(indexRes).isNotEmpty();
assertThat(indexRes.get(0).xmlResId).isEqualTo(mFragment.getPreferenceScreenResId());
}
+
+ @Test
+ public void isPageSearchEnabled_uiDisabled_returnsFalse() {
+ boolean isSearchEnabled =
+ mFragment.SEARCH_INDEX_DATA_PROVIDER.isPageSearchEnabled(mContext);
+
+ assertThat(isSearchEnabled).isFalse();
+ }
+
+ @Test
+ public void isPageSearchEnabled_uiEnabled_returnsTrue() {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+ "true",
+ /* makeDefault= */ false);
+ doReturn(mConfigDefaultContentProtectionService)
+ .when(mContext)
+ .getString(config_defaultContentProtectionService);
+
+ boolean isSearchEnabled =
+ mFragment.SEARCH_INDEX_DATA_PROVIDER.isPageSearchEnabled(mContext);
+
+ assertThat(isSearchEnabled).isTrue();
+ }
}
diff --git a/tests/robotests/src/com/android/settings/security/ContentProtectionPreferenceUtilsTest.java b/tests/robotests/src/com/android/settings/security/ContentProtectionPreferenceUtilsTest.java
new file mode 100644
index 0000000..9b49434
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/security/ContentProtectionPreferenceUtilsTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import static com.android.internal.R.string.config_defaultContentProtectionService;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.provider.DeviceConfig;
+import android.view.contentcapture.ContentCaptureManager;
+
+import com.android.settings.testutils.shadow.ShadowDeviceConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(
+ shadows = {
+ ShadowDeviceConfig.class,
+ })
+public class ContentProtectionPreferenceUtilsTest {
+ private static final String PACKAGE_NAME = "com.test.package";
+
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName(PACKAGE_NAME, "TestClass");
+
+ private String mConfigDefaultContentProtectionService = COMPONENT_NAME.flattenToString();
+
+ @Mock private Context mMockContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @After
+ public void tearDown() {
+ ShadowDeviceConfig.reset();
+ }
+
+ @Test
+ public void isAvailable_bothEnabled_true() {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+ "true",
+ /* makeDefault= */ false);
+ when(mMockContext.getString(config_defaultContentProtectionService))
+ .thenReturn(mConfigDefaultContentProtectionService);
+
+ assertThat(ContentProtectionPreferenceUtils.isAvailable(mMockContext)).isTrue();
+ }
+
+ @Test
+ public void isAvailable_onlyUiEnabled_false() {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+ "true",
+ /* makeDefault= */ false);
+
+ assertThat(ContentProtectionPreferenceUtils.isAvailable(mMockContext)).isFalse();
+ }
+
+ @Test
+ public void isAvailable_onlyServiceEnabled_false() {
+ when(mMockContext.getString(config_defaultContentProtectionService))
+ .thenReturn(mConfigDefaultContentProtectionService);
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+ "false",
+ /* makeDefault= */ false);
+
+ assertThat(ContentProtectionPreferenceUtils.isAvailable(mMockContext)).isFalse();
+ }
+
+ @Test
+ public void isAvailable_emptyComponentName_false() {
+ when(mMockContext.getString(config_defaultContentProtectionService))
+ .thenReturn("");
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+ "true",
+ /* makeDefault= */ false);
+
+ assertThat(ContentProtectionPreferenceUtils.isAvailable(mMockContext)).isFalse();
+ }
+
+ @Test
+ public void isAvailable_blankComponentName_false() {
+ when(mMockContext.getString(config_defaultContentProtectionService))
+ .thenReturn(" ");
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+ "true",
+ /* makeDefault= */ false);
+
+ assertThat(ContentProtectionPreferenceUtils.isAvailable(mMockContext)).isFalse();
+ }
+
+ @Test
+ public void isAvailable_invalidComponentName_false() {
+ when(mMockContext.getString(config_defaultContentProtectionService))
+ .thenReturn("invalid");
+
+ assertThat(ContentProtectionPreferenceUtils.isAvailable(mMockContext)).isFalse();
+ }
+
+
+ @Test
+ public void isAvailable_bothDisabled_false() {
+ DeviceConfig.setProperty(
+ DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
+ ContentCaptureManager.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
+ "false",
+ /* makeDefault= */ false);
+
+ assertThat(ContentProtectionPreferenceUtils.isAvailable(mMockContext)).isFalse();
+ }
+}
diff --git a/tests/unit/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest.java b/tests/unit/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest.java
index a402d91..8304e5d 100644
--- a/tests/unit/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest.java
+++ b/tests/unit/src/com/android/settings/development/graphicsdriver/GraphicsDriverEnableAngleAsSystemDriverControllerJUnitTest.java
@@ -20,7 +20,6 @@
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController.Injector;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController.PROPERTY_DEBUG_ANGLE_DEVELOPER_OPTION;
import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController.PROPERTY_PERSISTENT_GRAPHICS_EGL;
-import static com.android.settings.development.graphicsdriver.GraphicsDriverEnableAngleAsSystemDriverController.PROPERTY_RO_GFX_ANGLE_SUPPORTED;
import static com.google.common.truth.Truth.assertThat;
@@ -181,31 +180,13 @@
}
@Test
- public void updateState_angleNotSupported_PreferenceShouldDisabled() {
- when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())).thenReturn("");
- mController.updateState(mPreference);
- assertThat(mPreference.isEnabled()).isFalse();
- }
-
- @Test
- public void updateState_angleNotSupported_PreferenceShouldNotBeChecked() {
- when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any())).thenReturn("");
- mController.updateState(mPreference);
- assertThat(mPreference.isChecked()).isFalse();
- }
-
- @Test
- public void updateState_angleSupported_PreferenceShouldEnabled() {
- when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any()))
- .thenReturn("true");
+ public void updateState_PreferenceShouldEnabled() {
mController.updateState(mPreference);
assertThat(mPreference.isEnabled()).isTrue();
}
@Test
- public void updateState_angleSupported_angleIsSystemGLESDriver_PreferenceShouldBeChecked() {
- when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any()))
- .thenReturn("true");
+ public void updateState_angleIsSystemGLESDriver_PreferenceShouldBeChecked() {
when(mSystemPropertiesMock.get(eq(PROPERTY_PERSISTENT_GRAPHICS_EGL), any()))
.thenReturn(ANGLE_DRIVER_SUFFIX);
mController.updateState(mPreference);
@@ -213,10 +194,7 @@
}
@Test
- public void
- updateState_angleSupported_angleIsNotSystemGLESDriver_PreferenceShouldNotBeChecked() {
- when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any()))
- .thenReturn("true");
+ public void updateState_angleIsNotSystemGLESDriver_PreferenceShouldNotBeChecked() {
when(mSystemPropertiesMock.get(eq(PROPERTY_PERSISTENT_GRAPHICS_EGL), any())).thenReturn("");
mController.updateState(mPreference);
assertThat(mPreference.isChecked()).isFalse();
@@ -232,8 +210,6 @@
// Test that onDeveloperOptionSwitchDisabled,
// persist.graphics.egl updates to ""
- when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any()))
- .thenReturn("true");
mController.onDeveloperOptionsSwitchDisabled();
propertyChangeSignal1.wait(100);
final String systemEGLDriver = SystemProperties.get(PROPERTY_PERSISTENT_GRAPHICS_EGL);
@@ -245,16 +221,12 @@
@Test
public void onDeveloperOptionSwitchDisabled_PreferenceShouldNotBeChecked() {
- when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any()))
- .thenReturn("true");
mController.onDeveloperOptionsSwitchDisabled();
assertThat(mPreference.isChecked()).isFalse();
}
@Test
public void onDeveloperOptionSwitchDisabled_PreferenceShouldDisabled() {
- when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any()))
- .thenReturn("true");
mController.onDeveloperOptionsSwitchDisabled();
assertThat(mPreference.isEnabled()).isFalse();
}
@@ -480,8 +452,6 @@
// Test that when debug.graphics.angle.developeroption.enable is false:
when(mSystemPropertiesMock.getBoolean(eq(PROPERTY_DEBUG_ANGLE_DEVELOPER_OPTION),
anyBoolean())).thenReturn(false);
- when(mSystemPropertiesMock.get(eq(PROPERTY_RO_GFX_ANGLE_SUPPORTED), any()))
- .thenReturn("true");
// 1. "Enable ANGLE" switch is on, the switch should be enabled.
when(mSystemPropertiesMock.get(eq(PROPERTY_PERSISTENT_GRAPHICS_EGL), any()))
diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceLockControllerTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceLockControllerTest.java
new file mode 100644
index 0000000..0d9db7e
--- /dev/null
+++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceLockControllerTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.privatespace;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.preference.Preference;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.privatespace.onelock.PrivateSpaceLockController;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class PrivateSpaceLockControllerTest {
+ @Mock
+ private Context mContext;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Mock SettingsPreferenceFragment mSettingsPreferenceFragment;
+ @Mock
+ LockPatternUtils mLockPatternUtils;
+
+ private Preference mPreference;
+ private PrivateSpaceLockController mPrivateSpaceLockController;
+
+ /** Required setup before a test. */
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = ApplicationProvider.getApplicationContext();
+ final String preferenceKey = "unlock_set_or_change_private_lock";
+
+ mPreference = new Preference(ApplicationProvider.getApplicationContext());
+ mPreference.setKey(preferenceKey);
+
+ final FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest();
+ when(featureFactory.securityFeatureProvider.getLockPatternUtils(mContext))
+ .thenReturn(mLockPatternUtils);
+
+ mPrivateSpaceLockController = new PrivateSpaceLockController(mContext,
+ mSettingsPreferenceFragment);
+ }
+
+ /** Tests that the controller is always available. */
+ @Test
+ public void getAvailabilityStatus_returnsAvailable() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
+ assertThat(mPrivateSpaceLockController.isAvailable()).isEqualTo(true);
+ }
+
+ /** Tests that preference is disabled and summary says same as device lock. */
+ @Test
+ public void getSummary_whenScreenLock() {
+ doReturn(false).when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt());
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
+ mPrivateSpaceLockController.updateState(mPreference);
+ assertThat(mPreference.isEnabled()).isFalse();
+ assertThat(mPreference.getSummary().toString()).isEqualTo("Same as device screen lock");
+ }
+
+ /** Tests that preference is enabled and summary is Pattern. */
+ @Test
+ public void getSummary_whenProfileLockPattern() {
+ doReturn(true)
+ .when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt());
+ doReturn(CREDENTIAL_TYPE_PATTERN)
+ .when(mLockPatternUtils).getCredentialTypeForUser(anyInt());
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
+ mPrivateSpaceLockController.updateState(mPreference);
+ assertThat(mPreference.isEnabled()).isTrue();
+ assertThat(mPreference.getSummary().toString()).isEqualTo("Pattern");
+ }
+
+ /** Tests that preference is enabled and summary is Pin. */
+ @Test
+ public void getSummary_whenProfileLockPin() {
+ doReturn(true).when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt());
+ doReturn(CREDENTIAL_TYPE_PIN).when(mLockPatternUtils).getCredentialTypeForUser(anyInt());
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
+ mPrivateSpaceLockController.updateState(mPreference);
+ assertThat(mPreference.isEnabled()).isTrue();
+ assertThat(mPreference.getSummary().toString()).isEqualTo("PIN");
+ }
+
+ /** Tests that preference is enabled and summary is Password. */
+ @Test
+ public void getSummary_whenProfileLockPassword() {
+ doReturn(true)
+ .when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt());
+ doReturn(CREDENTIAL_TYPE_PASSWORD)
+ .when(mLockPatternUtils).getCredentialTypeForUser(anyInt());
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
+ mPrivateSpaceLockController.updateState(mPreference);
+ assertThat(mPreference.isEnabled()).isTrue();
+ assertThat(mPreference.getSummary().toString()).isEqualTo("Password");
+ }
+}
diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
new file mode 100644
index 0000000..0a2f3d1
--- /dev/null
+++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.privatespace;
+
+import static com.android.settings.privatespace.PrivateSpaceMaintainer.HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL;
+import static com.android.settings.privatespace.PrivateSpaceMaintainer.HIDE_PRIVATE_SPACE_ENTRY_POINT_ENABLED_VAL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.privatespace.PrivateSpaceMaintainer.ErrorDeletingPrivateSpace;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class PrivateSpaceMaintainerTest {
+ private Context mContext;
+ private ContentResolver mContentResolver;
+
+ /** Required setup before a test. */
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = ApplicationProvider.getApplicationContext();
+ mContentResolver = mContext.getContentResolver();
+ }
+
+ /** Tests that {@link PrivateSpaceMaintainer#deletePrivateSpace()} deletes PS when PS exists. */
+ @Test
+ public void deletePrivateSpace_psExists_deletesPS() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ privateSpaceMaintainer.createPrivateSpace();
+ ErrorDeletingPrivateSpace errorDeletingPrivateSpace =
+ privateSpaceMaintainer.deletePrivateSpace();
+ assertThat(errorDeletingPrivateSpace)
+ .isEqualTo(ErrorDeletingPrivateSpace.DELETE_PS_ERROR_NONE);
+ assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isFalse();
+ }
+
+ /**
+ * Tests that {@link PrivateSpaceMaintainer#deletePrivateSpace()} returns error when PS does
+ * not exist.
+ */
+ @Test
+ public void deletePrivateSpace_psDoesNotExist_returnsNoPSError() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ ErrorDeletingPrivateSpace errorDeletingPrivateSpace =
+ privateSpaceMaintainer.deletePrivateSpace();
+ assertThat(errorDeletingPrivateSpace)
+ .isEqualTo(ErrorDeletingPrivateSpace.DELETE_PS_ERROR_NO_PRIVATE_SPACE);
+ assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isFalse();
+ }
+
+ /** Tests that {@link PrivateSpaceMaintainer#createPrivateSpace()} when PS exists creates PS. */
+ @Test
+ public void createPrivateSpace_psDoesNotExist_createsPS() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ privateSpaceMaintainer.deletePrivateSpace();
+ assertThat(privateSpaceMaintainer.createPrivateSpace()).isTrue();
+ assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isTrue();
+ }
+
+ /**
+ * Tests that {@link PrivateSpaceMaintainer#createPrivateSpace()} when PS exists still
+ * returns true.
+ */
+ @Test
+ public void createPrivateSpace_psExists_returnsFalse() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ privateSpaceMaintainer.deletePrivateSpace();
+ assertThat(privateSpaceMaintainer.createPrivateSpace()).isTrue();
+ assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isTrue();
+ assertThat(privateSpaceMaintainer.createPrivateSpace()).isTrue();
+ }
+
+ /**
+ * Tests that {@link PrivateSpaceMaintainer#createPrivateSpace()} when no PS exists resets PS
+ * Settings.
+ */
+ @Test
+ public void createPrivateSpace_psDoesNotExist_resetsPSSettings() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ Settings.Secure.putInt(
+ mContentResolver,
+ Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT,
+ HIDE_PRIVATE_SPACE_ENTRY_POINT_ENABLED_VAL);
+
+ privateSpaceMaintainer.deletePrivateSpace();
+ privateSpaceMaintainer.createPrivateSpace();
+ assertThat(privateSpaceMaintainer.getHidePrivateSpaceEntryPointSetting())
+ .isEqualTo(HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL);
+ }
+
+ /**
+ * Tests that {@link PrivateSpaceMaintainer#createPrivateSpace()} when PS exist does not reset
+ * PS Settings.
+ */
+ @Test
+ public void createPrivateSpace_psExists_doesNotResetPSSettings() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ privateSpaceMaintainer.createPrivateSpace();
+ Settings.Secure.putInt(
+ mContentResolver,
+ Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT,
+ HIDE_PRIVATE_SPACE_ENTRY_POINT_ENABLED_VAL);
+
+ privateSpaceMaintainer.createPrivateSpace();
+ assertThat(privateSpaceMaintainer.getHidePrivateSpaceEntryPointSetting())
+ .isEqualTo(HIDE_PRIVATE_SPACE_ENTRY_POINT_ENABLED_VAL);
+ }
+}
diff --git a/tests/unit/src/com/android/settings/privatespace/UseOneLockControllerTest.java b/tests/unit/src/com/android/settings/privatespace/UseOneLockControllerTest.java
index e7ebb37..744a8ec 100644
--- a/tests/unit/src/com/android/settings/privatespace/UseOneLockControllerTest.java
+++ b/tests/unit/src/com/android/settings/privatespace/UseOneLockControllerTest.java
@@ -16,36 +16,105 @@
package com.android.settings.privatespace;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+
import static com.google.common.truth.Truth.assertThat;
-import android.content.Context;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.os.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.preference.Preference;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.settings.privatespace.onelock.UseOneLockController;
+import com.android.settings.testutils.FakeFeatureFactory;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class UseOneLockControllerTest {
@Mock private Context mContext;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private UseOneLockController mUseOneLockController;
+ private Preference mPreference;
+
+ @Mock
+ LockPatternUtils mLockPatternUtils;
/** Required setup before a test. */
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
mContext = ApplicationProvider.getApplicationContext();
final String preferenceKey = "private_space_use_one_lock";
+ mPreference = new Preference(mContext);
+ final FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest();
+ when(featureFactory.securityFeatureProvider.getLockPatternUtils(mContext))
+ .thenReturn(mLockPatternUtils);
mUseOneLockController = new UseOneLockController(mContext, preferenceKey);
+
}
/** Tests that the controller is always available. */
@Test
public void getAvailabilityStatus_returnsAvailable() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
assertThat(mUseOneLockController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
+
+
+ /** Tests that summary in controller is Pattern. */
+ @Test
+ public void getSummary_whenProfileLockPattern() {
+ doReturn(true)
+ .when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt());
+ doReturn(CREDENTIAL_TYPE_PATTERN)
+ .when(mLockPatternUtils).getCredentialTypeForUser(anyInt());
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
+ mUseOneLockController.updateState(mPreference);
+ assertThat(mUseOneLockController.getSummary().toString()).isEqualTo("Pattern");
+ }
+
+ /** Tests that summary in controller is PIN. */
+ @Test
+ public void getSummary_whenProfileLockPin() {
+ doReturn(true)
+ .when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt());
+ doReturn(CREDENTIAL_TYPE_PIN).when(mLockPatternUtils).getCredentialTypeForUser(anyInt());
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
+ mUseOneLockController.updateState(mPreference);
+ assertThat(mUseOneLockController.getSummary().toString()).isEqualTo("PIN");
+ }
+
+ /** Tests that summary in controller is Password. */
+ @Test
+ public void getSummary_whenProfileLockPassword() {
+ doReturn(true)
+ .when(mLockPatternUtils).isSeparateProfileChallengeEnabled(anyInt());
+ doReturn(CREDENTIAL_TYPE_PASSWORD)
+ .when(mLockPatternUtils).getCredentialTypeForUser(anyInt());
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
+ mUseOneLockController.updateState(mPreference);
+ assertThat(mUseOneLockController.getSummary().toString()).isEqualTo("Password");
+ }
}