Merge "[Panlingual] Should show confirm dialog when use the action of accessibility to change the default locale Bug: 303777391 Test: manual" into main
diff --git a/Android.bp b/Android.bp
index 5a1224c..0777aa7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -76,12 +76,15 @@
"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",
@@ -99,7 +102,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..37b03ba 100644
--- a/aconfig/Android.bp
+++ b/aconfig/Android.bp
@@ -25,3 +25,14 @@
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",
+}
\ 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/res/layout-v34/settingslib_main_switch_bar.xml b/res/layout-v34/settingslib_main_switch_bar.xml
new file mode 100644
index 0000000..3a44d2a
--- /dev/null
+++ b/res/layout-v34/settingslib_main_switch_bar.xml
@@ -0,0 +1,63 @@
+<?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_height="wrap_content"
+ android:layout_width="match_parent"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingTop="@dimen/settingslib_switchbar_margin"
+ android:paddingBottom="@dimen/settingslib_switchbar_margin"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/frame"
+ android:minHeight="@dimen/settingslib_min_switch_bar_height"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:paddingStart="@dimen/settingslib_switchbar_padding_left"
+ android:paddingEnd="@dimen/settingslib_switchbar_padding_right"
+ android:background="@drawable/settingslib_switch_bar_bg">
+
+ <TextView
+ android:id="@+id/switch_text"
+ android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_marginEnd="@dimen/settingslib_switch_title_margin"
+ android:layout_marginVertical="@dimen/settingslib_switch_title_margin"
+ android:layout_gravity="center_vertical"
+ android:ellipsize="end"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ style="@style/MainSwitchText.Settingslib" />
+
+ <com.google.android.material.materialswitch.MaterialSwitch
+ android:id="@android:id/switch_widget"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:background="@null"
+ android:clickable="false"
+ android:focusable="false"
+ android:theme="@style/Theme.Material3.DynamicColors.DayNight" />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/dialog_audio_sharing_disconnect.xml b/res/layout/dialog_audio_sharing_disconnect.xml
new file mode 100644
index 0000000..09bac40
--- /dev/null
+++ b/res/layout/dialog_audio_sharing_disconnect.xml
@@ -0,0 +1,47 @@
+<?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_disconnect_description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAlignment="center"
+ android:layout_gravity="center"/>
+
+ <com.android.internal.widget.RecyclerView
+ android:visibility="visible"
+ android:id="@+id/device_btn_list"
+ android:nestedScrollingEnabled="false"
+ android:overScrollMode="never"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+
+ <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/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/config.xml b/res/values/config.xml
index f50e918..7af29c8 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -761,6 +761,9 @@
<item></item>
</string-array>
+ <!-- Whether to display the "Enable wireless display" menu -->
+ <bool name="config_show_wifi_display_enable_menu">true</bool>
+
<!-- List of packages that should be hidden for MVNO. Do not translate -->
<string-array name="datausage_hiding_carrier_service_package_names" translatable="false"/>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 94a2205..cffaaa8 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] -->
@@ -1228,16 +1244,8 @@
<string name="privatespace_hide_on_summary">On</string>
<!-- System category for the Private Space page. [CHAR LIMIT=30] -->
<string name="private_space_category_system">System</string>
- <!-- Title for the preference to create Private Space. [CHAR LIMIT=60] -->
- <string name="private_space_create_title">Create Private Space</string>
<!-- Title for the preference to delete Private Space. [CHAR LIMIT=60] -->
<string name="private_space_delete_title">Delete Private Space</string>
- <!-- Toast to show when the private space was created. [CHAR LIMIT=NONE] -->
- <string name="private_space_created">Private Space successfully created</string>
- <!-- Toast to show when the private space already exists. [CHAR LIMIT=NONE] -->
- <string name="private_space_already_exists">Private Space already exists</string>
- <!-- Toast to show when the private space could not be created. [CHAR LIMIT=NONE] -->
- <string name="private_space_create_failed">Private Space could not be created</string>
<!-- Toast to show when the private space was deleted. [CHAR LIMIT=NONE] -->
<string name="private_space_deleted">Private Space successfully deleted</string>
<!-- Toast to show when the private space could not be deleted. [CHAR LIMIT=NONE] -->
@@ -6432,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] -->
@@ -6448,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] -->
@@ -6467,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 33243e1..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
@@ -44,12 +56,6 @@
android:title="@string/private_space_category_system">
<Preference
- android:key="private_space_create"
- android:title="@string/private_space_create_title"
- settings:controller="com.android.settings.privatespace.CreatePrivateSpaceController"
- settings:searchable="false" />
-
- <Preference
android:key="private_space_delete"
android:title="@string/private_space_delete_title"
settings:controller="com.android.settings.privatespace.DeletePrivateSpaceController"
@@ -57,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/layout-v34/settingslib_main_switch.xml b/res/xml/widevine_settings.xml
similarity index 60%
rename from res/layout-v34/settingslib_main_switch.xml
rename to res/xml/widevine_settings.xml
index 5ce4581..1c118f0 100644
--- a/res/layout-v34/settingslib_main_switch.xml
+++ b/res/xml/widevine_settings.xml
@@ -15,13 +15,15 @@
~ limitations under the License.
-->
-<com.google.android.material.materialswitch.MaterialSwitch
+<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@android:id/switch_widget"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:background="@null"
- android:clickable="false"
- android:focusable="false"
- android:theme="@style/Theme.Material3.DynamicColors.DayNight" />
+ 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/res/xml/wifi_network_details_fragment2.xml b/res/xml/wifi_network_details_fragment2.xml
index 56e7b04..daff20f 100644
--- a/res/xml/wifi_network_details_fragment2.xml
+++ b/res/xml/wifi_network_details_fragment2.xml
@@ -169,15 +169,11 @@
settings:enableCopying="true"/>
</PreferenceCategory>
- <!-- IPv6 Details -->
- <PreferenceCategory
- android:key="ipv6_category"
- android:title="@string/wifi_details_ipv6_address_header"
- android:selectable="false">
- <Preference
- android:key="ipv6_addresses"
- android:selectable="false"
- settings:enableCopying="true"/>
- </PreferenceCategory>
+ <!-- IPv6 address -->
+ <Preference
+ android:title="@string/wifi_details_ipv6_address_header"
+ android:key="ipv6_addresses"
+ android:selectable="false"
+ settings:enableCopying="true"/>
</PreferenceScreen>
diff --git a/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java b/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java
index 3e3674c..0dbf05e 100644
--- a/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java
+++ b/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java
@@ -32,7 +32,6 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.text.TextUtils;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import android.view.accessibility.AccessibilityManager;
@@ -112,9 +111,7 @@
return new LaunchFragmentArguments(destination, /* arguments= */ null);
}
- if (ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.equals(componentName)
- && FeatureFlagUtils.isEnabled(getContext(),
- FeatureFlagUtils.SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE)) {
+ if (ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.equals(componentName)) {
final String destination = AccessibilityHearingAidsFragment.class.getName();
return new LaunchFragmentArguments(destination, /* arguments= */ null);
}
diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java
index 3aad141..fab6e47 100644
--- a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java
+++ b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java
@@ -25,9 +25,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.Bundle;
import android.text.TextUtils;
-import android.util.FeatureFlagUtils;
import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentManager;
@@ -35,7 +33,6 @@
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
-import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.bluetooth.BluetoothCallback;
@@ -116,17 +113,7 @@
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (TextUtils.equals(preference.getKey(), getPreferenceKey())) {
- final CachedBluetoothDevice device = mHelper.getConnectedHearingAidDevice();
- if (FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE)) {
- launchHearingAidPage();
- return true;
- }
- if (device == null) {
- launchHearingAidInstructionDialog();
- } else {
- launchBluetoothDeviceDetailSetting(device);
- }
+ launchHearingAidPage();
return true;
}
return false;
@@ -215,29 +202,6 @@
mHearingAidPreference = preference;
}
- @VisibleForTesting
- void launchBluetoothDeviceDetailSetting(final CachedBluetoothDevice device) {
- if (device == null) {
- return;
- }
- final Bundle args = new Bundle();
- args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS,
- device.getDevice().getAddress());
-
- new SubSettingLauncher(mContext)
- .setDestination(BluetoothDeviceDetailsFragment.class.getName())
- .setArguments(args)
- .setTitleRes(R.string.device_details_title)
- .setSourceMetricsCategory(getMetricsCategory())
- .launch();
- }
-
- @VisibleForTesting
- void launchHearingAidInstructionDialog() {
- HearingAidDialogFragment fragment = HearingAidDialogFragment.newInstance();
- fragment.show(mFragmentManager, HearingAidDialogFragment.class.toString());
- }
-
private void launchHearingAidPage() {
new SubSettingLauncher(mContext)
.setDestination(AccessibilityHearingAidsFragment.class.getName())
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/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/bluetooth/BluetoothDetailsHearingDeviceControlsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
index c4a4221..18ad210 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsController.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.text.TextUtils;
-import android.util.FeatureFlagUtils;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
@@ -51,8 +50,7 @@
@Override
public boolean isAvailable() {
- return mCachedDevice.isHearingAidDevice() && FeatureFlagUtils.isEnabled(mContext,
- FeatureFlagUtils.SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE);
+ return mCachedDevice.isHearingAidDevice();
}
@Override
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
index 73857f2..a3dace6 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java
@@ -126,9 +126,12 @@
pref.setOnPreferenceClickListener(this);
pref.setOrder(profile.getOrdinal());
- if (profile instanceof LeAudioProfile && !isModelNameInAllowList(
+ boolean isLeEnabledByDefault =
+ SystemProperties.getBoolean(LE_AUDIO_CONNECTION_BY_DEFAULT_PROPERTY, true);
+
+ if (profile instanceof LeAudioProfile && (!isLeEnabledByDefault || !isModelNameInAllowList(
BluetoothUtils.getStringMetaData(mCachedDevice.getDevice(),
- METADATA_MODEL_NAME))) {
+ METADATA_MODEL_NAME)))) {
pref.setSummary(R.string.device_details_leaudio_toggle_summary);
}
return pref;
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 0a6795f..b0f8b8f 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java
@@ -17,7 +17,9 @@
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;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
@@ -41,13 +43,18 @@
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.android.settingslib.bluetooth.LocalBluetoothProfile;
+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;
import java.util.concurrent.Executors;
@@ -67,6 +74,67 @@
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) {
+ Log.d(
+ TAG,
+ "onBroadcastStarted(), reason = "
+ + reason
+ + ", broadcastId = "
+ + broadcastId);
+ }
+
+ @Override
+ 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) {
+ Log.d(
+ TAG,
+ "onBroadcastMetadataChanged(), broadcastId = "
+ + broadcastId
+ + ", metadata = "
+ + metadata);
+ addSourceToTargetDevices(mTargetSinks);
+ mTargetSinks = new ArrayList<>();
+ }
+
+ @Override
+ public void onBroadcastStopped(int reason, int broadcastId) {
+ Log.d(
+ TAG,
+ "onBroadcastStopped(), reason = "
+ + reason
+ + ", broadcastId = "
+ + broadcastId);
+ }
+
+ @Override
+ public void onBroadcastStopFailed(int reason) {
+ Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason);
+ // TODO: handle broadcast stop fail
+ }
+
+ @Override
+ public void onBroadcastUpdated(int reason, int broadcastId) {}
+
+ @Override
+ public void onBroadcastUpdateFailed(int reason, int broadcastId) {}
+
+ @Override
+ public void onPlaybackStarted(int reason, int broadcastId) {}
+
+ @Override
+ public void onPlaybackStopped(int reason, int broadcastId) {}
+ };
private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
new BluetoothLeBroadcastAssistant.Callback() {
@@ -169,8 +237,8 @@
Log.d(TAG, "onStart() Bluetooth is not supported on this device");
return;
}
- if (mAssistant == null) {
- Log.d(TAG, "onStart() Broadcast assistant is not supported on this device");
+ if (mBroadcast == null || mAssistant == null) {
+ Log.d(TAG, "onStart() Broadcast or assistant is not supported on this device");
return;
}
if (mBluetoothDeviceUpdater == null) {
@@ -178,6 +246,7 @@
return;
}
mLocalBtManager.getEventManager().registerCallback(this);
+ mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
mBluetoothDeviceUpdater.registerCallback();
mBluetoothDeviceUpdater.refreshPreference();
@@ -189,8 +258,8 @@
Log.d(TAG, "onStop() Bluetooth is not supported on this device");
return;
}
- if (mAssistant == null) {
- Log.d(TAG, "onStop() Broadcast assistant is not supported on this device");
+ if (mBroadcast == null || mAssistant == null) {
+ Log.d(TAG, "onStop() Broadcast or assistant is not supported on this device");
return;
}
if (mBluetoothDeviceUpdater == null) {
@@ -200,9 +269,12 @@
mLocalBtManager.getEventManager().unregisterCallback(this);
// TODO: verify the reason for failing to unregister
try {
+ mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
} catch (IllegalArgumentException e) {
- Log.e(TAG, "Fail to unregister assistant callback due to " + e.getMessage());
+ Log.e(
+ TAG,
+ "Fail to unregister broadcast or assistant callback due to " + e.getMessage());
}
mBluetoothDeviceUpdater.unregisterCallback();
}
@@ -263,25 +335,39 @@
Log.d(TAG, "Ignore onProfileConnectionStateChanged, not connected state");
return;
}
- List<LocalBluetoothProfile> supportedProfiles = cachedDevice.getProfiles();
- boolean isLeAudioSupported = false;
- for (LocalBluetoothProfile profile : supportedProfiles) {
- if (profile instanceof LeAudioProfile && profile.isEnabled(cachedDevice.getDevice())) {
- isLeAudioSupported = true;
- }
- if (profile.getProfileId() != bluetoothProfile
- && profile.getConnectionStatus(cachedDevice.getDevice())
- == BluetoothProfile.STATE_CONNECTED) {
- Log.d(
- TAG,
- "Ignore onProfileConnectionStateChanged, not the first connected profile");
- return;
- }
+ if (mFragment == null) {
+ Log.d(TAG, "Ignore onProfileConnectionStateChanged, no host fragment");
+ return;
}
- // Show stop audio sharing dialog when an ineligible (not le audio) remote device connected
- // during a sharing session.
- if (isBroadcasting() && !isLeAudioSupported) {
- if (mFragment != null) {
+ if (mAssistant == null && mBroadcast == null) {
+ Log.d(
+ TAG,
+ "Ignore onProfileConnectionStateChanged, no broadcast or assistant supported");
+ return;
+ }
+ boolean isLeAudioSupported = isLeAudioSupported(cachedDevice);
+ // For eligible (LE audio) remote device, we only check its connected LE audio profile.
+ if (isLeAudioSupported && bluetoothProfile != BluetoothProfile.LE_AUDIO) {
+ Log.d(
+ TAG,
+ "Ignore onProfileConnectionStateChanged, not the le profile for le audio"
+ + " device");
+ return;
+ }
+ boolean isFirstConnectedProfile = isFirstConnectedProfile(cachedDevice, bluetoothProfile);
+ // For ineligible (non LE audio) remote device, we only check its first connected profile.
+ if (!isLeAudioSupported && !isFirstConnectedProfile) {
+ Log.d(
+ TAG,
+ "Ignore onProfileConnectionStateChanged, not the first connected profile for"
+ + " non le audio device");
+ return;
+ }
+ if (!isLeAudioSupported) {
+ // Handle connected ineligible (non LE audio) remote device
+ if (isBroadcasting()) {
+ // Show stop audio sharing dialog when an ineligible (non LE audio) remote device
+ // connected during a sharing session.
AudioSharingStopDialogFragment.show(
mFragment,
cachedDevice.getName(),
@@ -289,6 +375,85 @@
mBroadcast.stopBroadcast(mBroadcast.getLatestBroadcastId());
});
}
+ // Do nothing for ineligible (non LE audio) remote device when no sharing session.
+ } else {
+ Map<Integer, List<CachedBluetoothDevice>> groupedDevices =
+ fetchConnectedDevicesByGroupId();
+ // Handle connected eligible (LE audio) remote device
+ if (isBroadcasting()) {
+ // Show audio sharing switch or join dialog according to device count in the sharing
+ // session.
+ ArrayList<AudioSharingDeviceItem> deviceItemsInSharingSession =
+ buildDeviceItemsInSharingSession(groupedDevices);
+ // Show audio sharing switch dialog when the third eligible (LE audio) remote device
+ // connected during a sharing session.
+ if (deviceItemsInSharingSession.size() >= 2) {
+ AudioSharingDisconnectDialogFragment.show(
+ mFragment,
+ deviceItemsInSharingSession,
+ cachedDevice.getName(),
+ (AudioSharingDeviceItem item) -> {
+ // Remove all sources from the device user clicked
+ for (CachedBluetoothDevice device :
+ groupedDevices.get(item.getGroupId())) {
+ for (BluetoothLeBroadcastReceiveState source :
+ mAssistant.getAllSources(device.getDevice())) {
+ mAssistant.removeSource(
+ device.getDevice(), source.getSourceId());
+ }
+ }
+ // Add current broadcast to the latest connected device
+ mAssistant.addSource(
+ cachedDevice.getDevice(),
+ mBroadcast.getLatestBluetoothLeBroadcastMetadata(),
+ /* isGroupOp= */ true);
+ });
+ } else {
+ // 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 {
+ 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(
+ new AudioSharingDeviceItem(device.getName(), device.getGroupId()));
+ }
+ // 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);
+ });
+ }
+ }
}
}
@@ -307,7 +472,93 @@
fragment.getMetricsCategory());
}
+ private boolean isLeAudioSupported(CachedBluetoothDevice cachedDevice) {
+ return cachedDevice.getProfiles().stream()
+ .anyMatch(
+ profile ->
+ profile instanceof LeAudioProfile
+ && profile.isEnabled(cachedDevice.getDevice()));
+ }
+
+ private boolean isFirstConnectedProfile(
+ CachedBluetoothDevice cachedDevice, int bluetoothProfile) {
+ return cachedDevice.getProfiles().stream()
+ .noneMatch(
+ profile ->
+ profile.getProfileId() != bluetoothProfile
+ && profile.getConnectionStatus(cachedDevice.getDevice())
+ == BluetoothProfile.STATE_CONNECTED);
+ }
+
private boolean isBroadcasting() {
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);
+ }
+ 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;
+ }
+ }
+ }
+ return deviceItems;
+ }
+
+ private void addSourceToTargetDevices(List<BluetoothDevice> sinks) {
+ if (sinks.isEmpty() || mBroadcast == null || mAssistant == null) {
+ Log.d(TAG, "Skip adding source to target.");
+ return;
+ }
+ BluetoothLeBroadcastMetadata broadcastMetadata =
+ mBroadcast.getLatestBluetoothLeBroadcastMetadata();
+ if (broadcastMetadata == null) {
+ Log.e(TAG, "Error: There is no broadcastMetadata.");
+ return;
+ }
+ 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/AudioSharingDisconnectDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
new file mode 100644
index 0000000..1840f58
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDisconnectDialogFragment.java
@@ -0,0 +1,124 @@
+/*
+ * 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.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.flags.Flags;
+
+import java.util.ArrayList;
+
+public class AudioSharingDisconnectDialogFragment extends InstrumentedDialogFragment {
+ private static final String TAG = "AudioSharingDisconnectDialog";
+
+ private static final String BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS =
+ "bundle_key_device_to_disconnect_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 device item to disconnect from the audio sharing in the
+ * dialog.
+ *
+ * @param item The device item clicked.
+ */
+ void onItemClick(AudioSharingDeviceItem item);
+ }
+
+ private static DialogEventListener sListener;
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.DIALOG_AUDIO_SHARING_SWITCH_DEVICE;
+ }
+
+ /**
+ * Display the {@link AudioSharingDisconnectDialogFragment} dialog.
+ *
+ * @param host The Fragment this dialog will be hosted.
+ */
+ 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_TO_DISCONNECT_ITEMS, deviceItems);
+ bundle.putString(BUNDLE_KEY_NEW_DEVICE_NAME, newDeviceName);
+ AudioSharingDisconnectDialogFragment dialog =
+ new AudioSharingDisconnectDialogFragment();
+ dialog.setArguments(bundle);
+ dialog.show(manager, TAG);
+ }
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Bundle arguments = requireArguments();
+ ArrayList<AudioSharingDeviceItem> deviceItems =
+ arguments.getParcelableArrayList(BUNDLE_KEY_DEVICE_TO_DISCONNECT_ITEMS);
+ String newDeviceName = arguments.getString(BUNDLE_KEY_NEW_DEVICE_NAME);
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(getActivity())
+ .setTitle("Choose headphone to disconnect")
+ .setCancelable(false);
+ View rootView =
+ LayoutInflater.from(builder.getContext())
+ .inflate(R.layout.dialog_audio_sharing_disconnect, /* parent= */ null);
+ TextView subTitle = rootView.findViewById(R.id.share_audio_disconnect_description);
+ subTitle.setText(
+ "To share audio with " + newDeviceName + ", disconnect another pair of headphone");
+ RecyclerView recyclerView = rootView.findViewById(R.id.device_btn_list);
+ recyclerView.setAdapter(
+ new AudioSharingDeviceAdapter(
+ deviceItems,
+ (AudioSharingDeviceItem item) -> {
+ sListener.onItemClick(item);
+ dismiss();
+ }));
+ recyclerView.setLayoutManager(
+ new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
+ Button cancelBtn = rootView.findViewById(R.id.cancel_btn);
+ cancelBtn.setOnClickListener(
+ v -> {
+ dismiss();
+ });
+ AlertDialog dialog = builder.setView(rootView).create();
+ dialog.setCanceledOnTouchOutside(false);
+ return dialog;
+ }
+}
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..3f9f48e 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -58,11 +58,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 +202,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();
@@ -326,8 +334,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);
});
}
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/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/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/privatespace/CreatePrivateSpaceController.java b/src/com/android/settings/privatespace/CreatePrivateSpaceController.java
deleted file mode 100644
index 3214988..0000000
--- a/src/com/android/settings/privatespace/CreatePrivateSpaceController.java
+++ /dev/null
@@ -1,74 +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 android.text.TextUtils;
-import android.widget.Toast;
-
-import androidx.preference.Preference;
-
-import com.android.settings.R;
-import com.android.settings.core.BasePreferenceController;
-
-// TODO(b/293569406): Remove this when we have the setup flow in place to create PS
-/**
- * Temp Controller to create the private space from the PS Settings page. This is to allow PM, UX,
- * and other folks to play around with PS before the PS setup flow is ready.
- */
-public final class CreatePrivateSpaceController extends BasePreferenceController {
-
- public CreatePrivateSpaceController(Context context, String preferenceKey) {
- super(context, preferenceKey);
- }
-
- @Override
- public int getAvailabilityStatus() {
- return AVAILABLE;
- }
-
- @Override
- public boolean handlePreferenceTreeClick(Preference preference) {
- if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) {
- return false;
- }
-
- if (PrivateSpaceMaintainer.getInstance(mContext).doesPrivateSpaceExist()) {
- showPrivateSpaceAlreadyExistsToast();
- return super.handlePreferenceTreeClick(preference);
- }
-
- if (PrivateSpaceMaintainer.getInstance(mContext).createPrivateSpace()) {
- showPrivateSpaceCreatedToast();
- } else {
- showPrivateSpaceCreationFailedToast();
- }
- return super.handlePreferenceTreeClick(preference);
- }
-
- private void showPrivateSpaceCreatedToast() {
- Toast.makeText(mContext, R.string.private_space_created, Toast.LENGTH_SHORT).show();
- }
-
- private void showPrivateSpaceCreationFailedToast() {
- Toast.makeText(mContext, R.string.private_space_create_failed, Toast.LENGTH_SHORT).show();
- }
-
- private void showPrivateSpaceAlreadyExistsToast() {
- Toast.makeText(mContext, R.string.private_space_already_exists, Toast.LENGTH_SHORT).show();
- }
-}
diff --git a/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java b/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java
index f72bcd9..5d00329 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceDashboardFragment.java
@@ -17,25 +17,13 @@
package com.android.settings.privatespace;
import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.os.Flags;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
-import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settingslib.search.SearchIndexable;
-
-import java.util.List;
/** Fragment representing the Private Space dashboard in Settings. */
-@SearchIndexable
public class PrivateSpaceDashboardFragment extends DashboardFragment {
private static final String TAG = "PrivateSpaceDashboardFragment";
- private static final String KEY_CREATE_PROFILE_PREFERENCE = "private_space_create";
- private static final String KEY_DELETE_PROFILE_PREFERENCE = "private_space_delete";
- private static final String KEY_ONE_LOCK_PREFERENCE = "private_space_use_one_lock";
- private static final String KEY_PS_HIDDEN_PREFERENCE = "private_space_hidden";
@Override
protected int getPreferenceScreenResId() {
@@ -51,23 +39,4 @@
protected String getLogTag() {
return TAG;
}
-
- public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider(R.xml.private_space_settings) {
- @Override
- protected boolean isPageSearchEnabled(Context context) {
- return SafetyCenterManagerWrapper.get().isEnabled(context)
- && Flags.allowPrivateProfile();
- }
-
- @Override
- public List<String> getNonIndexableKeys(Context context) {
- List<String> keys = super.getNonIndexableKeys(context);
- keys.add(KEY_CREATE_PROFILE_PREFERENCE);
- keys.add(KEY_DELETE_PROFILE_PREFERENCE);
- keys.add(KEY_ONE_LOCK_PREFERENCE);
- keys.add(KEY_PS_HIDDEN_PREFERENCE);
- return keys;
- }
- };
}
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/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/src/com/android/settings/wfd/WifiDisplaySettings.java b/src/com/android/settings/wfd/WifiDisplaySettings.java
index 96f067c..2ec69c4 100644
--- a/src/com/android/settings/wfd/WifiDisplaySettings.java
+++ b/src/com/android/settings/wfd/WifiDisplaySettings.java
@@ -140,7 +140,6 @@
mWifiP2pChannel = mWifiP2pManager.initialize(context, Looper.getMainLooper(), null);
addPreferencesFromResource(R.xml.wifi_display_settings);
- setHasOptionsMenu(true);
}
@Override
@@ -197,8 +196,9 @@
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- if (mWifiDisplayStatus != null && mWifiDisplayStatus.getFeatureState()
- != WifiDisplayStatus.FEATURE_STATE_UNAVAILABLE) {
+ if (getResources().getBoolean(R.bool.config_show_wifi_display_enable_menu)
+ && mWifiDisplayStatus != null && mWifiDisplayStatus.getFeatureState()
+ != WifiDisplayStatus.FEATURE_STATE_UNAVAILABLE) {
MenuItem item = menu.add(Menu.NONE, MENU_ID_ENABLE_WIFI_DISPLAY, 0,
R.string.wifi_display_enable_menu_item);
item.setCheckable(true);
diff --git a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
index 5ab8807..faa0c3b 100644
--- a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
+++ b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
@@ -59,7 +59,6 @@
import androidx.annotation.VisibleForTesting;
import androidx.core.text.BidiFormatter;
import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
@@ -184,7 +183,6 @@
private Preference mSubnetPref;
private Preference mDnsPref;
private Preference mTypePref;
- private PreferenceCategory mIpv6Category;
private Preference mIpv6AddressPref;
private final IconInjector mIconInjector;
private final Clock mClock;
@@ -376,8 +374,6 @@
mSubnetPref = screen.findPreference(KEY_SUBNET_MASK_PREF);
mDnsPref = screen.findPreference(KEY_DNS_PREF);
mTypePref = screen.findPreference(KEY_WIFI_TYPE_PREF);
-
- mIpv6Category = screen.findPreference(KEY_IPV6_CATEGORY);
mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
}
@@ -824,7 +820,7 @@
mSubnetPref.setVisible(false);
mGatewayPref.setVisible(false);
mDnsPref.setVisible(false);
- mIpv6Category.setVisible(false);
+ mIpv6AddressPref.setVisible(false);
return;
}
@@ -864,11 +860,11 @@
updatePreference(mDnsPref, dnsServers);
if (ipv6Addresses.length() > 0) {
+ mIpv6AddressPref.setVisible(true);
mIpv6AddressPref.setSummary(
BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString()));
- mIpv6Category.setVisible(true);
} else {
- mIpv6Category.setVisible(false);
+ mIpv6AddressPref.setVisible(false);
}
}
diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java
index 995d74f..c105d08 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java
@@ -32,7 +32,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.util.FeatureFlagUtils;
import android.view.accessibility.AccessibilityManager;
import androidx.test.core.app.ApplicationProvider;
@@ -158,8 +157,6 @@
@Test
public void onCreate_hearingAidsComponentName_launchAccessibilityHearingAidsFragment() {
- FeatureFlagUtils.setEnabled(mContext,
- FeatureFlagUtils.SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, true);
Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_COMPONENT_NAME,
ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java
index 3333782..bb15378 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java
@@ -18,11 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
@@ -35,7 +31,6 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.util.FeatureFlagUtils;
import androidx.preference.Preference;
import androidx.test.core.app.ApplicationProvider;
@@ -111,8 +106,6 @@
@Before
public void setUp() {
- FeatureFlagUtils.setEnabled(mContext,
- FeatureFlagUtils.SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, true);
mShadowApplication = shadowOf((Application) ApplicationProvider.getApplicationContext());
setupEnvironment();
@@ -252,37 +245,6 @@
}
@Test
- public void handleHearingAidPreferenceClick_noHearingAid_launchHearingAidInstructionDialog() {
- FeatureFlagUtils.setEnabled(mContext,
- FeatureFlagUtils.SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, false);
- mPreferenceController = spy(new AccessibilityHearingAidPreferenceController(mContext,
- HEARING_AID_PREFERENCE));
- mPreferenceController.setPreference(mHearingAidPreference);
- doNothing().when(mPreferenceController).launchHearingAidInstructionDialog();
-
- mPreferenceController.handlePreferenceTreeClick(mHearingAidPreference);
-
- verify(mPreferenceController).launchHearingAidInstructionDialog();
- }
-
- @Test
- public void handleHearingAidPreferenceClick_withHearingAid_launchBluetoothDeviceDetailSetting
- () {
- FeatureFlagUtils.setEnabled(mContext,
- FeatureFlagUtils.SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, false);
- mPreferenceController = spy(new AccessibilityHearingAidPreferenceController(mContext,
- HEARING_AID_PREFERENCE));
- mPreferenceController.setPreference(mHearingAidPreference);
- when(mHearingAidProfile.getConnectedDevices()).thenReturn(generateHearingAidDeviceList());
- when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
- doNothing().when(mPreferenceController).launchBluetoothDeviceDetailSetting(any());
-
- mPreferenceController.handlePreferenceTreeClick(mHearingAidPreference);
-
- verify(mPreferenceController).launchBluetoothDeviceDetailSetting(mCachedBluetoothDevice);
- }
-
- @Test
public void onServiceConnected_onHearingAidProfileConnected_updateSummary() {
when(mCachedBluetoothDevice.getDeviceSide()).thenReturn(
HearingAidInfo.DeviceSide.SIDE_LEFT);
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java
index bf4e055..364d299 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceControlsControllerTest.java
@@ -23,7 +23,6 @@
import android.content.Context;
import android.content.Intent;
-import android.util.FeatureFlagUtils;
import androidx.preference.Preference;
@@ -63,8 +62,6 @@
@Test
public void isAvailable_isHearingAidDevice_available() {
- FeatureFlagUtils.setEnabled(mContext,
- FeatureFlagUtils.SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, true);
when(mCachedDevice.isHearingAidDevice()).thenReturn(true);
assertThat(mController.isAvailable()).isTrue();
@@ -72,8 +69,6 @@
@Test
public void isAvailable_isNotHearingAidDevice_notAvailable() {
- FeatureFlagUtils.setEnabled(mContext,
- FeatureFlagUtils.SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, true);
when(mCachedDevice.isHearingAidDevice()).thenReturn(false);
assertThat(mController.isAvailable()).isFalse();
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/batterytip/detectors/IncompatibleChargerDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/IncompatibleChargerDetectorTest.java
index 3f65a67..c0f6108 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/IncompatibleChargerDetectorTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/IncompatibleChargerDetectorTest.java
@@ -81,6 +81,7 @@
when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
when(mUsbPortStatus.isConnected()).thenReturn(true);
- when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[] {1});
+ when(mUsbPortStatus.getComplianceWarnings())
+ .thenReturn(new int[] {UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY});
}
}
diff --git a/tests/robotests/src/com/android/settings/security/ContentProtectionPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/security/ContentProtectionPreferenceFragmentTest.java
index 2bd91a6..3b5dbd6 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,16 @@
}
@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);
@@ -91,6 +118,24 @@
}
@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);
+ final List<String> allKeys =
+ XmlTestUtils.getKeysFromPreferenceXml(
+ mContext, R.layout.content_protection_preference_fragment);
+
+ assertThat(nonIndexableKeys).containsAnyIn(allKeys);
+ }
+
+ @Test
public void searchIndexProvider_shouldIndexResource() {
final List<SearchIndexableResource> indexRes =
ContentProtectionPreferenceFragment.SEARCH_INDEX_DATA_PROVIDER
@@ -100,4 +145,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/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");
+ }
}