Merge changes I96776484,Ic2ea10d9 into main
* changes:
Add BluetoothLeAudioModePreferenceControllerTest
Change the switcher to switch LE audio mode with broadcast
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b956f67..98cb31a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -627,6 +627,27 @@
android:value="true" />
</activity>
+ <activity android:name="Settings$SatelliteSettingActivity"
+ android:label="@string/satellite_setting"
+ android:exported="true"
+ android:configChanges="orientation|keyboardHidden|screenSize">
+ <intent-filter android:priority="1">
+ <action android:name="android.settings.SATELLITE_SETTING" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.VOICE_LAUNCH" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
+ android:value="true" />
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.network.telephony.SatelliteSetting" />
+ <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
+ android:value="@string/menu_key_network"/>
+ </activity>
+
<activity android:name="Settings$ApnSettingsActivity"
android:label="@string/apn_settings"
android:exported="true"
@@ -641,7 +662,7 @@
<category android:name="android.intent.category.VOICE_LAUNCH" />
</intent-filter>
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
- android:value="true" />
+ android:value="true" />
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.network.apn.ApnSettings" />
<meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
@@ -2262,6 +2283,21 @@
android:value="true" />
</activity>
+ <activity
+ android:name="Settings$AccessibilityEditShortcutsActivity"
+ android:label="@string/accessibility_settings"
+ android:exported="true"
+ android:permission="android.permission.MANAGE_ACCESSIBILITY">
+ <intent-filter android:priority="1">
+ <action android:name="android.settings.ACCESSIBILITY_SHORTCUT_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment" />
+ <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
+ android:value="@string/menu_key_accessibility"/>
+ </activity>
+
<activity android:name=".accessibility.AccessibilitySettingsForSetupWizardActivity"
android:icon="@drawable/ic_accessibility_suggestion"
android:label="@string/vision_settings_title"
diff --git a/aconfig/accessibility/accessibility_flags.aconfig b/aconfig/accessibility/accessibility_flags.aconfig
index 1c03a27..ad770fb 100644
--- a/aconfig/accessibility/accessibility_flags.aconfig
+++ b/aconfig/accessibility/accessibility_flags.aconfig
@@ -9,6 +9,19 @@
bug: "300302098"
}
+flag {
+ name: "enable_hearing_aid_preset_control"
+ namespace: "accessibility"
+ description: "Allows users to control hearing aid preset in the Bluetooth device details page."
+ bug: "300015207"
+}
+
+flag {
+ name: "enable_hearing_aid_volume_offset_control"
+ namespace: "accessibility"
+ description: "Allows users to control hearing aid volume offset in the Bluetooth device details page."
+ bug: "301198830"
+}
flag {
name: "remove_qs_tooltip_in_suw"
diff --git a/aconfig/settings_connecteddevice_flag_declarations.aconfig b/aconfig/settings_connecteddevice_flag_declarations.aconfig
index 1a3afed..5ba2129 100644
--- a/aconfig/settings_connecteddevice_flag_declarations.aconfig
+++ b/aconfig/settings_connecteddevice_flag_declarations.aconfig
@@ -27,3 +27,10 @@
description: "Gates whether to require an auth challenge for changing USB preferences"
bug: "317367746"
}
+
+flag {
+ name: "enable_saved_devices_order_by_recency"
+ namespace: "pixel_cross_device_control"
+ description: "Order the saved bluetooth devices by most recently connected."
+ bug: "306160434"
+}
\ No newline at end of file
diff --git a/res-product/values/strings.xml b/res-product/values/strings.xml
index 0e1713c..c9dc248 100644
--- a/res-product/values/strings.xml
+++ b/res-product/values/strings.xml
@@ -719,9 +719,9 @@
<!-- An explanation text that the password needs to be solved because the device was factory reset. [CHAR LIMIT=100] -->
<string name="lockpassword_confirm_your_password_details_frp" product="device">Your device was reset to factory settings. To use this device, enter your previous password.</string>
<!-- Message of incompatible charging battery tip [CHAR LIMIT=NONE] -->
- <string name="battery_tip_incompatible_charging_message" product="default">Battery charging slowly or won\'t charge. Check if the cable and power adapter work with your phone</string>
- <string name="battery_tip_incompatible_charging_message" product="device">Battery charging slowly or won\'t charge. Check if the cable and power adapter work with your device</string>
- <string name="battery_tip_incompatible_charging_message" product="tablet">Battery charging slowly or won\'t charge. Check if the cable and power adapter work with your tablet</string>
+ <string name="battery_tip_incompatible_charging_message" product="default">Your phone may be charging slowly or not charging. For faster charging, use a recommended cable and adapter.</string>
+ <string name="battery_tip_incompatible_charging_message" product="device">Your device may be charging slowly or not charging. For faster charging, use a recommended cable and adapter.</string>
+ <string name="battery_tip_incompatible_charging_message" product="tablet">Your tablet may be charging slowly or not charging. For faster charging, use a recommended cable and adapter.</string>
<!-- Description for using device controls feature with a locked phone [CHAR LIMIT=NONE] -->
<string name="lockscreen_trivial_controls_summary" product="default">Without unlocking your phone</string>
diff --git a/res/drawable/ic_battery_incompatible_charger.xml b/res/drawable/ic_battery_incompatible_charger.xml
new file mode 100644
index 0000000..dd84c07
--- /dev/null
+++ b/res/drawable/ic_battery_incompatible_charger.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="33dp"
+ android:height="33dp"
+ android:viewportWidth="33"
+ android:viewportHeight="33">
+ <path
+ android:pathData="M29.136,29.137L3.864,3.864L1.911,5.803L8.278,12.169C8.278,12.224 8.25,19.938 8.25,19.938L13.063,24.75V28.875H19.938V24.75L20.405,24.283L27.198,31.076L29.136,29.137ZM17.188,23.609V26.125H15.813V23.609L11,18.769V14.878L18.452,22.33L17.188,23.609ZM11,7.109L16.266,12.375H22V18.109L24.282,20.392L24.75,19.924V12.375C24.75,10.863 23.513,9.626 22,9.626V4.126H19.25V9.626H13.75V4.126H11V7.109Z"
+ android:fillColor="?android:attr/colorAccent"/>
+</vector>
diff --git a/res/drawable/ic_block_24px.xml b/res/drawable/ic_block_24px.xml
new file mode 100644
index 0000000..1c80dc1
--- /dev/null
+++ b/res/drawable/ic_block_24px.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2024 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q534,800 584,782.5Q634,765 676,732L228,284Q195,326 177.5,376Q160,426 160,480Q160,614 253,707Q346,800 480,800ZM732,676Q765,634 782.5,584Q800,534 800,480Q800,346 707,253Q614,160 480,160Q426,160 376,177.5Q326,195 284,228L732,676Z"/>
+</vector>
diff --git a/res/drawable/ic_check_circle_24px.xml b/res/drawable/ic_check_circle_24px.xml
new file mode 100644
index 0000000..c0fdefb
--- /dev/null
+++ b/res/drawable/ic_check_circle_24px.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2024 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M424,664L706,382L650,326L424,552L310,438L254,494L424,664ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
+</vector>
diff --git a/res/drawable/ic_satellite_alt_24px.xml b/res/drawable/ic_satellite_alt_24px.xml
new file mode 100644
index 0000000..f9ca7dc
--- /dev/null
+++ b/res/drawable/ic_satellite_alt_24px.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2024 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M560,928L560,848Q677,848 758.5,766.5Q840,685 840,568L920,568Q920,643 891.5,708.5Q863,774 814.5,822.5Q766,871 700.5,899.5Q635,928 560,928ZM560,768L560,688Q610,688 645,653Q680,618 680,568L760,568Q760,651 701.5,709.5Q643,768 560,768ZM222,903Q207,903 192,897Q177,891 165,880L23,738Q12,726 6,711Q0,696 0,681Q0,665 6,650.5Q12,636 23,625L150,498Q173,475 207,474.5Q241,474 264,497L314,547L342,519L292,469Q269,446 269,413Q269,380 292,357L349,300Q372,277 405.5,277Q439,277 462,300L512,350L540,322L490,272Q467,249 467,215.5Q467,182 490,159L617,32Q629,20 644,14Q659,8 674,8Q689,8 703.5,14Q718,20 730,32L872,174Q884,185 889.5,199.5Q895,214 895,230Q895,245 889.5,260Q884,275 872,287L745,414Q722,437 688.5,437Q655,437 632,414L582,364L554,392L604,442Q627,465 626.5,498.5Q626,532 603,555L547,611Q524,634 490.5,634Q457,634 434,611L384,561L356,589L406,639Q429,662 428.5,696Q428,730 405,753L278,880Q267,891 252.5,897Q238,903 222,903ZM222,824Q222,824 222,824Q222,824 222,824L264,782L122,640L80,682Q80,682 80,682Q80,682 80,682L222,824ZM307,739L349,697Q349,697 349,697Q349,697 349,697L207,555Q207,555 207,555Q207,555 207,555L165,597L307,739ZM491,555Q491,555 491,555Q491,555 491,555L547,499Q547,499 547,499Q547,499 547,499L405,357Q405,357 405,357Q405,357 405,357L349,413Q349,413 349,413Q349,413 349,413L491,555ZM689,357Q689,357 689,357Q689,357 689,357L731,315L589,173L547,215Q547,215 547,215Q547,215 547,215L689,357ZM774,272L816,230Q816,230 816,230Q816,230 816,230L674,88Q674,88 674,88Q674,88 674,88L632,130L774,272ZM448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456L448,456Q448,456 448,456Q448,456 448,456Z"/>
+</vector>
diff --git a/res/drawable/ic_signal_cellular_nodata_24px.xml b/res/drawable/ic_signal_cellular_nodata_24px.xml
new file mode 100644
index 0000000..9b9f391
--- /dev/null
+++ b/res/drawable/ic_signal_cellular_nodata_24px.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2024 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M676,860L620,804L704,720L620,636L676,580L760,664L844,580L900,636L817,720L900,804L844,860L760,777L676,860ZM80,880L880,81L880,508Q862,497 842,490.5Q822,484 800,480L800,274L273,800L530,800Q538,823 550,843Q562,863 577,880L80,880ZM273,800L800,274L800,274Q800,274 800,274Q800,274 800,274Q724,350 662,411.5Q600,473 540.5,532.5Q481,592 417,656Q353,720 273,800Q273,800 273,800Q273,800 273,800L273,800Z"/>
+</vector>
diff --git a/res/drawable/satellite_more_information_background_outline.xml b/res/drawable/satellite_more_information_background_outline.xml
new file mode 100644
index 0000000..b11ef07
--- /dev/null
+++ b/res/drawable/satellite_more_information_background_outline.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="24dp"/>
+ <solid android:color="@android:color/transparent"/>
+ <stroke
+ android:width="1dp"
+ android:color="?android:attr/textColorPrimary"/>
+</shape>
diff --git a/res/layout/qrcode_scanner_fragment.xml b/res/layout/qrcode_scanner_fragment.xml
index e6d1c32..d402dc3 100644
--- a/res/layout/qrcode_scanner_fragment.xml
+++ b/res/layout/qrcode_scanner_fragment.xml
@@ -17,7 +17,6 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
@@ -26,36 +25,22 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="3"
- android:layout_marginBottom="35dp">
+ android:layout_marginBottom="55dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingStart="40dp"
+ android:paddingEnd="40dp"
android:layout_gravity="bottom"
android:gravity="center"
android:orientation="vertical">
- <ImageView
- android:src="@drawable/ic_qr_code_scanner"
- android:tint="?androidprv:attr/materialColorPrimaryContainer"
- android:layout_width="@dimen/qrcode_icon_size"
- android:layout_height="@dimen/qrcode_icon_size"
- android:contentDescription="@null"/>
-
<TextView
style="@style/QrCodeScanner"
- android:textSize="24sp"
- android:text="@string/bluetooth_find_broadcast_button_scan"
+ android:text="Scan an audio stream QR code to listen with the active LE device"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="19dp"/>
-
- <TextView
- style="@style/QrCodeScanner"
- android:text="@string/bt_le_audio_scan_qr_code_scanner"
- android:gravity="center"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"/>
+ android:layout_marginTop="20dp"/>
</LinearLayout>
</LinearLayout>
diff --git a/res/layout/satellite_setting_more_information_layout.xml b/res/layout/satellite_setting_more_information_layout.xml
new file mode 100644
index 0000000..ce2fabe
--- /dev/null
+++ b/res/layout/satellite_setting_more_information_layout.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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:background="@drawable/satellite_more_information_background_outline"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:focusable="false"
+ android:gravity="center_vertical">
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:focusable="false"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:maxLines="10" />
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
+ android:focusable="false"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:maxLines="10" />
+
+</LinearLayout>
diff --git a/res/navigation/privatespace_main_context_nav.xml b/res/navigation/privatespace_main_context_nav.xml
index 7849458..f76afac 100644
--- a/res/navigation/privatespace_main_context_nav.xml
+++ b/res/navigation/privatespace_main_context_nav.xml
@@ -52,6 +52,9 @@
<action
android:id="@+id/action_advance_login_error"
app:destination="@id/ps_account_error_fragment"/>
+ <action
+ android:id="@+id/action_success_fragment"
+ app:destination="@id/ps_profile_success_fragment"/>
</fragment>
<fragment android:id="@+id/ps_profile_lock_fragment"
android:name="com.android.settings.privatespace.PrivateSpaceSetLockFragment"
diff --git a/res/values/config.xml b/res/values/config.xml
index f3e2a7a..433620d 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -42,6 +42,10 @@
<string name="config_avatar_picker_action" translatable="false">
com.android.avatarpicker.FULL_SCREEN_ACTIVITY
</string>
+ <!-- User avatar dialog result broadcast explicit package -->
+ <string name="config_avatar_picker_package" translatable="false">
+ com.android.avatarpicker
+ </string>
<!-- Package name and fully-qualified class name for the wallpaper picker activity. -->
<string name="config_wallpaper_picker_package" translatable="false">com.android.settings</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 863b86e..b760f68 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1342,10 +1342,14 @@
<string name="private_space_done_label">Done</string>
<!-- Toast to show on private space setup completion informing user to scroll down All apps to access private space. [CHAR LIMIT=60] -->
<string name="private_space_scrolldown_to_access">Scroll down to find private space</string>
- <!-- Title for private space account login error screen. [CHAR LIMIT=60] -->
- <string name="private_space_retry_signin_title">Sign in to set up a private space</string>
+ <!-- Title for private space account login error screen. [CHAR LIMIT=30] -->
+ <string name="private_space_retry_signin_title">Sign in</string>
<!-- Summary for the private space account login error screen. [CHAR LIMIT=NONE] -->
- <string name="private_space_retry_summary">You need to sign in to an account to set up a private space</string>
+ <string name="private_space_retry_summary">Sign in to an account to use with your private space</string>
+ <!-- Label for button to skip private space account sign in. [CHAR LIMIT=30] -->
+ <string name="private_space_skip_login_label">Not now</string>
+ <!-- Label for button to coninue with private space account sign in. [CHAR LIMIT=30] -->
+ <string name="private_space_continue_login_label">Continue</string>
<!-- private space lock setup screen title. This title is asking the user to choose a type of screen lock (such as a pattern, PIN, or password) that they need to enter to unlock private space. [CHAR LIMIT=60] -->
<string name="private_space_lock_setup_title">Choose a lock for your private space</string>
<!-- private space lock setup screen description [CHAR LIMIT=NONE] -->
@@ -1740,7 +1744,7 @@
<string name="bluetooth_pairing_request">Pair with <xliff:g id="device_name">%1$s</xliff:g>?</string>
<!-- Message when a bluetooth device from a coordinated set is bonding late. [CHAR LIMIT=NONE] -->
- <string name="bluetooth_pairing_group_late_bonding">Add new member to the existing coordinated set</string>
+ <string name="bluetooth_pairing_group_late_bonding">Confirm to add the second piece of your audio device</string>
<!-- Message when bluetooth is informing the user of the pairing key. [CHAR LIMIT=NONE] -->
<string name="bluetooth_pairing_key_msg">Bluetooth pairing code</string>
@@ -1763,7 +1767,7 @@
<string name="bluetooth_enter_passkey_other_device">You may also need to type this passkey on the other device.</string>
<!-- Pairing dialog text to remind user the pairing including all of the devices in a coordinated set. [CHAR LIMIT=NONE] -->
- <string name="bluetooth_paring_group_msg">Confirm to pair with the coordinated set</string>
+ <string name="bluetooth_paring_group_msg">Confirm to pair with the audio device</string>
<!-- Checkbox message in pairing dialogs. [CHAR LIMIT=NONE] -->
<string name="bluetooth_pairing_shares_phonebook">Allow access to your contacts and call history</string>
@@ -3257,6 +3261,10 @@
<!-- Summary of the communal settings under Settings > Communal [CHAR LIMIT=50] -->
<string name="communal_settings_summary">Communal settings</string>
+ <!-- _satellite_setting_preference_layout -->
+ <!-- _satellite_setting_preference_layout screen title-->
+ <string name="satellite_setting">Satellite Messaging</string>
+
<!-- APN Settings -->
<!-- APN settings screen title -->
<string name="apn_settings">APNs</string>
@@ -3371,7 +3379,7 @@
<!-- Erase Euicc dialog and SD card & phone storage settings screen, title for the menu option and checkbox to let user decide whether erase eSIM data together [CHAR LIMIT=50] -->
<string name="reset_esim_title">Erase eSIMs</string>
<!-- Erase Euicc dialog and SD card & phone storage settings screen, message for the checkbox to let user decide whether erase eSIM data together [CHAR LIMIT=NONE] -->
- <string name="reset_esim_desc">This won’t cancel any mobile service plans. To download replacement SIMs, contact your carrier.</string>
+ <string name="reset_esim_desc">This won’t cancel your mobile service plan. To get a replacement SIM, contact your carrier.</string>
<!-- SD card & phone storage settings screen, button on screen after user selects Reset network settings -->
<string name="reset_network_button_text">Reset settings</string>
@@ -3388,9 +3396,9 @@
<!-- Reset settings complete toast text [CHAR LIMIT=75] -->
<string name="reset_network_complete_toast">Network settings have been reset</string>
<!-- Title of the error message shown when error happens during erase eSIM data [CHAR LIMIT=NONE] -->
- <string name="reset_esim_error_title">Can\u2019t erase SIMs</string>
+ <string name="reset_esim_error_title">Can\u2019t erase eSIMs</string>
<!-- Message of the error message shown when error happens during erase eSIM data [CHAR LIMIT=NONE] -->
- <string name="reset_esim_error_msg">eSIMs can\u2019t be erased due to an error.\n\nRestart your device and try again.</string>
+ <string name="reset_esim_error_msg">Something went wrong and your eSIMs weren\u2019t erased.\n\nRestart your device and try again.</string>
<!-- Main Clear -->
<!-- Button title to factory data reset the entire device [CHAR LIMIT=NONE] -->
@@ -3406,7 +3414,7 @@
<!-- SD card & phone storage settings screen, message on screen after user selects Reset phone button [CHAR LIMIT=NONE] -->
<string name="main_clear_final_desc">All of your personal information and downloaded apps will be deleted. You can\u2019t undo this action.</string>
<!-- SD card & phone storage settings screen, message on screen after user selects Reset phone button [CHAR LIMIT=NONE] -->
- <string name="main_clear_final_desc_esim">All of your personal information, including downloaded apps & SIMs, will be deleted. You can\u2019t undo this action.</string>
+ <string name="main_clear_final_desc_esim">All of your personal information, including downloaded apps & eSIMs, will be deleted. You can\u2019t undo this action.</string>
<!-- Main clear confirmation screen title [CHAR LIMIT=33] -->
<string name="main_clear_confirm_title">Erase all data?</string>
<!-- Error message for users that aren't allowed to factory reset [CHAR LIMIT=none] -->
@@ -4304,6 +4312,10 @@
<string name="bounce_keys">Bounce keys</string>
<!-- Summary text for the 'Bounce keys' preference sub-screen. [CHAR LIMIT=100] -->
<string name="bounce_keys_summary">Enable Bounce keys for physical keyboard accessibility</string>
+ <!-- Title for the 'Slow keys' preference switch. [CHAR LIMIT=35] -->
+ <string name="slow_keys">Slow keys</string>
+ <!-- Summary text for the 'Slow keys' preference sub-screen. [CHAR LIMIT=100] -->
+ <string name="slow_keys_summary">Enable Slow keys for physical keyboard accessibility</string>
<!-- Title for the 'Sticky keys' preference switch. [CHAR LIMIT=35] -->
<string name="sticky_keys">Sticky keys</string>
<!-- Summary text for the 'Sticky keys' preference sub-screen. [CHAR LIMIT=100] -->
@@ -5613,18 +5625,6 @@
<string name="battery_tip_limited_temporarily_title">Charging optimized to protect your battery</string>
<!-- Summary for the battery limited temporarily tip [CHAR LIMIT=NONE] -->
<string name="battery_tip_limited_temporarily_summary">To help extend your battery\'s lifespan, charging is optimized</string>
- <!-- Title for the battery dock defender future bypass tip [CHAR LIMIT=NONE] -->
- <string name="battery_tip_dock_defender_future_bypass_title">Charging optimized to protect your battery</string>
- <!-- Summary for the battery dock defender future bypass tip [CHAR LIMIT=NONE] -->
- <string name="battery_tip_dock_defender_future_bypass_summary">To help extend your battery\'s lifespan, charging is optimized while docked</string>
- <!-- Title for the battery dock defender active tip [CHAR LIMIT=NONE] -->
- <string name="battery_tip_dock_defender_active_title">Charging optimized to protect your battery</string>
- <!-- Summary for the battery dock defender active tip [CHAR LIMIT=NONE] -->
- <string name="battery_tip_dock_defender_active_summary">To help extend your battery\'s lifespan, charging is optimized while docked</string>
- <!-- Title for the battery dock defender temporarily bypassed tip [CHAR LIMIT=NONE] -->
- <string name="battery_tip_dock_defender_temporarily_bypassed_title">Charging to full</string>
- <!-- Summary for the battery dock defender temporarily bypassed tip [CHAR LIMIT=NONE] -->
- <string name="battery_tip_dock_defender_temporarily_bypassed_summary">To protect your battery, charging will be optimized the next time your tablet is docked</string>
<!-- Content description for the battery limited temporarily tip secondary button [CHAR LIMIT=NONE] -->
<string name="battery_tip_limited_temporarily_sec_button_content_description">Learn more about charging is paused</string>
<!-- Text of battery limited temporarily tip resume charge button. [CHAR LIMIT=NONE] -->
@@ -5676,7 +5676,7 @@
<!-- Charge to full button for battery defender tips [CHAR LIMIT=NONE] -->
<string name="battery_tip_charge_to_full_button">Charge to full</string>
<!-- Title of incompatible charging battery tip [CHAR LIMIT=NONE] -->
- <string name="battery_tip_incompatible_charging_title">Issue with charging accessory</string>
+ <string name="battery_tip_incompatible_charging_title">Check charging accessory</string>
<!-- Content description for the incompatible charging battery tip button [CHAR LIMIT=NONE] -->
<string name="battery_tip_incompatible_charging_content_description">Learn more about incompatible charging</string>
@@ -9624,10 +9624,6 @@
<string name="permit_voice_activation_apps">Allow voice activation</string>
<!-- Description for a setting which controls whether an app can be voice activated [CHAR LIMIT=NONE] -->
<string name ="allow_voice_activation_apps_description">Voice activation turns-on approved apps, hands-free, using voice command. Built-in adaptive sensing ensures data stays private only to you.\n\n<a href="">More about protected adaptive sensing</a></string>
- <!-- Label for a setting which controls whether an app can receive sandboxed detection training data [CHAR LIMIT=NONE] -->
- <string name = "permit_receive_sandboxed_detection_training_data">Improve voice activation</string>
- <!-- Description for a setting which controls whether an app can receive sandboxed detection training data [CHAR LIMIT=NONE] -->
- <string name= "receive_sandboxed_detection_training_data_description">This device uses private intelligence to improve the voice activation model. Apps can receive summarized updates that are aggregated across many users to maintain privacy while improving the model for everyone.\n\n<a href="">More about private intelligence</a></string>
<!-- Manage full screen intent permission title [CHAR LIMIT=40] -->
<string name="full_screen_intent_title">Full screen notifications</string>
@@ -11375,7 +11371,7 @@
<!-- Mobile network details page. Label for an option that lets the user delete an eSIM from
the device. [CHAR LIMIT=60] -->
- <string name="mobile_network_erase_sim">Erase SIM</string>
+ <string name="mobile_network_erase_sim">Erase eSIM</string>
<!-- Title for preferred network type [CHAR LIMIT=NONE] -->
<string name="preferred_network_mode_title">Preferred network type</string>
@@ -11420,6 +11416,43 @@
<string name="mobile_data_usage_title">App data usage</string>
<!-- Summary to show the current network mode is invalid. [CHAR LIMIT=NONE]-->
<string name="mobile_network_mode_error">Invalid Network Mode <xliff:g id="networkModeId" example="0">%1$d</xliff:g>. Ignore.</string>
+ <!-- Title for _satellite_setting_preference_layout in mobile network settings [CHAR LIMIT=60] -->
+ <string name="satellite_setting_title">Satellite messaging</string>
+ <!-- Summary for _satellite_setting_preference_layout. [CHAR LIMIT=NONE]-->
+ <string name="satellite_setting_enabled_summary">Send and receive text messages by satellite. Included with your account.</string>
+ <!-- Summary for _satellite_setting_preference_layout. [CHAR LIMIT=NONE]-->
+ <string name="satellite_setting_disabled_summary">Send and receive text messages by satellite. Non included with your account.</string>
+ <!-- Search keywords for "_satellite_setting_preference_layout" [CHAR_LIMIT=NONE] -->
+ <string name="keywords_satellite_setting">Satellite messaging</string>
+ <!-- Category name "About satellite messaging" [CHAR_LIMIT=NONE] -->
+ <string name="category_name_about_satellite_messaging">About satellite messaging</string>
+ <!-- Summary for category "About satellite messaging" [CHAR_LIMIT=NONE] -->
+ <string name="title_about_satellite_setting">You can send and receive text messages by satellite as part of an eligible <xliff:g id="carrier_name" example="T-Mobile">%1$s</xliff:g> account</string>
+ <!-- Category title "Your mobile plan" [CHAR_LIMIT=NONE] -->
+ <string name="category_title_your_satellite_plan">Your <xliff:g id="carrier_name" example="T-Mobile">%1$s</xliff:g> plan</string>
+ <!-- Title for category "Your mobile plan when satellite is included in plan" [CHAR_LIMIT=NONE] -->
+ <string name="title_have_satellite_plan">Satellite messaging is included with your account</string>
+ <!-- Title for category "Your mobile plan when satellite is not included in plan" [CHAR_LIMIT=NONE] -->
+ <string name="title_no_satellite_plan">Satellite messaging isn\u2019t included with your account</string>
+ <!-- text view add satellite messaging" [CHAR_LIMIT=NONE] -->
+ <string name="summary_add_satellite_setting">Add satellite messaging</string>
+ <!-- Category name "How it works" [CHAR_LIMIT=NONE] -->
+ <string name="category_name_how_it_works">How it works</string>
+ <!-- Title for satellite connection guide [CHAR_LIMIT=NONE] -->
+ <string name="title_satellite_connection_guide">When you don\u2019t have a mobile network</string>
+ <!-- Summary for satellite connection guide [CHAR_LIMIT=NONE] -->
+ <string name="summary_satellite_connection_guide">Your phone will auto-connect to a satellite. For the best connection, keep a clear view of the sky.</string>
+ <!-- Title for satellite supported service [CHAR_LIMIT=NONE] -->
+ <string name="title_supported_service">After your phone connects to a satellite</string>
+ <!-- Summary for satellite supported service [CHAR_LIMIT=NONE] -->
+ <string name="summary_supported_service">You can text anyone, including emergency services. Your phone will reconnect to a mobile network when available.</string>
+ <!-- learn more text - more about satellite messaging [CHAR_LIMIT=NONE] -->
+ <string name="satellite_setting_summary_more_information">Satellite messaging may take longer and is available only in some areas, Weather and certain structures may affect your satellite connection. Calling by satellite isn\u2019t available.\n\nIt may take some time for changes to your account to show in Settings. Contact <xliff:g id="carrier_name" example="T-Mobile">%1$s</xliff:g> for details.</string>
+ <!-- more about satellite messaging [CHAR_LIMIT=NONE] -->
+ <string name="more_about_satellite_messaging">More about satellite messaging</string>
+ <!-- URL for more info about satellite messaging [CHAR LIMIT=60] -->
+ <string name="more_info_satellite_messaging_link" translatable="false"></string>
+
<!-- Title for Apn settings in mobile network settings [CHAR LIMIT=60] -->
<string name="mobile_network_apn_title">Access Point Names</string>
<!-- Search keywords for "Access Point Names" [CHAR_LIMIT=NONE] -->
@@ -11443,11 +11476,11 @@
<!-- Title of confirmation dialog asking the user if they want to switch subscription. [CHAR_LIMIT=NONE] -->
<string name="sim_action_switch_sub_dialog_mep_title">Use <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>?</string>
<!-- Body text of confirmation dialog for switching subscription that involves switching SIM slots. Indicates that only one SIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
- <string name="sim_action_switch_sub_dialog_text">Only one SIM can be active at a time.\n\nSwitching to <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> won\u2019t cancel your <xliff:g id="from_carrier_name" example="Sprint">%2$s</xliff:g> service.</string>
- <!-- Body text of confirmation dialog for switching subscription between two eSIM profiles. Indicates that only 1 eSIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
- <string name="sim_action_switch_sub_dialog_text_downloaded">Only 1 eSIM can be active at a time.\n\nSwitching to <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> won\u2019t cancel your <xliff:g id="from_carrier_name" example="Sprint">%2$s</xliff:g> service.</string>
+ <string name="sim_action_switch_sub_dialog_text">Only 1 SIM can be on at a time.\n\nSwitching to <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> won\u2019t cancel your <xliff:g id="from_carrier_name" example="Sprint">%2$s</xliff:g> service.</string>
+ <!-- Body text of confirmation dialog for switching subscription between two eSIM profiles. Indicates that only 1 eSIM can be on at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
+ <string name="sim_action_switch_sub_dialog_text_downloaded">Only 1 eSIM can be on at a time.\n\nSwitching to <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> won\u2019t cancel your <xliff:g id="from_carrier_name" example="Sprint">%2$s</xliff:g> service.</string>
<!-- Body text of confirmation dialog for switching subscription between two eSIM profiles. Indicates that only one SIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
- <string name="sim_action_switch_sub_dialog_text_single_sim">Only one SIM can be active at a time.\n\nSwitching won\u2019t cancel your <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> service.</string>
+ <string name="sim_action_switch_sub_dialog_text_single_sim">Only 1 SIM can be on at a time.\n\nSwitching won\u2019t cancel your <xliff:g id="to_carrier_name" example="Google Fi">%1$s</xliff:g> service.</string>
<!-- Body text of confirmation dialog for switching subscription between two eSIM profiles. Indicates that only one downloaded SIM can be active at a time. Also that switching will not cancel the user's mobile service plan. [CHAR_LIMIT=NONE] -->
<string name="sim_action_switch_sub_dialog_mep_text">You can use 2 SIMs at a time. To use <xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g>, turn off another SIM.</string>
<!-- Text of confirm button in the confirmation dialog asking the user if they want to switch subscription. [CHAR_LIMIT=NONE] -->
@@ -11598,15 +11631,15 @@
<!-- Title on confirmation dialog asking the user if they want to erase the eSIM from the device. [CHAR_LIMIT=NONE] -->
<string name="erase_sim_dialog_title">Erase this eSIM?</string>
<!-- Body text in confirmation dialog indicating what erasing a SIM entails. [CHAR_LIMIT=NONE] -->
- <string name="erase_sim_dialog_text">Erasing this SIM removes <xliff:g id="carrier_name_a" example="Google Fi">%1$s</xliff:g> service from this device.\n\nService for <xliff:g id="carrier_name_b" example="Google Fi">%1$s</xliff:g> won\'t be canceled.</string>
+ <string name="erase_sim_dialog_text">This removes <xliff:g id="carrier_name_a" example="Google Fi">%1$s</xliff:g> service from this device, but your <xliff:g id="carrier_name_b" example="Google Fi">%1$s</xliff:g> plan won\'t be canceled.</string>
<!-- Button label to erase the eSIM [CHAR_LIMIT=20] -->
<string name="erase_sim_confirm_button">Erase</string>
<!-- Status message indicating the device is in the process of erasing the SIM. [CHAR_LIMIT=NONE] -->
- <string name="erasing_sim">Erasing SIM…</string>
+ <string name="erasing_sim">Erasing eSIM…</string>
<!-- Title of error message indicating the device could not erase the SIM. [CHAR_LIMIT=NONE] -->
- <string name="erase_sim_fail_title">Can\'t erase SIM</string>
+ <string name="erase_sim_fail_title">Can\'t erase eSIM</string>
<!-- Body text of error message indicating the device could not erase the SIM due to an error. [CHAR_LIMIT=NONE] -->
- <string name="erase_sim_fail_text">This SIM can\'t be erased due to an error.\n\nRestart your device and try again.</string>
+ <string name="erase_sim_fail_text">Something went wrong and this eSIM wasn\'t erased.\n\nRestart your device and try again.</string>
<!-- Title for Network connection request Dialog [CHAR LIMIT=60] -->
<string name="network_connection_request_dialog_title">Connect to device</string>
@@ -12011,9 +12044,9 @@
<!-- Provider Model: title of eSIMs category. [CHAR LIMIT=50] -->
<string name="downloaded_sims_category_title">eSIMs</string>
<!-- Provider Model: summary of Active in SIM category. [CHAR LIMIT=50] -->
- <string name="sim_category_active_sim">Active</string>
+ <string name="sim_category_active_sim">On</string>
<!-- Provider Model: summary of Inactive in SIM category. [CHAR LIMIT=50] -->
- <string name="sim_category_inactive_sim">Inactive</string>
+ <string name="sim_category_inactive_sim">Off</string>
<!-- Provider Model: summary of default config. [CHAR LIMIT=50] -->
<string name="sim_category_default_active_sim">\u0020/ Default for <xliff:g name="default_sim_config" example=" / Default for calls">%1$s</xliff:g></string>
<!-- Provider Model: summary of default call. [CHAR LIMIT=50] -->
diff --git a/res/xml/bluetooth_audio_sharing.xml b/res/xml/bluetooth_audio_sharing.xml
index d3aad22..5de8dfc 100644
--- a/res/xml/bluetooth_audio_sharing.xml
+++ b/res/xml/bluetooth_audio_sharing.xml
@@ -45,9 +45,15 @@
<com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreference
android:key="audio_sharing_stream_name"
- android:summary="********"
- android:title="Stream name"
+ android:title="Name"
settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingNamePreferenceController" />
+
+ <com.android.settings.widget.ValidatedEditTextPreference
+ android:key="audio_sharing_stream_password"
+ android:summary="********"
+ android:title="Password"
+ settings:controller="com.android.settings.connecteddevice.audiosharing.AudioSharingPasswordPreferenceController" />
+
<SwitchPreferenceCompat
android:key="audio_sharing_stream_compatibility"
android:title="Improve compatibility"
diff --git a/res/xml/bluetooth_audio_streams.xml b/res/xml/bluetooth_audio_streams.xml
index 95ee710..e7e708e 100644
--- a/res/xml/bluetooth_audio_streams.xml
+++ b/res/xml/bluetooth_audio_streams.xml
@@ -18,23 +18,31 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
- android:title="@string/audio_streams_title">
+ android:title="Find an audio stream">
- <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_streams_qr_code_summary"
- settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController" />
+ <com.android.settingslib.widget.TopIntroPreference
+ android:key="audio_streams_top_intro"
+ android:title="Listen to a device that's sharing audio or to a nearby Auracast broadcast"
+ settings:searchable="false"/>
<Preference
android:key="audio_streams_active_device"
- android:title="Listen with"
+ android:title="Your audio device"
settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsActiveDeviceController" />
<com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryPreference
android:key="audio_streams_nearby_category"
- android:title="@string/audio_streams_pref_title"
- settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController" />
+ android:title="Audio streams nearby"
+ settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController">
+
+ <Preference
+ android:key="audio_streams_scan_qr_code"
+ android:title="Scan a QR code"
+ android:icon="@drawable/ic_add_24dp"
+ android:summary="Start listening by scanning a stream's QR code"
+ android:order="0"
+ settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController" />
+
+ </com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryPreference>
</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/bluetooth_audio_streams_dialog.xml b/res/xml/bluetooth_audio_streams_dialog.xml
index 502e55a..024e537 100644
--- a/res/xml/bluetooth_audio_streams_dialog.xml
+++ b/res/xml/bluetooth_audio_streams_dialog.xml
@@ -16,6 +16,7 @@
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -23,70 +24,78 @@
android:id="@+id/dialog_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:paddingStart="25dp"
+ android:paddingEnd="25dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/broadcast_dialog_margin"
+ android:layout_marginBottom="25dp"
android:orientation="vertical">
<ImageView
android:id="@+id/dialog_icon"
- android:layout_width="36dp"
- android:layout_height="36dp"
- android:layout_marginTop="@dimen/broadcast_dialog_icon_margin_top"
- android:layout_marginBottom="@dimen/broadcast_dialog_title_img_margin_top"
+ android:layout_width="30dp"
+ android:layout_height="30dp"
+ android:layout_marginTop="24dp"
android:layout_gravity="center"
android:src="@drawable/ic_bt_audio_sharing"/>
<TextView
- style="@style/BroadcastDialogTitleStyle"
android:id="@+id/dialog_title"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="15dp"
android:gravity="center"
android:layout_gravity="center"/>
<TextView
- style="@style/BroadcastDialogBodyStyle"
android:id="@+id/dialog_subtitle"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small"
+ android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="15dp"
android:gravity="center"
android:layout_gravity="center"
android:visibility="gone"/>
<TextView
- style="@style/BroadcastDialogBodyStyle"
android:id="@+id/dialog_subtitle_2"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="15dp"
android:gravity="center"
android:layout_gravity="center"
android:visibility="gone"/>
</LinearLayout>
- <LinearLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/broadcast_dialog_margin"
- android:orientation="horizontal">
+ android:layout_marginBottom="@dimen/broadcast_dialog_margin">
<Button
android:id="@+id/left_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="16dp"
- android:layout_weight="1"
- android:visibility="invisible"/>
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ style="@style/BroadcastActionButton"/>
<Button
android:id="@+id/right_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_marginRight="16dp"
- android:visibility="invisible"/>
- </LinearLayout>
+ android:visibility="gone"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ style="@style/BroadcastActionButton"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</FrameLayout>
\ No newline at end of file
diff --git a/res/xml/bluetooth_audio_streams_qr_code.xml b/res/xml/bluetooth_audio_streams_qr_code.xml
index c750963..50b1429 100644
--- a/res/xml/bluetooth_audio_streams_qr_code.xml
+++ b/res/xml/bluetooth_audio_streams_qr_code.xml
@@ -36,7 +36,7 @@
android:gravity="start"
android:textSize="15sp"
android:textColor="?android:attr/textColorPrimary"
- android:text="Scan this QR code with another device connected to LE audio headphones to start sharing audio"/>
+ android:text="To listen to this audio stream, other people can connect compatible headphones to their Android device. They can then scan this QR code."/>
<LinearLayout
android:layout_width="match_parent"
@@ -50,6 +50,13 @@
android:layout_width="@dimen/qrcode_size"
android:layout_height="@dimen/qrcode_size"
android:src="@android:color/transparent"/>
+
+ <TextView
+ android:id="@+id/password"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15sp"
+ android:textColor="?android:attr/textColorPrimary"/>
</LinearLayout>
</LinearLayout>
diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml
index 59c0c21..4703a14 100644
--- a/res/xml/configure_notification_settings.xml
+++ b/res/xml/configure_notification_settings.xml
@@ -27,8 +27,7 @@
android:order="10"
android:title="@string/app_notification_field"
android:summary="@string/app_notification_field_summary"
- android:fragment="com.android.settings.applications.manageapplications.ManageApplications"
- settings:searchable="false">
+ android:fragment="com.android.settings.applications.manageapplications.ManageApplications">
<extra
android:name="classname"
android:value="com.android.settings.Settings$NotificationAppListActivity"/>
diff --git a/res/xml/mobile_network_settings.xml b/res/xml/mobile_network_settings.xml
index 038688e..b5d0c59 100644
--- a/res/xml/mobile_network_settings.xml
+++ b/res/xml/mobile_network_settings.xml
@@ -208,6 +208,7 @@
<PreferenceCategory
android:key="network_operators_category_key"
android:title="@string/network_operator_category"
+ settings:allowDividerBelow="true"
settings:controller="com.android.settings.network.telephony.NetworkPreferenceCategoryController">
<com.android.settings.spa.preference.ComposePreference
@@ -221,12 +222,19 @@
settings:controller="com.android.settings.network.telephony.gsm.OpenNetworkSelectPagePreferenceController"/>
</PreferenceCategory>
+ <com.android.settingslib.RestrictedPreference
+ android:key="telephony_satellite_setting_key"
+ android:persistent="false"
+ android:title="@string/satellite_setting_title"
+ settings:keywords="@string/keywords_satellite_setting"
+ settings:controller=
+ "com.android.settings.network.telephony.SatelliteSettingPreferenceController"/>
+
<!--We want separate APN setting from reset of settings because we want user to change it with caution-->
<com.android.settingslib.RestrictedPreference
android:key="telephony_apn_key"
android:persistent="false"
android:title="@string/mobile_network_apn_title"
- settings:allowDividerAbove="true"
settings:keywords="@string/keywords_access_point_names"
settings:controller="com.android.settings.network.telephony.ApnPreferenceController"/>
@@ -249,7 +257,7 @@
android:title="@string/require_cellular_encryption_title"
android:summary="@string/require_cellular_encryption_summary"
settings:controller=
- "com.android.settings.network.telephony.NullAlgorithmsPreferenceController" />
+ "com.android.settings.network.telephony.NullAlgorithmsPreferenceController"/>
<SwitchPreferenceCompat
android:key="nr_advanced_calling"
diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml
index 1a8ee08..04f248e 100644
--- a/res/xml/network_provider_internet.xml
+++ b/res/xml/network_provider_internet.xml
@@ -52,7 +52,6 @@
settings:keywords="@string/keywords_more_mobile_networks"
settings:userRestriction="no_config_mobile_networks"
settings:isPreferenceVisible="@bool/config_show_sim_info"
- settings:allowDividerAbove="true"
settings:useAdminDisabledSummary="true"
settings:searchable="@bool/config_show_sim_info"/>
diff --git a/res/xml/physical_keyboard_settings.xml b/res/xml/physical_keyboard_settings.xml
index dc424d1..b95f23e 100644
--- a/res/xml/physical_keyboard_settings.xml
+++ b/res/xml/physical_keyboard_settings.xml
@@ -44,16 +44,22 @@
android:title="@string/keyboard_a11y_category">
<SwitchPreference
- android:key="accessibility_bounce_keys"
- android:title="@string/bounce_keys"
- android:summary="@string/bounce_keys_summary"
- android:defaultValue="false" />
-
- <SwitchPreference
android:key="accessibility_sticky_keys"
android:title="@string/sticky_keys"
android:summary="@string/sticky_keys_summary"
android:defaultValue="false" />
+ <SwitchPreference
+ android:key="accessibility_slow_keys"
+ android:title="@string/slow_keys"
+ android:summary="@string/slow_keys_summary"
+ android:defaultValue="false" />
+
+ <SwitchPreference
+ android:key="accessibility_bounce_keys"
+ android:title="@string/bounce_keys"
+ android:summary="@string/bounce_keys_summary"
+ android:defaultValue="false" />
+
</PreferenceCategory>
</PreferenceScreen>
diff --git a/res/xml/satellite_setting.xml b/res/xml/satellite_setting.xml
new file mode 100644
index 0000000..9eb1763
--- /dev/null
+++ b/res/xml/satellite_setting.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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:key="satellite_setting"
+ android:title="@string/satellite_setting"
+ settings:keywords="@string/keywords_satellite_setting">
+
+ <PreferenceCategory
+ android:title="@string/category_name_about_satellite_messaging">
+
+ <com.android.settingslib.widget.TopIntroPreference
+ android:key="key_about_satellite_messaging"/>
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="key_category_your_satellite_plan"
+ android:title="@string/category_title_your_satellite_plan">
+
+ <Preference
+ android:key="key_your_satellite_plan"
+ android:icon="?android:attr/textColorPrimary"/>
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:key="key_category_how_it_works"
+ android:title="@string/category_name_how_it_works"
+ android:clickable="false"
+ android:focusable="false">
+
+ <Preference
+ android:key="key_satellite_connection_guide"
+ android:title="@string/title_satellite_connection_guide"
+ android:summary="@string/summary_satellite_connection_guide"
+ android:icon="@drawable/ic_signal_cellular_nodata_24px"/>
+
+ <Preference
+ android:key="key_supported_service"
+ android:title="@string/title_supported_service"
+ android:summary="@string/summary_supported_service"
+ android:icon="@drawable/ic_satellite_alt_24px"/>
+ </PreferenceCategory>
+
+ <com.android.settingslib.widget.FooterPreference
+ android:key="satellite_setting_extra_info_footer_pref"
+ android:layout="@layout/satellite_setting_more_information_layout"
+ android:selectable="false"
+ settings:searchable="false"/>
+
+</PreferenceScreen>
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 52f5e5b..63ce331 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -102,6 +102,7 @@
public static class DevelopmentSettingsActivity extends SettingsActivity { /* empty */ }
public static class AccessibilitySettingsActivity extends SettingsActivity { /* empty */ }
public static class AccessibilityDetailsSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class AccessibilityEditShortcutsActivity extends SettingsActivity { /* empty */ }
public static class CaptioningSettingsActivity extends SettingsActivity { /* empty */ }
public static class AccessibilityInversionSettingsActivity extends SettingsActivity { /* empty */ }
public static class AccessibilityContrastSettingsActivity extends SettingsActivity { /* empty */ }
@@ -345,6 +346,7 @@
/* empty */
}
+ public static class SatelliteSettingActivity extends SettingsActivity { /* empty */ }
public static class ApnSettingsActivity extends SettingsActivity { /* empty */ }
public static class WifiCallingSettingsActivity extends SettingsActivity { /* empty */ }
public static class MemorySettingsActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index 25fd3a2..108e331 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -54,7 +54,9 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
+import android.hardware.face.Face;
import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
@@ -1333,4 +1335,64 @@
return dreamsSupported && (!dreamsOnlyEnabledForDockUser || canCurrentUserDream(context));
}
+
+ /**
+ * Removes fingerprint templates enrolled for a given user.
+ *
+ * @param context application context.
+ * @param userId the id of the relevant user
+ */
+ public static void removeEnrolledFingerprintForUser(Context context, int userId) {
+ FingerprintManager fingerprintManager = getFingerprintManagerOrNull(context);
+ if (fingerprintManager != null && fingerprintManager.hasEnrolledTemplates(userId)) {
+ fingerprintManager.removeAll(userId,
+ fingerprintManagerRemovalCallback(userId));
+ }
+ }
+
+ /**
+ * Removes face templates enrolled for a given user.
+ *
+ * @param context application context.
+ * @param userId the id of the relevant user
+ */
+ public static void removeEnrolledFaceForUser(Context context, int userId) {
+ FaceManager faceManager = getFaceManagerOrNull(context);
+ if (faceManager != null && faceManager.hasEnrolledTemplates(userId)) {
+ faceManager.removeAll(userId, faceManagerRemovalCallback(userId));
+ }
+ }
+
+ private static FaceManager.RemovalCallback faceManagerRemovalCallback(int userId) {
+ return new FaceManager.RemovalCallback() {
+ @Override
+ public void onRemovalError(@Nullable Face face, int errMsgId, CharSequence err) {
+ Log.e(TAG, "Unable to remove face template for user " + userId + ", error: " + err);
+ }
+
+ @Override
+ public void onRemovalSucceeded(Face face, int remaining) {
+ if (remaining == 0) {
+ Log.d(TAG, "Enrolled face templates removed for user " + userId);
+ }
+ }
+ };
+ }
+
+ private static FingerprintManager.RemovalCallback fingerprintManagerRemovalCallback(
+ int userId) {
+ return new FingerprintManager.RemovalCallback() {
+ @Override
+ public void onRemovalError(@Nullable Fingerprint fp, int errMsgId, CharSequence err) {
+ Log.e(TAG, "Unable to remove fingerprint for user " + userId + " , error: " + err);
+ }
+
+ @Override
+ public void onRemovalSucceeded(Fingerprint fp, int remaining) {
+ if (remaining == 0) {
+ Log.d(TAG, "Enrolled fingerprints removed for user " + userId);
+ }
+ }
+ };
+ }
}
diff --git a/src/com/android/settings/accessibility/FontSizeData.java b/src/com/android/settings/accessibility/FontSizeData.java
index 1226d25..096710d 100644
--- a/src/com/android/settings/accessibility/FontSizeData.java
+++ b/src/com/android/settings/accessibility/FontSizeData.java
@@ -25,6 +25,7 @@
import android.provider.Settings;
import com.android.settingslib.R;
+import com.android.window.flags.Flags;
import java.util.Arrays;
import java.util.List;
@@ -38,12 +39,11 @@
FontSizeData(Context context) {
super(context);
-
final Resources resources = getContext().getResources();
final ContentResolver resolver = getContext().getContentResolver();
final List<String> strEntryValues =
Arrays.asList(resources.getStringArray(R.array.entryvalues_font_size));
- setDefaultValue(FONT_SCALE_DEF_VALUE);
+ setDefaultValue(getFontScaleDefValue(resolver));
final float currentScale =
Settings.System.getFloat(resolver, Settings.System.FONT_SCALE, getDefaultValue());
setInitialIndex(fontSizeValueToIndex(currentScale, strEntryValues.toArray(new String[0])));
@@ -78,4 +78,10 @@
}
return indices.length - 1;
}
+
+ private float getFontScaleDefValue(ContentResolver resolver) {
+ return Flags.configurableFontScaleDefault() ? Settings.System.getFloat(resolver,
+ Settings.System.DEFAULT_DEVICE_FONT_SCALE, FONT_SCALE_DEF_VALUE)
+ : FONT_SCALE_DEF_VALUE;
+ }
}
diff --git a/src/com/android/settings/accessibility/rtt/TelecomUtil.java b/src/com/android/settings/accessibility/rtt/TelecomUtil.java
index 53c988a..8317298 100644
--- a/src/com/android/settings/accessibility/rtt/TelecomUtil.java
+++ b/src/com/android/settings/accessibility/rtt/TelecomUtil.java
@@ -64,7 +64,7 @@
return Optional.empty();
}
SubscriptionManager subscriptionManager = context.getSystemService(
- SubscriptionManager.class);
+ SubscriptionManager.class).createForAllUserProfiles();
List<SubscriptionInfo> subscriptionInfos =
subscriptionManager.getActiveSubscriptionInfoList();
if (subscriptionInfos == null) {
diff --git a/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragment.java b/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragment.java
index 6666554..7a1dd4b 100644
--- a/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragment.java
@@ -270,6 +270,7 @@
}
mShortcutTargets = Set.of(targets);
+ // TODO(318748373): use 'targets' to populate title when no title is given
}
@Override
diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
index 2023299..2f04b62 100644
--- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java
@@ -20,6 +20,7 @@
import android.app.Activity;
import android.app.Dialog;
+import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -656,7 +657,11 @@
CombinedProviderInfo.createSettingsActivityIntent(
mContext, packageName, settingsActivity, getUser());
if (settingsIntent != null) {
- mContext.startActivity(settingsIntent);
+ try {
+ mContext.startActivity(settingsIntent);
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "Failed to open settings activity", e);
+ }
}
}
});
diff --git a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
index d2400bb..0fb1769 100644
--- a/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
+++ b/src/com/android/settings/applications/credentials/DefaultCombinedPreferenceController.java
@@ -16,6 +16,7 @@
package com.android.settings.applications.credentials;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.credentials.CredentialManager;
@@ -26,6 +27,7 @@
import android.service.autofill.AutofillService;
import android.service.autofill.AutofillServiceInfo;
import android.view.autofill.AutofillManager;
+import android.util.Slog;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
@@ -132,7 +134,11 @@
new PrimaryProviderPreference.Delegate() {
public void onOpenButtonClicked() {
if (settingsActivityIntent != null) {
- startActivity(settingsActivityIntent);
+ try {
+ startActivity(settingsActivityIntent);
+ } catch (ActivityNotFoundException e) {
+ Slog.e(TAG, "Failed to open settings activity", e);
+ }
}
}
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
index 82e987e..dca115b 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
+++ b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
@@ -59,6 +59,7 @@
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_WIFI_ACCESS
import com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_WRITE_SETTINGS
import com.android.settings.spa.app.AllAppListPageProvider
+import com.android.settings.spa.app.battery.BatteryOptimizationModeAppListPageProvider
import com.android.settings.spa.app.appcompat.UserAspectRatioAppsPageProvider
import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
@@ -70,7 +71,6 @@
import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider
import com.android.settings.spa.app.specialaccess.TurnScreenOnAppsAppListProvider
import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
-import com.android.settings.spa.app.storage.StorageAppListPageProvider
import com.android.settings.spa.notification.AppListNotificationsPageProvider
import com.android.settings.spa.system.AppLanguagesPageProvider
@@ -127,6 +127,7 @@
// TODO(b/292165031) enable once sorting is supported
//LIST_TYPE_STORAGE -> StorageAppListPageProvider.Apps.name
//LIST_TYPE_GAMES -> StorageAppListPageProvider.Games.name
+ LIST_TYPE_BATTERY_OPTIMIZATION -> BatteryOptimizationModeAppListPageProvider.name
else -> null
}
}
diff --git a/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java b/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java
index 7a2ae04..33f8b73 100644
--- a/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceController.java
@@ -17,6 +17,7 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -36,13 +37,16 @@
import com.android.settings.connecteddevice.dock.DockUpdater;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.Flags;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class PreviouslyConnectedDevicePreferenceController extends BasePreferenceController
implements LifecycleObserver, OnStart, OnStop, DevicePreferenceCallback {
@@ -56,11 +60,12 @@
private final List<Preference> mDevicesList = new ArrayList<>();
private final List<Preference> mDockDevicesList = new ArrayList<>();
+ private final Map<BluetoothDevice, Preference> mDevicePreferenceMap = new HashMap<>();
+ private final BluetoothAdapter mBluetoothAdapter;
private PreferenceGroup mPreferenceGroup;
private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
private DockUpdater mSavedDockUpdater;
- private BluetoothAdapter mBluetoothAdapter;
@VisibleForTesting
Preference mSeeAllPreference;
@@ -81,7 +86,11 @@
mSavedDockUpdater = FeatureFactory.getFeatureFactory().getDockUpdaterFeatureProvider()
.getSavedDockUpdater(context, this);
mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (Flags.enableSavedDevicesOrderByRecency()) {
+ mBluetoothAdapter = context.getSystemService(BluetoothManager.class).getAdapter();
+ } else {
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ }
}
@Override
@@ -114,6 +123,9 @@
mContext.registerReceiver(mReceiver, mIntentFilter,
Context.RECEIVER_EXPORTED_UNAUDITED);
mBluetoothDeviceUpdater.refreshPreference();
+ if (Flags.enableSavedDevicesOrderByRecency()) {
+ updatePreferenceGroup();
+ }
}
@Override
@@ -131,19 +143,37 @@
@Override
public void onDeviceAdded(Preference preference) {
- final List<BluetoothDevice> bluetoothDevices =
- mBluetoothAdapter.getMostRecentlyConnectedDevices();
- final int index = preference instanceof BluetoothDevicePreference
- ? bluetoothDevices.indexOf(((BluetoothDevicePreference) preference)
- .getBluetoothDevice().getDevice()) : DOCK_DEVICE_INDEX;
- if (DEBUG) {
- Log.d(TAG, "onDeviceAdded() " + preference.getTitle() + ", index of : " + index);
- for (BluetoothDevice device : bluetoothDevices) {
- Log.d(TAG, "onDeviceAdded() most recently device : " + device.getName());
+ if (Flags.enableSavedDevicesOrderByRecency()) {
+ if (preference instanceof BluetoothDevicePreference) {
+ mDevicePreferenceMap.put(
+ ((BluetoothDevicePreference) preference).getBluetoothDevice().getDevice(),
+ preference);
+ } else {
+ mDockDevicesList.add(preference);
}
+ if (DEBUG) {
+ Log.d(TAG, "onDeviceAdded() " + preference.getTitle());
+ }
+ updatePreferenceGroup();
+ } else {
+ final List<BluetoothDevice> bluetoothDevices =
+ mBluetoothAdapter.getMostRecentlyConnectedDevices();
+ final int index =
+ preference instanceof BluetoothDevicePreference
+ ? bluetoothDevices.indexOf(
+ ((BluetoothDevicePreference) preference)
+ .getBluetoothDevice()
+ .getDevice())
+ : DOCK_DEVICE_INDEX;
+ if (DEBUG) {
+ Log.d(TAG, "onDeviceAdded() " + preference.getTitle() + ", index of : " + index);
+ for (BluetoothDevice device : bluetoothDevices) {
+ Log.d(TAG, "onDeviceAdded() most recently device : " + device.getName());
+ }
+ }
+ addPreference(index, preference);
+ updatePreferenceVisibility();
}
- addPreference(index, preference);
- updatePreferenceVisibility();
}
private void addPreference(int index, Preference preference) {
@@ -194,13 +224,57 @@
@Override
public void onDeviceRemoved(Preference preference) {
- if (preference instanceof BluetoothDevicePreference) {
- mDevicesList.remove(preference);
+ if (Flags.enableSavedDevicesOrderByRecency()) {
+ if (preference instanceof BluetoothDevicePreference) {
+ mDevicePreferenceMap.remove(
+ ((BluetoothDevicePreference) preference).getBluetoothDevice().getDevice(),
+ preference);
+ } else {
+ mDockDevicesList.remove(preference);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "onDeviceRemoved() " + preference.getTitle());
+ }
+ updatePreferenceGroup();
} else {
- mDockDevicesList.remove(preference);
- }
+ if (preference instanceof BluetoothDevicePreference) {
+ mDevicesList.remove(preference);
+ } else {
+ mDockDevicesList.remove(preference);
+ }
- addPreference();
+ addPreference();
+ updatePreferenceVisibility();
+ }
+ }
+
+ /** Sort the preferenceGroup by most recently used. */
+ public void updatePreferenceGroup() {
+ mPreferenceGroup.removeAll();
+ mPreferenceGroup.addPreference(mSeeAllPreference);
+ if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
+ // Bluetooth is supported
+ int order = 0;
+ for (BluetoothDevice device : mBluetoothAdapter.getMostRecentlyConnectedDevices()) {
+ Preference preference = mDevicePreferenceMap.getOrDefault(device, null);
+ if (preference != null) {
+ preference.setOrder(order);
+ mPreferenceGroup.addPreference(preference);
+ order += 1;
+ }
+ if (order == MAX_DEVICE_NUM) {
+ break;
+ }
+ }
+ for (Preference preference : mDockDevicesList) {
+ if (order == MAX_DEVICE_NUM) {
+ break;
+ }
+ preference.setOrder(order);
+ mPreferenceGroup.addPreference(preference);
+ order += 1;
+ }
+ }
updatePreferenceVisibility();
}
diff --git a/src/com/android/settings/connecteddevice/SavedDeviceGroupController.java b/src/com/android/settings/connecteddevice/SavedDeviceGroupController.java
index fb35dd9..c73481d 100644
--- a/src/com/android/settings/connecteddevice/SavedDeviceGroupController.java
+++ b/src/com/android/settings/connecteddevice/SavedDeviceGroupController.java
@@ -15,6 +15,9 @@
*/
package com.android.settings.connecteddevice;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -23,18 +26,25 @@
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
+import com.android.settings.bluetooth.BluetoothDevicePreference;
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.bluetooth.SavedBluetoothDeviceUpdater;
import com.android.settings.connecteddevice.dock.DockUpdater;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.Flags;
import com.android.settings.overlay.DockUpdaterFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
/**
* Controller to maintain the {@link PreferenceGroup} for all
* saved devices. It uses {@link DevicePreferenceCallback} to add/remove {@link Preference}
@@ -45,6 +55,10 @@
private static final String KEY = "saved_device_list";
+ private final Map<BluetoothDevice, Preference> mDevicePreferenceMap = new HashMap<>();
+ private final List<Preference> mDockDevicesList = new ArrayList<>();
+ private final BluetoothAdapter mBluetoothAdapter;
+
@VisibleForTesting
PreferenceGroup mPreferenceGroup;
private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
@@ -57,6 +71,7 @@
FeatureFactory.getFeatureFactory().getDockUpdaterFeatureProvider();
mSavedDockUpdater =
dockUpdaterFeatureProvider.getSavedDockUpdater(context, this);
+ mBluetoothAdapter = context.getSystemService(BluetoothManager.class).getAdapter();
}
@Override
@@ -64,6 +79,9 @@
mBluetoothDeviceUpdater.registerCallback();
mSavedDockUpdater.registerCallback();
mBluetoothDeviceUpdater.refreshPreference();
+ if (Flags.enableSavedDevicesOrderByRecency()) {
+ updatePreferenceGroup();
+ }
}
@Override
@@ -101,17 +119,63 @@
@Override
public void onDeviceAdded(Preference preference) {
- if (mPreferenceGroup.getPreferenceCount() == 0) {
- mPreferenceGroup.setVisible(true);
+ if (Flags.enableSavedDevicesOrderByRecency()) {
+ mPreferenceGroup.addPreference(preference);
+ if (preference instanceof BluetoothDevicePreference) {
+ mDevicePreferenceMap.put(
+ ((BluetoothDevicePreference) preference).getBluetoothDevice().getDevice(),
+ preference);
+ } else {
+ mDockDevicesList.add(preference);
+ }
+ updatePreferenceGroup();
+ } else {
+ if (mPreferenceGroup.getPreferenceCount() == 0) {
+ mPreferenceGroup.setVisible(true);
+ }
+ mPreferenceGroup.addPreference(preference);
}
- mPreferenceGroup.addPreference(preference);
}
@Override
public void onDeviceRemoved(Preference preference) {
- mPreferenceGroup.removePreference(preference);
- if (mPreferenceGroup.getPreferenceCount() == 0) {
+ if (Flags.enableSavedDevicesOrderByRecency()) {
+ mPreferenceGroup.removePreference(preference);
+ if (preference instanceof BluetoothDevicePreference) {
+ mDevicePreferenceMap.remove(
+ ((BluetoothDevicePreference) preference).getBluetoothDevice().getDevice(),
+ preference);
+ } else {
+ mDockDevicesList.remove(preference);
+ }
+ updatePreferenceGroup();
+ } else {
+ mPreferenceGroup.removePreference(preference);
+ if (mPreferenceGroup.getPreferenceCount() == 0) {
+ mPreferenceGroup.setVisible(false);
+ }
+ }
+ }
+
+ /** Sort the preferenceGroup by most recently used. */
+ public void updatePreferenceGroup() {
+ if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
+ // Bluetooth is unsupported or disabled
mPreferenceGroup.setVisible(false);
+ } else {
+ mPreferenceGroup.setVisible(true);
+ int order = 0;
+ for (BluetoothDevice device : mBluetoothAdapter.getMostRecentlyConnectedDevices()) {
+ Preference preference = mDevicePreferenceMap.getOrDefault(device, null);
+ if (preference != null) {
+ preference.setOrder(order);
+ order += 1;
+ }
+ }
+ for (Preference preference : mDockDevicesList) {
+ preference.setOrder(order);
+ order += 1;
+ }
}
}
@@ -130,4 +194,9 @@
public void setSavedDockUpdater(DockUpdater savedDockUpdater) {
mSavedDockUpdater = savedDockUpdater;
}
+
+ @VisibleForTesting
+ void setPreferenceGroup(PreferenceGroup preferenceGroup) {
+ mPreferenceGroup = preferenceGroup;
+ }
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
index 81465ed..44c947d 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
@@ -19,6 +19,8 @@
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
import android.widget.ImageButton;
import androidx.preference.PreferenceViewHolder;
@@ -30,6 +32,7 @@
public class AudioSharingNamePreference extends ValidatedEditTextPreference {
private static final String TAG = "AudioSharingNamePreference";
+ private boolean mShowQrCodeIcon = false;
public AudioSharingNamePreference(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
@@ -58,17 +61,50 @@
setWidgetLayoutResource(R.layout.preference_widget_qrcode);
}
+ void setShowQrCodeIcon(boolean show) {
+ mShowQrCodeIcon = show;
+ notifyChanged();
+ }
+
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- final ImageButton shareButton = (ImageButton) holder.findViewById(R.id.button_icon);
+
+ ImageButton shareButton = (ImageButton) holder.findViewById(R.id.button_icon);
+ View divider =
+ holder.findViewById(
+ com.android.settingslib.widget.preference.twotarget.R.id
+ .two_target_divider);
+
+ if (shareButton != null && divider != null) {
+ if (mShowQrCodeIcon) {
+ configureVisibleStateForQrCodeIcon(shareButton, divider);
+ } else {
+ configureInvisibleStateForQrCodeIcon(shareButton, divider);
+ }
+ } else {
+ Log.w(TAG, "onBindViewHolder() : shareButton or divider is null!");
+ }
+ }
+
+ private void configureVisibleStateForQrCodeIcon(ImageButton shareButton, View divider) {
+ divider.setVisibility(View.VISIBLE);
+ shareButton.setVisibility(View.VISIBLE);
shareButton.setImageDrawable(getContext().getDrawable(R.drawable.ic_qrcode_24dp));
- shareButton.setOnClickListener(
- unused ->
- new SubSettingLauncher(getContext())
- .setTitleText("Audio sharing QR code")
- .setDestination(AudioStreamsQrCodeFragment.class.getName())
- .setSourceMetricsCategory(SettingsEnums.AUDIO_SHARING_SETTINGS)
- .launch());
+ shareButton.setOnClickListener(unused -> launchAudioSharingQrCodeFragment());
+ }
+
+ private void configureInvisibleStateForQrCodeIcon(ImageButton shareButton, View divider) {
+ divider.setVisibility(View.INVISIBLE);
+ shareButton.setVisibility(View.INVISIBLE);
+ shareButton.setOnClickListener(null);
+ }
+
+ private void launchAudioSharingQrCodeFragment() {
+ new SubSettingLauncher(getContext())
+ .setTitleText("Audio sharing QR code")
+ .setDestination(AudioStreamsQrCodeFragment.class.getName())
+ .setSourceMetricsCategory(SettingsEnums.AUDIO_SHARING_SETTINGS)
+ .launch();
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
index a3eb188..644e05e 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
@@ -16,25 +16,128 @@
package com.android.settings.connecteddevice.audiosharing;
+import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.isBroadcasting;
+
+import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.content.Context;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import com.android.settings.bluetooth.Utils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.widget.ValidatedEditTextPreference;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
public class AudioSharingNamePreferenceController extends BasePreferenceController
- implements ValidatedEditTextPreference.Validator, Preference.OnPreferenceChangeListener {
+ implements ValidatedEditTextPreference.Validator,
+ Preference.OnPreferenceChangeListener,
+ DefaultLifecycleObserver {
private static final String TAG = "AudioSharingNamePreferenceController";
-
+ private static final boolean DEBUG = BluetoothUtils.D;
private static final String PREF_KEY = "audio_sharing_stream_name";
- private AudioSharingNameTextValidator mAudioSharingNameTextValidator;
+ private final BluetoothLeBroadcast.Callback mBroadcastCallback =
+ new BluetoothLeBroadcast.Callback() {
+ @Override
+ public void onBroadcastMetadataChanged(
+ int broadcastId, BluetoothLeBroadcastMetadata metadata) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onBroadcastMetadataChanged() broadcastId : "
+ + broadcastId
+ + " metadata: "
+ + metadata);
+ }
+ updateQrCodeIcon(true);
+ }
+
+ @Override
+ public void onBroadcastStartFailed(int reason) {}
+
+ @Override
+ public void onBroadcastStarted(int reason, int broadcastId) {}
+
+ @Override
+ public void onBroadcastStopFailed(int reason) {}
+
+ @Override
+ public void onBroadcastStopped(int reason, int broadcastId) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "onBroadcastStopped() reason : "
+ + reason
+ + " broadcastId: "
+ + broadcastId);
+ }
+ updateQrCodeIcon(false);
+ }
+
+ @Override
+ public void onBroadcastUpdateFailed(int reason, int broadcastId) {
+ Log.w(TAG, "onBroadcastUpdateFailed() reason : " + reason);
+ // Do nothing if update failed.
+ }
+
+ @Override
+ public void onBroadcastUpdated(int reason, int broadcastId) {
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastUpdated() reason : " + reason);
+ }
+ updateBroadcastName();
+ }
+
+ @Override
+ public void onPlaybackStarted(int reason, int broadcastId) {}
+
+ @Override
+ public void onPlaybackStopped(int reason, int broadcastId) {}
+ };
+
+ @Nullable private final LocalBluetoothManager mLocalBtManager;
+ @Nullable private final LocalBluetoothLeBroadcast mBroadcast;
+ private final Executor mExecutor;
+ private final AudioSharingNameTextValidator mAudioSharingNameTextValidator;
+ @Nullable private AudioSharingNamePreference mPreference;
public AudioSharingNamePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
+ mLocalBtManager = Utils.getLocalBluetoothManager(context);
+ mBroadcast =
+ (mLocalBtManager != null)
+ ? mLocalBtManager.getProfileManager().getLeAudioBroadcastProfile()
+ : null;
mAudioSharingNameTextValidator = new AudioSharingNameTextValidator();
+ mExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ @Override
+ public void onStart(@NonNull LifecycleOwner owner) {
+ if (mBroadcast != null) {
+ mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
+ }
+ }
+
+ @Override
+ public void onStop(@NonNull LifecycleOwner owner) {
+ if (mBroadcast != null) {
+ mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
+ }
}
@Override
@@ -43,16 +146,76 @@
}
@Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ if (mPreference != null) {
+ mPreference.setValidator(this);
+ updateBroadcastName();
+ updateQrCodeIcon(isBroadcasting(mLocalBtManager));
+ }
+ }
+
+ @Override
public String getPreferenceKey() {
return PREF_KEY;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
- // TODO: update broadcast when name is changed.
+ if (mPreference != null
+ && mPreference.getSummary() != null
+ && ((String) newValue).contentEquals(mPreference.getSummary())) {
+ return false;
+ }
+
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ if (mBroadcast != null) {
+ mBroadcast.setProgramInfo((String) newValue);
+ if (isBroadcasting(mLocalBtManager)) {
+ // Update broadcast, UI update will be handled after callback
+ mBroadcast.updateBroadcast();
+ } else {
+ // Directly update UI if no ongoing broadcast
+ updateBroadcastName();
+ }
+ }
+ });
return true;
}
+ private void updateBroadcastName() {
+ if (mPreference != null) {
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ if (mBroadcast != null) {
+ String name = mBroadcast.getProgramInfo();
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mPreference != null) {
+ mPreference.setText(name);
+ mPreference.setSummary(name);
+ }
+ });
+ }
+ });
+ }
+ }
+
+ private void updateQrCodeIcon(boolean show) {
+ if (mPreference != null) {
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mPreference != null) {
+ mPreference.setShowQrCodeIcon(show);
+ }
+ });
+ }
+ }
+
@Override
public boolean isTextValid(String value) {
return mAudioSharingNameTextValidator.isTextValid(value);
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidator.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidator.java
index 9492961..2022eb2 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidator.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidator.java
@@ -18,10 +18,27 @@
import com.android.settings.widget.ValidatedEditTextPreference;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Validator for Audio Sharing Name, which should be a UTF-8 encoded string containing a minimum of
+ * 4 characters and a maximum of 32 human-readable characters.
+ */
public class AudioSharingNameTextValidator implements ValidatedEditTextPreference.Validator {
+ private static final int MIN_LENGTH = 4;
+ private static final int MAX_LENGTH = 32;
+
@Override
public boolean isTextValid(String value) {
- // TODO: Add validate rule if applicable.
- return true;
+ if (value == null || value.length() < MIN_LENGTH || value.length() > MAX_LENGTH) {
+ return false;
+ }
+ return isValidUTF8(value);
+ }
+
+ private static boolean isValidUTF8(String value) {
+ byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
+ String reconstructedString = new String(bytes, StandardCharsets.UTF_8);
+ return value.equals(reconstructedString);
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceController.java
new file mode 100644
index 0000000..da0eb2e
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceController.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.connecteddevice.audiosharing;
+
+import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.widget.ValidatedEditTextPreference;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class AudioSharingPasswordPreferenceController extends BasePreferenceController
+ implements ValidatedEditTextPreference.Validator,
+ Preference.OnPreferenceChangeListener,
+ DefaultLifecycleObserver {
+ private static final String PREF_KEY = "audio_sharing_stream_password";
+
+ private final BluetoothLeBroadcast.Callback mBroadcastCallback =
+ new BluetoothLeBroadcast.Callback() {
+ @Override
+ public void onBroadcastMetadataChanged(
+ int broadcastId, BluetoothLeBroadcastMetadata metadata) {}
+
+ @Override
+ public void onBroadcastStartFailed(int reason) {}
+
+ @Override
+ public void onBroadcastStarted(int reason, int broadcastId) {}
+
+ @Override
+ public void onBroadcastStopFailed(int reason) {}
+
+ @Override
+ public void onBroadcastStopped(int reason, int broadcastId) {}
+
+ @Override
+ public void onBroadcastUpdateFailed(int reason, int broadcastId) {}
+
+ @Override
+ public void onBroadcastUpdated(int reason, int broadcastId) {}
+
+ @Override
+ public void onPlaybackStarted(int reason, int broadcastId) {}
+
+ @Override
+ public void onPlaybackStopped(int reason, int broadcastId) {}
+ };
+ @Nullable private final LocalBluetoothLeBroadcast mBroadcast;
+ private final Executor mExecutor;
+ private final AudioSharingPasswordValidator mAudioSharingPasswordValidator;
+ @Nullable private ValidatedEditTextPreference mPreference;
+
+ public AudioSharingPasswordPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mBroadcast =
+ Utils.getLocalBtManager(context).getProfileManager().getLeAudioBroadcastProfile();
+ mAudioSharingPasswordValidator = new AudioSharingPasswordValidator();
+ mExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ @Override
+ public void onStart(@NonNull LifecycleOwner owner) {
+ if (mBroadcast != null) {
+ mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
+ }
+ }
+
+ @Override
+ public void onStop(@NonNull LifecycleOwner owner) {
+ if (mBroadcast != null) {
+ mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
+ }
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ if (mPreference != null) {
+ mPreference.setValidator(this);
+ }
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return PREF_KEY;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ // TODO(chelseahao): implement
+ return true;
+ }
+
+ @Override
+ public boolean isTextValid(String value) {
+ return mAudioSharingPasswordValidator.isTextValid(value);
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordValidator.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordValidator.java
new file mode 100644
index 0000000..dbb40ec
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordValidator.java
@@ -0,0 +1,51 @@
+/*
+ * 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 com.android.settings.widget.ValidatedEditTextPreference;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Validator for Audio Sharing Password, which should be a UTF-8 string that has at least 4 octets
+ * and should not exceed 16 octets.
+ */
+public class AudioSharingPasswordValidator implements ValidatedEditTextPreference.Validator {
+ private static final int MIN_OCTETS = 4;
+ private static final int MAX_OCTETS = 16;
+
+ @Override
+ public boolean isTextValid(String value) {
+ if (value == null
+ || getOctetsCount(value) < MIN_OCTETS
+ || getOctetsCount(value) > MAX_OCTETS) {
+ return false;
+ }
+
+ return isValidUTF8(value);
+ }
+
+ private static int getOctetsCount(String value) {
+ return value.getBytes(StandardCharsets.UTF_8).length;
+ }
+
+ private static boolean isValidUTF8(String value) {
+ byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
+ String reconstructedString = new String(bytes, StandardCharsets.UTF_8);
+ return value.equals(reconstructedString);
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
index 924b04d..242ce20 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
@@ -336,7 +336,7 @@
}
/** Returns if the broadcast is on-going. */
- public static boolean isBroadcasting(LocalBluetoothManager manager) {
+ public static boolean isBroadcasting(@Nullable LocalBluetoothManager manager) {
if (manager == null) return false;
LocalBluetoothLeBroadcast broadcast =
manager.getProfileManager().getLeAudioBroadcastProfile();
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
index bb729d6..47597cf 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
@@ -16,39 +16,170 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
+import android.util.Log;
+import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
+import com.android.settings.bluetooth.Utils;
import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.ActionButtonsPreference;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
public class AudioStreamButtonController extends BasePreferenceController
implements DefaultLifecycleObserver {
+ private static final String TAG = "AudioStreamButtonController";
private static final String KEY = "audio_stream_button";
+ private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
+ new AudioStreamsBroadcastAssistantCallback() {
+ @Override
+ public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {
+ super.onSourceRemoved(sink, sourceId, reason);
+ updateButton();
+ }
+
+ @Override
+ public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {
+ super.onSourceRemoveFailed(sink, sourceId, reason);
+ updateButton();
+ }
+
+ @Override
+ public void onReceiveStateChanged(
+ BluetoothDevice sink,
+ int sourceId,
+ BluetoothLeBroadcastReceiveState state) {
+ super.onReceiveStateChanged(sink, sourceId, state);
+ if (mAudioStreamsHelper.isConnected(state)) {
+ updateButton();
+ }
+ }
+
+ @Override
+ public void onSourceAddFailed(
+ BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {
+ super.onSourceAddFailed(sink, source, reason);
+ updateButton();
+ }
+
+ @Override
+ public void onSourceLost(int broadcastId) {
+ super.onSourceLost(broadcastId);
+ updateButton();
+ }
+ };
+
+ private final AudioStreamsRepository mAudioStreamsRepository =
+ AudioStreamsRepository.getInstance();
+ private final Executor mExecutor;
+ private final AudioStreamsHelper mAudioStreamsHelper;
+ private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
private @Nullable ActionButtonsPreference mPreference;
private int mBroadcastId = -1;
public AudioStreamButtonController(Context context, String preferenceKey) {
super(context, preferenceKey);
+ mExecutor = Executors.newSingleThreadExecutor();
+ mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context));
+ mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
+ }
+
+ @Override
+ public void onStart(@NonNull LifecycleOwner owner) {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "onStart(): LeBroadcastAssistant is null!");
+ return;
+ }
+ mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
+ }
+
+ @Override
+ public void onStop(@NonNull LifecycleOwner owner) {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "onStop(): LeBroadcastAssistant is null!");
+ return;
+ }
+ mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
}
@Override
public final void displayPreference(PreferenceScreen screen) {
mPreference = screen.findPreference(getPreferenceKey());
- if (mPreference != null) {
- mPreference.setButton1Enabled(true);
- // TODO(chelseahao): update this based on stream connection state
- mPreference
- .setButton1Text(R.string.bluetooth_device_context_disconnect)
- .setButton1Icon(R.drawable.ic_settings_close);
- }
+ updateButton();
super.displayPreference(screen);
}
+ private void updateButton() {
+ if (mPreference != null) {
+ if (mAudioStreamsHelper.getAllConnectedSources().stream()
+ .map(BluetoothLeBroadcastReceiveState::getBroadcastId)
+ .anyMatch(connectedBroadcastId -> connectedBroadcastId == mBroadcastId)) {
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mPreference != null) {
+ mPreference.setButton1Enabled(true);
+ mPreference
+ .setButton1Text(
+ R.string.bluetooth_device_context_disconnect)
+ .setButton1Icon(R.drawable.ic_settings_close)
+ .setButton1OnClickListener(
+ unused -> {
+ if (mPreference != null) {
+ mPreference.setButton1Enabled(false);
+ }
+ mAudioStreamsHelper.removeSource(mBroadcastId);
+ });
+ }
+ });
+ } else {
+ View.OnClickListener clickToRejoin =
+ unused ->
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ var metadata =
+ mAudioStreamsRepository.getSavedMetadata(
+ mContext, mBroadcastId);
+ if (metadata != null) {
+ mAudioStreamsHelper.addSource(metadata);
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mPreference != null) {
+ mPreference.setButton1Enabled(
+ false);
+ }
+ });
+ }
+ });
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mPreference != null) {
+ mPreference.setButton1Enabled(true);
+ mPreference
+ .setButton1Text(R.string.bluetooth_device_context_connect)
+ .setButton1Icon(R.drawable.ic_add_24dp)
+ .setButton1OnClickListener(clickToRejoin);
+ }
+ });
+ }
+ } else {
+ Log.w(TAG, "updateButton(): preference is null!");
+ }
+ }
+
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialog.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialog.java
index 5981c9e..131c8f6 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialog.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamConfirmDialog.java
@@ -104,7 +104,7 @@
private Dialog getErrorDialog() {
return new AudioStreamsDialogFragment.DialogBuilder(mActivity)
.setTitle("Can't listen to audio stream")
- .setSubTitle1("Can't play this audio stream. Learn more")
+ .setSubTitle2("Can't play this audio stream. Learn more")
.setRightButtonText("Close")
.setRightButtonOnClickListener(
unused -> {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
index 89f24bc..3524543 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
@@ -16,22 +16,64 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
+import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
+import com.android.settings.bluetooth.Utils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.LayoutPreference;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
import javax.annotation.Nullable;
public class AudioStreamHeaderController extends BasePreferenceController
implements DefaultLifecycleObserver {
+ private static final String TAG = "AudioStreamHeaderController";
private static final String KEY = "audio_stream_header";
+ private final Executor mExecutor;
+ private final AudioStreamsHelper mAudioStreamsHelper;
+ @Nullable private final LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
+ private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
+ new AudioStreamsBroadcastAssistantCallback() {
+ @Override
+ public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {
+ super.onSourceRemoved(sink, sourceId, reason);
+ updateSummary();
+ }
+
+ @Override
+ public void onSourceLost(int broadcastId) {
+ super.onSourceLost(broadcastId);
+ updateSummary();
+ }
+
+ @Override
+ public void onReceiveStateChanged(
+ BluetoothDevice sink,
+ int sourceId,
+ BluetoothLeBroadcastReceiveState state) {
+ super.onReceiveStateChanged(sink, sourceId, state);
+ if (mAudioStreamsHelper.isConnected(state)) {
+ updateSummary();
+ }
+ }
+ };
+
private @Nullable EntityHeaderController mHeaderController;
private @Nullable DashboardFragment mFragment;
private String mBroadcastName = "";
@@ -39,6 +81,27 @@
public AudioStreamHeaderController(Context context, String preferenceKey) {
super(context, preferenceKey);
+ mExecutor = Executors.newSingleThreadExecutor();
+ mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context));
+ mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
+ }
+
+ @Override
+ public void onStart(@NonNull LifecycleOwner owner) {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "onStart(): LeBroadcastAssistant is null!");
+ return;
+ }
+ mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
+ }
+
+ @Override
+ public void onStop(@NonNull LifecycleOwner owner) {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "onStop(): LeBroadcastAssistant is null!");
+ return;
+ }
+ mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
}
@Override
@@ -55,14 +118,37 @@
}
mHeaderController.setIcon(
screen.getContext().getDrawable(R.drawable.ic_bt_audio_sharing));
- // TODO(chelseahao): update this based on stream connection state
- mHeaderController.setSummary("Listening now");
- mHeaderController.done(true);
screen.addPreference(headerPreference);
+ updateSummary();
}
super.displayPreference(screen);
}
+ private void updateSummary() {
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ var latestSummary =
+ mAudioStreamsHelper.getAllConnectedSources().stream()
+ .map(
+ BluetoothLeBroadcastReceiveState
+ ::getBroadcastId)
+ .anyMatch(
+ connectedBroadcastId ->
+ connectedBroadcastId
+ == mBroadcastId)
+ ? "Listening now"
+ : "";
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mHeaderController != null) {
+ mHeaderController.setSummary(latestSummary);
+ mHeaderController.done(true);
+ }
+ });
+ });
+ }
+
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreference.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreference.java
index 678f952..c2e1178 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreference.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamPreference.java
@@ -21,8 +21,10 @@
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.View;
import androidx.annotation.Nullable;
+import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settingslib.widget.TwoTargetPreference;
@@ -56,7 +58,6 @@
}
mIsConnected = isConnected;
setSummary(summary);
- setOrder(isConnected ? 0 : 1);
setOnPreferenceClickListener(onPreferenceClickListener);
notifyChanged();
}
@@ -70,6 +71,23 @@
mAudioStream.setState(state);
}
+ void setAudioStreamMetadata(BluetoothLeBroadcastMetadata metadata) {
+ mAudioStream.setMetadata(metadata);
+ }
+
+ int getAudioStreamBroadcastId() {
+ return mAudioStream.getBroadcastId();
+ }
+
+ int getAudioStreamRssi() {
+ return mAudioStream.getRssi();
+ }
+
+ @Nullable
+ BluetoothLeBroadcastMetadata getAudioStreamMetadata() {
+ return mAudioStream.getMetadata();
+ }
+
AudioStreamsProgressCategoryController.AudioStreamState getAudioStreamState() {
return mAudioStream.getState();
}
@@ -84,25 +102,31 @@
return R.layout.preference_widget_lock;
}
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ View divider =
+ holder.findViewById(
+ com.android.settingslib.widget.preference.twotarget.R.id
+ .two_target_divider);
+ if (divider != null) {
+ divider.setVisibility(View.GONE);
+ }
+ }
+
static AudioStreamPreference fromMetadata(
- Context context,
- BluetoothLeBroadcastMetadata source,
- AudioStreamsProgressCategoryController.AudioStreamState streamState) {
+ Context context, BluetoothLeBroadcastMetadata source) {
AudioStreamPreference preference = new AudioStreamPreference(context, /* attrs= */ null);
preference.setTitle(getBroadcastName(source));
- preference.setAudioStream(new AudioStream(source.getBroadcastId(), streamState));
+ preference.setAudioStream(new AudioStream(source));
return preference;
}
static AudioStreamPreference fromReceiveState(
- Context context,
- BluetoothLeBroadcastReceiveState receiveState,
- AudioStreamsProgressCategoryController.AudioStreamState streamState) {
+ Context context, BluetoothLeBroadcastReceiveState receiveState) {
AudioStreamPreference preference = new AudioStreamPreference(context, /* attrs= */ null);
preference.setTitle(getBroadcastName(receiveState));
- preference.setAudioStream(
- new AudioStream(
- receiveState.getSourceId(), receiveState.getBroadcastId(), streamState));
+ preference.setAudioStream(new AudioStream(receiveState));
return preference;
}
@@ -127,41 +151,45 @@
}
private static final class AudioStream {
- private int mSourceId;
- private int mBroadcastId;
- private AudioStreamsProgressCategoryController.AudioStreamState mState;
+ private static final int UNAVAILABLE = -1;
+ @Nullable private BluetoothLeBroadcastMetadata mMetadata;
+ @Nullable private BluetoothLeBroadcastReceiveState mReceiveState;
+ private AudioStreamsProgressCategoryController.AudioStreamState mState =
+ AudioStreamsProgressCategoryController.AudioStreamState.UNKNOWN;
- private AudioStream(
- int broadcastId, AudioStreamsProgressCategoryController.AudioStreamState state) {
- mBroadcastId = broadcastId;
- mState = state;
+ private AudioStream(BluetoothLeBroadcastMetadata metadata) {
+ mMetadata = metadata;
}
- private AudioStream(
- int sourceId,
- int broadcastId,
- AudioStreamsProgressCategoryController.AudioStreamState state) {
- mSourceId = sourceId;
- mBroadcastId = broadcastId;
- mState = state;
+ private AudioStream(BluetoothLeBroadcastReceiveState receiveState) {
+ mReceiveState = receiveState;
}
- // TODO(chelseahao): use this to handleSourceRemoved
- private int getSourceId() {
- return mSourceId;
- }
-
- // TODO(chelseahao): use this to handleSourceRemoved
private int getBroadcastId() {
- return mBroadcastId;
+ return mMetadata != null
+ ? mMetadata.getBroadcastId()
+ : mReceiveState != null ? mReceiveState.getBroadcastId() : UNAVAILABLE;
+ }
+
+ private int getRssi() {
+ return mMetadata != null ? mMetadata.getRssi() : Integer.MAX_VALUE;
}
private AudioStreamsProgressCategoryController.AudioStreamState getState() {
return mState;
}
+ @Nullable
+ private BluetoothLeBroadcastMetadata getMetadata() {
+ return mMetadata;
+ }
+
private void setState(AudioStreamsProgressCategoryController.AudioStreamState state) {
mState = state;
}
+
+ private void setMetadata(BluetoothLeBroadcastMetadata metadata) {
+ mMetadata = metadata;
+ }
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsBroadcastAssistantCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsBroadcastAssistantCallback.java
index 84e753c..9fb5b21 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsBroadcastAssistantCallback.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsBroadcastAssistantCallback.java
@@ -24,21 +24,12 @@
import com.android.settingslib.bluetooth.BluetoothUtils;
-import java.util.Locale;
-
public class AudioStreamsBroadcastAssistantCallback
implements BluetoothLeBroadcastAssistant.Callback {
private static final String TAG = "AudioStreamsBroadcastAssistantCallback";
private static final boolean DEBUG = BluetoothUtils.D;
- private final AudioStreamsProgressCategoryController mCategoryController;
-
- public AudioStreamsBroadcastAssistantCallback(
- AudioStreamsProgressCategoryController audioStreamsProgressCategoryController) {
- mCategoryController = audioStreamsProgressCategoryController;
- }
-
@Override
public void onReceiveStateChanged(
BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
@@ -52,45 +43,30 @@
+ " state: "
+ state);
}
- mCategoryController.handleSourceConnected(state);
}
@Override
public void onSearchStartFailed(int reason) {
Log.w(TAG, "onSearchStartFailed() reason : " + reason);
- mCategoryController.showToast(
- String.format(Locale.US, "Failed to start scanning, reason %d", reason));
}
@Override
public void onSearchStarted(int reason) {
- if (mCategoryController == null) {
- Log.w(TAG, "onSearchStarted() : mCategoryController is null!");
- return;
- }
if (DEBUG) {
Log.d(TAG, "onSearchStarted() reason : " + reason);
}
- mCategoryController.setScanning(true);
}
@Override
public void onSearchStopFailed(int reason) {
Log.w(TAG, "onSearchStopFailed() reason : " + reason);
- mCategoryController.showToast(
- String.format(Locale.US, "Failed to stop scanning, reason %d", reason));
}
@Override
public void onSearchStopped(int reason) {
- if (mCategoryController == null) {
- Log.w(TAG, "onSearchStopped() : mCategoryController is null!");
- return;
- }
if (DEBUG) {
Log.d(TAG, "onSearchStopped() reason : " + reason);
}
- mCategoryController.setScanning(false);
}
@Override
@@ -106,8 +82,6 @@
+ " reason: "
+ reason);
}
- mCategoryController.showToast(
- String.format(Locale.US, "Failed to join broadcast, reason %d", reason));
}
@Override
@@ -126,14 +100,9 @@
@Override
public void onSourceFound(BluetoothLeBroadcastMetadata source) {
- if (mCategoryController == null) {
- Log.w(TAG, "onSourceFound() : mCategoryController is null!");
- return;
- }
if (DEBUG) {
Log.d(TAG, "onSourceFound() broadcastId : " + source.getBroadcastId());
}
- mCategoryController.handleSourceFound(source);
}
@Override
@@ -141,7 +110,6 @@
if (DEBUG) {
Log.d(TAG, "onSourceLost() broadcastId : " + broadcastId);
}
- mCategoryController.handleSourceLost(broadcastId);
}
@Override
@@ -153,12 +121,6 @@
@Override
public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {
Log.w(TAG, "onSourceRemoveFailed() sourceId : " + sourceId + " reason : " + reason);
- mCategoryController.showToast(
- String.format(
- Locale.US,
- "Failed to remove source %d for sink %s",
- sourceId,
- sink.getAddress()));
}
@Override
@@ -166,8 +128,5 @@
if (DEBUG) {
Log.d(TAG, "onSourceRemoved() sourceId : " + sourceId + " reason : " + reason);
}
- mCategoryController.showToast(
- String.format(
- Locale.US, "Source %d removed for sink %s", sourceId, sink.getAddress()));
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
new file mode 100644
index 0000000..34ffc91
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 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.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.util.Log;
+
+import java.util.Locale;
+
+public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastAssistantCallback {
+ private static final String TAG = "AudioStreamsProgressCategoryCallback";
+
+ private final AudioStreamsProgressCategoryController mCategoryController;
+
+ public AudioStreamsProgressCategoryCallback(
+ AudioStreamsProgressCategoryController audioStreamsProgressCategoryController) {
+ mCategoryController = audioStreamsProgressCategoryController;
+ }
+
+ @Override
+ public void onReceiveStateChanged(
+ BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
+ super.onReceiveStateChanged(sink, sourceId, state);
+ mCategoryController.handleSourceConnected(state);
+ }
+
+ @Override
+ public void onSearchStartFailed(int reason) {
+ super.onSearchStartFailed(reason);
+ mCategoryController.showToast(
+ String.format(Locale.US, "Failed to start scanning, reason %d", reason));
+ }
+
+ @Override
+ public void onSearchStarted(int reason) {
+ super.onSearchStarted(reason);
+ if (mCategoryController == null) {
+ Log.w(TAG, "onSearchStarted() : mCategoryController is null!");
+ return;
+ }
+ mCategoryController.setScanning(true);
+ }
+
+ @Override
+ public void onSearchStopFailed(int reason) {
+ super.onSearchStopFailed(reason);
+ mCategoryController.showToast(
+ String.format(Locale.US, "Failed to stop scanning, reason %d", reason));
+ }
+
+ @Override
+ public void onSearchStopped(int reason) {
+ super.onSearchStopped(reason);
+ if (mCategoryController == null) {
+ Log.w(TAG, "onSearchStopped() : mCategoryController is null!");
+ return;
+ }
+ mCategoryController.setScanning(false);
+ }
+
+ @Override
+ public void onSourceAddFailed(
+ BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {
+ super.onSourceAddFailed(sink, source, reason);
+ mCategoryController.showToast(
+ String.format(Locale.US, "Failed to join broadcast, reason %d", reason));
+ }
+
+ @Override
+ public void onSourceFound(BluetoothLeBroadcastMetadata source) {
+ super.onSourceFound(source);
+ if (mCategoryController == null) {
+ Log.w(TAG, "onSourceFound() : mCategoryController is null!");
+ return;
+ }
+ mCategoryController.handleSourceFound(source);
+ }
+
+ @Override
+ public void onSourceLost(int broadcastId) {
+ super.onSourceLost(broadcastId);
+ mCategoryController.handleSourceLost(broadcastId);
+ }
+
+ @Override
+ public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {
+ super.onSourceRemoveFailed(sink, sourceId, reason);
+ mCategoryController.showToast(
+ String.format(
+ Locale.US,
+ "Failed to remove source %d for sink %s",
+ sourceId,
+ sink.getAddress()));
+ }
+
+ @Override
+ public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {
+ super.onSourceRemoved(sink, sourceId, reason);
+ mCategoryController.handleSourceRemoved();
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
index cb9975d..c6f342a 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController.REQUEST_SCAN_BT_BROADCAST_QR_CODE;
+
import static java.util.Collections.emptyList;
import android.app.AlertDialog;
@@ -43,8 +45,10 @@
import com.android.settings.R;
import com.android.settings.bluetooth.Utils;
import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
+import com.android.settings.connecteddevice.audiosharing.audiostreams.qrcode.QrCodeScanModeActivity;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.bluetooth.BluetoothBroadcastUtils;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -53,6 +57,7 @@
import com.android.settingslib.utils.ThreadUtils;
import java.nio.charset.StandardCharsets;
+import java.util.Comparator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -74,25 +79,77 @@
}
};
+ private final Preference.OnPreferenceClickListener mAddSourceOrShowDialog =
+ preference -> {
+ var p = (AudioStreamPreference) preference;
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "preferenceClicked(): attempt to join broadcast id : "
+ + p.getAudioStreamBroadcastId());
+ }
+ var source = p.getAudioStreamMetadata();
+ if (source != null) {
+ if (source.isEncrypted()) {
+ ThreadUtils.postOnMainThread(() -> launchPasswordDialog(source, p));
+ } else {
+ moveToState(p, AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE);
+ }
+ }
+ return true;
+ };
+
+ private final Preference.OnPreferenceClickListener mLaunchDetailFragment =
+ preference -> {
+ var p = (AudioStreamPreference) preference;
+ Bundle broadcast = new Bundle();
+ broadcast.putString(
+ AudioStreamDetailsFragment.BROADCAST_NAME_ARG, (String) p.getTitle());
+ broadcast.putInt(
+ AudioStreamDetailsFragment.BROADCAST_ID_ARG, p.getAudioStreamBroadcastId());
+
+ new SubSettingLauncher(mContext)
+ .setTitleText("Audio stream details")
+ .setDestination(AudioStreamDetailsFragment.class.getName())
+ // TODO(chelseahao): Add logging enum
+ .setSourceMetricsCategory(SettingsEnums.PAGE_UNKNOWN)
+ .setArguments(broadcast)
+ .launch();
+ return true;
+ };
+
+ private final AudioStreamsRepository mAudioStreamsRepository =
+ AudioStreamsRepository.getInstance();
+
enum AudioStreamState {
+ UNKNOWN,
// When mTimedSourceFromQrCode is present and this source has not been synced.
WAIT_FOR_SYNC,
// When source has been synced but not added to any sink.
SYNCED,
// When addSource is called for this source and waiting for response.
- WAIT_FOR_SOURCE_ADD,
+ ADD_SOURCE_WAIT_FOR_RESPONSE,
// Source is added to active sink.
SOURCE_ADDED,
}
+ private final Comparator<AudioStreamPreference> mComparator =
+ Comparator.<AudioStreamPreference, Boolean>comparing(
+ p ->
+ p.getAudioStreamState()
+ == AudioStreamsProgressCategoryController
+ .AudioStreamState.SOURCE_ADDED)
+ .thenComparingInt(AudioStreamPreference::getAudioStreamRssi)
+ .reversed();
+
private final Executor mExecutor;
- private final AudioStreamsBroadcastAssistantCallback mBroadcastAssistantCallback;
+ private final AudioStreamsProgressCategoryCallback mBroadcastAssistantCallback;
private final AudioStreamsHelper mAudioStreamsHelper;
private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
private final @Nullable LocalBluetoothManager mBluetoothManager;
private final ConcurrentHashMap<Integer, AudioStreamPreference> mBroadcastIdToPreferenceMap =
new ConcurrentHashMap<>();
- private TimedSourceFromQrCode mTimedSourceFromQrCode;
+ private @Nullable TimedSourceFromQrCode mTimedSourceFromQrCode;
private AudioStreamsProgressCategoryPreference mCategoryPreference;
private AudioStreamsDashboardFragment mFragment;
@@ -102,7 +159,7 @@
mBluetoothManager = Utils.getLocalBtManager(mContext);
mAudioStreamsHelper = new AudioStreamsHelper(mBluetoothManager);
mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
- mBroadcastAssistantCallback = new AudioStreamsBroadcastAssistantCallback(this);
+ mBroadcastAssistantCallback = new AudioStreamsProgressCategoryCallback(this);
}
@Override
@@ -155,41 +212,18 @@
}
void handleSourceFound(BluetoothLeBroadcastMetadata source) {
- Preference.OnPreferenceClickListener addSourceOrShowDialog =
- preference -> {
- if (DEBUG) {
- Log.d(
- TAG,
- "preferenceClicked(): attempt to join broadcast id : "
- + source.getBroadcastId());
- }
- if (source.isEncrypted()) {
- ThreadUtils.postOnMainThread(
- () ->
- launchPasswordDialog(
- source, (AudioStreamPreference) preference));
- } else {
- mAudioStreamsHelper.addSource(source);
- ((AudioStreamPreference) preference)
- .setAudioStreamState(AudioStreamState.WAIT_FOR_SOURCE_ADD);
- updatePreferenceConnectionState(
- (AudioStreamPreference) preference,
- AudioStreamState.WAIT_FOR_SOURCE_ADD,
- null);
- }
- return true;
- };
-
var broadcastIdFound = source.getBroadcastId();
mBroadcastIdToPreferenceMap.compute(
broadcastIdFound,
(k, v) -> {
if (v == null) {
- return addNewPreference(
- source, AudioStreamState.SYNCED, addSourceOrShowDialog);
+ // No existing preference for this source founded, add one and set initial
+ // state to SYNCED.
+ return addNewPreference(source, AudioStreamState.SYNCED);
}
var fromState = v.getAudioStreamState();
- if (fromState == AudioStreamState.WAIT_FOR_SYNC) {
+ if (fromState == AudioStreamState.WAIT_FOR_SYNC
+ && mTimedSourceFromQrCode != null) {
var pendingSource = mTimedSourceFromQrCode.get();
if (pendingSource == null) {
Log.w(
@@ -198,15 +232,20 @@
+ fromState
+ " for broadcastId : "
+ broadcastIdFound);
- v.setAudioStreamState(AudioStreamState.SYNCED);
+ v.setAudioStreamMetadata(source);
+ moveToState(v, AudioStreamState.SYNCED);
return v;
}
- mAudioStreamsHelper.addSource(pendingSource);
- mTimedSourceFromQrCode.consumed();
- v.setAudioStreamState(AudioStreamState.WAIT_FOR_SOURCE_ADD);
- updatePreferenceConnectionState(
- v, AudioStreamState.WAIT_FOR_SOURCE_ADD, null);
+ // A preference with source founded is existed from a QR code scan. As the
+ // source is now synced, we update the preference with pendingSource from QR
+ // code scan and add source with it (since it has the password).
+ v.setAudioStreamMetadata(pendingSource);
+ moveToState(v, AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE);
} else {
+ // A preference with source founded existed either because it's already
+ // connected (SOURCE_ADDED), or other unexpected reason. We update the
+ // preference with this source and won't change it's state.
+ v.setAudioStreamMetadata(source);
if (fromState != AudioStreamState.SOURCE_ADDED) {
Log.w(
TAG,
@@ -229,18 +268,18 @@
metadataFromQrCode.getBroadcastId(),
(k, v) -> {
if (v == null) {
- mTimedSourceFromQrCode.waitForConsume();
- return addNewPreference(
- metadataFromQrCode, AudioStreamState.WAIT_FOR_SYNC, null);
+ // No existing preference for this source from the QR code scan, add one and
+ // set initial state to WAIT_FOR_SYNC.
+ return addNewPreference(metadataFromQrCode, AudioStreamState.WAIT_FOR_SYNC);
}
var fromState = v.getAudioStreamState();
if (fromState == AudioStreamState.SYNCED) {
- mAudioStreamsHelper.addSource(metadataFromQrCode);
- mTimedSourceFromQrCode.consumed();
- v.setAudioStreamState(AudioStreamState.WAIT_FOR_SOURCE_ADD);
- updatePreferenceConnectionState(
- v, AudioStreamState.WAIT_FOR_SOURCE_ADD, null);
+ // A preference with source from the QR code is existed because it has been
+ // founded during scanning, now we have the password, we can add source.
+ v.setAudioStreamMetadata(metadataFromQrCode);
+ moveToState(v, AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE);
} else {
+ v.setAudioStreamMetadata(metadataFromQrCode);
Log.w(
TAG,
"handleSourceFromQrCode(): unexpected state : "
@@ -265,54 +304,71 @@
mAudioStreamsHelper.removeSource(broadcastId);
}
+ void handleSourceRemoved() {
+ for (var entry : mBroadcastIdToPreferenceMap.entrySet()) {
+ var preference = entry.getValue();
+
+ // Look for preference has SOURCE_ADDED state, re-check if they are still connected. If
+ // not, means the source is removed from the sink, we move back the preference to SYNCED
+ // state.
+ if (preference.getAudioStreamState() == AudioStreamState.SOURCE_ADDED
+ && mAudioStreamsHelper.getAllConnectedSources().stream()
+ .noneMatch(
+ connected ->
+ connected.getBroadcastId()
+ == preference.getAudioStreamBroadcastId())) {
+
+ ThreadUtils.postOnMainThread(
+ () -> {
+ var metadata = preference.getAudioStreamMetadata();
+
+ if (metadata != null) {
+ moveToState(preference, AudioStreamState.SYNCED);
+ } else {
+ handleSourceLost(preference.getAudioStreamBroadcastId());
+ }
+ });
+
+ return;
+ }
+ }
+ }
+
void handleSourceConnected(BluetoothLeBroadcastReceiveState receiveState) {
if (!mAudioStreamsHelper.isConnected(receiveState)) {
return;
}
- var sourceAddedState = AudioStreamState.SOURCE_ADDED;
var broadcastIdConnected = receiveState.getBroadcastId();
mBroadcastIdToPreferenceMap.compute(
broadcastIdConnected,
(k, v) -> {
if (v == null) {
- return addNewPreference(
- receiveState,
- sourceAddedState,
- p -> launchDetailFragment(broadcastIdConnected));
+ // No existing preference for this source even if it's already connected,
+ // add one and set initial state to SOURCE_ADDED. This could happen because
+ // we retrieves the connected source during onStart() from
+ // AudioStreamsHelper#getAllConnectedSources() even before the source is
+ // founded by scanning.
+ return addNewPreference(receiveState, AudioStreamState.SOURCE_ADDED);
}
var fromState = v.getAudioStreamState();
- if (fromState == AudioStreamState.WAIT_FOR_SOURCE_ADD
+ if (fromState == AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE
|| fromState == AudioStreamState.SYNCED
- || fromState == AudioStreamState.WAIT_FOR_SYNC) {
- if (mTimedSourceFromQrCode != null) {
- mTimedSourceFromQrCode.consumed();
- }
+ || fromState == AudioStreamState.WAIT_FOR_SYNC
+ || fromState == AudioStreamState.SOURCE_ADDED) {
+ // Expected state, do nothing
} else {
- if (fromState != AudioStreamState.SOURCE_ADDED) {
- Log.w(
- TAG,
- "handleSourceConnected(): unexpected state : "
- + fromState
- + " for broadcastId : "
- + broadcastIdConnected);
- }
+ Log.w(
+ TAG,
+ "handleSourceConnected(): unexpected state : "
+ + fromState
+ + " for broadcastId : "
+ + broadcastIdConnected);
}
- v.setAudioStreamState(sourceAddedState);
- updatePreferenceConnectionState(
- v, sourceAddedState, p -> launchDetailFragment(broadcastIdConnected));
+ moveToState(v, AudioStreamState.SOURCE_ADDED);
return v;
});
}
- private static String getPreferenceSummary(AudioStreamState state) {
- return switch (state) {
- case WAIT_FOR_SYNC -> "Scanning...";
- case WAIT_FOR_SOURCE_ADD -> "Connecting...";
- case SOURCE_ADDED -> "Listening now";
- default -> "";
- };
- }
-
void showToast(String msg) {
AudioSharingUtils.toastMessage(mContext, msg);
}
@@ -322,7 +378,7 @@
ThreadUtils.postOnMainThread(
() -> {
if (mCategoryPreference != null) {
- mCategoryPreference.removeAll();
+ mCategoryPreference.removeAudioStreamPreferences();
mCategoryPreference.setVisible(hasActive);
}
});
@@ -348,7 +404,6 @@
Log.d(TAG, "startScanning()");
}
mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
- mLeBroadcastAssistant.startSearchingForSources(emptyList());
// Handle QR code scan and display currently connected streams
var unused =
@@ -358,6 +413,7 @@
mAudioStreamsHelper
.getAllConnectedSources()
.forEach(this::handleSourceConnected);
+ mLeBroadcastAssistant.startSearchingForSources(emptyList());
});
}
@@ -374,68 +430,93 @@
}
mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
if (mTimedSourceFromQrCode != null) {
- mTimedSourceFromQrCode.consumed();
+ mTimedSourceFromQrCode.cleanup();
+ mTimedSourceFromQrCode = null;
}
}
private AudioStreamPreference addNewPreference(
- BluetoothLeBroadcastReceiveState receiveState,
- AudioStreamState state,
- Preference.OnPreferenceClickListener onClickListener) {
- var preference = AudioStreamPreference.fromReceiveState(mContext, receiveState, state);
- updatePreferenceConnectionState(preference, state, onClickListener);
+ BluetoothLeBroadcastReceiveState receiveState, AudioStreamState state) {
+ var preference = AudioStreamPreference.fromReceiveState(mContext, receiveState);
+ moveToState(preference, state);
return preference;
}
private AudioStreamPreference addNewPreference(
- BluetoothLeBroadcastMetadata metadata,
- AudioStreamState state,
- Preference.OnPreferenceClickListener onClickListener) {
- var preference = AudioStreamPreference.fromMetadata(mContext, metadata, state);
- updatePreferenceConnectionState(preference, state, onClickListener);
+ BluetoothLeBroadcastMetadata metadata, AudioStreamState state) {
+ var preference = AudioStreamPreference.fromMetadata(mContext, metadata);
+ moveToState(preference, state);
return preference;
}
- private void updatePreferenceConnectionState(
- AudioStreamPreference preference,
- AudioStreamState state,
- Preference.OnPreferenceClickListener onClickListener) {
+ private void moveToState(AudioStreamPreference preference, AudioStreamState state) {
+ if (preference.getAudioStreamState() == state) {
+ return;
+ }
+ preference.setAudioStreamState(state);
+
+ // Perform action according to the new state
+ if (state == AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE) {
+ if (mTimedSourceFromQrCode != null) {
+ mTimedSourceFromQrCode.consumed(preference.getAudioStreamBroadcastId());
+ }
+ var metadata = preference.getAudioStreamMetadata();
+ if (metadata != null) {
+ mAudioStreamsHelper.addSource(metadata);
+ // Cache the metadata that used for add source, if source is added successfully, we
+ // will save it persistently.
+ mAudioStreamsRepository.cacheMetadata(metadata);
+ }
+ } else if (state == AudioStreamState.SOURCE_ADDED) {
+ if (mTimedSourceFromQrCode != null) {
+ mTimedSourceFromQrCode.consumed(preference.getAudioStreamBroadcastId());
+ }
+ // Saved connected metadata for user to re-join this broadcast later.
+ var cached =
+ mAudioStreamsRepository.getCachedMetadata(
+ preference.getAudioStreamBroadcastId());
+ if (cached != null) {
+ mAudioStreamsRepository.saveMetadata(mContext, cached);
+ }
+ } else if (state == AudioStreamState.WAIT_FOR_SYNC) {
+ if (mTimedSourceFromQrCode != null) {
+ mTimedSourceFromQrCode.waitForConsume();
+ }
+ }
+
+ // Get preference click listener according to the new state
+ Preference.OnPreferenceClickListener listener;
+ if (state == AudioStreamState.SYNCED) {
+ listener = mAddSourceOrShowDialog;
+ } else if (state == AudioStreamState.SOURCE_ADDED) {
+ listener = mLaunchDetailFragment;
+ } else {
+ listener = null;
+ }
+
+ // Get preference summary according to the new state
+ String summary;
+ if (state == AudioStreamState.WAIT_FOR_SYNC) {
+ summary = "Scanning...";
+ } else if (state == AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE) {
+ summary = "Connecting...";
+ } else if (state == AudioStreamState.SOURCE_ADDED) {
+ summary = "Listening now";
+ } else {
+ summary = "";
+ }
+
+ // Update UI
ThreadUtils.postOnMainThread(
() -> {
preference.setIsConnected(
- state == AudioStreamState.SOURCE_ADDED,
- getPreferenceSummary(state),
- onClickListener);
+ state == AudioStreamState.SOURCE_ADDED, summary, listener);
if (mCategoryPreference != null) {
- mCategoryPreference.addPreference(preference);
+ mCategoryPreference.addAudioStreamPreference(preference, mComparator);
}
});
}
- private boolean launchDetailFragment(int broadcastId) {
- if (!mBroadcastIdToPreferenceMap.containsKey(broadcastId)) {
- Log.w(
- TAG,
- "launchDetailFragment(): broadcastId not exist in BroadcastIdToPreferenceMap!");
- return false;
- }
- AudioStreamPreference preference = mBroadcastIdToPreferenceMap.get(broadcastId);
-
- Bundle broadcast = new Bundle();
- broadcast.putString(
- AudioStreamDetailsFragment.BROADCAST_NAME_ARG, (String) preference.getTitle());
- broadcast.putInt(AudioStreamDetailsFragment.BROADCAST_ID_ARG, broadcastId);
-
- new SubSettingLauncher(mContext)
- .setTitleText("Audio stream details")
- .setDestination(AudioStreamDetailsFragment.class.getName())
- // TODO(chelseahao): Add logging enum
- .setSourceMetricsCategory(SettingsEnums.PAGE_UNKNOWN)
- .setArguments(broadcast)
- .launch();
- return true;
- }
-
private void launchPasswordDialog(
BluetoothLeBroadcastMetadata source, AudioStreamPreference preference) {
View layout =
@@ -457,15 +538,16 @@
R.id.broadcast_edit_text))
.getText()
.toString();
- mAudioStreamsHelper.addSource(
+ var metadata =
new BluetoothLeBroadcastMetadata.Builder(source)
.setBroadcastCode(
code.getBytes(StandardCharsets.UTF_8))
- .build());
- preference.setAudioStreamState(
- AudioStreamState.WAIT_FOR_SOURCE_ADD);
- updatePreferenceConnectionState(
- preference, AudioStreamState.WAIT_FOR_SOURCE_ADD, null);
+ .build();
+ // Update the metadata after user entered the password
+ preference.setAudioStreamMetadata(metadata);
+ moveToState(
+ preference,
+ AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE);
})
.create();
alertDialog.show();
@@ -474,16 +556,17 @@
private AudioStreamsDialogFragment.DialogBuilder getNoLeDeviceDialog() {
return new AudioStreamsDialogFragment.DialogBuilder(mContext)
.setTitle("Connect compatible headphones")
- .setSubTitle1(
+ .setSubTitle2(
"To listen to an audio stream, first connect headphones that support LE"
+ " Audio to this device. Learn more")
.setLeftButtonText("Close")
.setLeftButtonOnClickListener(AlertDialog::dismiss)
.setRightButtonText("Connect a device")
.setRightButtonOnClickListener(
- unused ->
- mContext.startActivity(
- new Intent(Settings.ACTION_BLUETOOTH_SETTINGS)));
+ dialog -> {
+ mContext.startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
+ dialog.dismiss();
+ });
}
private AudioStreamsDialogFragment.DialogBuilder getBroadcastUnavailableDialog(
@@ -495,8 +578,18 @@
.setLeftButtonText("Close")
.setLeftButtonOnClickListener(AlertDialog::dismiss)
.setRightButtonText("Retry")
- // TODO(chelseahao): Add retry action
- .setRightButtonOnClickListener(AlertDialog::dismiss);
+ .setRightButtonOnClickListener(
+ dialog -> {
+ if (mFragment != null) {
+ Intent intent = new Intent(mContext, QrCodeScanModeActivity.class);
+ intent.setAction(
+ BluetoothBroadcastUtils
+ .ACTION_BLUETOOTH_LE_AUDIO_QR_CODE_SCANNER);
+ mFragment.startActivityForResult(
+ intent, REQUEST_SCAN_BT_BROADCAST_QR_CODE);
+ dialog.dismiss();
+ }
+ });
}
private class TimedSourceFromQrCode {
@@ -529,11 +622,18 @@
mTimer.start();
}
- private void consumed() {
+ private void cleanup() {
mTimer.cancel();
mSourceFromQrCode = null;
}
+ private void consumed(int broadcastId) {
+ if (mSourceFromQrCode == null || broadcastId != mSourceFromQrCode.getBroadcastId()) {
+ return;
+ }
+ cleanup();
+ }
+
private BluetoothLeBroadcastMetadata get() {
return mSourceFromQrCode;
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryPreference.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryPreference.java
index d259900..33adc31 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryPreference.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryPreference.java
@@ -19,9 +19,15 @@
import android.content.Context;
import android.util.AttributeSet;
+import androidx.annotation.NonNull;
+
import com.android.settings.ProgressCategory;
import com.android.settings.R;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
public class AudioStreamsProgressCategoryPreference extends ProgressCategory {
public AudioStreamsProgressCategoryPreference(Context context) {
@@ -46,6 +52,37 @@
init();
}
+ void addAudioStreamPreference(
+ @NonNull AudioStreamPreference preference,
+ Comparator<AudioStreamPreference> comparator) {
+ super.addPreference(preference);
+
+ List<AudioStreamPreference> preferences = getAllAudioStreamPreferences();
+ preferences.sort(comparator);
+ for (int i = 0; i < preferences.size(); i++) {
+ // setOrder to i + 1, since the order 0 preference should always be the
+ // "audio_streams_scan_qr_code"
+ preferences.get(i).setOrder(i + 1);
+ }
+ }
+
+ void removeAudioStreamPreferences() {
+ List<AudioStreamPreference> streams = getAllAudioStreamPreferences();
+ for (var toRemove : streams) {
+ removePreference(toRemove);
+ }
+ }
+
+ private List<AudioStreamPreference> getAllAudioStreamPreferences() {
+ List<AudioStreamPreference> streams = new ArrayList<>();
+ for (int i = 0; i < getPreferenceCount(); i++) {
+ if (getPreference(i) instanceof AudioStreamPreference) {
+ streams.add((AudioStreamPreference) getPreference(i));
+ }
+ }
+ return streams;
+ }
+
private void init() {
setEmptyTextRes(R.string.audio_streams_empty);
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java
index 42b38ee..2366e70 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java
@@ -24,6 +24,9 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settings.bluetooth.Utils;
@@ -34,6 +37,7 @@
import com.google.zxing.WriterException;
+import java.nio.charset.StandardCharsets;
import java.util.Optional;
public class AudioStreamsQrCodeFragment extends InstrumentedFragment {
@@ -49,30 +53,47 @@
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 ->
+
+ BluetoothLeBroadcastMetadata broadcastMetadata = getBroadcastMetadata();
+
+ if (broadcastMetadata != null) {
+ getQrCodeBitmap(broadcastMetadata)
+ .ifPresent(
+ bm -> {
((ImageView) view.requireViewById(R.id.qrcode_view))
- .setImageBitmap(bm));
+ .setImageBitmap(bm);
+ ((TextView) view.requireViewById(R.id.password))
+ .setText(
+ "Password: "
+ + new String(
+ broadcastMetadata
+ .getBroadcastCode(),
+ StandardCharsets.UTF_8));
+ });
+ }
return view;
}
- private Optional<Bitmap> getQrCodeBitmap() {
- String broadcastMetadata = getBroadcastMetadataQrCode();
- if (broadcastMetadata.isEmpty()) {
+ private Optional<Bitmap> getQrCodeBitmap(@Nullable BluetoothLeBroadcastMetadata metadata) {
+ if (metadata == null) {
Log.d(TAG, "onCreateView: broadcastMetadata is empty!");
return Optional.empty();
}
-
+ String metadataStr = BluetoothLeBroadcastMetadataExt.INSTANCE.toQrCodeString(metadata);
+ if (metadataStr.isEmpty()) {
+ Log.d(TAG, "onCreateView: metadataStr is empty!");
+ return Optional.empty();
+ }
+ Log.d("chelsea", metadataStr);
try {
int qrcodeSize = getContext().getResources().getDimensionPixelSize(R.dimen.qrcode_size);
- Bitmap bitmap = QrCodeGenerator.encodeQrCode(broadcastMetadata, qrcodeSize);
+ Bitmap bitmap = QrCodeGenerator.encodeQrCode(metadataStr, qrcodeSize);
return Optional.of(bitmap);
} catch (WriterException e) {
Log.d(
TAG,
"onCreateView: broadcastMetadata "
- + broadcastMetadata
+ + metadata
+ " qrCode generation exception "
+ e);
}
@@ -80,23 +101,24 @@
return Optional.empty();
}
- private String getBroadcastMetadataQrCode() {
+ @Nullable
+ private BluetoothLeBroadcastMetadata getBroadcastMetadata() {
LocalBluetoothLeBroadcast localBluetoothLeBroadcast =
Utils.getLocalBtManager(getActivity())
.getProfileManager()
.getLeAudioBroadcastProfile();
if (localBluetoothLeBroadcast == null) {
Log.d(TAG, "getBroadcastMetadataQrCode: localBluetoothLeBroadcast is null!");
- return "";
+ return null;
}
BluetoothLeBroadcastMetadata metadata =
localBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata();
if (metadata == null) {
Log.d(TAG, "getBroadcastMetadataQrCode: metadata is null!");
- return "";
+ return null;
}
- return BluetoothLeBroadcastMetadataExt.INSTANCE.toQrCodeString(metadata);
+ return metadata;
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsRepository.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsRepository.java
new file mode 100644
index 0000000..65245ac
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsRepository.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2024 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.bluetooth.BluetoothLeBroadcastMetadata;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.Nullable;
+
+/** Manages the caching and storage of Bluetooth audio stream metadata. */
+public class AudioStreamsRepository {
+
+ private static final String TAG = "AudioStreamsRepository";
+ private static final boolean DEBUG = BluetoothUtils.D;
+
+ private static final String PREF_KEY = "bluetooth_audio_stream_pref";
+ private static final String METADATA_KEY = "bluetooth_audio_stream_metadata";
+
+ @Nullable
+ private static AudioStreamsRepository sInstance = null;
+
+ private AudioStreamsRepository() {}
+
+ /**
+ * Gets the single instance of AudioStreamsRepository.
+ *
+ * @return The AudioStreamsRepository instance.
+ */
+ public static synchronized AudioStreamsRepository getInstance() {
+ if (sInstance == null) {
+ sInstance = new AudioStreamsRepository();
+ }
+ return sInstance;
+ }
+
+ private final ConcurrentHashMap<Integer, BluetoothLeBroadcastMetadata>
+ mBroadcastIdToMetadataCacheMap = new ConcurrentHashMap<>();
+
+ /**
+ * Caches BluetoothLeBroadcastMetadata in a local cache.
+ *
+ * @param metadata The BluetoothLeBroadcastMetadata to be cached.
+ */
+ void cacheMetadata(BluetoothLeBroadcastMetadata metadata) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "cacheMetadata(): broadcastId "
+ + metadata.getBroadcastId()
+ + " saved in local cache.");
+ }
+ mBroadcastIdToMetadataCacheMap.put(metadata.getBroadcastId(), metadata);
+ }
+
+ /**
+ * Gets cached BluetoothLeBroadcastMetadata by broadcastId.
+ *
+ * @param broadcastId The broadcastId to look up in the cache.
+ * @return The cached BluetoothLeBroadcastMetadata or null if not found.
+ */
+ @Nullable
+ BluetoothLeBroadcastMetadata getCachedMetadata(int broadcastId) {
+ var metadata = mBroadcastIdToMetadataCacheMap.get(broadcastId);
+ if (metadata == null) {
+ Log.w(
+ TAG,
+ "getCachedMetadata(): broadcastId not found in"
+ + " mBroadcastIdToMetadataCacheMap.");
+ return null;
+ }
+ return metadata;
+ }
+
+ /**
+ * Saves metadata to SharedPreferences asynchronously.
+ *
+ * @param context The context.
+ * @param metadata The BluetoothLeBroadcastMetadata to be saved.
+ */
+ void saveMetadata(Context context, BluetoothLeBroadcastMetadata metadata) {
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ SharedPreferences sharedPref =
+ context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE);
+ if (sharedPref != null) {
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(
+ METADATA_KEY,
+ BluetoothLeBroadcastMetadataExt.INSTANCE.toQrCodeString(
+ metadata));
+ editor.apply();
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "saveMetadata(): broadcastId "
+ + metadata.getBroadcastId()
+ + " metadata saved in storage.");
+ }
+ }
+ });
+ }
+
+ /**
+ * Gets saved metadata from SharedPreferences.
+ *
+ * @param context The context.
+ * @param broadcastId The broadcastId to retrieve metadata for.
+ * @return The saved BluetoothLeBroadcastMetadata or null if not found.
+ */
+ @Nullable
+ BluetoothLeBroadcastMetadata getSavedMetadata(Context context, int broadcastId) {
+ SharedPreferences sharedPref = context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE);
+ if (sharedPref != null) {
+ String savedMetadataStr = sharedPref.getString(METADATA_KEY, null);
+ if (savedMetadataStr == null) {
+ Log.w(TAG, "getSavedMetadata(): savedMetadataStr is null");
+ return null;
+ }
+ var savedMetadata =
+ BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(
+ savedMetadataStr);
+ if (savedMetadata == null || savedMetadata.getBroadcastId() != broadcastId) {
+ Log.w(TAG, "getSavedMetadata(): savedMetadata doesn't match broadcast Id.");
+ return null;
+ }
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "getSavedMetadata(): broadcastId "
+ + savedMetadata.getBroadcastId()
+ + " metadata found in storage.");
+ }
+ return savedMetadata;
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
index 549e725..24e1ca3 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
@@ -16,7 +16,6 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
@@ -58,14 +57,12 @@
};
private final LocalBluetoothManager mLocalBtManager;
- private final AudioStreamsHelper mAudioStreamsHelper;
private AudioStreamsDashboardFragment mFragment;
private Preference mPreference;
public AudioStreamsScanQrCodeController(Context context, String preferenceKey) {
super(context, preferenceKey);
mLocalBtManager = Utils.getLocalBtManager(mContext);
- mAudioStreamsHelper = new AudioStreamsHelper(mLocalBtManager);
}
public void setFragment(AudioStreamsDashboardFragment fragment) {
@@ -124,10 +121,6 @@
});
}
- void addSource(BluetoothLeBroadcastMetadata source) {
- mAudioStreamsHelper.addSource(source);
- }
-
private void updateVisibility() {
ThreadUtils.postOnBackgroundThread(
() -> {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeFragment.java
index 2b52039..378128d 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/qrcode/QrCodeScanModeFragment.java
@@ -229,6 +229,7 @@
}
mErrorMessage.setVisibility(View.INVISIBLE);
+ mTextureView.setVisibility(View.INVISIBLE);
triggerVibrationForQrCodeRecognition(getContext());
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index b40b6bf..8fee052 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -34,6 +34,7 @@
import com.android.settings.accessibility.ToggleColorInversionPreferenceFragment;
import com.android.settings.accessibility.ToggleDaltonizerPreferenceFragment;
import com.android.settings.accessibility.ToggleReduceBrightColorsPreferenceFragment;
+import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment;
import com.android.settings.accounts.AccountDashboardFragment;
import com.android.settings.accounts.AccountSyncSettings;
import com.android.settings.accounts.ChooseAccountFragment;
@@ -144,6 +145,7 @@
import com.android.settings.network.apn.ApnSettings;
import com.android.settings.network.telephony.MobileNetworkSettings;
import com.android.settings.network.telephony.NetworkSelectSettings;
+import com.android.settings.network.telephony.SatelliteSetting;
import com.android.settings.network.tether.TetherSettings;
import com.android.settings.nfc.PaymentSettings;
import com.android.settings.notification.ConfigureNotificationSettings;
@@ -249,6 +251,7 @@
AccessibilityDetailsSettingsFragment.class.getName(),
AccessibilitySettings.class.getName(),
AccessibilitySettingsForSetupWizard.class.getName(),
+ EditShortcutsPreferenceFragment.class.getName(),
TextReadingPreferenceFragment.class.getName(),
TextReadingPreferenceFragmentForSetupWizard.class.getName(),
CaptioningPropertiesFragment.class.getName(),
@@ -301,6 +304,7 @@
AppNotificationSettings.class.getName(),
NotificationAssistantPicker.class.getName(),
ChannelNotificationSettings.class.getName(),
+ SatelliteSetting.class.getName(),
ApnSettings.class.getName(),
ApnEditor.class.getName(),
WifiCallingSettings.class.getName(),
diff --git a/src/com/android/settings/development/ClearAdbKeysPreferenceController.java b/src/com/android/settings/development/ClearAdbKeysPreferenceController.java
index b39d874..69e6c69 100644
--- a/src/com/android/settings/development/ClearAdbKeysPreferenceController.java
+++ b/src/com/android/settings/development/ClearAdbKeysPreferenceController.java
@@ -52,6 +52,9 @@
@Override
public boolean isAvailable() {
+ // If the build is insecure (any -user build, 'ro.adb.secure=0'), adbd does not
+ // requests/store authorizations. There is no need for a "revoke authorizations"
+ // button.
return AdbProperties.secure().orElse(false);
}
diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
index 6d3bd6b..dc4aade 100644
--- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
@@ -112,11 +112,28 @@
/** Gets the {@link OptimizationMode} for associated app. */
@OptimizationMode
- public int getAppOptimizationMode() {
- refreshState();
+ public int getAppOptimizationMode(boolean refreshList) {
+ if (refreshList) {
+ mPowerAllowListBackend.refreshList();
+ }
+ mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName, mUid);
+ mMode =
+ mAppOpsManager.checkOpNoThrow(
+ AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mPackageName);
+ Log.d(
+ TAG,
+ String.format(
+ "refresh %s state, allowlisted = %s, mode = %d",
+ mPackageName, mAllowListed, mMode));
return getAppOptimizationMode(mMode, mAllowListed);
}
+ /** Gets the {@link OptimizationMode} for associated app. */
+ @OptimizationMode
+ public int getAppOptimizationMode() {
+ return getAppOptimizationMode(true);
+ }
+
/** Resets optimization mode for all applications. */
public static void resetAppOptimizationMode(
Context context, IPackageManager ipm, AppOpsManager aom) {
@@ -336,19 +353,6 @@
context, action, packageNameKey, createLogEvent(appStandbyMode, allowListed));
}
- private void refreshState() {
- mPowerAllowListBackend.refreshList();
- mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName, mUid);
- mMode =
- mAppOpsManager.checkOpNoThrow(
- AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mPackageName);
- Log.d(
- TAG,
- String.format(
- "refresh %s state, allowlisted = %s, mode = %d",
- mPackageName, mAllowListed, mMode));
- }
-
private static String createLogEvent(int appStandbyMode, boolean allowListed) {
return appStandbyMode < 0
? "Apply optimize setting ERROR"
diff --git a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProvider.java b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProvider.java
index 36ccdc6..cc333a5 100644
--- a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProvider.java
+++ b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProvider.java
@@ -35,5 +35,5 @@
boolean isBatteryInfoEnabled(Context context);
/** A way to add more battery tip detectors. */
- void addBatteryTipDetector(Context context, List<BatteryTip> tips);
+ void addBatteryTipDetector(Context context, List<BatteryTip> tips, BatteryInfo batteryInfo);
}
diff --git a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java
index badab69..f974b9d 100644
--- a/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java
+++ b/src/com/android/settings/fuelgauge/BatterySettingsFeatureProviderImpl.java
@@ -41,5 +41,6 @@
}
@Override
- public void addBatteryTipDetector(Context context, List<BatteryTip> tips) {}
+ public void addBatteryTipDetector(
+ Context context, List<BatteryTip> tips, BatteryInfo batteryInfo) {}
}
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
index 9d63046..5352105 100644
--- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
@@ -25,7 +25,6 @@
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.batterytip.detectors.BatteryDefenderDetector;
-import com.android.settings.fuelgauge.batterytip.detectors.DockDefenderDetector;
import com.android.settings.fuelgauge.batterytip.detectors.HighUsageDetector;
import com.android.settings.fuelgauge.batterytip.detectors.IncompatibleChargerDetector;
import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector;
@@ -66,11 +65,10 @@
tips.add(new LowBatteryDetector(context, policy, batteryInfo, isPowerSaveMode).detect());
tips.add(new HighUsageDetector(context, policy, mBatteryUsageStats, batteryInfo).detect());
tips.add(new BatteryDefenderDetector(batteryInfo, context).detect());
- tips.add(new DockDefenderDetector(batteryInfo, context).detect());
tips.add(new IncompatibleChargerDetector(context).detect());
FeatureFactory.getFeatureFactory()
.getBatterySettingsFeatureProvider()
- .addBatteryTipDetector(context, tips);
+ .addBatteryTipDetector(context, tips, batteryInfo);
Collections.sort(tips);
return tips;
}
diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/DockDefenderDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/DockDefenderDetector.java
deleted file mode 100644
index 14aeecd..0000000
--- a/src/com/android/settings/fuelgauge/batterytip/detectors/DockDefenderDetector.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.fuelgauge.batterytip.detectors;
-
-import android.content.Context;
-
-import com.android.settings.fuelgauge.BatteryInfo;
-import com.android.settings.fuelgauge.BatteryUtils;
-import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
-import com.android.settings.fuelgauge.batterytip.tips.DockDefenderTip;
-
-/** Detect whether the dock defender mode is enabled. */
-public class DockDefenderDetector implements BatteryTipDetector {
- private final BatteryInfo mBatteryInfo;
- private final Context mContext;
-
- public DockDefenderDetector(BatteryInfo batteryInfo, Context context) {
- mBatteryInfo = batteryInfo;
- mContext = context;
- }
-
- @Override
- public BatteryTip detect() {
- int mode = BatteryUtils.getCurrentDockDefenderMode(mContext, mBatteryInfo);
- return new DockDefenderTip(
- mode != BatteryUtils.DockDefenderMode.DISABLED
- ? BatteryTip.StateType.NEW
- : BatteryTip.StateType.INVISIBLE,
- mode);
- }
-}
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/DockDefenderTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/DockDefenderTip.java
deleted file mode 100644
index 2458351..0000000
--- a/src/com/android/settings/fuelgauge/batterytip/tips/DockDefenderTip.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.fuelgauge.batterytip.tips;
-
-import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Parcel;
-import android.util.Log;
-
-import androidx.preference.Preference;
-
-import com.android.settings.R;
-import com.android.settings.fuelgauge.BatteryUtils;
-import com.android.settings.fuelgauge.BatteryUtils.DockDefenderMode;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.widget.CardPreference;
-import com.android.settingslib.HelpUtils;
-import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-
-/** Tip to show dock defender status */
-public class DockDefenderTip extends BatteryTip {
- private static final String TAG = "DockDefenderTip";
- private int mMode;
-
- public DockDefenderTip(@StateType int state, @DockDefenderMode int mode) {
- super(TipType.DOCK_DEFENDER, state, false);
- mMode = mode;
- }
-
- private DockDefenderTip(Parcel in) {
- super(in);
- }
-
- public int getMode() {
- return mMode;
- }
-
- @Override
- public CharSequence getTitle(Context context) {
- switch (mMode) {
- case DockDefenderMode.FUTURE_BYPASS:
- return context.getString(R.string.battery_tip_dock_defender_future_bypass_title);
- case DockDefenderMode.ACTIVE:
- return context.getString(R.string.battery_tip_dock_defender_active_title);
- case DockDefenderMode.TEMPORARILY_BYPASSED:
- return context.getString(
- R.string.battery_tip_dock_defender_temporarily_bypassed_title);
- default:
- return null;
- }
- }
-
- @Override
- public CharSequence getSummary(Context context) {
- switch (mMode) {
- case DockDefenderMode.FUTURE_BYPASS:
- return context.getString(R.string.battery_tip_dock_defender_future_bypass_summary);
- case DockDefenderMode.ACTIVE:
- return context.getString(R.string.battery_tip_dock_defender_active_summary);
- case DockDefenderMode.TEMPORARILY_BYPASSED:
- return context.getString(
- R.string.battery_tip_dock_defender_temporarily_bypassed_summary);
- default:
- return null;
- }
- }
-
- @Override
- public int getIconId() {
- return mMode == DockDefenderMode.ACTIVE
- ? R.drawable.ic_battery_status_protected_24dp
- : R.drawable.ic_battery_dock_defender_untriggered_24dp;
- }
-
- @Override
- public void updateState(BatteryTip tip) {
- mState = tip.mState;
- if (tip instanceof DockDefenderTip) {
- mMode = ((DockDefenderTip) tip).mMode;
- }
- }
-
- @Override
- public void log(Context context, MetricsFeatureProvider metricsFeatureProvider) {
- metricsFeatureProvider.action(context, SettingsEnums.ACTION_DOCK_DEFENDER_TIP, mState);
- }
-
- @Override
- public void updatePreference(Preference preference) {
- super.updatePreference(preference);
- final Context context = preference.getContext();
-
- CardPreference cardPreference = castToCardPreferenceSafely(preference);
- if (cardPreference == null) {
- Log.e(TAG, "cast Preference to CardPreference failed");
- return;
- }
-
- cardPreference.setSelectable(false);
- switch (mMode) {
- case DockDefenderMode.FUTURE_BYPASS:
- case DockDefenderMode.ACTIVE:
- cardPreference.setPrimaryButtonText(
- context.getString(R.string.battery_tip_charge_to_full_button));
- cardPreference.setPrimaryButtonClickListener(
- unused -> {
- resumeCharging(context);
- mMode = DockDefenderMode.TEMPORARILY_BYPASSED;
- context.sendBroadcast(
- new Intent()
- .setAction(BatteryUtils.BYPASS_DOCK_DEFENDER_ACTION)
- .setPackage(context.getPackageName())
- .addFlags(
- Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND));
- updatePreference(preference);
- });
- cardPreference.setPrimaryButtonVisible(true);
- break;
- case DockDefenderMode.TEMPORARILY_BYPASSED:
- cardPreference.setPrimaryButtonVisible(false);
- break;
- default:
- cardPreference.setVisible(false);
- return;
- }
-
- cardPreference.setSecondaryButtonText(context.getString(R.string.learn_more));
- cardPreference.setSecondaryButtonClickListener(
- button ->
- button.startActivityForResult(
- HelpUtils.getHelpIntent(
- context,
- context.getString(R.string.help_url_dock_defender),
- /* backupContext */ ""), /* requestCode */
- 0));
- cardPreference.setSecondaryButtonVisible(true);
- cardPreference.setSecondaryButtonContentDescription(
- context.getString(
- R.string.battery_tip_limited_temporarily_sec_button_content_description));
- }
-
- private void resumeCharging(Context context) {
- final Intent intent =
- FeatureFactory.getFeatureFactory()
- .getPowerUsageFeatureProvider()
- .getResumeChargeIntent(true);
- if (intent != null) {
- context.sendBroadcast(intent);
- }
-
- Log.i(TAG, "send resume charging broadcast intent=" + intent);
- }
-
- public static final Creator CREATOR =
- new Creator() {
- public BatteryTip createFromParcel(Parcel in) {
- return new DockDefenderTip(in);
- }
-
- public BatteryTip[] newArray(int size) {
- return new DockDefenderTip[size];
- }
- };
-}
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java
index ca42141..882b755 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTip.java
@@ -52,7 +52,7 @@
@Override
public int getIconId() {
- return R.drawable.ic_battery_charger;
+ return R.drawable.ic_battery_incompatible_charger;
}
@Override
diff --git a/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapper.java b/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapper.java
index 8658fba..8924a0b 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapper.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/AnomalyEventWrapper.java
@@ -91,6 +91,10 @@
mRelatedBatteryDiffEntry = batteryDiffEntry;
}
+ int getAnomalyKeyNumber() {
+ return mPowerAnomalyEvent.getKey().getNumber();
+ }
+
String getEventId() {
return mPowerAnomalyEvent.hasEventId() ? mPowerAnomalyEvent.getEventId() : null;
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java
index 5a1fb26..8fa7b90 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsController.java
@@ -91,9 +91,11 @@
}
mCardPreference.setVisible(false);
mMetricsFeatureProvider.action(
- mContext,
- SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT,
- mAnomalyEventWrapper.getEventId());
+ /* attribution= */ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ /* action= */ SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT,
+ /* pageId= */ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ /* key= */ mAnomalyEventWrapper.getEventId(),
+ /* value= */ mAnomalyEventWrapper.getAnomalyKeyNumber());
}
void handleBatteryTipsCardUpdated(
@@ -105,8 +107,8 @@
return;
}
- // Get card preference strings and navigate fragment info
final String eventId = mAnomalyEventWrapper.getEventId();
+ final int anomalyKeyNumber = mAnomalyEventWrapper.getAnomalyKeyNumber();
// Update card & buttons preference
if (!mAnomalyEventWrapper.updateTipsCardPreference(mCardPreference)) {
@@ -122,7 +124,11 @@
mOnAnomalyConfirmListener.onAnomalyConfirm();
} else if (mAnomalyEventWrapper.launchSubSetting()) {
mMetricsFeatureProvider.action(
- mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, eventId);
+ /* attribution= */ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ /* action= */ SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT,
+ /* pageId= */ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ /* key= */ eventId,
+ /* value= */ anomalyKeyNumber);
}
});
mCardPreference.setOnRejectListener(
@@ -138,11 +144,19 @@
DatabaseUtils.setDismissedPowerAnomalyKeys(mContext, dismissRecordKey);
}
mMetricsFeatureProvider.action(
- mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_DISMISS, eventId);
+ /* attribution= */ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ /* action= */ SettingsEnums.ACTION_BATTERY_TIPS_CARD_DISMISS,
+ /* pageId= */ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ /* key= */ eventId,
+ /* value= */ anomalyKeyNumber);
});
mCardPreference.setVisible(true);
mMetricsFeatureProvider.action(
- mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, eventId);
+ /* attribution= */ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ /* action= */ SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW,
+ /* pageId= */ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ /* key= */ eventId,
+ /* value= */ anomalyKeyNumber);
}
}
diff --git a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java
index c40212b..c6b1bdb 100644
--- a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java
+++ b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java
@@ -41,6 +41,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityGestureNavigationTutorial;
import com.android.settings.core.SubSettingLauncher;
@@ -353,7 +354,7 @@
private boolean isAnyServiceSupportAccessibilityButton() {
final AccessibilityManager ams = getContext().getSystemService(AccessibilityManager.class);
final List<String> targets = ams.getAccessibilityShortcutTargets(
- AccessibilityManager.ACCESSIBILITY_BUTTON);
+ ShortcutConstants.UserShortcutType.SOFTWARE);
return !targets.isEmpty();
}
diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
index 7de505e..b06edb2 100644
--- a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
+++ b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java
@@ -70,6 +70,7 @@
private static final String KEYBOARD_A11Y_CATEGORY = "keyboard_a11y_category";
private static final String SHOW_VIRTUAL_KEYBOARD_SWITCH = "show_virtual_keyboard_switch";
private static final String ACCESSIBILITY_BOUNCE_KEYS = "accessibility_bounce_keys";
+ private static final String ACCESSIBILITY_SLOW_KEYS = "accessibility_slow_keys";
private static final String ACCESSIBILITY_STICKY_KEYS = "accessibility_sticky_keys";
private static final String KEYBOARD_SHORTCUTS_HELPER = "keyboard_shortcuts_helper";
private static final String MODIFIER_KEYS_SETTINGS = "modifier_keys_settings";
@@ -78,6 +79,8 @@
Secure.SHOW_IME_WITH_HARD_KEYBOARD);
private static final Uri sAccessibilityBounceKeysUri = Secure.getUriFor(
Secure.ACCESSIBILITY_BOUNCE_KEYS);
+ private static final Uri sAccessibilitySlowKeysUri = Secure.getUriFor(
+ Secure.ACCESSIBILITY_SLOW_KEYS);
private static final Uri sAccessibilityStickyKeysUri = Secure.getUriFor(
Secure.ACCESSIBILITY_STICKY_KEYS);
@@ -97,6 +100,8 @@
@Nullable
private TwoStatePreference mAccessibilityBounceKeys = null;
@Nullable
+ private TwoStatePreference mAccessibilitySlowKeys = null;
+ @Nullable
private TwoStatePreference mAccessibilityStickyKeys = null;
@@ -127,6 +132,8 @@
mKeyboardA11yCategory = Objects.requireNonNull(findPreference(KEYBOARD_A11Y_CATEGORY));
mAccessibilityBounceKeys = Objects.requireNonNull(
mKeyboardA11yCategory.findPreference(ACCESSIBILITY_BOUNCE_KEYS));
+ mAccessibilitySlowKeys = Objects.requireNonNull(
+ mKeyboardA11yCategory.findPreference(ACCESSIBILITY_SLOW_KEYS));
mAccessibilityStickyKeys = Objects.requireNonNull(
mKeyboardA11yCategory.findPreference(ACCESSIBILITY_STICKY_KEYS));
@@ -147,6 +154,9 @@
if (!InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
mKeyboardA11yCategory.removePreference(mAccessibilityBounceKeys);
}
+ if (!InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
+ mKeyboardA11yCategory.removePreference(mAccessibilitySlowKeys);
+ }
if (!InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
mKeyboardA11yCategory.removePreference(mAccessibilityStickyKeys);
}
@@ -196,6 +206,8 @@
mShowVirtualKeyboardSwitchPreferenceChangeListener);
Objects.requireNonNull(mAccessibilityBounceKeys).setOnPreferenceChangeListener(
mAccessibilityBounceKeysSwitchPreferenceChangeListener);
+ Objects.requireNonNull(mAccessibilitySlowKeys).setOnPreferenceChangeListener(
+ mAccessibilitySlowKeysSwitchPreferenceChangeListener);
Objects.requireNonNull(mAccessibilityStickyKeys).setOnPreferenceChangeListener(
mAccessibilityStickyKeysSwitchPreferenceChangeListener);
registerSettingsObserver();
@@ -208,6 +220,7 @@
mIm.unregisterInputDeviceListener(this);
Objects.requireNonNull(mShowVirtualKeyboardSwitch).setOnPreferenceChangeListener(null);
Objects.requireNonNull(mAccessibilityBounceKeys).setOnPreferenceChangeListener(null);
+ Objects.requireNonNull(mAccessibilitySlowKeys).setOnPreferenceChangeListener(null);
Objects.requireNonNull(mAccessibilityStickyKeys).setOnPreferenceChangeListener(null);
unregisterSettingsObserver();
}
@@ -315,10 +328,12 @@
updateShowVirtualKeyboardSwitch();
if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()
- || InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+ || InputSettings.isAccessibilityStickyKeysFeatureEnabled()
+ || InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
Objects.requireNonNull(mKeyboardA11yCategory).setOrder(2);
preferenceScreen.addPreference(mKeyboardA11yCategory);
updateAccessibilityBounceKeysSwitch();
+ updateAccessibilitySlowKeysSwitch();
updateAccessibilityStickyKeysSwitch();
}
}
@@ -356,6 +371,13 @@
mContentObserver,
UserHandle.myUserId());
}
+ if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
+ contentResolver.registerContentObserver(
+ sAccessibilitySlowKeysUri,
+ false,
+ mContentObserver,
+ UserHandle.myUserId());
+ }
if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
contentResolver.registerContentObserver(
sAccessibilityStickyKeysUri,
@@ -365,6 +387,7 @@
}
updateShowVirtualKeyboardSwitch();
updateAccessibilityBounceKeysSwitch();
+ updateAccessibilitySlowKeysSwitch();
updateAccessibilityStickyKeysSwitch();
}
@@ -385,6 +408,14 @@
InputSettings.isAccessibilityBounceKeysEnabled(getContext()));
}
+ private void updateAccessibilitySlowKeysSwitch() {
+ if (!InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
+ return;
+ }
+ Objects.requireNonNull(mAccessibilitySlowKeys).setChecked(
+ InputSettings.isAccessibilitySlowKeysEnabled(getContext()));
+ }
+
private void updateAccessibilityStickyKeysSwitch() {
if (!InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
return;
@@ -414,6 +445,13 @@
};
private final OnPreferenceChangeListener
+ mAccessibilitySlowKeysSwitchPreferenceChangeListener = (preference, newValue) -> {
+ InputSettings.setAccessibilitySlowKeysThreshold(getContext(),
+ ((Boolean) newValue) ? 500 : 0);
+ return true;
+ };
+
+ private final OnPreferenceChangeListener
mAccessibilityStickyKeysSwitchPreferenceChangeListener = (preference, newValue) -> {
InputSettings.setAccessibilityStickyKeysEnabled(getContext(), (Boolean) newValue);
return true;
@@ -426,6 +464,8 @@
updateShowVirtualKeyboardSwitch();
} else if (sAccessibilityBounceKeysUri.equals(uri)) {
updateAccessibilityBounceKeysSwitch();
+ } else if (sAccessibilitySlowKeysUri.equals(uri)) {
+ updateAccessibilitySlowKeysSwitch();
} else if (sAccessibilityStickyKeysUri.equals(uri)) {
updateAccessibilityStickyKeysSwitch();
}
diff --git a/src/com/android/settings/network/ActiveSubscriptionsListener.java b/src/com/android/settings/network/ActiveSubscriptionsListener.java
index 4967538..284cbb3 100644
--- a/src/com/android/settings/network/ActiveSubscriptionsListener.java
+++ b/src/com/android/settings/network/ActiveSubscriptionsListener.java
@@ -174,7 +174,8 @@
*/
public SubscriptionManager getSubscriptionManager() {
if (mSubscriptionManager == null) {
- mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
+ mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class)
+ .createForAllUserProfiles();
}
return mSubscriptionManager;
}
diff --git a/src/com/android/settings/network/MobileNetworkListFragment.kt b/src/com/android/settings/network/MobileNetworkListFragment.kt
index 09b1150..e722866 100644
--- a/src/com/android/settings/network/MobileNetworkListFragment.kt
+++ b/src/com/android/settings/network/MobileNetworkListFragment.kt
@@ -26,8 +26,11 @@
import com.android.settings.R
import com.android.settings.SettingsPreferenceFragment
import com.android.settings.dashboard.DashboardFragment
+import com.android.settings.flags.Flags
import com.android.settings.network.telephony.MobileNetworkUtils
import com.android.settings.search.BaseSearchIndexProvider
+import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
+import com.android.settings.spa.network.NetworkCellularGroupProvider
import com.android.settingslib.search.SearchIndexable
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import com.android.settingslib.spaprivileged.framework.common.userManager
@@ -40,6 +43,15 @@
collectAirplaneModeAndFinishIfOn()
}
+ override fun onCreate(icicle: Bundle?) {
+ super.onCreate(icicle)
+
+ if (Flags.isDualSimOnboardingEnabled()) {
+ context?.startSpaActivity(NetworkCellularGroupProvider.name);
+ finish()
+ }
+ }
+
override fun onResume() {
super.onResume()
// Disable the animation of the preference list
diff --git a/src/com/android/settings/network/NetworkProviderCallsSmsController.kt b/src/com/android/settings/network/NetworkProviderCallsSmsController.kt
index 7346e23..67247c1 100644
--- a/src/com/android/settings/network/NetworkProviderCallsSmsController.kt
+++ b/src/com/android/settings/network/NetworkProviderCallsSmsController.kt
@@ -28,6 +28,7 @@
import androidx.preference.PreferenceScreen
import com.android.settings.R
import com.android.settings.core.BasePreferenceController
+import com.android.settings.flags.Flags
import com.android.settingslib.RestrictedPreference
import com.android.settingslib.Utils
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
@@ -62,6 +63,7 @@
}
override fun getAvailabilityStatus() = when {
+ Flags.isDualSimOnboardingEnabled() -> UNSUPPORTED_ON_DEVICE
!SubscriptionUtil.isSimHardwareVisible(mContext) -> UNSUPPORTED_ON_DEVICE
!mContext.userManager.isAdminUser -> DISABLED_FOR_USER
else -> AVAILABLE
diff --git a/src/com/android/settings/network/NetworkProviderCallsSmsFragment.java b/src/com/android/settings/network/NetworkProviderCallsSmsFragment.java
index b5ad65a..ffe5b05 100644
--- a/src/com/android/settings/network/NetworkProviderCallsSmsFragment.java
+++ b/src/com/android/settings/network/NetworkProviderCallsSmsFragment.java
@@ -24,6 +24,7 @@
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.Flags;
import com.android.settings.network.telephony.CallsDefaultSubscriptionController;
import com.android.settings.network.telephony.NetworkProviderWifiCallingPreferenceController;
import com.android.settings.network.telephony.SmsDefaultSubscriptionController;
@@ -91,8 +92,9 @@
@Override
protected boolean isPageSearchEnabled(Context context) {
- return SubscriptionUtil.isSimHardwareVisible(context) &&
- context.getSystemService(UserManager.class).isAdminUser();
+ return !Flags.isDualSimOnboardingEnabled()
+ && SubscriptionUtil.isSimHardwareVisible(context)
+ && context.getSystemService(UserManager.class).isAdminUser();
}
};
}
diff --git a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
index ed930d4..f682002 100644
--- a/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
+++ b/src/com/android/settings/network/SubscriptionInfoListViewModel.kt
@@ -32,7 +32,19 @@
application.getSystemService(SubscriptionManager::class.java)!!
private val scope = viewModelScope + Dispatchers.Default
+ /**
+ * Getting the active Subscription list
+ */
+ //ToDo: renaming the function name
val subscriptionInfoListFlow = application.subscriptionsChangedFlow().map {
SubscriptionUtil.getActiveSubscriptions(subscriptionManager)
}.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())
+
+ /**
+ * Getting the Selectable SubscriptionInfo List from the SubscriptionManager's
+ * getAvailableSubscriptionInfoList
+ */
+ val selectableSubscriptionInfoListFlow = application.subscriptionsChangedFlow().map {
+ SubscriptionUtil.getSelectableSubscriptionInfoList(application)
+ }.stateIn(scope, SharingStarted.Eagerly, initialValue = emptyList())
}
diff --git a/src/com/android/settings/network/SubscriptionsPreferenceController.java b/src/com/android/settings/network/SubscriptionsPreferenceController.java
index 6601828..09ce02e 100644
--- a/src/com/android/settings/network/SubscriptionsPreferenceController.java
+++ b/src/com/android/settings/network/SubscriptionsPreferenceController.java
@@ -153,7 +153,8 @@
mPreferenceGroupKey = preferenceGroupKey;
mStartOrder = startOrder;
mTelephonyManager = context.getSystemService(TelephonyManager.class);
- mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
+ mSubscriptionManager = context.getSystemService(SubscriptionManager.class)
+ .createForAllUserProfiles();
mWifiManager = context.getSystemService(WifiManager.class);
mSubscriptionPreferences = new ArrayMap<>();
mSubscriptionsListener = new SubscriptionsChangeListener(context, this);
diff --git a/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java b/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java
index 0c3d61a..a26aa8a 100644
--- a/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java
+++ b/src/com/android/settings/network/SwitchToEuiccSubscriptionSidecar.java
@@ -101,7 +101,7 @@
}
SubscriptionManager subscriptionManager = getContext().getSystemService(
- SubscriptionManager.class);
+ SubscriptionManager.class).createForAllUserProfiles();
mActiveSubInfos = SubscriptionUtil.getActiveSubscriptions(subscriptionManager);
// To check whether the esim slot's port is active. If yes, skip setSlotMapping. If no,
diff --git a/src/com/android/settings/network/SwitchToRemovableSlotSidecar.java b/src/com/android/settings/network/SwitchToRemovableSlotSidecar.java
index a870f3b..4cf797f 100644
--- a/src/com/android/settings/network/SwitchToRemovableSlotSidecar.java
+++ b/src/com/android/settings/network/SwitchToRemovableSlotSidecar.java
@@ -92,7 +92,7 @@
mPhysicalSlotId = physicalSlotId;
mRemovedSubInfo = removedSubInfo;
SubscriptionManager subscriptionManager =
- getContext().getSystemService(SubscriptionManager.class);
+ getContext().getSystemService(SubscriptionManager.class).createForAllUserProfiles();
if (!mTelephonyManager.isMultiSimEnabled()
&& SubscriptionUtil.getActiveSubscriptions(subscriptionManager).stream().anyMatch(
SubscriptionInfo::isEmbedded)) {
diff --git a/src/com/android/settings/network/UiccSlotUtil.java b/src/com/android/settings/network/UiccSlotUtil.java
index f7737aa..5175c23 100644
--- a/src/com/android/settings/network/UiccSlotUtil.java
+++ b/src/com/android/settings/network/UiccSlotUtil.java
@@ -162,7 +162,7 @@
Log.d(TAG, "The SimSlotMapping: " + uiccSlotMappings);
SubscriptionManager subscriptionManager = context.getSystemService(
- SubscriptionManager.class);
+ SubscriptionManager.class).createForAllUserProfiles();
int excludedLogicalSlotIndex = getExcludedLogicalSlotIndex(uiccSlotMappings,
SubscriptionUtil.getActiveSubscriptions(subscriptionManager), removedSubInfo,
telMgr.isMultiSimEnabled());
@@ -203,7 +203,7 @@
}
SubscriptionManager subscriptionManager = context.getSystemService(
- SubscriptionManager.class);
+ SubscriptionManager.class).createForAllUserProfiles();
int excludedLogicalSlotIndex = getExcludedLogicalSlotIndex(uiccSlotMappings,
SubscriptionUtil.getActiveSubscriptions(subscriptionManager), removedSubInfo,
telMgr.isMultiSimEnabled());
@@ -222,7 +222,7 @@
List<UiccCardInfo> uiccCardInfos = telMgr.getUiccCardsInfo();
ImmutableList<UiccSlotInfo> slotInfos = UiccSlotUtil.getSlotInfos(telMgr);
SubscriptionManager subscriptionManager = context.getSystemService(
- SubscriptionManager.class);
+ SubscriptionManager.class).createForAllUserProfiles();
SubscriptionInfo subInfo = SubscriptionUtil.getSubById(subscriptionManager, subId);
// checking whether this is the removable esim. If it is, then return the removable slot id.
diff --git a/src/com/android/settings/network/WepNetworkDialogActivity.kt b/src/com/android/settings/network/WepNetworkDialogActivity.kt
index 2fa8784..d69630f 100644
--- a/src/com/android/settings/network/WepNetworkDialogActivity.kt
+++ b/src/com/android/settings/network/WepNetworkDialogActivity.kt
@@ -51,23 +51,24 @@
confirmButton = AlertDialogButton(
getString(R.string.wifi_settings_ssid_block_button_close)
) { finish() },
- dismissButton = AlertDialogButton(
- getString(R.string.wifi_settings_wep_networks_button_allow)
- ) {
- SubSettingLauncher(context)
- .setTitleText(context.getText(R.string.network_and_internet_preferences_title))
- .setSourceMetricsCategory(SettingsEnums.CONFIGURE_WIFI)
- .setDestination(ConfigureWifiSettings::class.java.getName())
- .launch()
- finish()
- },
+ dismissButton = if (wifiManager?.isWepSupported == true)
+ AlertDialogButton(
+ getString(R.string.wifi_settings_wep_networks_button_allow)
+ ) {
+ SubSettingLauncher(context)
+ .setTitleText(context.getText(R.string.network_and_internet_preferences_title))
+ .setSourceMetricsCategory(SettingsEnums.CONFIGURE_WIFI)
+ .setDestination(ConfigureWifiSettings::class.java.getName())
+ .launch()
+ finish()
+ } else null,
title = String.format(
getString(R.string.wifi_settings_wep_networks_blocked_title),
intent.getStringExtra(SSID) ?: SSID
),
text = {
Text(
- if (wifiManager?.isWepSupported == false)
+ if (wifiManager?.isWepSupported == true)
getString(R.string.wifi_settings_wep_networks_summary_toggle_off)
else getString(R.string.wifi_settings_wep_networks_summary_blocked_by_carrier),
modifier = Modifier.fillMaxWidth(),
diff --git a/src/com/android/settings/network/apn/ApnEditPageProvider.kt b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
index 79ce342..cea2b44 100644
--- a/src/com/android/settings/network/apn/ApnEditPageProvider.kt
+++ b/src/com/android/settings/network/apn/ApnEditPageProvider.kt
@@ -145,12 +145,7 @@
) {
Column {
if (apnData.validEnabled) {
- apnData = apnData.copy(
- networkType = ApnNetworkTypes.getNetworkType(
- networkTypeSelectedOptionsState
- )
- )
- valid = validateApnData(uriInit, apnData, context)
+ valid = validateApnData(apnData, context)
valid?.let {
Text(
text = it,
diff --git a/src/com/android/settings/network/apn/ApnRepository.kt b/src/com/android/settings/network/apn/ApnRepository.kt
index 2f16e69..ae655da 100644
--- a/src/com/android/settings/network/apn/ApnRepository.kt
+++ b/src/com/android/settings/network/apn/ApnRepository.kt
@@ -20,7 +20,6 @@
import android.content.Context
import android.net.Uri
import android.provider.Telephony
-import android.telephony.TelephonyManager
import android.util.Log
import com.android.settings.R
import com.android.settingslib.utils.ThreadUtils
@@ -200,14 +199,21 @@
}
}
-fun isItemExist(uri: Uri, apnData: ApnData, context: Context): String? {
- val contentValueMap = apnData.getContentValueMap(context)
- contentValueMap.remove(Telephony.Carriers.CARRIER_ENABLED)
+fun isItemExist(apnData: ApnData, context: Context): String? {
+ var contentValueMap = apnData.getContentValueMap(context)
+ val removedList = arrayListOf(
+ Telephony.Carriers.NAME, Telephony.Carriers.USER,
+ Telephony.Carriers.SERVER, Telephony.Carriers.PASSWORD, Telephony.Carriers.AUTH_TYPE,
+ Telephony.Carriers.TYPE, Telephony.Carriers.NETWORK_TYPE_BITMASK,
+ Telephony.Carriers.CARRIER_ENABLED
+ )
+ contentValueMap =
+ contentValueMap.filterNot { removedList.contains(it.key) } as MutableMap<String, Any>
val list = contentValueMap.entries.toList()
val selection = list.joinToString(" AND ") { "${it.key} = ?" }
val selectionArgs: Array<String> = list.map { it.value.toString() }.toTypedArray()
context.contentResolver.query(
- uri,
+ Telephony.Carriers.CONTENT_URI,
sProjection,
selection /* selection */,
selectionArgs /* selectionArgs */,
diff --git a/src/com/android/settings/network/apn/ApnStatus.kt b/src/com/android/settings/network/apn/ApnStatus.kt
index f9135fd..141ec08 100644
--- a/src/com/android/settings/network/apn/ApnStatus.kt
+++ b/src/com/android/settings/network/apn/ApnStatus.kt
@@ -173,7 +173,7 @@
context: Context,
uriInit: Uri
): String? {
- val errorMsg = validateApnData(uriInit, newApnData, context)
+ val errorMsg = validateApnData(newApnData, context)
if (errorMsg != null) {
return errorMsg
}
@@ -194,7 +194,7 @@
*
* @return An error message if the apn data is invalid, otherwise return null.
*/
-fun validateApnData(uri: Uri, apnData: ApnData, context: Context): String? {
+fun validateApnData(apnData: ApnData, context: Context): String? {
var errorMsg: String?
val name = apnData.name
val apn = apnData.apn
@@ -206,7 +206,7 @@
validateMMSC(true, apnData.mmsc, context)
}
if (errorMsg == null) {
- errorMsg = isItemExist(uri, apnData, context)
+ errorMsg = isItemExist(apnData, context)
}
if (errorMsg == null) {
errorMsg = validateAPNType(
diff --git a/src/com/android/settings/network/helper/SelectableSubscriptions.java b/src/com/android/settings/network/helper/SelectableSubscriptions.java
index 8e8f405..b4a3b57 100644
--- a/src/com/android/settings/network/helper/SelectableSubscriptions.java
+++ b/src/com/android/settings/network/helper/SelectableSubscriptions.java
@@ -146,7 +146,7 @@
}
protected SubscriptionManager getSubscriptionManager(Context context) {
- return context.getSystemService(SubscriptionManager.class);
+ return context.getSystemService(SubscriptionManager.class).createForAllUserProfiles();
}
protected List<SubscriptionInfo> getAvailableSubInfoList(Context context) {
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
index b4b40ef..4188f8d 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
@@ -255,6 +255,11 @@
roamingPreferenceController.init(getFragmentManager(), mSubId,
mMobileNetworkInfoEntity);
}
+ final SatelliteSettingPreferenceController satelliteSettingPreferenceController = use(
+ SatelliteSettingPreferenceController.class);
+ if (satelliteSettingPreferenceController != null) {
+ satelliteSettingPreferenceController.init(mSubId);
+ }
use(ApnPreferenceController.class).init(mSubId);
use(CarrierPreferenceController.class).init(mSubId);
use(DataUsagePreferenceController.class).init(mSubId);
diff --git a/src/com/android/settings/network/telephony/MobileNetworkUtils.java b/src/com/android/settings/network/telephony/MobileNetworkUtils.java
index c1a05f1..47515d8 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkUtils.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkUtils.java
@@ -357,8 +357,9 @@
final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class)
.createForSubscriptionId(subId);
final SubscriptionManager subscriptionManager = context.getSystemService(
- SubscriptionManager.class);
- telephonyManager.setDataEnabled(enabled);
+ SubscriptionManager.class).createForAllUserProfiles();
+ telephonyManager.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER,
+ enabled);
if (disableOtherSubscriptions) {
final List<SubscriptionInfo> subInfoList =
@@ -367,8 +368,10 @@
for (SubscriptionInfo subInfo : subInfoList) {
// We never disable mobile data for opportunistic subscriptions.
if (subInfo.getSubscriptionId() != subId && !subInfo.isOpportunistic()) {
- context.getSystemService(TelephonyManager.class).createForSubscriptionId(
- subInfo.getSubscriptionId()).setDataEnabled(false);
+ context.getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(subInfo.getSubscriptionId())
+ .setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER,
+ false);
}
}
}
@@ -666,39 +669,26 @@
* 2. Similar design which aligned with operator name displayed in status bar
*/
public static CharSequence getCurrentCarrierNameForDisplay(Context context, int subId) {
- final SubscriptionManager sm = context.getSystemService(SubscriptionManager.class);
- if (sm != null) {
- final SubscriptionInfo subInfo = getSubscriptionInfo(sm, subId);
- if (subInfo != null) {
- return subInfo.getCarrierName();
- }
+ final SubscriptionInfo subInfo = getSubscriptionInfo(context, subId);
+ if (subInfo != null) {
+ return subInfo.getCarrierName();
}
return getOperatorNameFromTelephonyManager(context);
}
public static CharSequence getCurrentCarrierNameForDisplay(Context context) {
- final SubscriptionManager sm = context.getSystemService(SubscriptionManager.class);
- if (sm != null) {
- final int subId = sm.getDefaultSubscriptionId();
- final SubscriptionInfo subInfo = getSubscriptionInfo(sm, subId);
- if (subInfo != null) {
- return subInfo.getCarrierName();
- }
+ final SubscriptionInfo subInfo = getSubscriptionInfo(context,
+ SubscriptionManager.getDefaultSubscriptionId());
+ if (subInfo != null) {
+ return subInfo.getCarrierName();
}
return getOperatorNameFromTelephonyManager(context);
}
- private static SubscriptionInfo getSubscriptionInfo(SubscriptionManager subManager, int subId) {
- List<SubscriptionInfo> subInfos = subManager.getActiveSubscriptionInfoList();
- if (subInfos == null) {
- return null;
- }
- for (SubscriptionInfo subInfo : subInfos) {
- if (subInfo.getSubscriptionId() == subId) {
- return subInfo;
- }
- }
- return null;
+ private static @Nullable SubscriptionInfo getSubscriptionInfo(Context context, int subId) {
+ SubscriptionManager sm = context.getSystemService(SubscriptionManager.class);
+ if (sm == null) return null;
+ return sm.createForAllUserProfiles().getActiveSubscriptionInfo(subId);
}
private static String getOperatorNameFromTelephonyManager(Context context) {
@@ -712,7 +702,7 @@
private static int[] getActiveSubscriptionIdList(Context context) {
final SubscriptionManager subscriptionManager = context.getSystemService(
- SubscriptionManager.class);
+ SubscriptionManager.class).createForAllUserProfiles();
final List<SubscriptionInfo> subInfoList =
subscriptionManager.getActiveSubscriptionInfoList();
if (subInfoList == null) {
diff --git a/src/com/android/settings/network/telephony/NetworkProviderWifiCallingGroup.java b/src/com/android/settings/network/telephony/NetworkProviderWifiCallingGroup.java
index cc0f22d..c5879ee 100644
--- a/src/com/android/settings/network/telephony/NetworkProviderWifiCallingGroup.java
+++ b/src/com/android/settings/network/telephony/NetworkProviderWifiCallingGroup.java
@@ -79,7 +79,8 @@
String preferenceGroupKey) {
super(context);
mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
- mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
+ mSubscriptionManager = context.getSystemService(SubscriptionManager.class)
+ .createForAllUserProfiles();
mPreferenceGroupKey = preferenceGroupKey;
mWifiCallingForSubPreferences = new ArrayMap<>();
setSubscriptionInfoList(context);
diff --git a/src/com/android/settings/network/telephony/SatelliteSetting.java b/src/com/android/settings/network/telephony/SatelliteSetting.java
new file mode 100644
index 0000000..ecfa8e4
--- /dev/null
+++ b/src/com/android/settings/network/telephony/SatelliteSetting.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 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.network.telephony;
+
+import android.app.Activity;
+import android.app.settings.SettingsEnums;
+import android.content.Intent;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.satellite.SatelliteManager;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.RestrictedDashboardFragment;
+import com.android.settingslib.HelpUtils;
+import com.android.settingslib.Utils;
+import com.android.settingslib.widget.FooterPreference;
+
+import java.util.Set;
+
+/** Handle Satellite Setting Preference Layout. */
+public class SatelliteSetting extends RestrictedDashboardFragment {
+ private static final String TAG = "SatelliteSetting";
+ public static final String PREF_KEY_ABOUT_SATELLITE_MESSAGING = "key_about_satellite_messaging";
+ public static final String PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN =
+ "key_category_your_satellite_plan";
+ public static final String PREF_KEY_YOUR_SATELLITE_PLAN = "key_your_satellite_plan";
+ public static final String PREF_KEY_CATEGORY_HOW_IT_WORKS = "key_category_how_it_works";
+ private static final String KEY_FOOTER_PREFERENCE = "satellite_setting_extra_info_footer_pref";
+ public static final String SUB_ID = "sub_id";
+
+ private Activity mActivity;
+ private TelephonyManager mTelephonymanager;
+ private SatelliteManager mSatelliteManager;
+ private int mSubId;
+
+ public SatelliteSetting() {
+ super(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.SATELLITE_SETTING;
+ }
+
+ @Override
+ public void onCreate(@NonNull Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mActivity = getActivity();
+ mTelephonymanager = mActivity.getSystemService(TelephonyManager.class);
+ mSatelliteManager = mActivity.getSystemService(SatelliteManager.class);
+ mSubId = mActivity.getIntent().getIntExtra(SUB_ID,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ updateDynamicPreferenceViews();
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.satellite_setting;
+ }
+
+ private void updateDynamicPreferenceViews() {
+ String operatorName = mTelephonymanager.getSimOperatorName(mSubId);
+ boolean isSatelliteEligible = isSatelliteEligible();
+
+ // About satellite messaging
+ Preference preference = findPreference(PREF_KEY_ABOUT_SATELLITE_MESSAGING);
+ preference.setTitle(
+ getResources().getString(R.string.title_about_satellite_setting, operatorName));
+
+ // Your mobile plan
+ PreferenceCategory prefCategory = findPreference(PREF_KEY_CATEGORY_YOUR_SATELLITE_PLAN);
+ prefCategory.setTitle(getResources().getString(R.string.category_title_your_satellite_plan,
+ operatorName));
+
+ preference = findPreference(PREF_KEY_YOUR_SATELLITE_PLAN);
+ Drawable icon;
+ if (isSatelliteEligible) {
+ /* In case satellite is allowed by carrier's entitlement server, the page will show
+ the check icon with guidance that satellite is included in user's mobile plan */
+ preference.setTitle(R.string.title_have_satellite_plan);
+ icon = getResources().getDrawable(R.drawable.ic_check_circle_24px);
+ } else {
+ /* Or, it will show the blocked icon with the guidance that satellite is not included
+ in user's mobile plan */
+ preference.setTitle(R.string.title_no_satellite_plan);
+ /* And, the link url provides more information via web page will be shown */
+ SpannableString spannable = new SpannableString(
+ getResources().getString(R.string.summary_add_satellite_setting));
+ spannable.setSpan(new UnderlineSpan(), 0, spannable.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ spannable.setSpan(new StyleSpan(Typeface.BOLD), 0, spannable.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ preference.setSummary(spannable);
+ /* The link will lead users to a guide page */
+ preference.setOnPreferenceClickListener(pref -> {
+ String url = getResources().getString(R.string.more_info_satellite_messaging_link);
+ if (!url.isEmpty()) {
+ Uri uri = Uri.parse(url);
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ startActivity(intent);
+ }
+ return true;
+ });
+ icon = getResources().getDrawable(R.drawable.ic_block_24px);
+ }
+ icon.setTintList(Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary));
+ preference.setIcon(icon);
+
+ /* Composes "How it works" section, which guides how users can use satellite messaging, when
+ satellite messaging is included in user's mobile plan, or it'll will be grey out. */
+ if (!isSatelliteEligible) {
+ PreferenceCategory category = findPreference(PREF_KEY_CATEGORY_HOW_IT_WORKS);
+ category.setEnabled(false);
+ category.setShouldDisableView(true);
+ }
+
+ // More about satellite messaging
+ FooterPreference footerPreference = findPreference(KEY_FOOTER_PREFERENCE);
+ if (footerPreference != null) {
+ footerPreference.setSummary(
+ getResources().getString(R.string.satellite_setting_summary_more_information,
+ operatorName));
+
+ final String[] link = new String[1];
+ link[0] = getResources().getString(R.string.more_info_satellite_messaging_link);
+ footerPreference.setLearnMoreAction(view -> {
+ if (!link[0].isEmpty()) {
+ Intent helpIntent = HelpUtils.getHelpIntent(mActivity, link[0],
+ this.getClass().getName());
+ if (helpIntent != null) {
+ mActivity.startActivityForResult(helpIntent, /*requestCode=*/ 0);
+ }
+ }
+ });
+ footerPreference.setLearnMoreText(
+ getResources().getString(R.string.more_about_satellite_messaging));
+
+ // TODO : b/320467418 add rounded rectangle border line to footer preference.
+ }
+ }
+
+ private boolean isSatelliteEligible() {
+ try {
+ Set<Integer> restrictionReason =
+ mSatelliteManager.getSatelliteAttachRestrictionReasonsForCarrier(mSubId);
+ return !restrictionReason.contains(
+ SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT);
+ } catch (SecurityException | IllegalStateException | IllegalArgumentException ex) {
+ loge(ex.toString());
+ return false;
+ }
+ }
+
+ private static void loge(String message) {
+ Log.e(TAG, message);
+ }
+}
diff --git a/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java b/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java
new file mode 100644
index 0000000..7de7fcb
--- /dev/null
+++ b/src/com/android/settings/network/telephony/SatelliteSettingPreferenceController.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 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.network.telephony;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.PersistableBundle;
+import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
+import android.telephony.satellite.SatelliteManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.network.CarrierConfigCache;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
+
+import java.util.Set;
+
+/**
+ * Preference controller for "Satellite Setting"
+ */
+public class SatelliteSettingPreferenceController extends
+ TelephonyBasePreferenceController implements LifecycleObserver, OnStart, OnStop {
+
+ private static final String TAG = "SatelliteSettingPreferenceController";
+
+ CarrierConfigCache mCarrierConfigCache;
+ SatelliteManager mSatelliteManager;
+ @Nullable private Boolean mIsSatelliteEligible = null;
+
+ public SatelliteSettingPreferenceController(@NonNull Context context, @NonNull String key) {
+ super(context, key);
+ mCarrierConfigCache = CarrierConfigCache.getInstance(context);
+ mSatelliteManager = context.getSystemService(SatelliteManager.class);
+ }
+
+ @Override
+ public int getAvailabilityStatus(int subId) {
+ final PersistableBundle carrierConfig = mCarrierConfigCache.getConfigForSubId(subId);
+ final boolean isSatelliteAttachSupported = carrierConfig.getBoolean(
+ CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL);
+
+ return isSatelliteAttachSupported ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ public void onStart() {
+ }
+
+ @Override
+ public void onStop() {
+ }
+
+ @Override
+ public void displayPreference(@NonNull PreferenceScreen screen) {
+ super.displayPreference(screen);
+ }
+
+ @Override
+ public void updateState(@Nullable Preference preference) {
+ super.updateState(preference);
+ if (preference != null) {
+ updateSummary(preference);
+ }
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(@NonNull Preference preference) {
+ if (getPreferenceKey().equals(preference.getKey())) {
+ // This activity runs in phone process, we must use intent to start
+ final Intent intent = new Intent(Settings.ACTION_SATELLITE_SETTING);
+ // This will setup the Home and Search affordance
+ intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, true);
+ intent.putExtra(SatelliteSetting.SUB_ID, mSubId);
+ mContext.startActivity(intent);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Set subId for Satellite Settings page.
+ * @param subId subscription ID.
+ */
+ public void init(int subId) {
+ logd("init(), subId=" + subId);
+ mSubId = subId;
+ }
+
+ private void updateSummary(Preference preference) {
+ try {
+ Set<Integer> restrictionReason =
+ mSatelliteManager.getSatelliteAttachRestrictionReasonsForCarrier(mSubId);
+ boolean isSatelliteEligible = !restrictionReason.contains(
+ SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT);
+ if (mIsSatelliteEligible == null || mIsSatelliteEligible != isSatelliteEligible) {
+ mIsSatelliteEligible = isSatelliteEligible;
+ String summary = mContext.getString(
+ mIsSatelliteEligible ? R.string.satellite_setting_enabled_summary
+ : R.string.satellite_setting_disabled_summary);
+ preference.setSummary(summary);
+ }
+ } catch (SecurityException | IllegalStateException | IllegalArgumentException ex) {
+ loge(ex.toString());
+ preference.setSummary(R.string.satellite_setting_disabled_summary);
+ }
+ }
+
+ private static void logd(String message) {
+ Log.d(TAG, message);
+ }
+
+ private static void loge(String message) {
+ Log.e(TAG, message);
+ }
+}
diff --git a/src/com/android/settings/network/telephony/SubscriptionActionDialogActivity.java b/src/com/android/settings/network/telephony/SubscriptionActionDialogActivity.java
index 391158f..10f8875 100644
--- a/src/com/android/settings/network/telephony/SubscriptionActionDialogActivity.java
+++ b/src/com/android/settings/network/telephony/SubscriptionActionDialogActivity.java
@@ -43,7 +43,8 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mSubscriptionManager = getSystemService(SubscriptionManager.class);
+ mSubscriptionManager = getSystemService(SubscriptionManager.class)
+ .createForAllUserProfiles();
setProgressState(PROGRESS_IS_NOT_SHOWING);
}
diff --git a/src/com/android/settings/notification/zen/ZenModeRuleSettingsBase.java b/src/com/android/settings/notification/zen/ZenModeRuleSettingsBase.java
index cda5b1a..ff217b5 100644
--- a/src/com/android/settings/notification/zen/ZenModeRuleSettingsBase.java
+++ b/src/com/android/settings/notification/zen/ZenModeRuleSettingsBase.java
@@ -19,7 +19,9 @@
import static android.app.NotificationManager.EXTRA_AUTOMATIC_RULE_ID;
import android.app.AutomaticZenRule;
+import android.app.Flags;
import android.app.NotificationManager;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@@ -100,10 +102,21 @@
public boolean onPreferenceClick(Preference preference) {
Bundle bundle = new Bundle();
bundle.putString(ZenCustomRuleSettings.RULE_ID, mId);
+
+ // When modes_api flag is on, we skip the radio button screen distinguishing
+ // between "default" and "custom" and take users directly to the custom
+ // settings screen.
+ String destination = ZenCustomRuleSettings.class.getName();
+ int sourceMetricsCategory = 0;
+ if (Flags.modesApi()) {
+ // From ZenRuleCustomPolicyPreferenceController#launchCustomSettings
+ destination = ZenCustomRuleConfigSettings.class.getName();
+ sourceMetricsCategory = SettingsEnums.ZEN_CUSTOM_RULE_SOUND_SETTINGS;
+ }
new SubSettingLauncher(mContext)
- .setDestination(ZenCustomRuleSettings.class.getName())
+ .setDestination(destination)
.setArguments(bundle)
- .setSourceMetricsCategory(0) // TODO
+ .setSourceMetricsCategory(sourceMetricsCategory)
.launch();
return true;
}
diff --git a/src/com/android/settings/privatespace/PrivateSpaceAccountLoginError.java b/src/com/android/settings/privatespace/PrivateSpaceAccountLoginError.java
index b64335b..fcb93b1 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceAccountLoginError.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceAccountLoginError.java
@@ -31,6 +31,7 @@
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.Nullable;
+import androidx.navigation.fragment.NavHostFragment;
import com.android.settings.R;
import com.android.settings.core.InstrumentedFragment;
@@ -55,11 +56,20 @@
final FooterBarMixin mixin = rootView.getMixin(FooterBarMixin.class);
mixin.setPrimaryButton(
new FooterButton.Builder(getContext())
- .setText(R.string.private_space_tryagain_label)
+ .setText(R.string.private_space_continue_login_label)
.setListener(nextScreen())
.setButtonType(FooterButton.ButtonType.NEXT)
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
.build());
+ mixin.setSecondaryButton(
+ new FooterButton.Builder(getContext())
+ .setText(R.string.private_space_skip_login_label)
+ .setListener(onSkip())
+ .setButtonType(FooterButton.ButtonType.CANCEL)
+ .setTheme(
+ androidx.appcompat.R.style
+ .Base_TextAppearance_AppCompat_Widget_Button)
+ .build());
OnBackPressedCallback callback =
new OnBackPressedCallback(true /* enabled by default */) {
@Override
@@ -96,4 +106,17 @@
}
};
}
+
+ private View.OnClickListener onSkip() {
+ return v -> {
+ mMetricsFeatureProvider.action(
+ getContext(), SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_SKIP_ACCOUNT_LOGIN);
+ mMetricsFeatureProvider.action(
+ getContext(),
+ SettingsEnums.ACTION_PRIVATE_SPACE_SETUP_ACCOUNT_LOGIN_SUCCESS,
+ false);
+ NavHostFragment.findNavController(PrivateSpaceAccountLoginError.this)
+ .navigate(R.id.action_success_fragment);
+ };
+ }
}
diff --git a/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java b/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java
index 271a219..2e3f284 100644
--- a/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java
+++ b/src/com/android/settings/privatespace/onelock/FaceFingerprintUnlockController.java
@@ -23,6 +23,7 @@
import androidx.preference.Preference;
import com.android.settings.R;
+import com.android.settings.Utils;
import com.android.settings.biometrics.combination.CombinedBiometricStatusPreferenceController;
import com.android.settings.privatespace.PrivateSpaceMaintainer;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -72,6 +73,8 @@
super.updateState(preference);
preference.setEnabled(true);
} else {
+ Utils.removeEnrolledFaceForUser(mContext, getUserId());
+ Utils.removeEnrolledFingerprintForUser(mContext, getUserId());
preference.setSummary(
mContext.getString(R.string.lock_settings_profile_unified_summary));
preference.setEnabled(false);
diff --git a/src/com/android/settings/privatespace/onelock/PrivateSpaceFacePreferenceController.java b/src/com/android/settings/privatespace/onelock/PrivateSpaceFacePreferenceController.java
index 2a5ff88..b841d9a 100644
--- a/src/com/android/settings/privatespace/onelock/PrivateSpaceFacePreferenceController.java
+++ b/src/com/android/settings/privatespace/onelock/PrivateSpaceFacePreferenceController.java
@@ -74,6 +74,7 @@
super.updateState(preference);
preference.setEnabled(true);
} else {
+ Utils.removeEnrolledFaceForUser(mContext, getUserId());
preference.setSummary(
mContext.getString(R.string.lock_settings_profile_unified_summary));
preference.setEnabled(false);
diff --git a/src/com/android/settings/privatespace/onelock/PrivateSpaceFingerprintPreferenceController.java b/src/com/android/settings/privatespace/onelock/PrivateSpaceFingerprintPreferenceController.java
index b6c4457..d484904 100644
--- a/src/com/android/settings/privatespace/onelock/PrivateSpaceFingerprintPreferenceController.java
+++ b/src/com/android/settings/privatespace/onelock/PrivateSpaceFingerprintPreferenceController.java
@@ -76,6 +76,7 @@
super.updateState(preference);
preference.setEnabled(true);
} else {
+ Utils.removeEnrolledFingerprintForUser(mContext, getUserId());
preference.setSummary(
mContext.getString(R.string.lock_settings_profile_unified_summary));
preference.setEnabled(false);
diff --git a/src/com/android/settings/security/ScreenPinningSettings.java b/src/com/android/settings/security/ScreenPinningSettings.java
index 99d6492..8690847 100644
--- a/src/com/android/settings/security/ScreenPinningSettings.java
+++ b/src/com/android/settings/security/ScreenPinningSettings.java
@@ -22,6 +22,7 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.res.Resources;
import android.icu.text.MessageFormat;
import android.os.Bundle;
import android.os.UserHandle;
@@ -30,6 +31,7 @@
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
@@ -45,7 +47,11 @@
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.search.SearchIndexableRaw;
import com.android.settingslib.widget.FooterPreference;
+
+import java.util.List;
+
/**
* Screen pinning settings.
*/
@@ -174,9 +180,8 @@
}
}
- private int getCurrentSecurityTitle() {
- int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(
- UserHandle.myUserId());
+ private static int getCurrentSecurityTitle(LockPatternUtils lockPatternUtils) {
+ int quality = lockPatternUtils.getKeyguardStoredPasswordQuality(UserHandle.myUserId());
switch (quality) {
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
@@ -187,7 +192,7 @@
case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
return R.string.screen_pinning_unlock_password;
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
- if (mLockPatternUtils.isLockPatternEnabled(UserHandle.myUserId())) {
+ if (lockPatternUtils.isLockPatternEnabled(UserHandle.myUserId())) {
return R.string.screen_pinning_unlock_pattern;
}
}
@@ -232,7 +237,7 @@
}
});
mUseScreenLock.setChecked(isScreenLockUsed());
- mUseScreenLock.setTitle(getCurrentSecurityTitle());
+ mUseScreenLock.setTitle(getCurrentSecurityTitle(mLockPatternUtils));
} else {
mFooterPreference.setSummary(getAppPinningContent());
mUseScreenLock.setEnabled(false);
@@ -252,8 +257,30 @@
}
/**
- * For search
+ * For search.
+ *
+ * This page only provides an index for the toggle preference of using screen lock for
+ * unpinning. The preference name will change with various lock configurations. Indexing data
+ * from XML isn't suitable since it uses a static title by default. So, we skip XML indexing
+ * by omitting the XML argument in the constructor and use a dynamic index method instead.
*/
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider(R.xml.screen_pinning_settings);
+ new BaseSearchIndexProvider() {
+
+ @NonNull
+ @Override
+ public List<SearchIndexableRaw> getDynamicRawDataToIndex(@NonNull Context context,
+ boolean enabled) {
+ List<SearchIndexableRaw> dynamicRaws =
+ super.getDynamicRawDataToIndex(context, enabled);
+ final SearchIndexableRaw raw = new SearchIndexableRaw(context);
+ final Resources res = context.getResources();
+ final LockPatternUtils lockPatternUtils = new LockPatternUtils(context);
+ raw.key = KEY_USE_SCREEN_LOCK;
+ raw.title = res.getString(getCurrentSecurityTitle(lockPatternUtils));
+ raw.screenTitle = res.getString(R.string.screen_pinning_title);
+ dynamicRaws.add(raw);
+ return dynamicRaws;
+ }
+ };
}
diff --git a/src/com/android/settings/security/SimLockPreferenceController.java b/src/com/android/settings/security/SimLockPreferenceController.java
index 8429a9f..8cc7234 100644
--- a/src/com/android/settings/security/SimLockPreferenceController.java
+++ b/src/com/android/settings/security/SimLockPreferenceController.java
@@ -44,8 +44,9 @@
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mCarrierConfigManager = (CarrierConfigManager)
mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
- mSubscriptionManager = (SubscriptionManager) context
- .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ mSubscriptionManager = ((SubscriptionManager) context
+ .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE))
+ .createForAllUserProfiles();
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
}
diff --git a/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragment.java b/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragment.java
index b1b5f8e..bd6a394 100644
--- a/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragment.java
+++ b/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragment.java
@@ -195,7 +195,7 @@
}
private SubscriptionManager getSubscriptionManager() {
- return getContext().getSystemService(SubscriptionManager.class);
+ return getContext().getSystemService(SubscriptionManager.class).createForAllUserProfiles();
}
@VisibleForTesting
diff --git a/src/com/android/settings/sim/SelectSpecificDataSimDialogFragment.java b/src/com/android/settings/sim/SelectSpecificDataSimDialogFragment.java
index 37f5445..b0b65f6 100644
--- a/src/com/android/settings/sim/SelectSpecificDataSimDialogFragment.java
+++ b/src/com/android/settings/sim/SelectSpecificDataSimDialogFragment.java
@@ -194,7 +194,7 @@
@VisibleForTesting
protected SubscriptionManager getSubscriptionManager() {
- return getContext().getSystemService(SubscriptionManager.class);
+ return getContext().getSystemService(SubscriptionManager.class).createForAllUserProfiles();
}
@Override
diff --git a/src/com/android/settings/sim/SimListDialogFragment.java b/src/com/android/settings/sim/SimListDialogFragment.java
index db2c4dc..fd44dc7 100644
--- a/src/com/android/settings/sim/SimListDialogFragment.java
+++ b/src/com/android/settings/sim/SimListDialogFragment.java
@@ -125,7 +125,7 @@
protected List<SubscriptionInfo> getCurrentSubscriptions() {
final SubscriptionManager manager = getContext().getSystemService(
- SubscriptionManager.class);
+ SubscriptionManager.class).createForAllUserProfiles();
return manager.getActiveSubscriptionInfoList();
}
diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
index d94e861..41852e5 100644
--- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt
+++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
@@ -22,6 +22,7 @@
import com.android.settings.spa.about.AboutPhonePageProvider
import com.android.settings.spa.app.AllAppListPageProvider
import com.android.settings.spa.app.AppsMainPageProvider
+import com.android.settings.spa.app.battery.BatteryOptimizationModeAppListPageProvider
import com.android.settings.spa.app.appcompat.UserAspectRatioAppsPageProvider
import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
import com.android.settings.spa.app.appinfo.CloneAppInfoSettingsProvider
@@ -47,6 +48,7 @@
import com.android.settings.spa.development.compat.PlatformCompatAppListPageProvider
import com.android.settings.spa.home.HomePageProvider
import com.android.settings.spa.network.NetworkAndInternetPageProvider
+import com.android.settings.spa.network.NetworkCellularGroupProvider
import com.android.settings.spa.network.SimOnboardingPageProvider
import com.android.settings.spa.notification.AppListNotificationsPageProvider
import com.android.settings.spa.notification.NotificationMainPageProvider
@@ -116,6 +118,8 @@
StorageAppListPageProvider.Games,
ApnEditPageProvider,
SimOnboardingPageProvider,
+ BatteryOptimizationModeAppListPageProvider,
+ NetworkCellularGroupProvider,
)
override val logger = if (FeatureFlagUtils.isEnabled(
diff --git a/src/com/android/settings/spa/app/battery/BatteryOptimizationModeAppListPageProvider.kt b/src/com/android/settings/spa/app/battery/BatteryOptimizationModeAppListPageProvider.kt
new file mode 100644
index 0000000..f077506
--- /dev/null
+++ b/src/com/android/settings/spa/app/battery/BatteryOptimizationModeAppListPageProvider.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 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.spa.app.battery
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import androidx.core.os.bundleOf
+import com.android.settings.R
+import com.android.settings.Utils
+import com.android.settings.core.SubSettingLauncher
+import com.android.settings.fuelgauge.AdvancedPowerUsageDetail
+import com.android.settings.fuelgauge.BatteryOptimizeUtils
+import com.android.settings.spa.app.AppRecordWithSize
+import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
+import com.android.settings.spa.app.rememberResetAppDialogPresenter
+import com.android.settingslib.fuelgauge.PowerAllowlistBackend
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.rememberContext
+import com.android.settingslib.spa.framework.util.filterItem
+import com.android.settingslib.spa.framework.util.mapItem
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.ui.SpinnerOption
+import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.installed
+import com.android.settingslib.spaprivileged.model.app.userHandle
+import com.android.settingslib.spaprivileged.template.app.AppList
+import com.android.settingslib.spaprivileged.template.app.AppListInput
+import com.android.settingslib.spaprivileged.template.app.AppListItem
+import com.android.settingslib.spaprivileged.template.app.AppListItemModel
+import com.android.settingslib.spaprivileged.template.app.AppListPage
+import kotlinx.coroutines.flow.Flow
+
+object BatteryOptimizationModeAppListPageProvider : SettingsPageProvider {
+ override val name = "BatteryOptimizationModeAppList"
+ private val owner = createSettingsPage()
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ BatteryOptimizationModeAppList()
+ }
+
+ fun buildInjectEntry() = SettingsEntryBuilder
+ .createInject(owner)
+ .setSearchDataFn { null }
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = stringResource(R.string.app_battery_usage_title)
+ override val onClick = navigator(name)
+ })
+ }
+}
+
+@Composable
+fun BatteryOptimizationModeAppList(
+ appList: @Composable AppListInput<AppRecordWithSize>.() -> Unit = { AppList() },
+) {
+ AppListPage(
+ title = stringResource(R.string.app_battery_usage_title),
+ listModel = rememberContext(::BatteryOptimizationModeAppListModel),
+ appList = appList,
+ )
+}
+
+class BatteryOptimizationModeAppListModel(
+ private val context: Context,
+) : AppListModel<AppRecordWithSize> {
+
+ override fun getSpinnerOptions(recordList: List<AppRecordWithSize>): List<SpinnerOption> =
+ OptimizationModeSpinnerItem.entries.map {
+ SpinnerOption(
+ id = it.ordinal,
+ text = context.getString(it.stringResId),
+ )
+ }
+
+ override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+ appListFlow.mapItem(::AppRecordWithSize)
+
+ override fun filter(
+ userIdFlow: Flow<Int>,
+ option: Int,
+ recordListFlow: Flow<List<AppRecordWithSize>>,
+ ): Flow<List<AppRecordWithSize>> {
+ PowerAllowlistBackend.getInstance(context).refreshList()
+ return recordListFlow.filterItem {
+ val appOptimizationMode = BatteryOptimizeUtils(context, it.app.uid, it.app.packageName)
+ .getAppOptimizationMode(/* refreshList */ false);
+ when (OptimizationModeSpinnerItem.entries.getOrNull(option)) {
+ OptimizationModeSpinnerItem.Restricted ->
+ appOptimizationMode == BatteryOptimizeUtils.MODE_RESTRICTED
+ OptimizationModeSpinnerItem.Optimized ->
+ appOptimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED
+ OptimizationModeSpinnerItem.Unrestricted ->
+ appOptimizationMode == BatteryOptimizeUtils.MODE_UNRESTRICTED
+ else -> (true)
+ }
+ }
+ }
+
+ @Composable
+ override fun getSummary(option: Int, record: AppRecordWithSize): () -> String = {
+ var summary = String()
+ val app = record.app
+ when {
+ !app.installed && !app.isArchived -> {
+ summary += context.getString(R.string.not_installed)
+ }
+
+ !app.enabled -> {
+ summary += context.getString(com.android.settingslib.R.string.disabled)
+ }
+ }
+ summary
+ }
+
+ @Composable
+ override fun AppListItemModel<AppRecordWithSize>.AppItem() {
+ AppListItem(onClick = {
+ val args = bundleOf(
+ AdvancedPowerUsageDetail.EXTRA_PACKAGE_NAME to record.app.packageName,
+ AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT to Utils.formatPercentage(0),
+ AdvancedPowerUsageDetail.EXTRA_UID to record.app.uid,
+ )
+ SubSettingLauncher(context)
+ .setDestination(AdvancedPowerUsageDetail::class.java.name)
+ .setTitleRes(R.string.battery_details_title)
+ .setArguments(args)
+ .setUserHandle(record.app.userHandle)
+ .setSourceMetricsCategory(AppInfoSettingsProvider.METRICS_CATEGORY)
+ .launch()
+ })
+ }
+}
+
+private enum class OptimizationModeSpinnerItem(val stringResId: Int) {
+ All(R.string.filter_all_apps),
+ Restricted(R.string.filter_battery_restricted_title),
+ Optimized(R.string.filter_battery_optimized_title),
+ Unrestricted(R.string.filter_battery_unrestricted_title);
+}
diff --git a/src/com/android/settings/spa/app/specialaccess/MediaRoutingControl.kt b/src/com/android/settings/spa/app/specialaccess/MediaRoutingControl.kt
index 91c4928..b9fb9b8 100644
--- a/src/com/android/settings/spa/app/specialaccess/MediaRoutingControl.kt
+++ b/src/com/android/settings/spa/app/specialaccess/MediaRoutingControl.kt
@@ -22,6 +22,7 @@
import android.app.settings.SettingsEnums
import android.companion.AssociationRequest
import android.content.Context
+import com.android.media.flags.Flags;
import com.android.settings.R
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
@@ -48,8 +49,9 @@
}
override fun isChangeable(record: AppOpPermissionRecord): Boolean {
- return super.isChangeable(record) && (this.roleManager
- ?.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH)
+ return Flags.enablePrivilegedRoutingForMediaRoutingControl()
+ && super.isChangeable(record)
+ && (this.roleManager?.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH)
?.contains(record.app.packageName) == true)
}
diff --git a/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt b/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt
index aafe493..1225806 100644
--- a/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt
+++ b/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt
@@ -20,24 +20,12 @@
import android.app.AppOpsManager
import android.app.settings.SettingsEnums
import android.content.Context
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.livedata.observeAsState
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.platform.LocalContext
import com.android.settings.R
import com.android.settings.overlay.FeatureFactory
-import com.android.settingslib.spa.widget.preference.SwitchPreference
-import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spaprivileged.model.app.AppOpsController
import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasGrantPermission
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
-import kotlinx.coroutines.Dispatchers
/**
* This class builds an App List under voice activation apps and the individual page which
@@ -56,97 +44,15 @@
override val appOp = AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
override val permission = Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO
override val setModeByUid = true
- private var receiveDetectionTrainingDataOpController:AppOpsController? = null
+
override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
super.setAllowed(record, newAllowed)
- if (!newAllowed && receiveDetectionTrainingDataOpController != null) {
- receiveDetectionTrainingDataOpController!!.setAllowed(false)
- }
logPermissionChange(newAllowed)
}
override fun isChangeable(record: AppOpPermissionRecord): Boolean =
super.isChangeable(record) && record.app.hasGrantPermission(permission)
- @Composable
- override fun InfoPageAdditionalContent(
- record: AppOpPermissionRecord,
- isAllowed: () -> Boolean?,
- ) {
- SwitchPreference(createReceiveDetectionTrainingDataOpSwitchModel(record, isAllowed))
- }
-
- @Composable
- private fun createReceiveDetectionTrainingDataOpSwitchModel(
- record: AppOpPermissionRecord,
- isReceiveSandBoxTriggerAudioOpAllowed: () -> Boolean?
- ): ReceiveDetectionTrainingDataOpSwitchModel {
- val context = LocalContext.current
- receiveDetectionTrainingDataOpController = remember {
- AppOpsController(
- context = context,
- app = record.app,
- op = AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
- )
- }
- val isReceiveDetectionTrainingDataOpAllowed = isReceiveDetectionTrainingDataOpAllowed(record, receiveDetectionTrainingDataOpController!!)
-
- return remember(record) {
- ReceiveDetectionTrainingDataOpSwitchModel(
- context,
- record,
- isReceiveSandBoxTriggerAudioOpAllowed,
- receiveDetectionTrainingDataOpController!!,
- isReceiveDetectionTrainingDataOpAllowed,
- )
- }.also { model -> LaunchedEffect(model, Dispatchers.Default) { model.initState() } }
- }
-
- private inner class ReceiveDetectionTrainingDataOpSwitchModel(
- context: Context,
- private val record: AppOpPermissionRecord,
- isReceiveSandBoxTriggerAudioOpAllowed: () -> Boolean?,
- receiveDetectionTrainingDataOpController: AppOpsController,
- isReceiveDetectionTrainingDataOpAllowed: () -> Boolean?,
- ) : SwitchPreferenceModel {
- private var appChangeable by mutableStateOf(true)
-
- override val title: String = context.getString(R.string.permit_receive_sandboxed_detection_training_data)
- override val summary: () -> String = { context.getString(R.string.receive_sandboxed_detection_training_data_description) }
- override val checked = { isReceiveDetectionTrainingDataOpAllowed() == true && isReceiveSandBoxTriggerAudioOpAllowed() == true }
- override val changeable = { appChangeable && isReceiveSandBoxTriggerAudioOpAllowed() == true }
-
- fun initState() {
- appChangeable = isChangeable(record)
- }
-
- override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
- receiveDetectionTrainingDataOpController.setAllowed(newChecked)
- }
- }
-
- @Composable
- private fun isReceiveDetectionTrainingDataOpAllowed(
- record: AppOpPermissionRecord,
- controller: AppOpsController
- ): () -> Boolean? {
- if (record.hasRequestBroaderPermission) {
- // Broader permission trumps the specific permission.
- return { true }
- }
-
- val mode = controller.mode.observeAsState()
- return {
- when (mode.value) {
- null -> null
- AppOpsManager.MODE_ALLOWED -> true
- AppOpsManager.MODE_DEFAULT -> record.app.hasGrantPermission(
- Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA)
- else -> false
- }
- }
- }
-
private fun logPermissionChange(newAllowed: Boolean) {
val category = when {
newAllowed -> SettingsEnums.APP_SPECIAL_PERMISSION_RECEIVE_SANDBOX_TRIGGER_AUDIO_ALLOW
diff --git a/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
new file mode 100644
index 0000000..e746d4a
--- /dev/null
+++ b/src/com/android/settings/spa/network/NetworkCellularGroupProvider.kt
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2024 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.spa.network
+
+import android.app.Application
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Bundle
+import android.os.UserManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import android.telephony.euicc.EuiccManager
+import android.util.Log
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.Message
+import androidx.compose.material.icons.outlined.Add
+import androidx.compose.material.icons.outlined.DataUsage
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableIntState
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.toMutableStateList
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
+import com.android.settings.R
+import com.android.settings.network.SubscriptionInfoListViewModel
+import com.android.settings.network.SubscriptionUtil
+import com.android.settings.network.telephony.MobileNetworkUtils
+import com.android.settings.wifi.WifiPickerTrackerHelper
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
+import com.android.settingslib.spa.widget.preference.ListPreferenceOption
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
+import com.android.settingslib.spa.widget.ui.SettingsIcon
+import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
+
+import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Showing the sim onboarding which is the process flow of sim switching on.
+ */
+object NetworkCellularGroupProvider : SettingsPageProvider {
+ override val name = "NetworkCellularGroupProvider"
+
+ private lateinit var subscriptionViewModel: SubscriptionInfoListViewModel
+ private val owner = createSettingsPage()
+
+ var selectableSubscriptionInfoList: List<SubscriptionInfo> = listOf()
+ var defaultVoiceSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ var defaultSmsSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ var defaultDataSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ var nonDds: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID
+
+ fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
+ .setUiLayoutFn {
+ // never using
+ Preference(object : PreferenceModel {
+ override val title = name
+ override val onClick = navigator(name)
+ })
+ }
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ val context = LocalContext.current
+ var selectableSubscriptionInfoListRemember = remember {
+ mutableListOf<SubscriptionInfo>().toMutableStateList()
+ }
+ var callsSelectedId = rememberSaveable {
+ mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ }
+ var textsSelectedId = rememberSaveable {
+ mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ }
+ var mobileDataSelectedId = rememberSaveable {
+ mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ }
+ var nonDdsRemember = rememberSaveable {
+ mutableIntStateOf(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ }
+
+ subscriptionViewModel = SubscriptionInfoListViewModel(
+ context.applicationContext as Application)
+
+ allOfFlows(context, subscriptionViewModel.selectableSubscriptionInfoListFlow)
+ .collectLatestWithLifecycle(LocalLifecycleOwner.current) {
+ selectableSubscriptionInfoListRemember.clear()
+ selectableSubscriptionInfoListRemember.addAll(selectableSubscriptionInfoList)
+ callsSelectedId.intValue = defaultVoiceSubId
+ textsSelectedId.intValue = defaultSmsSubId
+ mobileDataSelectedId.intValue = defaultDataSubId
+ nonDdsRemember.intValue = nonDds
+ }
+
+ PageImpl(selectableSubscriptionInfoListRemember,
+ callsSelectedId,
+ textsSelectedId,
+ mobileDataSelectedId,
+ nonDdsRemember)
+ }
+
+ private fun allOfFlows(context: Context,
+ selectableSubscriptionInfoListFlow: Flow<List<SubscriptionInfo>>) =
+ combine(
+ selectableSubscriptionInfoListFlow,
+ context.defaultVoiceSubscriptionFlow(),
+ context.defaultSmsSubscriptionFlow(),
+ context.defaultDefaultDataSubscriptionFlow(),
+ NetworkCellularGroupProvider::refreshUiStates,
+ ).flowOn(Dispatchers.Default)
+
+ fun refreshUiStates(
+ inputSelectableSubscriptionInfoList: List<SubscriptionInfo>,
+ inputDefaultVoiceSubId: Int,
+ inputDefaultSmsSubId: Int,
+ inputDefaultDateSubId: Int
+ ): Unit {
+ selectableSubscriptionInfoList = inputSelectableSubscriptionInfoList
+ defaultVoiceSubId = inputDefaultVoiceSubId
+ defaultSmsSubId = inputDefaultSmsSubId
+ defaultDataSubId = inputDefaultDateSubId
+ nonDds = if (defaultDataSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ } else {
+ selectableSubscriptionInfoList
+ .filter { info ->
+ (info.simSlotIndex != -1) && (info.subscriptionId != defaultDataSubId)
+ }
+ .map { it.subscriptionId }
+ .firstOrNull() ?: SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ }
+ }
+}
+
+@Composable
+fun PageImpl(selectableSubscriptionInfoList: List<SubscriptionInfo>,
+ defaultVoiceSubId: MutableIntState,
+ defaultSmsSubId: MutableIntState,
+ defaultDataSubId: MutableIntState,
+ nonDds: MutableIntState) {
+ val context = LocalContext.current
+ var activeSubscriptionInfoList: List<SubscriptionInfo> =
+ selectableSubscriptionInfoList.filter { subscriptionInfo ->
+ subscriptionInfo.simSlotIndex != -1
+ }
+ var subscriptionManager = context.getSystemService(SubscriptionManager::class.java)
+
+ val stringSims = stringResource(R.string.provider_network_settings_title)
+ RegularScaffold(title = stringSims) {
+ SimsSectionImpl(
+ context,
+ subscriptionManager,
+ selectableSubscriptionInfoList
+ )
+ PrimarySimSectionImpl(
+ subscriptionManager,
+ activeSubscriptionInfoList,
+ defaultVoiceSubId,
+ defaultSmsSubId,
+ defaultDataSubId,
+ nonDds
+ )
+ }
+}
+
+@Composable
+fun SimsSectionImpl(
+ context: Context,
+ subscriptionManager: SubscriptionManager?,
+ subscriptionInfoList: List<SubscriptionInfo>
+) {
+ val coroutineScope = rememberCoroutineScope()
+ for (subInfo in subscriptionInfoList) {
+ val checked = rememberSaveable() {
+ mutableStateOf(false)
+ }
+ //TODO: Add the Restricted TwoTargetSwitchPreference in SPA
+ TwoTargetSwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = subInfo.displayName.toString()
+ override val summary = { subInfo.number }
+ override val checked = {
+ coroutineScope.launch {
+ withContext(Dispatchers.Default) {
+ checked.value = subscriptionManager?.isSubscriptionEnabled(
+ subInfo.subscriptionId)?:false
+ }
+ }
+ checked.value
+ }
+ override val onCheckedChange = { newChecked: Boolean ->
+ startToggleSubscriptionDialog(context, subInfo, newChecked)
+ }
+ }
+ }) {
+ startMobileNetworkSettings(context, subInfo)
+ }
+ }
+
+ // + add sim
+ if (showEuiccSettings(context)) {
+ RestrictedPreference(
+ model = object : PreferenceModel {
+ override val title = stringResource(id = R.string.mobile_network_list_add_more)
+ override val icon = @Composable { SettingsIcon(Icons.Outlined.Add) }
+ override val onClick = {
+ startAddSimFlow(context)
+ }
+ },
+ restrictions = Restrictions(keys =
+ listOf(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)),
+ )
+ }
+}
+
+@Composable
+fun PrimarySimSectionImpl(
+ subscriptionManager: SubscriptionManager?,
+ activeSubscriptionInfoList: List<SubscriptionInfo>,
+ callsSelectedId: MutableIntState,
+ textsSelectedId: MutableIntState,
+ mobileDataSelectedId: MutableIntState,
+ nonDds: MutableIntState
+) {
+ var state = rememberSaveable { mutableStateOf(false) }
+ var callsAndSmsList = remember {
+ mutableListOf(ListPreferenceOption(id = -1, text = "Loading"))
+ }
+ var dataList = remember {
+ mutableListOf(ListPreferenceOption(id = -1, text = "Loading"))
+ }
+
+ if (activeSubscriptionInfoList.size >= 2) {
+ state.value = true
+ callsAndSmsList.clear()
+ dataList.clear()
+ for (info in activeSubscriptionInfoList) {
+ var item = ListPreferenceOption(
+ id = info.subscriptionId,
+ text = "${info.displayName}"
+ )
+ callsAndSmsList.add(item)
+ dataList.add(item)
+ }
+ callsAndSmsList.add(ListPreferenceOption(
+ id = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ text = stringResource(id = R.string.sim_calls_ask_first_prefs_title)
+ ))
+ } else {
+ // hide the primary sim
+ state.value = false
+ Log.d("NetworkCellularGroupProvider", "Hide primary sim")
+ }
+
+ if (state.value) {
+ val coroutineScope = rememberCoroutineScope()
+ var context = LocalContext.current
+ val telephonyManagerForNonDds: TelephonyManager? =
+ context.getSystemService(TelephonyManager::class.java)
+ ?.createForSubscriptionId(nonDds.intValue)
+ val automaticDataChecked = rememberSaveable() {
+ mutableStateOf(false)
+ }
+
+ Category(title = stringResource(id = R.string.primary_sim_title)) {
+ createPrimarySimListPreference(
+ stringResource(id = R.string.primary_sim_calls_title),
+ callsAndSmsList,
+ callsSelectedId,
+ ImageVector.vectorResource(R.drawable.ic_phone),
+ ) {
+ callsSelectedId.intValue = it
+ coroutineScope.launch {
+ setDefaultVoice(subscriptionManager, it)
+ }
+ }
+ createPrimarySimListPreference(
+ stringResource(id = R.string.primary_sim_texts_title),
+ callsAndSmsList,
+ textsSelectedId,
+ Icons.AutoMirrored.Outlined.Message,
+ ) {
+ textsSelectedId.intValue = it
+ coroutineScope.launch {
+ setDefaultSms(subscriptionManager, it)
+ }
+ }
+ createPrimarySimListPreference(
+ stringResource(id = R.string.mobile_data_settings_title),
+ dataList,
+ mobileDataSelectedId,
+ Icons.Outlined.DataUsage,
+ ) {
+ mobileDataSelectedId.intValue = it
+ coroutineScope.launch {
+ // TODO: to fix the WifiPickerTracker crash when create
+ // the wifiPickerTrackerHelper
+ setDefaultData(context,
+ subscriptionManager,
+ null/*wifiPickerTrackerHelper*/,
+ it)
+ }
+ }
+ }
+
+ val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title)
+ val autoDataSummary = stringResource(id = R.string.primary_sim_automatic_data_msg)
+ SwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = autoDataTitle
+ override val summary = { autoDataSummary }
+ override val changeable: () -> Boolean = {
+ nonDds.intValue != SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ }
+ override val checked = {
+ coroutineScope.launch {
+ withContext(Dispatchers.Default) {
+ automaticDataChecked.value = telephonyManagerForNonDds != null
+ && telephonyManagerForNonDds.isMobileDataPolicyEnabled(
+ TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH)
+ }
+ }
+ automaticDataChecked.value
+ }
+ override val onCheckedChange: ((Boolean) -> Unit)? =
+ { newChecked: Boolean ->
+ coroutineScope.launch {
+ setAutomaticData(telephonyManagerForNonDds, newChecked)
+ }
+ }
+ }
+ })
+ }
+}
+
+private fun Context.defaultVoiceSubscriptionFlow(): Flow<Int> =
+ merge(
+ flowOf(null), // kick an initial value
+ broadcastReceiverFlow(
+ IntentFilter(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED)
+ ),
+ ).map { SubscriptionManager.getDefaultVoiceSubscriptionId() }
+ .conflate().flowOn(Dispatchers.Default)
+
+private fun Context.defaultSmsSubscriptionFlow(): Flow<Int> =
+ merge(
+ flowOf(null), // kick an initial value
+ broadcastReceiverFlow(
+ IntentFilter(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED)
+ ),
+ ).map { SubscriptionManager.getDefaultSmsSubscriptionId() }
+ .conflate().flowOn(Dispatchers.Default)
+
+private fun Context.defaultDefaultDataSubscriptionFlow(): Flow<Int> =
+ merge(
+ flowOf(null), // kick an initial value
+ broadcastReceiverFlow(
+ IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ ),
+ ).map { SubscriptionManager.getDefaultDataSubscriptionId() }
+ .conflate().flowOn(Dispatchers.Default)
+
+private fun startToggleSubscriptionDialog(
+ context: Context,
+ subInfo: SubscriptionInfo,
+ newStatus: Boolean
+) {
+ SubscriptionUtil.startToggleSubscriptionDialogActivity(
+ context,
+ subInfo.subscriptionId,
+ newStatus
+ )
+}
+
+private fun startMobileNetworkSettings(context: Context, subInfo: SubscriptionInfo) {
+ MobileNetworkUtils.launchMobileNetworkSettings(context, subInfo)
+}
+
+private fun startAddSimFlow(context: Context) {
+ val intent = Intent(EuiccManager.ACTION_PROVISION_EMBEDDED_SUBSCRIPTION)
+ intent.putExtra(EuiccManager.EXTRA_FORCE_PROVISION, true)
+ context.startActivity(intent)
+}
+
+private fun showEuiccSettings(context: Context): Boolean {
+ return MobileNetworkUtils.showEuiccSettings(context)
+}
+
+private suspend fun setDefaultVoice(
+ subscriptionManager: SubscriptionManager?,
+ subId: Int): Unit = withContext(Dispatchers.Default) {
+ subscriptionManager?.setDefaultVoiceSubscriptionId(subId)
+}
+
+private suspend fun setDefaultSms(
+ subscriptionManager: SubscriptionManager?,
+ subId: Int): Unit = withContext(Dispatchers.Default) {
+ subscriptionManager?.setDefaultSmsSubId(subId)
+}
+
+private suspend fun setDefaultData(context: Context,
+ subscriptionManager: SubscriptionManager?,
+ wifiPickerTrackerHelper: WifiPickerTrackerHelper?,
+ subId: Int): Unit = withContext(Dispatchers.Default) {
+ subscriptionManager?.setDefaultDataSubId(subId)
+ MobileNetworkUtils.setMobileDataEnabled(
+ context,
+ subId,
+ true /* enabled */,
+ true /* disableOtherSubscriptions */)
+ if (wifiPickerTrackerHelper != null
+ && !wifiPickerTrackerHelper.isCarrierNetworkProvisionEnabled(subId)) {
+ wifiPickerTrackerHelper.setCarrierNetworkEnabled(true)
+ }
+}
+
+private suspend fun setAutomaticData(telephonyManager: TelephonyManager?, newState: Boolean): Unit =
+ withContext(Dispatchers.Default) {
+ telephonyManager?.setMobileDataPolicyEnabled(
+ TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH,
+ newState)
+ //TODO: setup backup calling
+ }
\ No newline at end of file
diff --git a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt
index 7704f84..5752a4f 100644
--- a/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt
+++ b/src/com/android/settings/spa/network/SimOnboardingPrimarySim.kt
@@ -108,21 +108,22 @@
list,
callsSelectedId,
ImageVector.vectorResource(R.drawable.ic_phone),
- true
+ onIdSelected = { callsSelectedId.intValue = it }
)
createPrimarySimListPreference(
stringResource(id = R.string.primary_sim_texts_title),
list,
textsSelectedId,
Icons.AutoMirrored.Outlined.Message,
- true
+ onIdSelected = { textsSelectedId.intValue = it }
)
+
createPrimarySimListPreference(
- stringResource(id = R.string.mobile_data_settings_title),
- list,
- mobileDataSelectedId,
+ stringResource(id = R.string.mobile_data_settings_title),
+ list,
+ mobileDataSelectedId,
Icons.Outlined.DataUsage,
- true
+ onIdSelected = { mobileDataSelectedId.intValue = it }
)
val autoDataTitle = stringResource(id = R.string.primary_sim_automatic_data_title)
@@ -140,17 +141,18 @@
@Composable
fun createPrimarySimListPreference(
- title: String,
- list: List<ListPreferenceOption>,
- selectedId: MutableIntState,
- icon: ImageVector,
- enable: Boolean
+ title: String,
+ list: List<ListPreferenceOption>,
+ selectedId: MutableIntState,
+ icon: ImageVector,
+ enable: Boolean = true,
+ onIdSelected: (id: Int) -> Unit
) = ListPreference(remember {
object : ListPreferenceModel {
override val title = title
override val options = list
override val selectedId = selectedId
- override val onIdSelected: (id: Int) -> Unit = { selectedId.intValue = it }
+ override val onIdSelected = onIdSelected
override val icon = @Composable {
SettingsIcon(icon)
}
diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java
index 29d136f..d39d980 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -35,6 +35,7 @@
import android.graphics.BlendMode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
+import android.multiuser.Flags;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -892,13 +893,24 @@
UserIcons.convertToBitmapAtUserIconSize(
activity.getResources(), newUserIcon)));
mMePreference.setIcon(newUserIcon);
+ if (Flags.avatarSync()) {
+ final String pkg = getString(R.string.config_avatar_picker_package);
+ final String action = pkg + ".set.confirm";
+ activity.sendBroadcast(new Intent(action).setPackage(pkg));
+ }
}
if (!TextUtils.isEmpty(newUserName) && !newUserName.equals(user.name)) {
mMePreference.setTitle(newUserName);
mUserManager.setUserName(user.id, newUserName);
}
- }, null);
+ }, () -> {
+ if (Flags.avatarSync()) {
+ final String pkg = getString(R.string.config_avatar_picker_package);
+ final String action = pkg + ".set.cancel";
+ activity.sendBroadcast(new Intent(action).setPackage(pkg));
+ }
+ });
}
private Dialog buildAddUserDialog(int userType) {
diff --git a/src/com/android/settings/wifi/WifiConfigController2.java b/src/com/android/settings/wifi/WifiConfigController2.java
index a885330..6a6244b 100644
--- a/src/com/android/settings/wifi/WifiConfigController2.java
+++ b/src/com/android/settings/wifi/WifiConfigController2.java
@@ -1390,7 +1390,11 @@
@VisibleForTesting
void setAnonymousIdVisible() {
- mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE);
+ View view = mView.findViewById(R.id.l_anonymous);
+ if (view.getVisibility() == View.VISIBLE) {
+ return;
+ }
+ view.setVisibility(View.VISIBLE);
mEapAnonymousView.setText(DEFAULT_ANONYMOUS_ID);
}
diff --git a/src/com/android/settings/wifi/WifiDialogActivity.java b/src/com/android/settings/wifi/WifiDialogActivity.java
index 7e901c2..eb3d88a 100644
--- a/src/com/android/settings/wifi/WifiDialogActivity.java
+++ b/src/com/android/settings/wifi/WifiDialogActivity.java
@@ -17,6 +17,7 @@
package com.android.settings.wifi;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.os.UserManager.DISALLOW_ADD_WIFI_CONFIG;
import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
import android.app.KeyguardManager;
@@ -122,7 +123,7 @@
}
super.onCreate(savedInstanceState);
- if (!isConfigWifiAllowed()) {
+ if (!isConfigWifiAllowed() || !isAddWifiConfigAllowed()) {
finish();
return;
}
@@ -393,6 +394,16 @@
return isConfigWifiAllowed;
}
+ @VisibleForTesting
+ boolean isAddWifiConfigAllowed() {
+ UserManager userManager = getSystemService(UserManager.class);
+ if (userManager != null && userManager.hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG)) {
+ Log.e(TAG, "The user is not allowed to add Wi-Fi configuration.");
+ return false;
+ }
+ return true;
+ }
+
private boolean hasWifiManager() {
if (mWifiManager != null) return true;
mWifiManager = getSystemService(WifiManager.class);
diff --git a/tests/robotests/src/com/android/settings/accounts/ChooseAccountPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/ChooseAccountPreferenceControllerTest.java
index 783eebe..59e656c 100644
--- a/tests/robotests/src/com/android/settings/accounts/ChooseAccountPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accounts/ChooseAccountPreferenceControllerTest.java
@@ -23,6 +23,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import android.accounts.AccountManager;
import android.accounts.AuthenticatorDescription;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
@@ -35,6 +36,7 @@
import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
@@ -44,33 +46,38 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class,
ShadowRestrictedLockUtilsInternal.class})
public class ChooseAccountPreferenceControllerTest {
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private Context mContext;
private ChooseAccountPreferenceController mController;
private Activity mActivity;
private PreferenceManager mPreferenceManager;
private PreferenceScreen mPreferenceScreen;
+ private ShadowAccountManager mAccountManager;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = ApplicationProvider.getApplicationContext();
mController = spy(new ChooseAccountPreferenceController(mContext, "controller_key"));
mActivity = Robolectric.setupActivity(FragmentActivity.class);
mPreferenceManager = new PreferenceManager(mContext);
mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
+ mAccountManager = (ShadowAccountManager) Shadows.shadowOf(AccountManager.get(mContext));
}
@After
@@ -108,7 +115,7 @@
final AuthenticatorDescription authDesc = new AuthenticatorDescription("com.acct1",
"com.android.settings",
R.string.header_add_an_account, 0, 0, 0, false);
- ShadowAccountManager.addAuthenticator(authDesc);
+ mAccountManager.addAuthenticator(authDesc);
final SyncAdapterType[] syncAdapters = {new SyncAdapterType("authority" /* authority */,
"com.acct1" /* accountType */, false /* userVisible */,
@@ -133,7 +140,7 @@
final AuthenticatorDescription authDesc = new AuthenticatorDescription("com.acct1",
"com.android.settings",
R.string.header_add_an_account, 0, 0, 0, false);
- ShadowAccountManager.addAuthenticator(authDesc);
+ mAccountManager.addAuthenticator(authDesc);
final SyncAdapterType[] syncAdapters = {new SyncAdapterType("authority" /* authority */,
"com.acct1" /* accountType */, false /* userVisible */,
@@ -158,7 +165,7 @@
final AuthenticatorDescription authDesc = new AuthenticatorDescription("com.acct1",
"com.android.settings",
R.string.header_add_an_account, 0, 0, 0, false);
- ShadowAccountManager.addAuthenticator(authDesc);
+ mAccountManager.addAuthenticator(authDesc);
final SyncAdapterType[] syncAdapters = {new SyncAdapterType("authority" /* authority */,
"com.acct1" /* accountType */, false /* userVisible */,
@@ -184,8 +191,8 @@
final AuthenticatorDescription authDesc2 = new AuthenticatorDescription("com.acct2",
"com.android.settings",
R.string.header_add_an_account, 0, 0, 0, false);
- ShadowAccountManager.addAuthenticator(authDesc);
- ShadowAccountManager.addAuthenticator(authDesc2);
+ mAccountManager.addAuthenticator(authDesc);
+ mAccountManager.addAuthenticator(authDesc2);
final SyncAdapterType[] syncAdapters = {new SyncAdapterType("authority" /* authority */,
"com.acct1" /* accountType */, false /* userVisible */,
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceControllerTest.java
index e351b71..005c131 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/PreviouslyConnectedDevicePreferenceControllerTest.java
@@ -27,9 +27,14 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Pair;
import androidx.preference.Preference;
@@ -42,11 +47,13 @@
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.connecteddevice.dock.DockUpdater;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.Flags;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.widget.SingleTargetGearPreference;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -70,6 +77,9 @@
private static final String FAKE_ADDRESS_4 = "AA:AA:AA:AA:AA:04";
private static final String FAKE_ADDRESS_5 = "AA:AA:AA:AA:AA:05";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock
private DashboardFragment mDashboardFragment;
@Mock
@@ -105,6 +115,9 @@
@Mock
private Drawable mDrawable;
+ @Mock private BluetoothManager mBluetoothManager;
+ @Mock private BluetoothAdapter mBluetoothAdapter;
+
private Context mContext;
private PreviouslyConnectedDevicePreferenceController mPreConnectedDeviceController;
private PreferenceGroup mPreferenceGroup;
@@ -117,10 +130,8 @@
mContext = spy(RuntimeEnvironment.application);
doReturn(mContext).when(mDashboardFragment).getContext();
doReturn(mPackageManager).when(mContext).getPackageManager();
- mPreConnectedDeviceController =
- new PreviouslyConnectedDevicePreferenceController(mContext, KEY);
- mPreConnectedDeviceController.setBluetoothDeviceUpdater(mBluetoothDeviceUpdater);
- mPreConnectedDeviceController.setSavedDockUpdater(mDockUpdater);
+ when(mContext.getSystemService(BluetoothManager.class)).thenReturn(mBluetoothManager);
+ when(mBluetoothManager.getAdapter()).thenReturn(mBluetoothAdapter);
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
when(mCachedDevice1.getDevice()).thenReturn(mBluetoothDevice1);
@@ -145,7 +156,13 @@
mMostRecentlyConnectedDevices.add(mBluetoothDevice4);
mMostRecentlyConnectedDevices.add(mBluetoothDevice3);
mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(mMostRecentlyConnectedDevices);
+ when(mBluetoothAdapter.getMostRecentlyConnectedDevices())
+ .thenReturn(mMostRecentlyConnectedDevices);
+ mPreConnectedDeviceController =
+ new PreviouslyConnectedDevicePreferenceController(mContext, KEY);
+ mPreConnectedDeviceController.setBluetoothDeviceUpdater(mBluetoothDeviceUpdater);
+ mPreConnectedDeviceController.setSavedDockUpdater(mDockUpdater);
mPreferenceGroup = spy(new PreferenceCategory(mContext));
doReturn(mPreferenceManager).when(mPreferenceGroup).getPreferenceManager();
mPreferenceGroup.setVisible(false);
@@ -249,6 +266,7 @@
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_SAVED_DEVICES_ORDER_BY_RECENCY)
public void onDeviceAdded_addPreferenceNotExistInRecentlyDevices_noCrash() {
final BluetoothDevicePreference preference = new BluetoothDevicePreference(
mContext, mCachedDevice5, true, BluetoothDevicePreference.SortType.TYPE_NO_SORT);
@@ -260,6 +278,18 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SAVED_DEVICES_ORDER_BY_RECENCY)
+ public void onDeviceAdded_addPreferenceNotExistInRecentlyDevices_doNothing() {
+ final BluetoothDevicePreference preference = new BluetoothDevicePreference(
+ mContext, mCachedDevice5, true, BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+
+ mPreConnectedDeviceController.onDeviceAdded(preference);
+
+ // 1 see all preference
+ assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+ }
+
+ @Test
public void onDeviceRemoved_removeLastDevice_showSeeAllPreference() {
final BluetoothDevicePreference preference1 = new BluetoothDevicePreference(
mContext, mCachedDevice1, true, BluetoothDevicePreference.SortType.TYPE_NO_SORT);
@@ -277,6 +307,7 @@
@Test
public void updatePreferenceVisibility_bluetoothIsEnable_shouldShowCorrectText() {
mShadowBluetoothAdapter.setEnabled(true);
+ when(mBluetoothAdapter.isEnabled()).thenReturn(true);
mPreConnectedDeviceController.updatePreferenceVisibility();
verify(mSeeAllPreference).setSummary("");
@@ -285,9 +316,78 @@
@Test
public void updatePreferenceVisibility_bluetoothIsDisable_shouldShowCorrectText() {
mShadowBluetoothAdapter.setEnabled(false);
+ when(mBluetoothAdapter.isEnabled()).thenReturn(false);
mPreConnectedDeviceController.updatePreferenceVisibility();
verify(mSeeAllPreference).setSummary(
mContext.getString(R.string.connected_device_see_all_summary));
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SAVED_DEVICES_ORDER_BY_RECENCY)
+ public void updatePreferenceGroup_bluetoothIsEnable_shouldOrderByMostRecentlyConnected() {
+ when(mBluetoothAdapter.isEnabled()).thenReturn(true);
+ final BluetoothDevicePreference preference4 =
+ new BluetoothDevicePreference(
+ mContext,
+ mCachedDevice4,
+ true,
+ BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+ final BluetoothDevicePreference preference3 =
+ new BluetoothDevicePreference(
+ mContext,
+ mCachedDevice3,
+ true,
+ BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+ final BluetoothDevicePreference preference2 =
+ new BluetoothDevicePreference(
+ mContext,
+ mCachedDevice2,
+ true,
+ BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+ mPreConnectedDeviceController.onDeviceAdded(preference4);
+ mPreConnectedDeviceController.onDeviceAdded(preference3);
+ mPreConnectedDeviceController.onDeviceAdded(preference2);
+
+ mPreConnectedDeviceController.updatePreferenceGroup();
+
+ // Refer to the order of {@link #mMostRecentlyConnectedDevices}, the first one is see all
+ // preference
+ assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(4);
+ assertThat(preference2.getOrder()).isEqualTo(0);
+ assertThat(preference4.getOrder()).isEqualTo(1);
+ assertThat(preference3.getOrder()).isEqualTo(2);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SAVED_DEVICES_ORDER_BY_RECENCY)
+ public void updatePreferenceGroup_bluetoothIsDisable_shouldShowOnlySeeAllPreference() {
+ when(mBluetoothAdapter.isEnabled()).thenReturn(false);
+ final BluetoothDevicePreference preference4 =
+ new BluetoothDevicePreference(
+ mContext,
+ mCachedDevice4,
+ true,
+ BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+ final BluetoothDevicePreference preference3 =
+ new BluetoothDevicePreference(
+ mContext,
+ mCachedDevice3,
+ true,
+ BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+ final BluetoothDevicePreference preference2 =
+ new BluetoothDevicePreference(
+ mContext,
+ mCachedDevice2,
+ true,
+ BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+ mPreConnectedDeviceController.onDeviceAdded(preference4);
+ mPreConnectedDeviceController.onDeviceAdded(preference3);
+ mPreConnectedDeviceController.onDeviceAdded(preference2);
+
+ mPreConnectedDeviceController.updatePreferenceGroup();
+
+ // 1 see all preference
+ assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/SavedDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/SavedDeviceGroupControllerTest.java
index d2c44f9..81c0c35 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/SavedDeviceGroupControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/SavedDeviceGroupControllerTest.java
@@ -25,29 +25,52 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.Pair;
import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager;
+import com.android.settings.bluetooth.BluetoothDevicePreference;
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.connecteddevice.dock.DockUpdater;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.Flags;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import java.util.ArrayList;
+import java.util.List;
+
@RunWith(RobolectricTestRunner.class)
public class SavedDeviceGroupControllerTest {
+ private static final String FAKE_ADDRESS_1 = "AA:AA:AA:AA:AA:01";
+ private static final String FAKE_ADDRESS_2 = "AA:AA:AA:AA:AA:02";
+ private static final String FAKE_ADDRESS_3 = "AA:AA:AA:AA:AA:03";
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock
private DashboardFragment mDashboardFragment;
@Mock
@@ -56,23 +79,57 @@
private DockUpdater mSavedDockUpdater;
@Mock
private PackageManager mPackageManager;
+ @Mock private BluetoothManager mBluetoothManager;
+ @Mock private BluetoothAdapter mBluetoothAdapter;
+ @Mock private CachedBluetoothDevice mCachedDevice1;
+ @Mock private CachedBluetoothDevice mCachedDevice2;
+ @Mock private CachedBluetoothDevice mCachedDevice3;
+ @Mock private BluetoothDevice mBluetoothDevice1;
+ @Mock private BluetoothDevice mBluetoothDevice2;
+ @Mock private BluetoothDevice mBluetoothDevice3;
+ @Mock private Drawable mDrawable;
+ @Mock private PreferenceManager mPreferenceManager;
private Context mContext;
private SavedDeviceGroupController mSavedDeviceGroupController;
private LifecycleOwner mLifecycleOwner;
private Lifecycle mLifecycle;
+ private PreferenceGroup mPreferenceGroup;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
mContext = spy(RuntimeEnvironment.application);
mLifecycleOwner = () -> mLifecycle;
mLifecycle = new Lifecycle(mLifecycleOwner);
doReturn(mContext).when(mDashboardFragment).getContext();
doReturn(mPackageManager).when(mContext).getPackageManager();
+
+ when(mCachedDevice1.getDevice()).thenReturn(mBluetoothDevice1);
+ when(mCachedDevice1.getAddress()).thenReturn(FAKE_ADDRESS_1);
+ when(mCachedDevice1.getDrawableWithDescription()).thenReturn(pairs);
+ when(mCachedDevice2.getDevice()).thenReturn(mBluetoothDevice2);
+ when(mCachedDevice2.getAddress()).thenReturn(FAKE_ADDRESS_2);
+ when(mCachedDevice2.getDrawableWithDescription()).thenReturn(pairs);
+ when(mCachedDevice3.getDevice()).thenReturn(mBluetoothDevice3);
+ when(mCachedDevice3.getAddress()).thenReturn(FAKE_ADDRESS_3);
+ when(mCachedDevice3.getDrawableWithDescription()).thenReturn(pairs);
+ final List<BluetoothDevice> mMostRecentlyConnectedDevices = new ArrayList<>();
+ mMostRecentlyConnectedDevices.add(mBluetoothDevice1);
+ mMostRecentlyConnectedDevices.add(mBluetoothDevice2);
+ mMostRecentlyConnectedDevices.add(mBluetoothDevice3);
+ when(mContext.getSystemService(BluetoothManager.class)).thenReturn(mBluetoothManager);
+ when(mBluetoothManager.getAdapter()).thenReturn(mBluetoothAdapter);
+ when(mBluetoothAdapter.getMostRecentlyConnectedDevices())
+ .thenReturn(mMostRecentlyConnectedDevices);
+
+ mPreferenceGroup = spy(new PreferenceCategory(mContext));
+ when(mPreferenceGroup.getPreferenceManager()).thenReturn(mPreferenceManager);
mSavedDeviceGroupController = new SavedDeviceGroupController(mContext);
mSavedDeviceGroupController.setBluetoothDeviceUpdater(mBluetoothDeviceUpdater);
mSavedDeviceGroupController.setSavedDockUpdater(mSavedDockUpdater);
+ mSavedDeviceGroupController.setPreferenceGroup(mPreferenceGroup);
}
@Test
@@ -118,4 +175,71 @@
assertThat(mSavedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
AVAILABLE);
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SAVED_DEVICES_ORDER_BY_RECENCY)
+ public void updatePreferenceGroup_bluetoothIsEnable_shouldOrderByMostRecentlyConnected() {
+ when(mBluetoothAdapter.isEnabled()).thenReturn(true);
+ final BluetoothDevicePreference preference3 =
+ new BluetoothDevicePreference(
+ mContext,
+ mCachedDevice3,
+ true,
+ BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+ final BluetoothDevicePreference preference2 =
+ new BluetoothDevicePreference(
+ mContext,
+ mCachedDevice2,
+ true,
+ BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+ final BluetoothDevicePreference preference1 =
+ new BluetoothDevicePreference(
+ mContext,
+ mCachedDevice1,
+ true,
+ BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+ mSavedDeviceGroupController.onDeviceAdded(preference3);
+ mSavedDeviceGroupController.onDeviceAdded(preference2);
+ mSavedDeviceGroupController.onDeviceAdded(preference1);
+
+ mSavedDeviceGroupController.updatePreferenceGroup();
+
+ // Refer to the order of {@link #mMostRecentlyConnectedDevices}
+ assertThat(mPreferenceGroup.isVisible()).isTrue();
+ assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(3);
+ assertThat(preference1.getOrder()).isEqualTo(0);
+ assertThat(preference2.getOrder()).isEqualTo(1);
+ assertThat(preference3.getOrder()).isEqualTo(2);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SAVED_DEVICES_ORDER_BY_RECENCY)
+ public void updatePreferenceGroup_bluetoothIsDisable_shouldShowNoPreference() {
+ when(mBluetoothAdapter.isEnabled()).thenReturn(false);
+ final BluetoothDevicePreference preference3 =
+ new BluetoothDevicePreference(
+ mContext,
+ mCachedDevice3,
+ true,
+ BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+ final BluetoothDevicePreference preference2 =
+ new BluetoothDevicePreference(
+ mContext,
+ mCachedDevice2,
+ true,
+ BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+ final BluetoothDevicePreference preference1 =
+ new BluetoothDevicePreference(
+ mContext,
+ mCachedDevice2,
+ true,
+ BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+ mSavedDeviceGroupController.onDeviceAdded(preference3);
+ mSavedDeviceGroupController.onDeviceAdded(preference2);
+ mSavedDeviceGroupController.onDeviceAdded(preference1);
+
+ mSavedDeviceGroupController.updatePreferenceGroup();
+
+ assertThat(mPreferenceGroup.isVisible()).isFalse();
+ }
}
diff --git a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationFragmentTest.java b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationFragmentTest.java
index e30759a..22fec8f 100644
--- a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectLocationFragmentTest.java
@@ -16,27 +16,58 @@
package com.android.settings.dashboard.profileselector;
+import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
+import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
+
import static com.android.settings.dashboard.profileselector.ProfileSelectFragment.EXTRA_PROFILE;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.UserInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.testutils.shadow.ShadowUserManager;
+
import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
-@Ignore("b/313569889")
+@Config(shadows = {
+ ShadowUserManager.class,
+})
@RunWith(RobolectricTestRunner.class)
public class ProfileSelectLocationFragmentTest {
+ private static final String PERSONAL_PROFILE_NAME = "personal";
+ private static final String WORK_PROFILE_NAME = "work";
+ private static final String PRIVATE_PROFILE_NAME = "private";
+ @Rule
+ public final MockitoRule rule = MockitoJUnit.rule();
+ private ShadowUserManager mUserManager;
private ProfileSelectLocationFragment mProfileSelectLocationFragment;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
- mProfileSelectLocationFragment = new ProfileSelectLocationFragment();
+ mUserManager = ShadowUserManager.getShadow();
+ mUserManager.addProfile(
+ new UserInfo(0, PERSONAL_PROFILE_NAME, null, 0, USER_TYPE_FULL_SYSTEM));
+ mUserManager.addProfile(
+ new UserInfo(1, WORK_PROFILE_NAME, null, 0, USER_TYPE_PROFILE_MANAGED));
+ mUserManager.addProfile(
+ new UserInfo(11, PRIVATE_PROFILE_NAME, null, 0, USER_TYPE_PROFILE_PRIVATE));
+ mProfileSelectLocationFragment = spy(new ProfileSelectLocationFragment());
+ when(mProfileSelectLocationFragment.getContext()).thenReturn(
+ ApplicationProvider.getApplicationContext());
}
@Test
@@ -46,7 +77,7 @@
EXTRA_PROFILE, -1)).isEqualTo(ProfileSelectFragment.ProfileType.PERSONAL);
assertThat(mProfileSelectLocationFragment.getFragments()[1].getArguments().getInt(
EXTRA_PROFILE, -1)).isEqualTo(ProfileSelectFragment.ProfileType.WORK);
- assertThat(mProfileSelectLocationFragment.getFragments()[1].getArguments().getInt(
+ assertThat(mProfileSelectLocationFragment.getFragments()[2].getArguments().getInt(
EXTRA_PROFILE, -1)).isEqualTo(ProfileSelectFragment.ProfileType.PRIVATE);
}
}
diff --git a/tests/robotests/src/com/android/settings/development/compat/PlatformCompatDashboardTest.java b/tests/robotests/src/com/android/settings/development/compat/PlatformCompatDashboardTest.java
index 5a679ad..86ceadc 100644
--- a/tests/robotests/src/com/android/settings/development/compat/PlatformCompatDashboardTest.java
+++ b/tests/robotests/src/com/android/settings/development/compat/PlatformCompatDashboardTest.java
@@ -41,7 +41,8 @@
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
-import androidx.preference.SwitchPreference;
+import androidx.preference.SwitchPreferenceCompat;
+import androidx.test.core.app.ApplicationProvider;
import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.compat.CompatibilityChangeInfo;
@@ -51,13 +52,13 @@
import com.android.settings.R;
import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Arrays;
@@ -67,6 +68,8 @@
@RunWith(RobolectricTestRunner.class)
public class PlatformCompatDashboardTest {
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private PlatformCompatDashboard mDashboard;
@Mock
@@ -87,7 +90,6 @@
@Before
public void setUp() throws RemoteException, NameNotFoundException {
- MockitoAnnotations.initMocks(this);
mChanges = new CompatibilityChangeInfo[5];
mChanges[0] = new CompatibilityChangeInfo(
1L, "Default_Enabled", 0, 0, false, false, "", false);
@@ -104,7 +106,7 @@
// By default, allow any change
when(mOverrideValidator.getOverrideAllowedState(anyLong(),anyString()))
.thenReturn(new OverrideAllowedState(ALLOWED, -1, -1));
- mContext = spy(RuntimeEnvironment.application);
+ mContext = spy(ApplicationProvider.getApplicationContext());
mPreferenceManager = new PreferenceManager(mContext);
mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
mApplicationInfo.packageName = APP_NAME;
@@ -141,7 +143,6 @@
R.string.platform_compat_selected_app_summary, APP_NAME, 1));
}
- @Ignore("b/313591873")
@Test
public void createPreferenceForChange_defaultEnabledChange_createCheckedEntry() {
CompatibilityChangeInfo enabledChange = mChanges[0];
@@ -152,15 +153,14 @@
Preference enabledPreference = mDashboard.createPreferenceForChange(mContext, enabledChange,
config);
- SwitchPreference enabledSwitchPreference = (SwitchPreference) enabledPreference;
+ SwitchPreferenceCompat enabledSwitchPreference = (SwitchPreferenceCompat) enabledPreference;
assertThat(enabledPreference.getSummary()).isEqualTo(mChanges[0].getName());
- assertThat(enabledPreference instanceof SwitchPreference).isTrue();
+ assertThat(enabledPreference instanceof SwitchPreferenceCompat).isTrue();
assertThat(enabledSwitchPreference.isChecked()).isTrue();
assertThat(enabledSwitchPreference.isEnabled()).isTrue();
}
- @Ignore("b/313591873")
@Test
public void createPreferenceForChange_defaultDisabledChange_createUncheckedEntry() {
CompatibilityChangeInfo disabledChange = mChanges[1];
@@ -172,12 +172,12 @@
disabledChange, config);
assertThat(disabledPreference.getSummary()).isEqualTo(mChanges[1].getName());
- SwitchPreference disabledSwitchPreference = (SwitchPreference) disabledPreference;
+ SwitchPreferenceCompat disabledSwitchPreference =
+ (SwitchPreferenceCompat) disabledPreference;
assertThat(disabledSwitchPreference.isChecked()).isFalse();
assertThat(disabledSwitchPreference.isEnabled()).isTrue();
}
- @Ignore("b/313591873")
@Test
public void createPreferenceForChange_cannotOverride_createDisabledEntry()
throws RemoteException {
@@ -191,15 +191,14 @@
Preference preference = mDashboard.createPreferenceForChange(mContext, enabledChange,
config);
- SwitchPreference switchPreference = (SwitchPreference) preference;
+ SwitchPreferenceCompat switchPreference = (SwitchPreferenceCompat) preference;
assertThat(preference.getSummary()).isEqualTo(mChanges[0].getName());
- assertThat(preference instanceof SwitchPreference).isTrue();
+ assertThat(preference instanceof SwitchPreferenceCompat).isTrue();
assertThat(switchPreference.isChecked()).isTrue();
assertThat(switchPreference.isEnabled()).isFalse();
}
- @Ignore("b/313591873")
@Test
public void createChangeCategoryPreference_enabledAndDisabled_hasTitleAndEntries() {
Set<Long> enabledChanges = new HashSet<>();
@@ -226,7 +225,7 @@
assertThat(category.getPreferenceCount()).isEqualTo(mChanges.length);
for (int i = 0; i < mChanges.length; ++i) {
Preference childPreference = category.getPreference(i);
- assertThat(childPreference instanceof SwitchPreference).isTrue();
+ assertThat(childPreference instanceof SwitchPreferenceCompat).isTrue();
}
}
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
index 5613c4e..bfe5d14 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
@@ -52,7 +52,6 @@
private static final int[] TIP_ORDER = {
BatteryTip.TipType.LOW_BATTERY,
BatteryTip.TipType.BATTERY_DEFENDER,
- BatteryTip.TipType.DOCK_DEFENDER,
BatteryTip.TipType.INCOMPATIBLE_CHARGER,
BatteryTip.TipType.HIGH_DEVICE_USAGE
};
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/DockDefenderDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/DockDefenderDetectorTest.java
deleted file mode 100644
index b90733d..0000000
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/DockDefenderDetectorTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.fuelgauge.batterytip.detectors;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.refEq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.provider.Settings;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import com.android.settings.fuelgauge.BatteryInfo;
-import com.android.settings.fuelgauge.BatteryUtils;
-import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
-import com.android.settings.fuelgauge.batterytip.tips.DockDefenderTip;
-import com.android.settings.testutils.BatteryTestUtils;
-import com.android.settings.testutils.FakeFeatureFactory;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-
-@RunWith(RobolectricTestRunner.class)
-public class DockDefenderDetectorTest {
-
- private BatteryInfo mBatteryInfo;
- private DockDefenderDetector mDockDefenderDetector;
- private Context mContext;
- private FakeFeatureFactory mFakeFeatureFactory;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = spy(ApplicationProvider.getApplicationContext());
- mBatteryInfo = new BatteryInfo();
- mBatteryInfo.pluggedStatus = BatteryManager.BATTERY_PLUGGED_DOCK;
- mDockDefenderDetector = new DockDefenderDetector(mBatteryInfo, mContext);
- Intent intent =
- BatteryTestUtils.getCustomBatteryIntent(
- BatteryManager.BATTERY_PLUGGED_DOCK,
- 50 /* level */,
- 100 /* scale */,
- BatteryManager.BATTERY_STATUS_CHARGING);
- doReturn(intent)
- .when(mContext)
- .registerReceiver(eq(null), refEq(new IntentFilter(Intent.ACTION_BATTERY_CHANGED)));
-
- Settings.Global.putInt(
- mContext.getContentResolver(),
- BatteryUtils.SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS,
- 0);
- mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
- }
-
- @Test
- public void testDetect_dockDefenderTemporarilyBypassed() {
- Settings.Global.putInt(
- mContext.getContentResolver(),
- BatteryUtils.SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS,
- 1);
-
- BatteryTip batteryTip = mDockDefenderDetector.detect();
-
- assertTrue(batteryTip instanceof DockDefenderTip);
- assertEquals(
- ((DockDefenderTip) batteryTip).getMode(),
- BatteryUtils.DockDefenderMode.TEMPORARILY_BYPASSED);
- }
-
- @Test
- public void testDetect_dockDefenderActive() {
- mBatteryInfo.isBatteryDefender = true;
- doReturn(true).when(mFakeFeatureFactory.powerUsageFeatureProvider).isExtraDefend();
-
- BatteryTip batteryTip = mDockDefenderDetector.detect();
-
- assertTrue(batteryTip instanceof DockDefenderTip);
- assertEquals(
- ((DockDefenderTip) batteryTip).getMode(), BatteryUtils.DockDefenderMode.ACTIVE);
- }
-
- @Test
- public void testDetect_dockDefenderFutureBypass() {
- mBatteryInfo.isBatteryDefender = false;
- doReturn(false).when(mFakeFeatureFactory.powerUsageFeatureProvider).isExtraDefend();
-
- BatteryTip batteryTip = mDockDefenderDetector.detect();
-
- assertTrue(batteryTip instanceof DockDefenderTip);
- assertEquals(
- ((DockDefenderTip) batteryTip).getMode(),
- BatteryUtils.DockDefenderMode.FUTURE_BYPASS);
- }
-
- @Test
- public void testDetect_overheatedTrue_dockDefenderDisabled() {
- mBatteryInfo.isBatteryDefender = true;
- doReturn(false).when(mFakeFeatureFactory.powerUsageFeatureProvider).isExtraDefend();
-
- BatteryTip batteryTip = mDockDefenderDetector.detect();
-
- assertTrue(batteryTip instanceof DockDefenderTip);
- assertEquals(
- ((DockDefenderTip) batteryTip).getMode(), BatteryUtils.DockDefenderMode.DISABLED);
- }
-
- @Test
- public void testDetect_pluggedInAC_dockDefenderDisabled() {
- mBatteryInfo.pluggedStatus = BatteryManager.BATTERY_PLUGGED_AC;
-
- BatteryTip batteryTip = mDockDefenderDetector.detect();
-
- assertTrue(batteryTip instanceof DockDefenderTip);
- assertEquals(
- ((DockDefenderTip) batteryTip).getMode(), BatteryUtils.DockDefenderMode.DISABLED);
- }
-
- @Test
- public void testDetect_overheatedTrueAndDockDefenderNotTriggered_dockDefenderDisabled() {
- doReturn(false).when(mFakeFeatureFactory.powerUsageFeatureProvider).isExtraDefend();
- mBatteryInfo.isBatteryDefender = true;
-
- BatteryTip batteryTip = mDockDefenderDetector.detect();
-
- assertTrue(batteryTip instanceof DockDefenderTip);
- assertEquals(
- ((DockDefenderTip) batteryTip).getMode(), BatteryUtils.DockDefenderMode.DISABLED);
- }
-}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/DockDefenderTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/DockDefenderTipTest.java
deleted file mode 100644
index 6d6c2e0..0000000
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/DockDefenderTipTest.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.fuelgauge.batterytip.tips;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.util.Log;
-
-import androidx.preference.Preference;
-import androidx.test.core.app.ApplicationProvider;
-
-import com.android.settings.R;
-import com.android.settings.fuelgauge.BatteryUtils;
-import com.android.settings.testutils.FakeFeatureFactory;
-import com.android.settings.widget.CardPreference;
-import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-
-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.shadows.ShadowLog;
-
-@RunWith(RobolectricTestRunner.class)
-public class DockDefenderTipTest {
- private Context mContext;
- private DockDefenderTip mDockDefenderTipFutureBypass;
- private DockDefenderTip mDockDefenderTipActive;
- private DockDefenderTip mDockDefenderTipTemporarilyBypassed;
- private DockDefenderTip mDockDefenderTipDisabled;
- private FakeFeatureFactory mFeatureFactory;
- private MetricsFeatureProvider mMetricsFeatureProvider;
-
- @Mock private Preference mPreference;
- @Mock private CardPreference mCardPreference;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mContext = ApplicationProvider.getApplicationContext();
- mFeatureFactory = FakeFeatureFactory.setupForTest();
- mMetricsFeatureProvider = mFeatureFactory.metricsFeatureProvider;
-
- mDockDefenderTipFutureBypass =
- new DockDefenderTip(
- BatteryTip.StateType.NEW, BatteryUtils.DockDefenderMode.FUTURE_BYPASS);
- mDockDefenderTipActive =
- new DockDefenderTip(BatteryTip.StateType.NEW, BatteryUtils.DockDefenderMode.ACTIVE);
- mDockDefenderTipTemporarilyBypassed =
- new DockDefenderTip(
- BatteryTip.StateType.NEW,
- BatteryUtils.DockDefenderMode.TEMPORARILY_BYPASSED);
- mDockDefenderTipDisabled =
- new DockDefenderTip(
- BatteryTip.StateType.INVISIBLE, BatteryUtils.DockDefenderMode.DISABLED);
-
- doReturn(mContext).when(mPreference).getContext();
- doReturn(mContext).when(mCardPreference).getContext();
- }
-
- @Test
- public void testGetTitle() {
- assertThat(mDockDefenderTipFutureBypass.getTitle(mContext).toString())
- .isEqualTo(
- mContext.getString(R.string.battery_tip_dock_defender_future_bypass_title));
- assertThat(mDockDefenderTipActive.getTitle(mContext).toString())
- .isEqualTo(mContext.getString(R.string.battery_tip_dock_defender_active_title));
- assertThat(mDockDefenderTipTemporarilyBypassed.getTitle(mContext).toString())
- .isEqualTo(
- mContext.getString(
- R.string.battery_tip_dock_defender_temporarily_bypassed_title));
- assertThat(mDockDefenderTipDisabled.getTitle(mContext)).isNull();
- }
-
- @Test
- public void testGetSummary() {
- assertThat(mDockDefenderTipFutureBypass.getSummary(mContext).toString())
- .isEqualTo(
- mContext.getString(
- R.string.battery_tip_dock_defender_future_bypass_summary));
- assertThat(mDockDefenderTipActive.getSummary(mContext).toString())
- .isEqualTo(mContext.getString(R.string.battery_tip_dock_defender_active_summary));
- assertThat(mDockDefenderTipTemporarilyBypassed.getSummary(mContext).toString())
- .isEqualTo(
- mContext.getString(
- R.string.battery_tip_dock_defender_temporarily_bypassed_summary));
- assertThat(mDockDefenderTipDisabled.getSummary(mContext)).isNull();
- }
-
- @Test
- public void testGetIconId_dockDefenderActive_getProtectedIcon() {
- assertThat(mDockDefenderTipActive.getIconId())
- .isEqualTo(R.drawable.ic_battery_status_protected_24dp);
- }
-
- @Test
- public void testGetIconId_dockDefenderNotActive_getUntriggeredIcon() {
- assertThat(mDockDefenderTipFutureBypass.getIconId())
- .isEqualTo(R.drawable.ic_battery_dock_defender_untriggered_24dp);
- assertThat(mDockDefenderTipTemporarilyBypassed.getIconId())
- .isEqualTo(R.drawable.ic_battery_dock_defender_untriggered_24dp);
- assertThat(mDockDefenderTipDisabled.getIconId())
- .isEqualTo(R.drawable.ic_battery_dock_defender_untriggered_24dp);
- }
-
- @Test
- public void testUpdateState() {
- mDockDefenderTipTemporarilyBypassed.updateState(mDockDefenderTipDisabled);
-
- assertThat(mDockDefenderTipTemporarilyBypassed.getState())
- .isEqualTo(BatteryTip.StateType.INVISIBLE);
- assertThat(mDockDefenderTipTemporarilyBypassed.getMode())
- .isEqualTo(BatteryUtils.DockDefenderMode.DISABLED);
- }
-
- @Test
- public void testLog() {
- mDockDefenderTipActive.log(mContext, mMetricsFeatureProvider);
-
- verify(mMetricsFeatureProvider)
- .action(
- mContext,
- SettingsEnums.ACTION_DOCK_DEFENDER_TIP,
- mDockDefenderTipActive.getState());
- }
-
- @Test
- public void testUpdatePreference_dockDefenderTipFutureBypass() {
- mDockDefenderTipFutureBypass.updatePreference(mCardPreference);
-
- verify(mCardPreference).setPrimaryButtonVisible(true);
- verify(mCardPreference)
- .setPrimaryButtonText(
- mContext.getString(R.string.battery_tip_charge_to_full_button));
- verifySecondaryButton();
- }
-
- @Test
- public void testUpdatePreference_dockDefenderTipActive() {
- mDockDefenderTipActive.updatePreference(mCardPreference);
-
- verify(mCardPreference).setPrimaryButtonVisible(true);
- verify(mCardPreference)
- .setPrimaryButtonText(
- mContext.getString(R.string.battery_tip_charge_to_full_button));
- verifySecondaryButton();
- }
-
- @Test
- public void testUpdatePreference_dockDefenderTipTemporarilyBypassed() {
- mDockDefenderTipTemporarilyBypassed.updatePreference(mCardPreference);
-
- verify(mCardPreference).setPrimaryButtonVisible(false);
- verify(mCardPreference, never()).setPrimaryButtonText(any());
- verifySecondaryButton();
- }
-
- private void verifySecondaryButton() {
- verify(mCardPreference).setSecondaryButtonText(mContext.getString(R.string.learn_more));
- verify(mCardPreference).setSecondaryButtonVisible(true);
- final String expectedContent =
- mContext.getString(
- R.string.battery_tip_limited_temporarily_sec_button_content_description);
- verify(mCardPreference).setSecondaryButtonContentDescription(expectedContent);
- }
-
- @Test
- public void updatePreference_castFail_logErrorMessage() {
- mDockDefenderTipActive.updatePreference(mPreference);
-
- assertThat(getLastErrorLog()).isEqualTo("cast Preference to CardPreference failed");
- }
-
- private String getLastErrorLog() {
- return ShadowLog.getLogsForTag(DockDefenderTip.class.getSimpleName()).stream()
- .filter(log -> log.type == Log.ERROR)
- .reduce((first, second) -> second)
- .orElse(createErrorLog("No Error Log"))
- .msg;
- }
-
- private ShadowLog.LogItem createErrorLog(String msg) {
- return new ShadowLog.LogItem(Log.ERROR, "tag", msg, null);
- }
-}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java
index 1d7ab66..c1d039b 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/IncompatibleChargerTipTest.java
@@ -79,7 +79,8 @@
@Test
public void getIcon_showIcon() {
- assertThat(mIncompatibleChargerTip.getIconId()).isEqualTo(R.drawable.ic_battery_charger);
+ assertThat(mIncompatibleChargerTip.getIconId())
+ .isEqualTo(R.drawable.ic_battery_incompatible_charger);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
index bdf81e4..b0bf241 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsCardPreferenceTest.java
@@ -120,12 +120,19 @@
assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, -1))
.isEqualTo(SettingsEnums.DISPLAY);
verify(mFeatureFactory.metricsFeatureProvider)
- .action(mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "BrightnessAnomaly");
+ .action(
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW,
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ "BrightnessAnomaly",
+ PowerAnomalyKey.KEY_BRIGHTNESS.getNumber());
verify(mFeatureFactory.metricsFeatureProvider)
.action(
- mContext,
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT,
- "BrightnessAnomaly");
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ "BrightnessAnomaly",
+ PowerAnomalyKey.KEY_BRIGHTNESS.getNumber());
}
@Test
@@ -146,14 +153,18 @@
.contains(PowerAnomalyKey.KEY_SCREEN_TIMEOUT.name());
verify(mFeatureFactory.metricsFeatureProvider)
.action(
- mContext,
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW,
- "ScreenTimeoutAnomaly");
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ "ScreenTimeoutAnomaly",
+ PowerAnomalyKey.KEY_SCREEN_TIMEOUT.getNumber());
verify(mFeatureFactory.metricsFeatureProvider)
.action(
- mContext,
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
SettingsEnums.ACTION_BATTERY_TIPS_CARD_DISMISS,
- "ScreenTimeoutAnomaly");
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ "ScreenTimeoutAnomaly",
+ PowerAnomalyKey.KEY_SCREEN_TIMEOUT.getNumber());
}
@Ignore("b/313582999")
@@ -173,9 +184,19 @@
verify(mBatteryChartPreferenceController).onHighlightSlotIndexUpdate(eq(1), eq(0));
verify(mBatteryChartPreferenceController).selectHighlightSlotIndex();
verify(mFeatureFactory.metricsFeatureProvider)
- .action(mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "AppAnomaly");
+ .action(
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW,
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ "AppAnomaly",
+ PowerAnomalyKey.KEY_APP_TOTAL_HIGHER_THAN_USUAL.getNumber());
verify(mFeatureFactory.metricsFeatureProvider)
- .action(mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT, "AppAnomaly");
+ .action(
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ SettingsEnums.ACTION_BATTERY_TIPS_CARD_ACCEPT,
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ "AppAnomaly",
+ PowerAnomalyKey.KEY_APP_TOTAL_HIGHER_THAN_USUAL.getNumber());
}
@Ignore("b/313582999")
@@ -194,8 +215,18 @@
verify(mBatteryChartPreferenceController).onHighlightSlotIndexUpdate(eq(1), eq(0));
verify(mBatteryChartPreferenceController, never()).selectHighlightSlotIndex();
verify(mFeatureFactory.metricsFeatureProvider)
- .action(mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "AppAnomaly");
+ .action(
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW,
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ "AppAnomaly",
+ PowerAnomalyKey.KEY_APP_TOTAL_HIGHER_THAN_USUAL.getNumber());
verify(mFeatureFactory.metricsFeatureProvider)
- .action(mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_DISMISS, "AppAnomaly");
+ .action(
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ SettingsEnums.ACTION_BATTERY_TIPS_CARD_DISMISS,
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ "AppAnomaly",
+ PowerAnomalyKey.KEY_APP_TOTAL_HIGHER_THAN_USUAL.getNumber());
}
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java
index e7c8e81..879a5c1 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryTipsControllerTest.java
@@ -90,7 +90,12 @@
// Check proto info
verify(mBatteryTipsCardPreference).setVisible(true);
verify(mFeatureFactory.metricsFeatureProvider)
- .action(mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "BrightnessAnomaly");
+ .action(
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW,
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ "BrightnessAnomaly",
+ PowerAnomalyKey.KEY_BRIGHTNESS.getNumber());
}
@Test
@@ -109,9 +114,11 @@
verify(mBatteryTipsCardPreference).setVisible(true);
verify(mFeatureFactory.metricsFeatureProvider)
.action(
- mContext,
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW,
- "ScreenTimeoutAnomaly");
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ "ScreenTimeoutAnomaly",
+ PowerAnomalyKey.KEY_SCREEN_TIMEOUT.getNumber());
}
@Test
@@ -138,9 +145,11 @@
verify(mBatteryTipsCardPreference).setVisible(true);
verify(mFeatureFactory.metricsFeatureProvider)
.action(
- mContext,
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW,
- "ScreenTimeoutAnomaly");
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ "ScreenTimeoutAnomaly",
+ PowerAnomalyKey.KEY_SCREEN_TIMEOUT.getNumber());
}
@Test
@@ -161,6 +170,11 @@
verify(mBatteryTipsCardPreference).setDismissButtonLabel("Got it");
verify(mBatteryTipsCardPreference).setVisible(true);
verify(mFeatureFactory.metricsFeatureProvider)
- .action(mContext, SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW, "AppAnomaly");
+ .action(
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ SettingsEnums.ACTION_BATTERY_TIPS_CARD_SHOW,
+ SettingsEnums.FUELGAUGE_BATTERY_HISTORY_DETAIL,
+ "AppAnomaly",
+ PowerAnomalyKey.KEY_APP_TOTAL_HIGHER_THAN_USUAL.getNumber());
}
}
diff --git a/tests/robotests/src/com/android/settings/network/telephony/MobileDataSliceTest.java b/tests/robotests/src/com/android/settings/network/telephony/MobileDataSliceTest.java
index ba37c30..8445fe2 100644
--- a/tests/robotests/src/com/android/settings/network/telephony/MobileDataSliceTest.java
+++ b/tests/robotests/src/com/android/settings/network/telephony/MobileDataSliceTest.java
@@ -114,7 +114,8 @@
mMobileDataSlice.onNotifyChange(intent);
- verify(mTelephonyManager).setDataEnabled(true);
+ verify(mTelephonyManager)
+ .setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER, true);
}
@Test
@@ -126,7 +127,8 @@
mMobileDataSlice.onNotifyChange(intent);
- verify(mTelephonyManager).setDataEnabled(false);
+ verify(mTelephonyManager)
+ .setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER, false);
}
@Test
@@ -138,7 +140,8 @@
mMobileDataSlice.onNotifyChange(intent);
- verify(mTelephonyManager, times(0)).setDataEnabled(true);
+ verify(mTelephonyManager, times(0))
+ .setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER, true);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeBackendTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeBackendTest.java
index 247d68e..32bf9af 100644
--- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeBackendTest.java
+++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeBackendTest.java
@@ -16,9 +16,9 @@
import static junit.framework.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,8 +30,6 @@
import android.provider.Settings;
import android.service.notification.ZenModeConfig;
-import com.android.settings.R;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -204,7 +202,11 @@
mBackend.saveConversationSenders(CONVERSATION_SENDERS_NONE);
ArgumentCaptor<Policy> captor = ArgumentCaptor.forClass(Policy.class);
- verify(mNotificationManager, times(1)).setNotificationPolicy(captor.capture());
+ if (android.app.Flags.modesApi()) {
+ verify(mNotificationManager).setNotificationPolicy(captor.capture(), eq(true));
+ } else {
+ verify(mNotificationManager).setNotificationPolicy(captor.capture());
+ }
Policy expected = new Policy(
PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_ALARMS,
@@ -228,7 +230,11 @@
mBackend.saveConversationSenders(CONVERSATION_SENDERS_ANYONE);
ArgumentCaptor<Policy> captor = ArgumentCaptor.forClass(Policy.class);
- verify(mNotificationManager, times(1)).setNotificationPolicy(captor.capture());
+ if (android.app.Flags.modesApi()) {
+ verify(mNotificationManager).setNotificationPolicy(captor.capture(), eq(true));
+ } else {
+ verify(mNotificationManager).setNotificationPolicy(captor.capture());
+ }
Policy expected = new Policy(PRIORITY_CATEGORY_CONVERSATIONS
| PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_ALARMS,
@@ -253,7 +259,11 @@
mBackend.saveSenders(PRIORITY_CATEGORY_CALLS, PRIORITY_SENDERS_ANY);
ArgumentCaptor<Policy> captor = ArgumentCaptor.forClass(Policy.class);
- verify(mNotificationManager, times(1)).setNotificationPolicy(captor.capture());
+ if (android.app.Flags.modesApi()) {
+ verify(mNotificationManager).setNotificationPolicy(captor.capture(), eq(true));
+ } else {
+ verify(mNotificationManager).setNotificationPolicy(captor.capture());
+ }
Policy expected = new Policy(PRIORITY_CATEGORY_CONVERSATIONS
| PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_ALARMS,
diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModeSliceBuilderTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModeSliceBuilderTest.java
index 71f896d..3f9e486 100644
--- a/tests/robotests/src/com/android/settings/notification/zen/ZenModeSliceBuilderTest.java
+++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModeSliceBuilderTest.java
@@ -17,13 +17,18 @@
package com.android.settings.notification.zen;
import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;
+import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.provider.Settings.Global.ZEN_MODE_OFF;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
-import android.provider.Settings;
import androidx.slice.Slice;
import androidx.slice.SliceMetadata;
@@ -39,9 +44,12 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
import java.util.List;
@@ -51,10 +59,17 @@
private Context mContext;
+ @Mock
+ private NotificationManager mNm;
+
@Before
public void setUp() {
mContext = RuntimeEnvironment.application;
+ MockitoAnnotations.initMocks(this);
+ ShadowApplication shadowApplication = ShadowApplication.getInstance();
+ shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm);
+
// Set-up specs for SliceMetadata.
SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
}
@@ -99,24 +114,27 @@
public void handleUriChange_turnOn_zenModeTurnsOn() {
final Intent intent = new Intent();
intent.putExtra(EXTRA_TOGGLE_STATE, true);
- NotificationManager.from(mContext).setZenMode(Settings.Global.ZEN_MODE_OFF, null, "");
ZenModeSliceBuilder.handleUriChange(mContext, intent);
- final int zenMode = NotificationManager.from(mContext).getZenMode();
- assertThat(zenMode).isEqualTo(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ if (android.app.Flags.modesApi()) {
+ verify(mNm).setZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), any(), any(), eq(true));
+ } else {
+ verify(mNm).setZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), any(), any());
+ }
}
@Test
public void handleUriChange_turnOff_zenModeTurnsOff() {
final Intent intent = new Intent();
intent.putExtra(EXTRA_TOGGLE_STATE, false);
- NotificationManager.from(mContext).setZenMode(
- Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, "");
ZenModeSliceBuilder.handleUriChange(mContext, intent);
- final int zenMode = NotificationManager.from(mContext).getZenMode();
- assertThat(zenMode).isEqualTo(Settings.Global.ZEN_MODE_OFF);
+ if (android.app.Flags.modesApi()) {
+ verify(mNm).setZenMode(eq(ZEN_MODE_OFF), any(), any(), eq(true));
+ } else {
+ verify(mNm).setZenMode(eq(ZEN_MODE_OFF), any(), any());
+ }
}
}
diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenOnboardingActivityTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenOnboardingActivityTest.java
index 257eaaf..01360fd 100644
--- a/tests/robotests/src/com/android/settings/notification/zen/ZenOnboardingActivityTest.java
+++ b/tests/robotests/src/com/android/settings/notification/zen/ZenOnboardingActivityTest.java
@@ -21,17 +21,19 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
import static com.android.settings.notification.zen.ZenOnboardingActivity.ALWAYS_SHOW_THRESHOLD;
-import static com.android.settings.notification.zen.ZenOnboardingActivity
- .PREF_KEY_SUGGESTION_FIRST_DISPLAY_TIME;
+import static com.android.settings.notification.zen.ZenOnboardingActivity.PREF_KEY_SUGGESTION_FIRST_DISPLAY_TIME;
import static com.android.settings.notification.zen.ZenOnboardingActivity.isSuggestionComplete;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Flags;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.content.Context;
@@ -40,7 +42,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settings.notification.zen.ZenOnboardingActivity;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
@@ -103,7 +104,11 @@
verify(mMetricsLogger).action(MetricsEvent.ACTION_ZEN_ONBOARDING_OK);
ArgumentCaptor<Policy> captor = ArgumentCaptor.forClass(Policy.class);
- verify(mNm).setNotificationPolicy(captor.capture());
+ if (android.app.Flags.modesApi()) {
+ verify(mNm).setNotificationPolicy(captor.capture(), eq(true));
+ } else {
+ verify(mNm).setNotificationPolicy(captor.capture());
+ }
Policy actual = captor.getValue();
assertThat(actual.priorityCategories).isEqualTo(PRIORITY_CATEGORY_ALARMS
@@ -123,7 +128,11 @@
mActivity.save(null);
verify(mMetricsLogger).action(MetricsEvent.ACTION_ZEN_ONBOARDING_KEEP_CURRENT_SETTINGS);
- verify(mNm, never()).setNotificationPolicy(any());
+ if (Flags.modesApi()) {
+ verify(mNm, never()).setNotificationPolicy(any(), anyBoolean());
+ } else {
+ verify(mNm, never()).setNotificationPolicy(any());
+ }
}
@Test
diff --git a/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java b/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java
index f177c19..87a798a 100644
--- a/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/panel/PanelSlicesAdapterTest.java
@@ -43,6 +43,7 @@
import androidx.lifecycle.LiveData;
import androidx.slice.Slice;
+import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settings.panel.PanelSlicesAdapter.SliceRowViewHolder;
@@ -50,14 +51,14 @@
import org.junit.Assert;
import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
@@ -69,6 +70,8 @@
@RunWith(RobolectricTestRunner.class)
@Config(shadows = PanelSlicesAdapterTest.ShadowLayoutInflater.class)
public class PanelSlicesAdapterTest {
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
private static LayoutInflater sLayoutInflater;
@@ -81,8 +84,7 @@
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = ApplicationProvider.getApplicationContext();
mPanelFeatureProvider = spy(new PanelFeatureProviderImpl());
mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
@@ -104,8 +106,7 @@
private void addTestLiveData(Uri uri) {
// Create a slice to return for the LiveData
- final Slice slice = spy(new Slice());
- doReturn(uri).when(slice).getUri();
+ final Slice slice = new Slice();
final LiveData<Slice> liveData = mock(LiveData.class);
when(liveData.getValue()).thenReturn(slice);
mData.put(uri, liveData);
@@ -126,7 +127,6 @@
/**
* ViewHolder should load and set the action label correctly.
*/
- @Ignore("b/313576125")
@Test
public void setActionLabel_loadsActionLabel() {
addTestLiveData(VOLUME_NOTIFICATION_URI);
@@ -167,7 +167,6 @@
return foundLabel;
}
- @Ignore("b/313576125")
@Test
public void sizeOfAdapter_shouldNotExceedMaxNum() {
for (int i = 0; i < MAX_NUM_OF_SLICES + 2; i++) {
@@ -186,7 +185,6 @@
assertThat(adapter.getData().size()).isEqualTo(MAX_NUM_OF_SLICES);
}
- @Ignore("b/313576125")
@Test
public void mediaOutputIndicatorSlice_notSliderPanel_noSliderLayout() {
addTestLiveData(MEDIA_OUTPUT_INDICATOR_SLICE_URI);
@@ -203,7 +201,6 @@
assertThat(viewHolder.mSliceSliderLayout).isNull();
}
- @Ignore("b/313576125")
@Test
public void onBindViewHolder_viewTypeSlider_verifyActionLabelSet() {
addTestLiveData(VOLUME_NOTIFICATION_URI);
diff --git a/tests/robotests/src/com/android/settings/security/ScreenPinningSettingsTest.java b/tests/robotests/src/com/android/settings/security/ScreenPinningSettingsTest.java
new file mode 100644
index 0000000..045ef65
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/security/ScreenPinningSettingsTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 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.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
+import com.android.settingslib.search.SearchIndexableRaw;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = ShadowLockPatternUtils.class)
+public class ScreenPinningSettingsTest {
+
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = ApplicationProvider.getApplicationContext();
+ }
+
+ @After
+ public void tearDown() {
+ ShadowLockPatternUtils.reset();
+ }
+
+ @Test
+ public void getDynamicRawDataToIndex_numericPassword_shouldIndexUnlockPinTitle() {
+ ShadowLockPatternUtils.setKeyguardStoredPasswordQuality(
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
+
+ final List<SearchIndexableRaw> indexRaws =
+ ScreenPinningSettings.SEARCH_INDEX_DATA_PROVIDER.getDynamicRawDataToIndex(
+ mContext, /* enabled= */ true);
+
+ assertThat(indexRaws.size()).isEqualTo(1);
+ assertThat(indexRaws.get(0).title).isEqualTo(
+ mContext.getString(R.string.screen_pinning_unlock_pin));
+ }
+
+ @Test
+ public void getDynamicRawDataToIndex_alphabeticPassword_shouldIndexUnlockPasswordTitle() {
+ ShadowLockPatternUtils.setKeyguardStoredPasswordQuality(
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC);
+
+ final List<SearchIndexableRaw> indexRaws =
+ ScreenPinningSettings.SEARCH_INDEX_DATA_PROVIDER.getDynamicRawDataToIndex(
+ mContext, /* enabled= */ true);
+
+ assertThat(indexRaws.size()).isEqualTo(1);
+ assertThat(indexRaws.get(0).title).isEqualTo(
+ mContext.getString(R.string.screen_pinning_unlock_password));
+ }
+
+ @Test
+ public void getDynamicRawDataToIndex_patternPassword_shouldIndexUnlockPatternTitle() {
+ ShadowLockPatternUtils.setKeyguardStoredPasswordQuality(
+ DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+ ShadowLockPatternUtils.setIsLockPatternEnabled(
+ UserHandle.myUserId(), /* isLockPatternEnabled= */ true);
+
+ final List<SearchIndexableRaw> indexRaws =
+ ScreenPinningSettings.SEARCH_INDEX_DATA_PROVIDER.getDynamicRawDataToIndex(
+ mContext, /* enabled= */ true);
+
+ assertThat(indexRaws.size()).isEqualTo(1);
+ assertThat(indexRaws.get(0).title).isEqualTo(
+ mContext.getString(R.string.screen_pinning_unlock_pattern));
+ }
+
+ @Test
+ public void getDynamicRawDataToIndex_nonePassword_shouldIndexUnlockNoneTitle() {
+ ShadowLockPatternUtils.setKeyguardStoredPasswordQuality(
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
+
+ final List<SearchIndexableRaw> indexRaws =
+ ScreenPinningSettings.SEARCH_INDEX_DATA_PROVIDER.getDynamicRawDataToIndex(
+ mContext, /* enabled= */ true);
+
+ assertThat(indexRaws.size()).isEqualTo(1);
+ assertThat(indexRaws.get(0).title).isEqualTo(
+ mContext.getString(R.string.screen_pinning_unlock_none));
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/security/SimLockPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/SimLockPreferenceControllerTest.java
index f38cc49..10e397c 100644
--- a/tests/robotests/src/com/android/settings/security/SimLockPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/security/SimLockPreferenceControllerTest.java
@@ -77,6 +77,7 @@
ShadowApplication shadowApplication = ShadowApplication.getInstance();
shadowApplication.setSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE,
mSubscriptionManager);
+ when(mSubscriptionManager.createForAllUserProfiles()).thenReturn(mSubscriptionManager);
shadowApplication.setSystemService(Context.CARRIER_CONFIG_SERVICE, mCarrierManager);
shadowApplication.setSystemService(Context.USER_SERVICE, mUserManager);
shadowApplication.setSystemService(Context.TELEPHONY_SERVICE, mTelephonyManager);
diff --git a/tests/robotests/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragmentTest.java b/tests/robotests/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragmentTest.java
index ad60d06..24b82b2 100644
--- a/tests/robotests/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/sim/EnableAutoDataSwitchDialogFragmentTest.java
@@ -68,6 +68,7 @@
doReturn(mContext).when(mFragment).getContext();
doReturn(mSubscriptionManager).when(mContext).getSystemService(SubscriptionManager.class);
+ doReturn(mSubscriptionManager).when(mSubscriptionManager).createForAllUserProfiles();
doReturn(mTelephonyManager).when(mContext).getSystemService(TelephonyManager.class);
doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccountManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccountManager.java
index dae17bc..aa2961c 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccountManager.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowAccountManager.java
@@ -31,7 +31,7 @@
import java.util.Map;
@Implements(AccountManager.class)
-public class ShadowAccountManager {
+public class ShadowAccountManager extends org.robolectric.shadows.ShadowAccountManager {
private static final Map<String, AuthenticatorDescription> sAuthenticators = new HashMap<>();
private static final Map<Integer, List<Account>> sAccountsByUserId = new HashMap<>();
@@ -41,7 +41,8 @@
return sAuthenticators.values().toArray(new AuthenticatorDescription[sAuthenticators.size()]);
}
- public static void addAuthenticator(AuthenticatorDescription authenticator) {
+ @Override
+ public void addAuthenticator(AuthenticatorDescription authenticator) {
sAuthenticators.put(authenticator.type, authenticator);
}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowSecureSettings.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowSecureSettings.java
index f209942..d773148 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowSecureSettings.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowSecureSettings.java
@@ -16,6 +16,8 @@
package com.android.settings.testutils.shadow;
+import static android.provider.Settings.DEFAULT_OVERRIDEABLE_BY_RESTORE;
+
import android.content.ContentResolver;
import android.provider.Settings;
@@ -24,12 +26,13 @@
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowSettings;
import java.util.Map;
import java.util.WeakHashMap;
@Implements(Settings.Secure.class)
-public class ShadowSecureSettings {
+public class ShadowSecureSettings extends ShadowSettings.ShadowSecure {
private static final Map<ContentResolver, Table<Integer, String, Object>> sUserDataMap =
new WeakHashMap<>();
@@ -48,6 +51,16 @@
}
}
+ /**
+ * Same implementation as Settings.Secure because robolectric.ShadowSettings.ShadowSecure
+ * overrides this API.
+ */
+ @Implementation
+ public static boolean putString(ContentResolver resolver, String name, String value) {
+ return putStringForUser(resolver, name, value, null, false,
+ resolver.getUserId(), DEFAULT_OVERRIDEABLE_BY_RESTORE);
+ }
+
@Implementation
public static String getStringForUser(ContentResolver resolver, String name, int userHandle) {
final Table<Integer, String, Object> userTable = getUserTable(resolver);
@@ -56,6 +69,15 @@
}
}
+ /**
+ * Same implementation as Settings.Secure because robolectric.ShadowSettings.ShadowSecure
+ * overrides this API.
+ */
+ @Implementation
+ public static boolean putInt(ContentResolver resolver, String name, int value) {
+ return putIntForUser(resolver, name, value, resolver.getUserId());
+ }
+
@Implementation
public static boolean putIntForUser(ContentResolver resolver, String name, int value,
int userHandle) {
@@ -66,6 +88,15 @@
}
}
+ /**
+ * Same implementation as Settings.Secure because robolectric.ShadowSettings.ShadowSecure
+ * overrides this API.
+ */
+ @Implementation
+ public static int getInt(ContentResolver resolver, String name, int def) {
+ return getIntForUser(resolver, name, def, resolver.getUserId());
+ }
+
@Implementation
public static int getIntForUser(ContentResolver resolver, String name, int def,
int userHandle) {
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowStorageManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowStorageManager.java
index d45c2ca..8dc3b8f 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowStorageManager.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowStorageManager.java
@@ -31,7 +31,7 @@
import java.util.List;
@Implements(StorageManager.class)
-public class ShadowStorageManager {
+public class ShadowStorageManager extends org.robolectric.shadows.ShadowStorageManager {
private static boolean sIsUnmountCalled;
private static boolean sIsForgetCalled;
diff --git a/tests/robotests/src/com/android/settings/wifi/WifiConfigController2Test.java b/tests/robotests/src/com/android/settings/wifi/WifiConfigController2Test.java
index d17414d..4fcc93d 100644
--- a/tests/robotests/src/com/android/settings/wifi/WifiConfigController2Test.java
+++ b/tests/robotests/src/com/android/settings/wifi/WifiConfigController2Test.java
@@ -21,9 +21,11 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -997,6 +999,18 @@
assertThat(anonymousId.getText().toString()).isEqualTo(DEFAULT_ANONYMOUS_ID);
}
+ @Test
+ public void setAnonymousIdVisible_viewIsVisible_doNotSetText() {
+ createController(mWifiEntry, WifiConfigUiBase2.MODE_CONNECT, false);
+ View anonymousLayout = mView.findViewById(R.id.l_anonymous);
+ mController.mEapAnonymousView = mock(TextView.class);
+ anonymousLayout.setVisibility(View.VISIBLE);
+
+ mController.setAnonymousIdVisible();
+
+ verify(mController.mEapAnonymousView, never()).setText(any(String.class));
+ }
+
private void setUpModifyingSavedCertificateConfigController(String savedCaCertificate,
String savedUserCertificate) {
final WifiConfiguration mockWifiConfig = spy(new WifiConfiguration());
diff --git a/tests/robotests/src/com/android/settings/wifi/WifiDialogActivityTest.java b/tests/robotests/src/com/android/settings/wifi/WifiDialogActivityTest.java
index ff0395d..d1cbd0e 100644
--- a/tests/robotests/src/com/android/settings/wifi/WifiDialogActivityTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/WifiDialogActivityTest.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.os.UserManager.DISALLOW_ADD_WIFI_CONFIG;
import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
import static com.android.settings.wifi.WifiDialogActivity.REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER;
@@ -50,7 +51,6 @@
import com.google.android.setupcompat.util.WizardManagerHelper;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -58,7 +58,6 @@
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
-@Ignore("b/314867581")
@RunWith(RobolectricTestRunner.class)
public class WifiDialogActivityTest {
@@ -243,6 +242,20 @@
}
@Test
+ public void isAddWifiConfigAllowed_hasNoUserRestriction_returnTrue() {
+ when(mUserManager.hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG)).thenReturn(false);
+
+ assertThat(mActivity.isAddWifiConfigAllowed()).isTrue();
+ }
+
+ @Test
+ public void isAddWifiConfigAllowed_hasUserRestriction_returnFalse() {
+ when(mUserManager.hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG)).thenReturn(true);
+
+ assertThat(mActivity.isAddWifiConfigAllowed()).isFalse();
+ }
+
+ @Test
public void hasPermissionForResult_noCallingPackage_returnFalse() {
when(mActivity.getCallingPackage()).thenReturn(null);
diff --git a/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java
index 0474f52..efea6fd 100644
--- a/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java
+++ b/tests/robotests/testutils/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java
@@ -50,6 +50,7 @@
private static Map<Integer, Boolean> sUserToVisiblePatternEnabledMap = new HashMap<>();
private static Map<Integer, Boolean> sUserToBiometricAllowedMap = new HashMap<>();
private static Map<Integer, Boolean> sUserToLockPatternEnabledMap = new HashMap<>();
+ private static Map<Integer, Integer> sKeyguardStoredPasswordQualityMap = new HashMap<>();
private static boolean sIsUserOwnsFrpCredential;
@@ -66,6 +67,7 @@
sUserToLockPatternEnabledMap.clear();
sDeviceEncryptionEnabled = false;
sIsUserOwnsFrpCredential = false;
+ sKeyguardStoredPasswordQualityMap.clear();
}
@Implementation
@@ -97,7 +99,7 @@
@Implementation
protected int getKeyguardStoredPasswordQuality(int userHandle) {
- return 1;
+ return sKeyguardStoredPasswordQualityMap.getOrDefault(userHandle, /* defaultValue= */ 1);
}
@Implementation
@@ -171,7 +173,7 @@
@Implementation
public boolean isLockPatternEnabled(int userId) {
- return sUserToBiometricAllowedMap.getOrDefault(userId, false);
+ return sUserToLockPatternEnabledMap.getOrDefault(userId, false);
}
public static void setIsLockPatternEnabled(int userId, boolean isLockPatternEnabled) {
@@ -238,4 +240,8 @@
public boolean isSeparateProfileChallengeEnabled(int userHandle) {
return false;
}
+
+ public static void setKeyguardStoredPasswordQuality(int quality) {
+ sKeyguardStoredPasswordQualityMap.put(UserHandle.myUserId(), quality);
+ }
}
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceControllerTest.kt
index 0deeafb..466a5d1 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/gsm/OpenNetworkSelectPagePreferenceControllerTest.kt
@@ -49,7 +49,8 @@
}
private val mockSubscriptionManager = mock<SubscriptionManager> {
- on { activeSubscriptionInfoList } doAnswer { listOf(subscriptionInfo) }
+ on { createForAllUserProfiles() } doReturn mock
+ on { getActiveSubscriptionInfo(SUB_ID) } doReturn subscriptionInfo
}
private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/ResetAppPreferencesTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/ResetAppPreferencesTest.kt
index ffd5831..b63c281 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/ResetAppPreferencesTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/ResetAppPreferencesTest.kt
@@ -45,7 +45,7 @@
}
private fun setResetAppPreferences() {
- val fakeMoreOptionsScope = object : MoreOptionsScope {
+ val fakeMoreOptionsScope = object : MoreOptionsScope() {
override fun dismiss() {}
}
composeTestRule.setContent {
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/battery/BatteryOptimizationModeAppListPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/battery/BatteryOptimizationModeAppListPageProviderTest.kt
new file mode 100644
index 0000000..da1e94c
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/battery/BatteryOptimizationModeAppListPageProviderTest.kt
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2024 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.spa.app
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.DisplaySettings
+import com.android.settings.R
+import com.android.settings.SettingsActivity
+import com.android.settings.fuelgauge.AdvancedPowerUsageDetail
+import com.android.settings.spa.app.battery.BatteryOptimizationModeAppList
+import com.android.settings.spa.app.battery.BatteryOptimizationModeAppListModel
+import com.android.settings.spa.app.battery.BatteryOptimizationModeAppListPageProvider
+import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
+import com.android.settingslib.spaprivileged.template.app.AppListInput
+import com.android.settingslib.spaprivileged.template.app.AppListItemModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class BatteryOptimizationModeAppListPageProviderTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val fakeNavControllerWrapper = FakeNavControllerWrapper()
+
+ private val packageManager = mock<PackageManager> {
+ on { getPackagesForUid(USER_ID) } doReturn arrayOf(PACKAGE_NAME)
+ }
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { packageManager } doReturn packageManager
+ }
+
+ @Test
+ fun batteryOptimizationModeAppListPageProvider_name() {
+ assertThat(BatteryOptimizationModeAppListPageProvider.name)
+ .isEqualTo("BatteryOptimizationModeAppList")
+ }
+
+ @Test
+ fun injectEntry_title() {
+ setInjectEntry()
+
+ composeTestRule.onNodeWithText(context.getString(R.string.app_battery_usage_title))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun injectEntry_onClick_navigate() {
+ setInjectEntry()
+
+ composeTestRule.onNodeWithText(context.getString(R.string.app_battery_usage_title))
+ .performClick()
+
+ assertThat(fakeNavControllerWrapper.navigateCalledWith)
+ .isEqualTo("BatteryOptimizationModeAppList")
+ }
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ BatteryOptimizationModeAppList {}
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.app_battery_usage_title))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun showInstantApps_isFalse() {
+ val input = getAppListInput()
+
+ assertThat(input.config.showInstantApps).isFalse()
+ }
+
+ @Test
+ fun item_labelDisplayed() {
+ setItemContent()
+
+ composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+ }
+
+ @Test
+ fun item_summaryDisplayed() {
+ setItemContent()
+
+ composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed()
+ }
+
+ @Test
+ fun item_onClick_navigate() {
+ setItemContent()
+ doNothing().whenever(context).startActivity(any())
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+
+ val intent = argumentCaptor<Intent> {
+ verify(context).startActivity(capture())
+ }.firstValue
+
+ assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))!!
+ .isEqualTo(AdvancedPowerUsageDetail::class.java.name)
+ val arguments = intent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS)!!
+ assertThat(arguments.getString(AdvancedPowerUsageDetail.EXTRA_PACKAGE_NAME))
+ .isEqualTo(PACKAGE_NAME)
+ }
+
+ @Test
+ fun BatteryOptimizationModeAppListModel_transform() = runTest {
+ val listModel = BatteryOptimizationModeAppListModel(context)
+
+ val recordListFlow = listModel.transform(flowOf(USER_ID), flowOf(listOf(APP)))
+
+ val recordList = recordListFlow.firstWithTimeoutOrNull()!!
+ assertThat(recordList).hasSize(1)
+ assertThat(recordList[0].app).isSameInstanceAs(APP)
+ }
+
+ @Test
+ fun listModelGetSummary_regular() {
+ val listModel = BatteryOptimizationModeAppListModel(context)
+
+ lateinit var summary: () -> String
+ composeTestRule.setContent {
+ summary = listModel.getSummary(option = 0, record = AppRecordWithSize(app = APP))
+ }
+
+ assertThat(summary()).isEmpty()
+ }
+
+ @Test
+ fun listModelGetSummary_disabled() {
+ val listModel = BatteryOptimizationModeAppListModel(context)
+ val disabledApp = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ flags = ApplicationInfo.FLAG_INSTALLED
+ enabled = false
+ }
+
+ lateinit var summary: () -> String
+ composeTestRule.setContent {
+ summary =
+ listModel.getSummary(option = 0, record = AppRecordWithSize(app = disabledApp))
+ }
+
+ assertThat(summary())
+ .isEqualTo(context.getString(com.android.settingslib.R.string.disabled))
+ }
+
+ @Test
+ fun listModelGetSummary_notInstalled() {
+ val listModel = BatteryOptimizationModeAppListModel(context)
+ val notInstalledApp = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+
+ lateinit var summary: () -> String
+ composeTestRule.setContent {
+ summary =
+ listModel.getSummary(option = 0, record = AppRecordWithSize(app = notInstalledApp))
+ }
+
+ assertThat(summary()).isEqualTo(context.getString(R.string.not_installed))
+ }
+
+ @Test
+ fun batteryOptimizationModeAppListModel_archivedApp() {
+ val app = mock<ApplicationInfo> {
+ on { loadUnbadgedIcon(any()) } doReturn UNBADGED_ICON
+ on { loadLabel(any()) } doReturn LABEL
+ }
+ app.isArchived = true
+ packageManager.stub {
+ on {
+ getApplicationInfoAsUser(PACKAGE_NAME, 0, USER_ID)
+ } doReturn app
+ }
+ composeTestRule.setContent {
+ fakeNavControllerWrapper.Wrapper {
+ with(BatteryOptimizationModeAppListModel(context)) {
+ AppListItemModel(
+ record = AppRecordWithSize(app = app),
+ label = LABEL,
+ summary = { SUMMARY },
+ ).AppItem()
+ }
+ }
+ }
+
+ composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+ }
+
+ @Test
+ fun batteryOptimizationModeAppListModel_NoStorageSummary() {
+ val listModel = BatteryOptimizationModeAppListModel(context)
+ val archivedApp = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ isArchived = true
+ }
+
+ lateinit var summary: () -> String
+ composeTestRule.setContent {
+ summary =
+ listModel.getSummary(option = 0, record = AppRecordWithSize(app = archivedApp))
+ }
+
+ assertThat(summary()).isEmpty()
+ }
+
+ private fun setInjectEntry() {
+ composeTestRule.setContent {
+ fakeNavControllerWrapper.Wrapper {
+ BatteryOptimizationModeAppListPageProvider.buildInjectEntry().build().UiLayout()
+ }
+ }
+ }
+
+ private fun getAppListInput(): AppListInput<AppRecordWithSize> {
+ lateinit var input: AppListInput<AppRecordWithSize>
+ composeTestRule.setContent {
+ BatteryOptimizationModeAppList {
+ SideEffect {
+ input = this
+ }
+ }
+ }
+ return input
+ }
+
+ private fun setItemContent() {
+ composeTestRule.setContent {
+ fakeNavControllerWrapper.Wrapper {
+ with(BatteryOptimizationModeAppListModel(context)) {
+ AppListItemModel(
+ record = AppRecordWithSize(app = APP),
+ label = LABEL,
+ summary = { SUMMARY },
+ ).AppItem()
+ }
+ }
+ }
+ }
+
+ private companion object {
+ const val USER_ID = 0
+ const val PACKAGE_NAME = "package.name"
+ const val LABEL = "Label"
+ const val SUMMARY = "Summary"
+ val UNBADGED_ICON = mock<Drawable>()
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ flags = ApplicationInfo.FLAG_INSTALLED
+ }
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaRoutingControlTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaRoutingControlTest.kt
index 5f0f2c6..2f4740e 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaRoutingControlTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/MediaRoutingControlTest.kt
@@ -23,9 +23,11 @@
import android.companion.AssociationRequest
import android.content.Context
import android.content.pm.ApplicationInfo
+import android.platform.test.flag.junit.SetFlagsRule
import androidx.lifecycle.MutableLiveData
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.media.flags.Flags
import com.android.settings.R
import com.android.settings.testutils.FakeFeatureFactory
import com.android.settingslib.spaprivileged.model.app.IAppOpsController
@@ -36,17 +38,19 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.verify
import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.Mockito.`when` as whenever
-import org.mockito.Mockito.verify
@RunWith(AndroidJUnit4::class)
class MediaRoutingControlTest {
@get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
+ @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule();
+
@Spy
private val context: Context = ApplicationProvider.getApplicationContext()
@@ -143,6 +147,7 @@
@Test
fun isChangeable_permissionRequestedByAppAndWatchCompanionRoleAssigned_shouldReturnTrue() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVILEGED_ROUTING_FOR_MEDIA_ROUTING_CONTROL)
val permissionRequestedRecord =
AppOpPermissionRecord(
app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
@@ -161,6 +166,7 @@
@Test
fun isChangeable_permissionNotRequestedByAppButWatchCompanionRoleAssigned_shouldReturnFalse() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVILEGED_ROUTING_FOR_MEDIA_ROUTING_CONTROL)
val permissionNotRequestedRecord =
AppOpPermissionRecord(
app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
@@ -179,6 +185,7 @@
@Test
fun isChangeable_permissionRequestedByAppButWatchCompanionRoleNotAssigned_shouldReturnFalse() {
+ setFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVILEGED_ROUTING_FOR_MEDIA_ROUTING_CONTROL)
val permissionRequestedRecord =
AppOpPermissionRecord(
app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
@@ -195,6 +202,25 @@
assertThat(isSpecialAccessChangeable).isFalse()
}
+ @Test
+ fun isChangeable_withFlagDisabled_shouldReturnFalse() {
+ setFlagsRule.disableFlags(Flags.FLAG_ENABLE_PRIVILEGED_ROUTING_FOR_MEDIA_ROUTING_CONTROL)
+ val permissionRequestedRecord =
+ AppOpPermissionRecord(
+ app = ApplicationInfo().apply { packageName = PACKAGE_NAME },
+ hasRequestPermission = true,
+ hasRequestBroaderPermission = false,
+ appOpsController =
+ FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+ whenever(mockRoleManager.getRoleHolders(AssociationRequest.DEVICE_PROFILE_WATCH))
+ .thenReturn(listOf(PACKAGE_NAME))
+
+ val isSpecialAccessChangeable = listModel.isChangeable(permissionRequestedRecord)
+
+ assertThat(isSpecialAccessChangeable).isFalse()
+ }
+
private class FakeAppOpsController(fakeMode: Int) : IAppOpsController {
override val mode = MutableLiveData(fakeMode)
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 327b6aa..9d2d686 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -32,6 +32,7 @@
"kotlinx_coroutines_test",
"flag-junit",
"Settings-testutils2",
+ "MediaDrmSettingsFlagsLib",
// Don't add SettingsLib libraries here - you can use them directly as they are in the
// instrumented Settings app.
],
diff --git a/tests/unit/src/com/android/settings/development/widevine/ForceL3FallbackPreferenceControllerTest.java b/tests/unit/src/com/android/settings/development/widevine/ForceL3FallbackPreferenceControllerTest.java
new file mode 100644
index 0000000..f67a4af
--- /dev/null
+++ b/tests/unit/src/com/android/settings/development/widevine/ForceL3FallbackPreferenceControllerTest.java
@@ -0,0 +1,136 @@
+/*
+* 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 static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+import static org.junit.Assume.assumeNoException;
+
+import android.content.Context;
+import android.media.MediaDrm;
+import android.media.UnsupportedSchemeException;
+import android.media.NotProvisionedException;
+import android.sysprop.WidevineProperties;
+import android.util.Log;
+import android.content.Context;
+
+import com.android.settings.media_drm.Flags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.preference.SwitchPreference;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.UUID;
+
+@RunWith(AndroidJUnit4.class)
+public class ForceL3FallbackPreferenceControllerTest {
+
+ private static final String PREF_KEY = "force_l3_fallback";
+ private static final UUID WIDEVINE_UUID =
+ new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
+ private static final String TAG = "ForceL3FallbackPreferenceControllerTest";
+
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ private Context mContext;
+ private ForceL3FallbackPreferenceController mController;
+ private SwitchPreference mPreference;
+
+ @Before
+ public void setUp() {
+ mContext = ApplicationProvider.getApplicationContext();
+ mController = new ForceL3FallbackPreferenceController(mContext, PREF_KEY);
+ mPreference = new SwitchPreference(mContext);
+ WidevineProperties.forcel3_enabled(false);
+ }
+
+ @Test
+ public void updateState_flagEnabled_checkPreference() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_FORCE_L3_ENABLED);
+ mController.updateState(mPreference);
+ assertThat(mPreference.isEnabled()).isTrue();
+ assertThat(mPreference.isChecked()).isFalse();
+ assertThat(WidevineProperties.forcel3_enabled().orElse(false)).isFalse();
+
+ // Toggle to true
+ mController.setChecked(true);
+ mController.updateState(mPreference);
+ assertThat(WidevineProperties.forcel3_enabled().orElse(false)).isTrue();
+ assertThat(mPreference.isEnabled()).isTrue();
+ assertThat(mPreference.isChecked()).isTrue();
+
+ // Toggle to false
+ mController.setChecked(false);
+ mController.updateState(mPreference);
+ assertThat(WidevineProperties.forcel3_enabled().orElse(false)).isFalse();
+ assertThat(mPreference.isEnabled()).isTrue();
+ assertThat(mPreference.isChecked()).isFalse();
+
+ // Test flag rollback
+ mController.setChecked(true);
+ mController.updateState(mPreference);
+ assertThat(mPreference.isChecked()).isTrue();
+ assertThat(WidevineProperties.forcel3_enabled().orElse(false)).isTrue();
+ mSetFlagsRule.disableFlags(Flags.FLAG_FORCE_L3_ENABLED);
+ mController.updateState(mPreference);
+ assertThat(mPreference.isEnabled()).isFalse();
+ assertThat(mPreference.isChecked()).isFalse();
+ assertThat(WidevineProperties.forcel3_enabled().orElse(false)).isFalse();
+ }
+
+ @Test
+ public void updateState_flagDisabled_checkPreference() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_FORCE_L3_ENABLED);
+ mController.updateState(mPreference);
+ assertThat(mPreference.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void updateState_checkWidevine() throws Exception {
+ MediaDrm drm;
+ try {
+ drm = new MediaDrm(WIDEVINE_UUID);
+ assumeTrue(drm.getPropertyString("securityLevel").equals("L1"));
+ mSetFlagsRule.enableFlags(Flags.FLAG_FORCE_L3_ENABLED);
+ drm.close();
+ } catch (UnsupportedSchemeException ex) {
+ assumeNoException(ex);
+ }
+
+ // L3 enforced
+ mController.setChecked(true);
+ mController.updateState(mPreference);
+ assertThat(WidevineProperties.forcel3_enabled().orElse(false)).isTrue();
+ assertThat(mPreference.isEnabled()).isTrue();
+ assertThat(mPreference.isChecked()).isTrue();
+ drm = new MediaDrm(WIDEVINE_UUID);
+ assertThat(drm.getPropertyString("securityLevel")).isEqualTo("L3");
+
+ // Switch back to L1
+ mController.setChecked(false);
+ mController.updateState(mPreference);
+ drm.close();
+ drm = new MediaDrm(WIDEVINE_UUID);
+ assertThat(drm.getPropertyString("securityLevel")).isEqualTo("L1");
+ }
+}
diff --git a/tests/unit/src/com/android/settings/network/ActiveSubscriptionsListenerTest.java b/tests/unit/src/com/android/settings/network/ActiveSubscriptionsListenerTest.java
index 27bc40c..40649a9 100644
--- a/tests/unit/src/com/android/settings/network/ActiveSubscriptionsListenerTest.java
+++ b/tests/unit/src/com/android/settings/network/ActiveSubscriptionsListenerTest.java
@@ -77,6 +77,7 @@
mContext = spy(ApplicationProvider.getApplicationContext());
when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
+ doReturn(mSubscriptionManager).when(mSubscriptionManager).createForAllUserProfiles();
mActiveSubscriptions = new ArrayList<SubscriptionInfo>();
addMockSubscription(SUB_ID1);
diff --git a/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java b/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java
index 5261b3e..75c49b3 100644
--- a/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java
+++ b/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java
@@ -77,6 +77,7 @@
when(mTelephonyManager.getUiccCardsInfo()).thenReturn(mUiccCardInfo);
when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
+ when(mSubscriptionManager.createForAllUserProfiles()).thenReturn(mSubscriptionManager);
when(mSubscriptionManager.getAllSubscriptionInfoList()).thenReturn(mSubscriptionInfoList);
}
diff --git a/tests/unit/src/com/android/settings/network/telephony/MobileNetworkUtilsTest.java b/tests/unit/src/com/android/settings/network/telephony/MobileNetworkUtilsTest.java
index 1043fdf..947ba75 100644
--- a/tests/unit/src/com/android/settings/network/telephony/MobileNetworkUtilsTest.java
+++ b/tests/unit/src/com/android/settings/network/telephony/MobileNetworkUtilsTest.java
@@ -113,6 +113,7 @@
mContext = spy(ApplicationProvider.getApplicationContext());
when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
+ when(mSubscriptionManager.createForAllUserProfiles()).thenReturn(mSubscriptionManager);
when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
when(mTelephonyManager.createForSubscriptionId(SUB_ID_1)).thenReturn(mTelephonyManager);
when(mTelephonyManager.createForSubscriptionId(SUB_ID_2)).thenReturn(mTelephonyManager2);
@@ -147,24 +148,30 @@
public void setMobileDataEnabled_setEnabled_enabled() {
MobileNetworkUtils.setMobileDataEnabled(mContext, SUB_ID_1, true, false);
- verify(mTelephonyManager).setDataEnabled(true);
- verify(mTelephonyManager2, never()).setDataEnabled(anyBoolean());
+ verify(mTelephonyManager)
+ .setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER, true);
+ verify(mTelephonyManager2, never())
+ .setDataEnabledForReason(anyInt(), anyBoolean());
}
@Test
public void setMobileDataEnabled_setDisabled_disabled() {
MobileNetworkUtils.setMobileDataEnabled(mContext, SUB_ID_2, true, false);
- verify(mTelephonyManager2).setDataEnabled(true);
- verify(mTelephonyManager, never()).setDataEnabled(anyBoolean());
+ verify(mTelephonyManager2)
+ .setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER, true);
+ verify(mTelephonyManager, never())
+ .setDataEnabledForReason(anyInt(), anyBoolean());
}
@Test
public void setMobileDataEnabled_disableOtherSubscriptions() {
MobileNetworkUtils.setMobileDataEnabled(mContext, SUB_ID_1, true, true);
- verify(mTelephonyManager).setDataEnabled(true);
- verify(mTelephonyManager2).setDataEnabled(false);
+ verify(mTelephonyManager)
+ .setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER, true);
+ verify(mTelephonyManager2)
+ .setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_USER, false);
}
@Test
diff --git a/tests/unit/src/com/android/settings/network/telephony/NetworkProviderWifiCallingGroupTest.java b/tests/unit/src/com/android/settings/network/telephony/NetworkProviderWifiCallingGroupTest.java
index 9cd12fe..95f8390 100644
--- a/tests/unit/src/com/android/settings/network/telephony/NetworkProviderWifiCallingGroupTest.java
+++ b/tests/unit/src/com/android/settings/network/telephony/NetworkProviderWifiCallingGroupTest.java
@@ -37,7 +37,6 @@
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.util.FeatureFlagUtils;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
@@ -100,6 +99,7 @@
when(mContext.getSystemService(CarrierConfigManager.class)).thenReturn(
mCarrierConfigManager);
when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
+ when(mSubscriptionManager.createForAllUserProfiles()).thenReturn(mSubscriptionManager);
when(mContext.getSystemService(TelecomManager.class)).thenReturn(mTelecomManager);
when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
diff --git a/tests/unit/src/com/android/settings/network/telephony/TelephonyBasePreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/TelephonyBasePreferenceControllerTest.java
index 4fb31f2..3588d38 100644
--- a/tests/unit/src/com/android/settings/network/telephony/TelephonyBasePreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/network/telephony/TelephonyBasePreferenceControllerTest.java
@@ -55,6 +55,7 @@
mContext = spy(ApplicationProvider.getApplicationContext());
when(mContext.getSystemService(SubscriptionManager.class))
.thenReturn(mSubscriptionManager);
+ when(mSubscriptionManager.createForAllUserProfiles()).thenReturn(mSubscriptionManager);
when(mSubscriptionInfo.getSubscriptionId()).thenReturn(VALID_SUB_ID);
mPreferenceController = new TestPreferenceController(mContext, "prefKey");
}