Merge "Add device to to cached device manager if it's not present" into main
diff --git a/Android.bp b/Android.bp
index cb898be..0c6d8d1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -125,6 +125,9 @@
"telephony-common",
"ims-common",
],
+ flags_packages: [
+ "android.app.flags-aconfig",
+ ],
}
platform_compat_config {
@@ -155,6 +158,9 @@
optimize: {
proguard_flags_files: ["proguard.flags"],
},
+ flags_packages: [
+ "android.app.flags-aconfig",
+ ],
}
android_library_import {
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ca96328..8cfd9b5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1287,20 +1287,63 @@
</activity>
<activity
- android:name="Settings$ZenModeSettingsActivity"
+ android:name="Settings$ModesSettingsActivity"
android:label="@string/zen_mode_settings_title"
android:icon="@drawable/ic_homepage_notification"
android:exported="true">
- <intent-filter android:priority="1">
+ <intent-filter android:priority="1"
+ android:featureFlag="android.app.modes_ui">
<action android:name="android.settings.ZEN_MODE_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
- <intent-filter android:priority="1">
+ <intent-filter android:priority="1"
+ android:featureFlag="android.app.modes_ui">
<action android:name="android.settings.ZEN_MODE_PRIORITY_SETTINGS" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
- <intent-filter android:priority="41">
+ <intent-filter android:priority="41"
+ android:featureFlag="android.app.modes_ui">
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.settings.SHORTCUT" />
+ </intent-filter>
+ <intent-filter android:priority="10"
+ android:featureFlag="android.app.modes_ui">
+ <action android:name="android.settings.ZEN_MODE_AUTOMATION_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter android:priority="10"
+ android:featureFlag="android.app.modes_ui">
+ <action android:name="android.settings.ACTION_CONDITION_PROVIDER_SETTINGS" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.notification.modes.ZenModesListFragment"/>
+ <meta-data android:name="com.android.settings.HIGHLIGHT_MENU_KEY"
+ android:value="@string/menu_key_notifications"/>
+ <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
+ android:value="true" />
+ </activity>
+
+ <activity
+ android:name="Settings$ZenModeSettingsActivity"
+ android:label="@string/zen_mode_settings_title"
+ android:icon="@drawable/ic_homepage_notification"
+ android:exported="true">
+ <intent-filter android:priority="1"
+ android:featureFlag="!android.app.modes_ui">
+ <action android:name="android.settings.ZEN_MODE_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter android:priority="1"
+ android:featureFlag="!android.app.modes_ui">
+ <action android:name="android.settings.ZEN_MODE_PRIORITY_SETTINGS" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter android:priority="41"
+ android:featureFlag="!android.app.modes_ui">
<action android:name="android.intent.action.MAIN" />
<category android:name="com.android.settings.SHORTCUT" />
</intent-filter>
@@ -1313,6 +1356,20 @@
</activity>
<activity
+ android:name="Settings$ModeSettingsActivity"
+ android:exported="true">
+ <intent-filter android:priority="1"
+ android:featureFlag="android.app.modes_ui">
+ <action android:name="android.settings.AUTOMATIC_ZEN_RULE_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.notification.modes.ZenModeFragment"/>
+ <meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
+ android:value="true" />
+ </activity>
+
+ <activity
android:name=".notification.zen.ZenSuggestionActivity"
android:label="@string/zen_mode_settings_title"
android:icon="@drawable/ic_suggestion_dnd"
@@ -1351,11 +1408,13 @@
android:label="@string/zen_mode_automation_settings_title"
android:icon="@drawable/ic_notifications"
android:exported="true">
- <intent-filter android:priority="1">
+ <intent-filter android:priority="10"
+ android:featureFlag="!android.app.modes_ui">
<action android:name="android.settings.ZEN_MODE_AUTOMATION_SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
- <intent-filter android:priority="1">
+ <intent-filter android:priority="10"
+ android:featureFlag="!android.app.modes_ui">
<action android:name="android.settings.ACTION_CONDITION_PROVIDER_SETTINGS" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
@@ -2832,17 +2891,20 @@
<!-- Note this must not be exported since it returns the password in the intent -->
<activity android:name=".password.ConfirmLockPattern$InternalActivity"
android:exported="false"
+ android:enableOnBackInvokedCallback="false"
android:theme="@style/GlifTheme.Light"/>
<!-- Note this must not be exported since it returns the password in the intent -->
<activity android:name=".password.ConfirmLockPassword$InternalActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize"
+ android:enableOnBackInvokedCallback="false"
android:theme="@style/GlifTheme.Light"/>
<activity android:name=".password.SetupChooseLockGeneric"
android:theme="@style/GlifTheme.Light"
android:exported="true"
+ android:enableOnBackInvokedCallback="false"
android:label="@string/lock_settings_picker_title">
<intent-filter android:priority="1">
<action android:name="com.android.settings.SETUP_LOCK_SCREEN" />
@@ -2852,16 +2914,19 @@
<activity android:name=".password.SetupChooseLockGeneric$InternalActivity"
android:exported="false"
+ android:enableOnBackInvokedCallback="false"
android:excludeFromRecents="true" />
<activity android:name=".password.ChooseLockGeneric"
android:label="@string/lockpassword_choose_lock_generic_header"
android:excludeFromRecents="true"
+ android:enableOnBackInvokedCallback="false"
android:exported="false" />
<activity android:name=".password.SetNewPasswordActivity"
android:theme="@android:style/Theme.NoDisplay"
android:exported="true"
+ android:enableOnBackInvokedCallback="false"
android:excludeFromRecents="true" >
<intent-filter android:priority="1">
<action android:name="android.app.action.SET_NEW_PASSWORD" />
@@ -2907,24 +2972,29 @@
<activity android:name=".password.ChooseLockGeneric$InternalActivity"
android:exported="false"
android:label="@string/lockpassword_choose_lock_generic_header"
+ android:enableOnBackInvokedCallback="false"
android:excludeFromRecents="true" />
<activity android:name=".password.SetupChooseLockPattern"
android:exported="false"
+ android:enableOnBackInvokedCallback="false"
android:theme="@style/GlifTheme.Light" />
<activity android:name=".password.ChooseLockPattern"
android:exported="false"
+ android:enableOnBackInvokedCallback="false"
android:theme="@style/GlifTheme.Light" />
<activity android:name=".password.SetupChooseLockPassword"
android:exported="false"
android:theme="@style/GlifTheme.Light"
+ android:enableOnBackInvokedCallback="false"
android:windowSoftInputMode="stateVisible|adjustResize" />
<activity android:name=".password.ChooseLockPassword"
android:exported="false"
android:theme="@style/GlifTheme.Light"
+ android:enableOnBackInvokedCallback="false"
android:windowSoftInputMode="stateVisible|adjustResize"/>
<activity
diff --git a/aconfig/settings_connecteddevice_flag_declarations.aconfig b/aconfig/settings_connecteddevice_flag_declarations.aconfig
index 7942ccd..693e398 100644
--- a/aconfig/settings_connecteddevice_flag_declarations.aconfig
+++ b/aconfig/settings_connecteddevice_flag_declarations.aconfig
@@ -9,13 +9,26 @@
}
flag {
+ name: "rotation_connected_display_setting"
+ namespace: "display_manager"
+ description: "Allow changing rotation of the connected display."
+ bug: "294015706"
+}
+
+flag {
+ name: "resolution_and_enable_connected_display_setting"
+ namespace: "display_manager"
+ description: "Allow enabling/disabling and changing resolution of the connected display."
+ bug: "253296253"
+}
+
+flag {
name: "enable_auth_challenge_for_usb_preferences"
namespace: "safety_center"
description: "Gates whether to require an auth challenge for changing USB preferences"
bug: "317367746"
}
-
flag {
name: "enable_bonded_bluetooth_device_searchable"
namespace: "pixel_cross_device_control"
@@ -24,4 +37,4 @@
metadata {
purpose: PURPOSE_BUGFIX
}
-}
\ No newline at end of file
+}
diff --git a/res/drawable/external_display_mirror_landscape.xml b/res/drawable/external_display_mirror_landscape.xml
new file mode 100644
index 0000000..4272ddb
--- /dev/null
+++ b/res/drawable/external_display_mirror_landscape.xml
@@ -0,0 +1,55 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="232.02106dp"
+ android:viewportHeight="214"
+ android:viewportWidth="380"
+ android:width="412dp">
+ <path
+ android:pathData="M16,0L364,0A16,16 0,0 1,380 16L380,198A16,16 0,0 1,364 214L16,214A16,16 0,0 1,0 198L0,16A16,16 0,0 1,16 0z"
+ android:fillColor="#00000000"/>
+ <path
+ android:pathData="M150.5,38L327.5,38A5.5,5.5 0,0 1,333 43.5L333,138.5A5.5,5.5 0,0 1,327.5 144L150.5,144A5.5,5.5 0,0 1,145 138.5L145,43.5A5.5,5.5 0,0 1,150.5 38z"
+ android:fillColor="#80868B"/>
+ <path
+ android:pathData="M150.58,39L327.42,39A4.58,4.58 0,0 1,332 43.58L332,138.42A4.58,4.58 0,0 1,327.42 143L150.58,143A4.58,4.58 0,0 1,146 138.42L146,43.58A4.58,4.58 0,0 1,150.58 39z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M254.25,144H223.75L221.52,173.34C221.48,173.82 221.08,174.18 220.6,174.18H211.37C211.25,174.18 211.12,174.21 211.01,174.26C210.11,174.65 210.39,176 211.37,176H266.63C267.61,176 267.89,174.65 266.99,174.26C266.88,174.21 266.75,174.18 266.63,174.18H257.4C256.92,174.18 256.52,173.82 256.48,173.34L254.25,144Z"
+ android:fillColor="#5F6368"/>
+ <path
+ android:pathData="M330,53L330,129A3,3 0,0 1,327 132L151,132A3,3 0,0 1,148 129L148,53A3,3 0,0 1,151 50L327,50A3,3 0,0 1,330 53z"
+ android:strokeWidth="2"
+ android:fillColor="#00000000"
+ android:strokeColor="#E0E994"/>
+ <path
+ android:pathData="M113,91.08V86.55C113,86.25 112.88,85.96 112.67,85.75C112.45,85.54 112.17,85.42 111.86,85.41V61.64C111.84,60.15 111.24,58.72 110.17,57.66C109.1,56.61 107.66,56.01 106.16,56H53.71C52.2,56.01 50.75,56.61 49.68,57.67C48.62,58.73 48.01,60.17 48,61.66V170.34C48.01,171.83 48.62,173.27 49.68,174.33C50.75,175.39 52.2,175.99 53.71,176H106.16C107.67,175.99 109.11,175.39 110.18,174.33C111.25,173.27 111.85,171.83 111.86,170.34V114.86C112.16,114.86 112.45,114.74 112.67,114.52C112.88,114.31 113,114.03 113,113.73V102.4C113,102.1 112.88,101.82 112.67,101.6C112.45,101.39 112.17,101.27 111.86,101.27V92.21C112.16,92.21 112.45,92.09 112.67,91.88C112.88,91.67 113,91.38 113,91.08ZM110.72,170.34C110.72,171.54 110.24,172.69 109.38,173.54C108.53,174.39 107.37,174.87 106.16,174.87H53.71C52.5,174.87 51.34,174.39 50.48,173.54C49.62,172.69 49.14,171.54 49.14,170.34V61.64C49.14,60.44 49.62,59.29 50.48,58.44C51.34,57.59 52.5,57.11 53.71,57.11H106.16C107.37,57.11 108.53,57.59 109.38,58.44C110.24,59.29 110.72,60.44 110.72,61.64V170.34Z"
+ android:fillColor="#80868B"/>
+ <path
+ android:pathData="M54,59L106,59A3,3 0,0 1,109 62L109,170A3,3 0,0 1,106 173L54,173A3,3 0,0 1,51 170L51,62A3,3 0,0 1,54 59z"
+ android:strokeColor="#E0E994"
+ android:strokeWidth="2"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M80,184.72V189.7C80,193.73 83.27,197 87.3,197H164.7C168.73,197 172,193.73 172,189.7V144"
+ android:strokeColor="#5F6368"
+ android:strokeWidth="0.684"
+ android:fillColor="#00000000"/>
+ <path
+ android:pathData="M77,176H83V184.09C83,184.59 82.59,185 82.09,185H77.91C77.41,185 77,184.59 77,184.09V176Z"
+ android:fillColor="#5F6368"/>
+</vector>
diff --git a/res/drawable/external_display_mirror_portrait.xml b/res/drawable/external_display_mirror_portrait.xml
new file mode 100644
index 0000000..0fe7f93
--- /dev/null
+++ b/res/drawable/external_display_mirror_portrait.xml
@@ -0,0 +1,55 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="232.02106dp"
+ android:viewportHeight="214"
+ android:viewportWidth="380"
+ android:width="412dp" >
+ <path
+ android:pathData="M16,0L364,0A16,16 0,0 1,380 16L380,198A16,16 0,0 1,364 214L16,214A16,16 0,0 1,0 198L0,16A16,16 0,0 1,16 0z"
+ android:fillColor="#00000000"/>
+ <path
+ android:pathData="M150.5,38L327.5,38A5.5,5.5 0,0 1,333 43.5L333,138.5A5.5,5.5 0,0 1,327.5 144L150.5,144A5.5,5.5 0,0 1,145 138.5L145,43.5A5.5,5.5 0,0 1,150.5 38z"
+ android:fillColor="#80868B"/>
+ <path
+ android:pathData="M150.58,39L327.42,39A4.58,4.58 0,0 1,332 43.58L332,138.42A4.58,4.58 0,0 1,327.42 143L150.58,143A4.58,4.58 0,0 1,146 138.42L146,43.58A4.58,4.58 0,0 1,150.58 39z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M254.25,144H223.75L221.52,173.34C221.48,173.82 221.08,174.18 220.6,174.18H211.37C211.25,174.18 211.12,174.21 211.01,174.26C210.11,174.65 210.39,176 211.37,176H266.63C267.61,176 267.89,174.65 266.99,174.26C266.88,174.21 266.75,174.18 266.63,174.18H257.4C256.92,174.18 256.52,173.82 256.48,173.34L254.25,144Z"
+ android:fillColor="#5F6368"/>
+ <path
+ android:pathData="M216,41L262,41A3,3 0,0 1,265 44L265,138A3,3 0,0 1,262 141L216,141A3,3 0,0 1,213 138L213,44A3,3 0,0 1,216 41z"
+ android:strokeWidth="2"
+ android:fillColor="#00000000"
+ android:strokeColor="#E0E994"/>
+ <path
+ android:pathData="M113,91.08V86.55C113,86.25 112.88,85.96 112.67,85.75C112.45,85.54 112.17,85.42 111.86,85.41V61.64C111.84,60.15 111.24,58.72 110.17,57.66C109.1,56.61 107.66,56.01 106.16,56H53.71C52.2,56.01 50.75,56.61 49.68,57.67C48.62,58.73 48.01,60.17 48,61.66V170.34C48.01,171.83 48.62,173.27 49.68,174.33C50.75,175.39 52.2,175.99 53.71,176H106.16C107.67,175.99 109.11,175.39 110.18,174.33C111.25,173.27 111.85,171.83 111.86,170.34V114.86C112.16,114.86 112.45,114.74 112.67,114.52C112.88,114.31 113,114.03 113,113.73V102.4C113,102.1 112.88,101.82 112.67,101.6C112.45,101.39 112.17,101.27 111.86,101.27V92.21C112.16,92.21 112.45,92.09 112.67,91.88C112.88,91.67 113,91.38 113,91.08ZM110.72,170.34C110.72,171.54 110.24,172.69 109.38,173.54C108.53,174.39 107.37,174.87 106.16,174.87H53.71C52.5,174.87 51.34,174.39 50.48,173.54C49.62,172.69 49.14,171.54 49.14,170.34V61.64C49.14,60.44 49.62,59.29 50.48,58.44C51.34,57.59 52.5,57.11 53.71,57.11H106.16C107.37,57.11 108.53,57.59 109.38,58.44C110.24,59.29 110.72,60.44 110.72,61.64V170.34Z"
+ android:fillColor="#80868B"/>
+ <path
+ android:pathData="M54,59L106,59A3,3 0,0 1,109 62L109,170A3,3 0,0 1,106 173L54,173A3,3 0,0 1,51 170L51,62A3,3 0,0 1,54 59z"
+ android:strokeColor="#E0E994"
+ android:strokeWidth="2"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M80,184.72V189.7C80,193.73 83.27,197 87.3,197H164.7C168.73,197 172,193.73 172,189.7V144"
+ android:strokeColor="#5F6368"
+ android:strokeWidth="0.684"
+ android:fillColor="#00000000"/>
+ <path
+ android:pathData="M77,176H83V184.09C83,184.59 82.59,185 82.09,185H77.91C77.41,185 77,184.59 77,184.09V176Z"
+ android:fillColor="#5F6368"/>
+</vector>
diff --git a/res/drawable/ic_do_not_disturb_on_24dp.xml b/res/drawable/ic_do_not_disturb_on_24dp.xml
deleted file mode 100644
index cace8d4..0000000
--- a/res/drawable/ic_do_not_disturb_on_24dp.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="?android:attr/colorControlNormal">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10c5.52,0 10,-4.48 10,-10C22,6.48 17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8c0,-4.41 3.59,-8 8,-8c4.41,0 8,3.59 8,8C20,16.41 16.41,20 12,20z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M7,11h10v2h-10z"/>
-</vector>
diff --git a/res/drawable/ic_external_display_32dp.xml b/res/drawable/ic_external_display_32dp.xml
new file mode 100644
index 0000000..3e18282
--- /dev/null
+++ b/res/drawable/ic_external_display_32dp.xml
@@ -0,0 +1,32 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="32dp"
+ android:viewportWidth="32"
+ android:viewportHeight="32">
+ <path
+ android:pathData="M16,16m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0"
+ android:fillColor="#FAFBD8"/>
+ <group>
+ <clip-path
+ android:pathData="M5.333,5.332h21.333v21.333h-21.333z"/>
+ <path
+ android:pathData="M12.689,23.288V21.643H14.333V19.976H9C8.555,19.976 8.17,19.813 7.844,19.488C7.518,19.162 7.355,18.769 7.355,18.31V9.665C7.355,9.206 7.518,8.814 7.844,8.488C8.17,8.162 8.555,7.999 9,7.999H23C23.444,7.999 23.829,8.162 24.155,8.488C24.481,8.814 24.644,9.206 24.644,9.665V18.31C24.644,18.769 24.481,19.162 24.155,19.488C23.829,19.813 23.444,19.976 23,19.976H17.666V21.643H19.311V23.288H12.689ZM9,18.31H23V9.665H9V18.31ZM9,18.31V9.665V18.31Z"
+ android:fillColor="#8E964B"/>
+ </group>
+</vector>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index d972e13..d346474 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -179,6 +179,9 @@
<dimen name="pointer_fill_style_circle_padding">8dp</dimen>
<dimen name="pointer_fill_style_shape_default_stroke">1dp</dimen>
<dimen name="pointer_fill_style_shape_hovered_stroke">3dp</dimen>
+ <dimen name="pointer_scale_padding">8dp</dimen>
+ <item name="pointer_scale_size_start" format="float" type="dimen">1.0</item>
+ <item name="pointer_scale_size_end" format="float" type="dimen">2.5</item>
<!-- RemoteAuth-->
<dimen name="remoteauth_fragment_padding_horizontal">40dp</dimen>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index f62ccae..5427cdd 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -36,4 +36,8 @@
<integer name="enrollment_progress_minimum_time_display">0</integer>
<!-- The time (in millis) to wait to collect messages in fingerprint enrollment before displaying it. -->
<integer name="enrollment_collect_time">0</integer>
+
+ <!-- PointerIcon Settings -->
+ <integer name="pointer_scale_seek_bar_start">0</integer>
+ <integer name="pointer_scale_seek_bar_end">3</integer>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 075056d..4f295b2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1893,6 +1893,37 @@
<!-- Nfc developer settings: The confirm button of the popup dialog. [CHAR_LIMIT=60] -->
<string name="nfc_reboot_dialog_confirm">Restart</string>
+ <!-- External Display settings. The keywords for searching. [CHAR LIMIT=40] -->
+ <string name="keywords_external_display">mirror, external display, connected display, usb display, resolution, rotation</string>
+ <!-- External Display settings. When external display is enabled. [CHAR LIMIT=40] -->
+ <string name="external_display_on">On</string>
+ <!-- External Display settings. When external display is disabled. [CHAR LIMIT=40] -->
+ <string name="external_display_off">Off</string>
+ <!-- External Display settings. The title of the screen. [CHAR LIMIT=40] -->
+ <string name="external_display_settings_title">External Display</string>
+ <!-- External Display use. The title of the use preference. [CHAR LIMIT=40] -->
+ <string name="external_display_use_title">Use external display</string>
+ <!-- External Display resolution settings. The title of the screen. [CHAR LIMIT=40] -->
+ <string name="external_display_resolution_settings_title">Display resolution</string>
+ <!-- External Display settings. Text that appears when scanning for devices is finished and no nearby device was found [CHAR LIMIT=40]-->
+ <string name="external_display_not_found">External display is disconnected</string>
+ <!-- External Display settings. Rotation of the external display -->
+ <string name="external_display_rotation">Rotation</string>
+ <!-- External Display settings. Standard rotation of the external display -->
+ <string name="external_display_standard_rotation">Standard</string>
+ <!-- External Display settings. 90 rotation of the external display -->
+ <string name="external_display_rotation_90">90°</string>
+ <!-- External Display settings. 180 rotation of the external display -->
+ <string name="external_display_rotation_180">180°</string>
+ <!-- External Display settings. 180 rotation of the external display -->
+ <string name="external_display_rotation_270">270°</string>
+ <!-- External Display settings. Footer title -->
+ <string name="external_display_change_resolution_footer_title">Changing rotation or resolution may stop any apps that are currently running</string>
+ <!-- External Display settings. No Displays footer title -->
+ <string name="external_display_not_found_footer_title">Your device must be connected to an external display to mirror your screen</string>
+ <!-- External Display settings. More resolution options -->
+ <string name="external_display_more_options_title">More options</string>
+
<!-- Wifi Display settings. The title of the screen. [CHAR LIMIT=40] -->
<string name="wifi_display_settings_title">Cast</string>
<!-- Wifi Display settings. The keywords of the setting. [CHAR LIMIT=NONE] -->
@@ -4571,6 +4602,12 @@
<!-- On Languages & input settings screen, setting summary. Setting for mouse pointer speed. [CHAR LIMIT=35] -->
<string name="pointer_speed">Pointer speed</string>
+ <!-- Setting for mouse pointer scale. [CHAR LIMIT=35] -->
+ <string name="pointer_scale">Pointer scale</string>
+ <!-- Content description for decreasing pointer scale. [CHAR LIMIT=35] -->
+ <string name="pointer_scale_decrease_content_description">Decrease pointer scale</string>
+ <!-- Setting for mouse pointer scale. [CHAR LIMIT=35] -->
+ <string name="pointer_scale_increase_content_description">Increase pointer scale</string>
<!-- On Languages & input settings screen, heading. Inside the "Languages & input settings" screen, this is the header for settings that relate to game controller devices. [CHAR LIMIT=40] -->
<string name="game_controller_settings_category">Game Controller</string>
@@ -7268,6 +7305,8 @@
<string name="help_url_install_certificate" translatable="false"></string>
<!-- Help URL, Tap & pay [DO NOT TRANSLATE] -->
<string name="help_url_nfc_payment" translatable="false"></string>
+ <!-- Help URL, External display [DO NOT TRANSLATE] -->
+ <string name="help_url_external_display" translatable="false"></string>
<!-- Help URL, Remote display [DO NOT TRANSLATE] -->
<string name="help_url_remote_display" translatable="false"></string>
<!-- Help URL, Face [DO NOT TRANSLATE] -->
@@ -7930,16 +7969,28 @@
<!-- Zen Modes: Option to add an automatic schedule for a mode. [CHAR_LIMIT=40] -->
<string name="zen_mode_select_schedule">Select activation type</string>
- <!-- Zen Modes: Option to choose a time-based schedule for a mode. [CHAR_LIMIT=40] -->
+ <!-- Priority Modes: Option to choose a time-based schedule for a mode. [CHAR_LIMIT=40] -->
<string name="zen_mode_select_schedule_time">Time</string>
- <!-- Zen Modes: Example text for the option to choose a time-based schedule for a mode. [CHAR_LIMIT=60] -->
+ <!-- Priority Modes: Example text for the option to choose a time-based schedule for a mode. [CHAR_LIMIT=60] -->
<string name="zen_mode_select_schedule_time_example">Ex. \"9:30 – 5:00 PM\"</string>
- <!-- Zen Modes: Option to choose a calendar-events-based schedule for a mode. [CHAR_LIMIT=40] -->
+ <!-- Priority Modes: Option to choose a calendar-events-based schedule for a mode. [CHAR_LIMIT=40] -->
<string name="zen_mode_select_schedule_calendar">Calendar</string>
- <!-- Zen Modes: Example text for the option to choose a calendar-events-based schedule for a mode. [CHAR_LIMIT=60] -->
+ <!-- Priority Modes: Example text for the option to choose a calendar-events-based schedule for a mode. [CHAR_LIMIT=60] -->
<string name="zen_mode_select_schedule_calendar_example">Ex. \"Personal calendar\"</string>
+ <!-- Priority Modes: Short text that indicates that a mode is currently on (active). [CHAR_LIMIT=10] -->
+ <string name="zen_mode_active_text">ON</string>
+
+ <!-- Priority Modes: Format string for the "current state + trigger description summary for rules in the list. [CHAR_LIMIT=10] -->
+ <string name="zen_mode_format_status_and_trigger" translatable="false"><xliff:g id="current_status" example="ON">%1$s</xliff:g> • <xliff:g id="trigger_description" example="Mon-Fri, 23:00-7:00">%2$s</xliff:g></string>
+
+ <!-- Priority Modes: Call to action for a mode that is disabled and needs to be configured. [CHAR_LIMIT=40] -->
+ <string name="zen_mode_disabled_tap_to_set_up">Tap to set up</string>
+
+ <!-- Priority Modes: Indicates that a mode is disabled by the user. [CHAR_LIMIT=40] -->
+ <string name="zen_mode_disabled_by_user">Paused</string>
+
<!-- Subtitle for the Do not Disturb slice. [CHAR LIMIT=50]-->
<string name="zen_mode_slice_subtitle">Limit interruptions</string>
@@ -7955,9 +8006,15 @@
<!-- Do not disturb: Title for dialog that allows users to delete DND rules/schedules[CHAR LIMIT=40] -->
<string name="zen_mode_delete_automatic_rules">Delete schedules</string>
- <!-- Do not disturb: Delete text button presented in a dialog to confirm the user would like to delete the selected DND rules. [CHAR LIMIT=30] -->
+ <!-- Do not disturb: Delete text button presented in a dialog to confirm the user would like to delete the selected DND rules. [CHAR LIMIT=30] -->
<string name="zen_mode_schedule_delete">Delete</string>
+ <!-- Do not disturb: Menu option for deleting a mode on its configuration page [CHAR LIMIT=40] -->
+ <string name="zen_mode_menu_delete_mode">Delete mode</string>
+
+ <!-- Do not disturb: Confirmation dialog asking the user whether they would like to delete the named mode [CHAR LIMIT: 40] -->
+ <string name="zen_mode_delete_mode_confirmation">Delete \"<xliff:g id="mode" example="My Schedule">%1$s</xliff:g>\" mode?</string>
+
<!-- Do not disturb: Edit label for button that allows user to edit the dnd schedule name. [CHAR LIMIT=30] -->
<string name="zen_mode_rule_name_edit">Edit</string>
@@ -12643,7 +12700,7 @@
<string name="default_print_service_main_switch_title">Use print service</string>
<!-- Title for multiple users main switch. [CHAR LIMIT=50] -->
- <string name="multiple_users_main_switch_title">Allow multiple users</string>
+ <string name="multiple_users_main_switch_title">Allow user switch</string>
<!-- Search keywords for the "Allow Multiple Users" section in Multiple Users Screen. [CHAR LIMIT=NONE] -->
<string name="multiple_users_main_switch_keywords">allow, multiple, user, permit, many</string>
<!-- Search keywords for the Users Screen. [CHAR LIMIT=NONE] -->
diff --git a/res/xml/external_display_resolution_settings.xml b/res/xml/external_display_resolution_settings.xml
new file mode 100644
index 0000000..6ac6b1a
--- /dev/null
+++ b/res/xml/external_display_resolution_settings.xml
@@ -0,0 +1,20 @@
+<?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"
+ android:title="@string/external_display_resolution_settings_title">
+</PreferenceScreen>
diff --git a/res/xml/external_display_settings.xml b/res/xml/external_display_settings.xml
new file mode 100644
index 0000000..0047211
--- /dev/null
+++ b/res/xml/external_display_settings.xml
@@ -0,0 +1,22 @@
+<?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"
+ settings:keywords="@string/keywords_external_display"
+ android:title="@string/external_display_settings_title">
+</PreferenceScreen>
diff --git a/res/xml/trackpad_settings.xml b/res/xml/trackpad_settings.xml
index 1eb16b7..04422dd 100644
--- a/res/xml/trackpad_settings.xml
+++ b/res/xml/trackpad_settings.xml
@@ -66,9 +66,19 @@
android:key="pointer_fill_style"
android:title="@string/pointer_fill_style"
android:order="50"
- android:dialogTitle="@string/pointer_fill_style"
settings:controller="com.android.settings.inputmethod.PointerFillStylePreferenceController"/>
+ <com.android.settings.widget.LabeledSeekBarPreference
+ android:key="pointer_scale"
+ android:title="@string/pointer_scale"
+ android:order="70"
+ android:max="@integer/pointer_scale_seek_bar_end"
+ settings:iconStart="@drawable/ic_remove_24dp"
+ settings:iconStartContentDescription="@string/pointer_scale_decrease_content_description"
+ settings:iconEnd="@drawable/ic_add_24dp"
+ settings:iconEndContentDescription="@string/pointer_scale_increase_content_description"
+ settings:controller="com.android.settings.inputmethod.PointerScaleSeekBarController" />
+
<com.android.settingslib.widget.ButtonPreference
android:key="trackpad_touch_gesture"
android:title="@string/trackpad_touch_gesture"
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 3367bf1..e3bb1a1 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -18,6 +18,8 @@
import static android.provider.Settings.ACTION_PRIVACY_SETTINGS;
+import android.annotation.FlaggedApi;
+import android.app.Flags;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Bundle;
@@ -317,11 +319,13 @@
public static class PrintSettingsActivity extends SettingsActivity { /* empty */ }
public static class PrintJobSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeSettingsActivity extends SettingsActivity { /* empty */ }
- public static class ZenModeBehaviorSettingsActivity extends SettingsActivity { /* empty */ }
- public static class ZenModeBlockedEffectsSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeAutomationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeScheduleRuleSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenModeEventRuleSettingsActivity extends SettingsActivity { /* empty */ }
+ @FlaggedApi(Flags.FLAG_MODES_UI)
+ public static class ModeSettingsActivity extends SettingsActivity { /* empty */ }
+ @FlaggedApi(Flags.FLAG_MODES_UI)
+ public static class ModesSettingsActivity extends SettingsActivity { /* empty */ }
public static class SoundSettingsActivity extends SettingsActivity { /* empty */ }
public static class ConfigureNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class ConversationListSettingsActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/SettingsPreferenceFragmentBase.java b/src/com/android/settings/SettingsPreferenceFragmentBase.java
new file mode 100644
index 0000000..dd2e287
--- /dev/null
+++ b/src/com/android/settings/SettingsPreferenceFragmentBase.java
@@ -0,0 +1,94 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.search.Indexable;
+
+/**
+ * Base class for fragment suitable for unit testing.
+ */
+public abstract class SettingsPreferenceFragmentBase extends SettingsPreferenceFragment
+ implements Indexable {
+ @Override
+ @SuppressWarnings({"RequiresNullabilityAnnotation"})
+ public void onCreate(final Bundle icicle) {
+ super.onCreate(icicle);
+ onCreateCallback(icicle);
+ }
+
+ @Override
+ @SuppressWarnings({"RequiresNullabilityAnnotation"})
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ onActivityCreatedCallback(savedInstanceState);
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull final Bundle outState) {
+ super.onSaveInstanceState(outState);
+ onSaveInstanceStateCallback(outState);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ onStartCallback();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ onStopCallback();
+ }
+
+ protected Activity getCurrentActivity() {
+ return getActivity();
+ }
+
+ /**
+ * Callback called from {@link #onCreate}
+ */
+ public abstract void onCreateCallback(@Nullable Bundle icicle);
+
+ /**
+ * Callback called from {@link #onActivityCreated}
+ */
+ public abstract void onActivityCreatedCallback(@Nullable Bundle savedInstanceState);
+
+ /**
+ * Callback called from {@link #onStart}
+ */
+ public abstract void onStartCallback();
+
+ /**
+ * Callback called from {@link #onStop}
+ */
+ public abstract void onStopCallback();
+
+ /**
+ * Callback called from {@link #onSaveInstanceState}
+ */
+ public void onSaveInstanceStateCallback(@NonNull final Bundle outState) {
+ // Do nothing.
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
index 56a3005..2548b95 100644
--- a/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
+++ b/src/com/android/settings/connecteddevice/ConnectedDeviceGroupController.java
@@ -15,6 +15,8 @@
*/
package com.android.settings.connecteddevice;
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isExternalDisplaySettingsPageEnabled;
+
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.input.InputManager;
@@ -22,6 +24,8 @@
import android.util.Log;
import android.view.InputDevice;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
@@ -31,12 +35,15 @@
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
import com.android.settings.bluetooth.Utils;
+import com.android.settings.connecteddevice.display.ExternalDisplayUpdater;
import com.android.settings.connecteddevice.dock.DockUpdater;
import com.android.settings.connecteddevice.stylus.StylusDeviceUpdater;
import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.FeatureFlags;
+import com.android.settings.flags.FeatureFlagsImpl;
import com.android.settings.flags.Flags;
import com.android.settings.overlay.DockUpdaterFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
@@ -64,6 +71,8 @@
@VisibleForTesting
PreferenceGroup mPreferenceGroup;
+ @Nullable
+ private ExternalDisplayUpdater mExternalDisplayUpdater;
private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
private ConnectedUsbDeviceUpdater mConnectedUsbDeviceUpdater;
private DockUpdater mConnectedDockUpdater;
@@ -71,6 +80,8 @@
private final PackageManager mPackageManager;
private final InputManager mInputManager;
private final LocalBluetoothManager mLocalBluetoothManager;
+ @NonNull
+ private final FeatureFlags mFeatureFlags = new FeatureFlagsImpl();
public ConnectedDeviceGroupController(Context context) {
super(context, KEY);
@@ -81,6 +92,10 @@
@Override
public void onStart() {
+ if (mExternalDisplayUpdater != null) {
+ mExternalDisplayUpdater.registerCallback();
+ }
+
if (mBluetoothDeviceUpdater != null) {
mBluetoothDeviceUpdater.registerCallback();
mBluetoothDeviceUpdater.refreshPreference();
@@ -101,6 +116,10 @@
@Override
public void onStop() {
+ if (mExternalDisplayUpdater != null) {
+ mExternalDisplayUpdater.unregisterCallback();
+ }
+
if (mBluetoothDeviceUpdater != null) {
mBluetoothDeviceUpdater.unregisterCallback();
}
@@ -127,6 +146,10 @@
if (isAvailable()) {
final Context context = screen.getContext();
+ if (mExternalDisplayUpdater != null) {
+ mExternalDisplayUpdater.initPreference(context);
+ }
+
if (mBluetoothDeviceUpdater != null) {
mBluetoothDeviceUpdater.setPrefContext(context);
mBluetoothDeviceUpdater.forceUpdate();
@@ -150,7 +173,8 @@
@Override
public int getAvailabilityStatus() {
- return (hasBluetoothFeature()
+ return (hasExternalDisplayFeature()
+ || hasBluetoothFeature()
|| hasUsbFeature()
|| hasUsiStylusFeature()
|| mConnectedDockUpdater != null)
@@ -180,11 +204,13 @@
}
@VisibleForTesting
- void init(BluetoothDeviceUpdater bluetoothDeviceUpdater,
+ void init(@Nullable ExternalDisplayUpdater externalDisplayUpdater,
+ BluetoothDeviceUpdater bluetoothDeviceUpdater,
ConnectedUsbDeviceUpdater connectedUsbDeviceUpdater,
DockUpdater connectedDockUpdater,
StylusDeviceUpdater connectedStylusDeviceUpdater) {
+ mExternalDisplayUpdater = externalDisplayUpdater;
mBluetoothDeviceUpdater = bluetoothDeviceUpdater;
mConnectedUsbDeviceUpdater = connectedUsbDeviceUpdater;
mConnectedDockUpdater = connectedDockUpdater;
@@ -197,7 +223,10 @@
FeatureFactory.getFeatureFactory().getDockUpdaterFeatureProvider();
final DockUpdater connectedDockUpdater =
dockUpdaterFeatureProvider.getConnectedDockUpdater(context, this);
- init(hasBluetoothFeature()
+ init(hasExternalDisplayFeature()
+ ? new ExternalDisplayUpdater(this, fragment.getMetricsCategory())
+ : null,
+ hasBluetoothFeature()
? new ConnectedBluetoothDeviceUpdater(context, this,
fragment.getMetricsCategory())
: null,
@@ -210,6 +239,19 @@
: null);
}
+ /**
+ * @return trunk stable feature flags.
+ */
+ @VisibleForTesting
+ @NonNull
+ public FeatureFlags getFeatureFlags() {
+ return mFeatureFlags;
+ }
+
+ private boolean hasExternalDisplayFeature() {
+ return isExternalDisplaySettingsPageEnabled(getFeatureFlags());
+ }
+
private boolean hasBluetoothFeature() {
return mPackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
index 0bb6b60..bfccdc4 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreference.java
@@ -95,14 +95,14 @@
}
private void configureInvisibleStateForQrCodeIcon(ImageButton shareButton, View divider) {
- divider.setVisibility(View.INVISIBLE);
- shareButton.setVisibility(View.INVISIBLE);
+ divider.setVisibility(View.GONE);
+ shareButton.setVisibility(View.GONE);
shareButton.setOnClickListener(null);
}
private void launchAudioSharingQrCodeFragment() {
new SubSettingLauncher(getContext())
- .setTitleText(getContext().getString(R.string.audio_streams_qr_code_page_title))
+ .setTitleRes(R.string.audio_streams_qr_code_page_title)
.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 24b8f20..894ba48 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
@@ -26,6 +26,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
@@ -56,7 +57,8 @@
private static final boolean DEBUG = BluetoothUtils.D;
private static final String PREF_KEY = "audio_sharing_stream_name";
- private final BluetoothLeBroadcast.Callback mBroadcastCallback =
+ @VisibleForTesting
+ final BluetoothLeBroadcast.Callback mBroadcastCallback =
new BluetoothLeBroadcast.Callback() {
@Override
public void onBroadcastMetadataChanged(
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceController.java
index 258cf3b..14930e1 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceController.java
@@ -19,12 +19,19 @@
import static com.android.settings.connecteddevice.audiosharing.AudioSharingUtils.isBroadcasting;
import android.app.settings.SettingsEnums;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
import android.util.Log;
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;
@@ -41,15 +48,19 @@
public class AudioSharingPasswordPreferenceController extends BasePreferenceController
implements ValidatedEditTextPreference.Validator,
- AudioSharingPasswordPreference.OnDialogEventListener {
-
+ AudioSharingPasswordPreference.OnDialogEventListener,
+ DefaultLifecycleObserver {
private static final String TAG = "AudioSharingPasswordPreferenceController";
private static final String PREF_KEY = "audio_sharing_stream_password";
private static final String SHARED_PREF_NAME = "audio_sharing_settings";
private static final String SHARED_PREF_KEY = "default_password";
+ @Nullable private final ContentResolver mContentResolver;
+ @Nullable private final SharedPreferences mSharedPref;
@Nullable private final LocalBluetoothManager mBtManager;
@Nullable private final LocalBluetoothLeBroadcast mBroadcast;
@Nullable private AudioSharingPasswordPreference mPreference;
+ private final ContentObserver mSettingsObserver;
+ private final SharedPreferences.OnSharedPreferenceChangeListener mSharedPrefChangeListener;
private final AudioSharingPasswordValidator mAudioSharingPasswordValidator;
private final MetricsFeatureProvider mMetricsFeatureProvider;
@@ -61,10 +72,45 @@
? mBtManager.getProfileManager().getLeAudioBroadcastProfile()
: null;
mAudioSharingPasswordValidator = new AudioSharingPasswordValidator();
+ mContentResolver = context.getContentResolver();
+ mSettingsObserver = new PasswordSettingsObserver();
+ mSharedPref = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
+ mSharedPrefChangeListener = new PasswordSharedPrefChangeListener();
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
}
@Override
+ public void onStart(@NonNull LifecycleOwner owner) {
+ if (!isAvailable()) {
+ Log.d(TAG, "Feature is not available.");
+ return;
+ }
+ if (mContentResolver != null) {
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE),
+ false,
+ mSettingsObserver);
+ }
+ if (mSharedPref != null) {
+ mSharedPref.registerOnSharedPreferenceChangeListener(mSharedPrefChangeListener);
+ }
+ }
+
+ @Override
+ public void onStop(@NonNull LifecycleOwner owner) {
+ if (!isAvailable()) {
+ Log.d(TAG, "Feature is not available.");
+ return;
+ }
+ if (mContentResolver != null) {
+ mContentResolver.unregisterContentObserver(mSettingsObserver);
+ }
+ if (mSharedPref != null) {
+ mSharedPref.unregisterOnSharedPreferenceChangeListener(mSharedPrefChangeListener);
+ }
+ }
+
+ @Override
public int getAvailabilityStatus() {
return AudioSharingUtils.isFeatureEnabled() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
@@ -125,7 +171,6 @@
persistDefaultPassword(mContext, password);
mBroadcast.setBroadcastCode(
isPublicBroadcast ? new byte[0] : password.getBytes());
- updatePreference();
mMetricsFeatureProvider.action(
mContext,
SettingsEnums.ACTION_AUDIO_STREAM_PASSWORD_UPDATED,
@@ -164,32 +209,52 @@
});
}
- private static void persistDefaultPassword(Context context, String defaultPassword) {
+ private class PasswordSettingsObserver extends ContentObserver {
+ PasswordSettingsObserver() {
+ super(new Handler(Looper.getMainLooper()));
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ Log.d(TAG, "onChange, broadcast password has been changed");
+ updatePreference();
+ }
+ }
+
+ private class PasswordSharedPrefChangeListener
+ implements SharedPreferences.OnSharedPreferenceChangeListener {
+ @Override
+ public void onSharedPreferenceChanged(
+ SharedPreferences sharedPreferences, @Nullable String key) {
+ if (!SHARED_PREF_KEY.equals(key)) {
+ return;
+ }
+ Log.d(TAG, "onSharedPreferenceChanged, default password has been changed");
+ updatePreference();
+ }
+ }
+
+ private void persistDefaultPassword(Context context, String defaultPassword) {
if (getDefaultPassword(context).equals(defaultPassword)) {
return;
}
-
- SharedPreferences sharedPref =
- context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
- if (sharedPref == null) {
+ if (mSharedPref == null) {
Log.w(TAG, "persistDefaultPassword(): sharedPref is empty!");
return;
}
- SharedPreferences.Editor editor = sharedPref.edit();
+ SharedPreferences.Editor editor = mSharedPref.edit();
editor.putString(SHARED_PREF_KEY, defaultPassword);
editor.apply();
}
- private static String getDefaultPassword(Context context) {
- SharedPreferences sharedPref =
- context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
- if (sharedPref == null) {
+ private String getDefaultPassword(Context context) {
+ if (mSharedPref == null) {
Log.w(TAG, "getDefaultPassword(): sharedPref is empty!");
return "";
}
- String value = sharedPref.getString(SHARED_PREF_KEY, "");
+ String value = mSharedPref.getString(SHARED_PREF_KEY, "");
if (value != null && value.isEmpty()) {
Log.w(TAG, "getDefaultPassword(): default password is empty!");
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseState.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseState.java
index 24a28dd..7be01a2 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseState.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseState.java
@@ -36,7 +36,8 @@
@Nullable private static AddSourceWaitForResponseState sInstance = null;
- private AddSourceWaitForResponseState() {}
+ @VisibleForTesting
+ AddSourceWaitForResponseState() {}
static AddSourceWaitForResponseState getInstance() {
if (sInstance == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java
index b0c5b6b..4bb8475 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandler.java
@@ -35,10 +35,10 @@
private static final boolean DEBUG = BluetoothUtils.D;
@VisibleForTesting static final int EMPTY_STRING_RES = 0;
- final AudioStreamsRepository mAudioStreamsRepository = AudioStreamsRepository.getInstance();
final Handler mHandler = new Handler(Looper.getMainLooper());
final MetricsFeatureProvider mMetricsFeatureProvider =
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
+ AudioStreamsRepository mAudioStreamsRepository = AudioStreamsRepository.getInstance();
AudioStreamStateHandler() {}
@@ -112,4 +112,9 @@
AudioStreamsProgressCategoryController.AudioStreamState getStateEnum() {
return AudioStreamsProgressCategoryController.AudioStreamState.UNKNOWN;
}
+
+ @VisibleForTesting
+ void setAudioStreamsRepositoryForTesting(AudioStreamsRepository repository) {
+ mAudioStreamsRepository = repository;
+ }
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdater.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdater.java
index ab22b07..47ee440 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdater.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdater.java
@@ -16,24 +16,22 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.text.TextUtils;
-import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settings.R;
import com.android.settings.bluetooth.Utils;
import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.utils.ThreadUtils;
public class AudioStreamsActiveDeviceSummaryUpdater implements BluetoothCallback {
- private static final String TAG = "AudioStreamsActiveDeviceSummaryUpdater";
- private static final boolean DEBUG = BluetoothUtils.D;
private final LocalBluetoothManager mBluetoothManager;
private Context mContext;
@Nullable private String mSummary;
@@ -47,17 +45,20 @@
}
@Override
- public void onActiveDeviceChanged(
- @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
- if (DEBUG) {
- Log.d(
- TAG,
- "onActiveDeviceChanged() with activeDevice : "
- + (activeDevice == null ? "null" : activeDevice.getAddress())
- + " on profile : "
- + bluetoothProfile);
+ public void onBluetoothStateChanged(@AdapterState int bluetoothState) {
+ if (bluetoothState == BluetoothAdapter.STATE_OFF) {
+ notifyChangeIfNeeded();
}
- if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
+ }
+
+ @Override
+ public void onProfileConnectionStateChanged(
+ @NonNull CachedBluetoothDevice cachedDevice,
+ @ConnectionState int state,
+ int bluetoothProfile) {
+ if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+ && (state == BluetoothAdapter.STATE_CONNECTED
+ || state == BluetoothAdapter.STATE_DISCONNECTED)) {
notifyChangeIfNeeded();
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
index 3174ace..0107c6e 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java
@@ -16,12 +16,12 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import com.android.settings.bluetooth.Utils;
@@ -44,9 +44,13 @@
private final BluetoothCallback mBluetoothCallback =
new BluetoothCallback() {
@Override
- public void onActiveDeviceChanged(
- @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
- if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
+ public void onProfileConnectionStateChanged(
+ @NonNull CachedBluetoothDevice cachedDevice,
+ @ConnectionState int state,
+ int bluetoothProfile) {
+ if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+ && (state == BluetoothAdapter.STATE_CONNECTED
+ || state == BluetoothAdapter.STATE_DISCONNECTED)) {
updateVisibility();
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
index 775186a..6e335a0 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
@@ -71,7 +71,8 @@
*
* @param source The LE broadcast metadata representing the audio source.
*/
- void addSource(BluetoothLeBroadcastMetadata source) {
+ @VisibleForTesting
+ public void addSource(BluetoothLeBroadcastMetadata source) {
if (mLeBroadcastAssistant == null) {
Log.w(TAG, "addSource(): LeBroadcastAssistant is null!");
return;
@@ -97,7 +98,8 @@
}
/** Removes sources from LE broadcasts associated for all active sinks based on broadcast Id. */
- void removeSource(int broadcastId) {
+ @VisibleForTesting
+ public void removeSource(int broadcastId) {
if (mLeBroadcastAssistant == null) {
Log.w(TAG, "removeSource(): LeBroadcastAssistant is null!");
return;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
index cb3a0da..3370d8d 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
@@ -19,7 +19,6 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.util.Log;
public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastAssistantCallback {
private static final String TAG = "AudioStreamsProgressCategoryCallback";
@@ -53,10 +52,6 @@
@Override
public void onSearchStarted(int reason) {
super.onSearchStarted(reason);
- if (mCategoryController == null) {
- Log.w(TAG, "onSearchStarted() : mCategoryController is null!");
- return;
- }
mCategoryController.setScanning(true);
}
@@ -69,10 +64,6 @@
@Override
public void onSearchStopped(int reason) {
super.onSearchStopped(reason);
- if (mCategoryController == null) {
- Log.w(TAG, "onSearchStopped() : mCategoryController is null!");
- return;
- }
mCategoryController.setScanning(false);
}
@@ -86,10 +77,6 @@
@Override
public void onSourceFound(BluetoothLeBroadcastMetadata source) {
super.onSourceFound(source);
- if (mCategoryController == null) {
- Log.w(TAG, "onSourceFound() : mCategoryController is null!");
- return;
- }
mCategoryController.handleSourceFound(source);
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
index 890879e..9bbf135 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
@@ -20,6 +20,7 @@
import android.app.AlertDialog;
import android.app.settings.SettingsEnums;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
@@ -27,10 +28,12 @@
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceScreen;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.bluetooth.Utils;
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
@@ -55,13 +58,35 @@
implements DefaultLifecycleObserver {
private static final String TAG = "AudioStreamsProgressCategoryController";
private static final boolean DEBUG = BluetoothUtils.D;
- private static final int UNSET_BROADCAST_ID = -1;
- private final BluetoothCallback mBluetoothCallback =
+ @VisibleForTesting static final int UNSET_BROADCAST_ID = -1;
+
+ @VisibleForTesting
+ final BluetoothCallback mBluetoothCallback =
new BluetoothCallback() {
@Override
- public void onActiveDeviceChanged(
- @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
- if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
+ public void onBluetoothStateChanged(@AdapterState int bluetoothState) {
+ Log.d(TAG, "onBluetoothStateChanged() with bluetoothState : " + bluetoothState);
+ if (bluetoothState == BluetoothAdapter.STATE_OFF) {
+ mExecutor.execute(() -> init());
+ }
+ }
+
+ @Override
+ public void onProfileConnectionStateChanged(
+ @NonNull CachedBluetoothDevice cachedDevice,
+ @ConnectionState int state,
+ int bluetoothProfile) {
+ Log.d(
+ TAG,
+ "onProfileConnectionStateChanged() with cachedDevice : "
+ + cachedDevice.getAddress()
+ + " with state : "
+ + state
+ + " on profile : "
+ + bluetoothProfile);
+ if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+ && (state == BluetoothAdapter.STATE_CONNECTED
+ || state == BluetoothAdapter.STATE_DISCONNECTED)) {
mExecutor.execute(() -> init());
}
}
@@ -92,7 +117,7 @@
SOURCE_ADDED,
}
- private final Executor mExecutor;
+ @VisibleForTesting Executor mExecutor;
private final AudioStreamsProgressCategoryCallback mBroadcastAssistantCallback;
private final AudioStreamsHelper mAudioStreamsHelper;
private final MediaControlHelper mMediaControlHelper;
@@ -103,7 +128,7 @@
private @Nullable BluetoothLeBroadcastMetadata mSourceFromQrCode;
private SourceOriginForLogging mSourceFromQrCodeOriginForLogging;
@Nullable private AudioStreamsProgressCategoryPreference mCategoryPreference;
- @Nullable private AudioStreamsDashboardFragment mFragment;
+ @Nullable private Fragment mFragment;
public AudioStreamsProgressCategoryController(Context context, String preferenceKey) {
super(context, preferenceKey);
@@ -142,12 +167,12 @@
mExecutor.execute(this::stopScanning);
}
- void setFragment(AudioStreamsDashboardFragment fragment) {
+ void setFragment(Fragment fragment) {
mFragment = fragment;
}
@Nullable
- AudioStreamsDashboardFragment getFragment() {
+ Fragment getFragment() {
return mFragment;
}
@@ -546,7 +571,8 @@
return preference;
}
- private void moveToState(AudioStreamPreference preference, AudioStreamState state) {
+ @VisibleForTesting
+ void moveToState(AudioStreamPreference preference, AudioStreamState state) {
AudioStreamStateHandler stateHandler =
switch (state) {
case SYNCED -> SyncedState.getInstance();
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
index 5f50be7..d0d82fb 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
@@ -16,6 +16,7 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;
@@ -47,9 +48,13 @@
final BluetoothCallback mBluetoothCallback =
new BluetoothCallback() {
@Override
- public void onActiveDeviceChanged(
- @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
- if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
+ public void onProfileConnectionStateChanged(
+ @NonNull CachedBluetoothDevice cachedDevice,
+ @ConnectionState int state,
+ int bluetoothProfile) {
+ if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+ && (state == BluetoothAdapter.STATE_CONNECTED
+ || state == BluetoothAdapter.STATE_DISCONNECTED)) {
updateVisibility();
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedState.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedState.java
index ee84429..88393ab 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedState.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedState.java
@@ -25,6 +25,7 @@
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.dashboard.DashboardFragment;
class SourceAddedState extends AudioStreamStateHandler {
@VisibleForTesting
@@ -32,7 +33,8 @@
@Nullable private static SourceAddedState sInstance = null;
- private SourceAddedState() {}
+ @VisibleForTesting
+ SourceAddedState() {}
static SourceAddedState getInstance() {
if (sInstance == null) {
@@ -80,13 +82,13 @@
AudioStreamDetailsFragment.BROADCAST_ID_ARG, p.getAudioStreamBroadcastId());
new SubSettingLauncher(p.getContext())
- .setTitleText(
- p.getContext().getString(R.string.audio_streams_detail_page_title))
+ .setTitleRes(R.string.audio_streams_detail_page_title)
.setDestination(AudioStreamDetailsFragment.class.getName())
.setSourceMetricsCategory(
- controller.getFragment() == null
+ !(controller.getFragment() instanceof DashboardFragment)
? SettingsEnums.PAGE_UNKNOWN
- : controller.getFragment().getMetricsCategory())
+ : ((DashboardFragment) controller.getFragment())
+ .getMetricsCategory())
.setArguments(broadcast)
.launch();
return true;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncState.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncState.java
index 55f61fd..9689b26 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncState.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncState.java
@@ -39,7 +39,8 @@
@Nullable private static WaitForSyncState sInstance = null;
- private WaitForSyncState() {}
+ @VisibleForTesting
+ WaitForSyncState() {}
static WaitForSyncState getInstance() {
if (sInstance == null) {
@@ -114,7 +115,8 @@
SettingsEnums.DIALOG_AUDIO_STREAM_MAIN_WAIT_FOR_SYNC_TIMEOUT);
}
- private void launchQrCodeScanFragment(Context context, Fragment fragment) {
+ @VisibleForTesting
+ void launchQrCodeScanFragment(Context context, Fragment fragment) {
new SubSettingLauncher(context)
.setTitleRes(R.string.audio_streams_main_page_scan_qr_code_title)
.setDestination(AudioStreamsQrCodeScanFragment.class.getName())
diff --git a/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java b/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java
new file mode 100644
index 0000000..09f8e92
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragment.java
@@ -0,0 +1,544 @@
+/*
+ * 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.display;
+
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_HELP_URL;
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DISPLAY_ID_ARG;
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE;
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplayAllowed;
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isUseDisplaySettingEnabled;
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isResolutionSettingEnabled;
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isRotationSettingEnabled;
+
+import android.app.Activity;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Display;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragmentBase;
+import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DisplayListener;
+import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.Injector;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.Indexable;
+import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.widget.FooterPreference;
+import com.android.settingslib.widget.IllustrationPreference;
+import com.android.settingslib.widget.MainSwitchPreference;
+import com.android.settingslib.widget.TwoTargetPreference;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The Settings screen for External Displays configuration and connection management.
+ */
+@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
+public class ExternalDisplayPreferenceFragment extends SettingsPreferenceFragmentBase
+ implements Indexable {
+ static final int EXTERNAL_DISPLAY_SETTINGS_RESOURCE = R.xml.external_display_settings;
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(EXTERNAL_DISPLAY_SETTINGS_RESOURCE);
+ static final String DISPLAYS_LIST_PREFERENCE_KEY = "displays_list_preference";
+ static final String EXTERNAL_DISPLAY_USE_PREFERENCE_KEY = "external_display_use_preference";
+ static final String EXTERNAL_DISPLAY_ROTATION_KEY = "external_display_rotation";
+ static final String EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY = "external_display_resolution";
+ static final int EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE =
+ R.string.external_display_change_resolution_footer_title;
+ static final int EXTERNAL_DISPLAY_LANDSCAPE_DRAWABLE =
+ R.drawable.external_display_mirror_landscape;
+ static final int EXTERANAL_DISPLAY_TITLE_RESOURCE =
+ R.string.external_display_settings_title;
+ static final int EXTERNAL_DISPLAY_USE_TITLE_RESOURCE =
+ R.string.external_display_use_title;
+ static final int EXTERNAL_DISPLAY_NOT_FOUND_FOOTER_RESOURCE =
+ R.string.external_display_not_found_footer_title;
+ static final int EXTERNAL_DISPLAY_PORTRAIT_DRAWABLE =
+ R.drawable.external_display_mirror_portrait;
+ static final int EXTERNAL_DISPLAY_ROTATION_TITLE_RESOURCE =
+ R.string.external_display_rotation;
+ static final int EXTERNAL_DISPLAY_RESOLUTION_TITLE_RESOURCE =
+ R.string.external_display_resolution_settings_title;
+ @VisibleForTesting
+ static final String PREVIOUSLY_SHOWN_LIST_KEY = "mPreviouslyShownListOfDisplays";
+ private boolean mStarted;
+ @Nullable
+ private MainSwitchPreference mUseDisplayPref;
+ @Nullable
+ private IllustrationPreference mImagePreference;
+ @Nullable
+ private Preference mResolutionPreference;
+ @Nullable
+ private ListPreference mRotationPref;
+ @Nullable
+ private FooterPreference mFooterPreference;
+ @Nullable
+ private PreferenceCategory mDisplaysPreference;
+ @Nullable
+ private Injector mInjector;
+ @Nullable
+ private String[] mRotationEntries;
+ @Nullable
+ private String[] mRotationEntriesValues;
+ @NonNull
+ private final Runnable mUpdateRunnable = this::update;
+ private final DisplayListener mListener = new DisplayListener() {
+ @Override
+ public void update(int displayId) {
+ scheduleUpdate();
+ }
+ };
+ private boolean mPreviouslyShownListOfDisplays;
+
+ public ExternalDisplayPreferenceFragment() {}
+
+ @VisibleForTesting
+ ExternalDisplayPreferenceFragment(@NonNull Injector injector) {
+ mInjector = injector;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY;
+ }
+
+ @Override
+ public int getHelpResource() {
+ return EXTERNAL_DISPLAY_HELP_URL;
+ }
+
+ @Override
+ public void onSaveInstanceStateCallback(@NonNull Bundle outState) {
+ outState.putSerializable(PREVIOUSLY_SHOWN_LIST_KEY,
+ (Serializable) mPreviouslyShownListOfDisplays);
+ }
+
+ @Override
+ public void onCreateCallback(@Nullable Bundle icicle) {
+ if (mInjector == null) {
+ mInjector = new Injector(getPrefContext());
+ }
+ addPreferencesFromResource(EXTERNAL_DISPLAY_SETTINGS_RESOURCE);
+ }
+
+ @Override
+ public void onActivityCreatedCallback(@Nullable Bundle savedInstanceState) {
+ restoreState(savedInstanceState);
+ View view = getView();
+ TextView emptyView = null;
+ if (view != null) {
+ emptyView = (TextView) view.findViewById(android.R.id.empty);
+ }
+ if (emptyView != null) {
+ emptyView.setText(EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE);
+ setEmptyView(emptyView);
+ }
+ }
+
+ @Override
+ public void onStartCallback() {
+ mStarted = true;
+ if (mInjector == null) {
+ return;
+ }
+ mInjector.registerDisplayListener(mListener);
+ scheduleUpdate();
+ }
+
+ @Override
+ public void onStopCallback() {
+ mStarted = false;
+ if (mInjector == null) {
+ return;
+ }
+ mInjector.unregisterDisplayListener(mListener);
+ unscheduleUpdate();
+ }
+
+ /**
+ * @return id of the preference.
+ */
+ @Override
+ protected int getPreferenceScreenResId() {
+ return EXTERNAL_DISPLAY_SETTINGS_RESOURCE;
+ }
+
+ @VisibleForTesting
+ protected void launchResolutionSelector(@NonNull final Context context, final int displayId) {
+ final Bundle args = new Bundle();
+ args.putInt(DISPLAY_ID_ARG, displayId);
+ new SubSettingLauncher(context)
+ .setDestination(ResolutionPreferenceFragment.class.getName())
+ .setArguments(args)
+ .setSourceMetricsCategory(getMetricsCategory()).launch();
+ }
+
+ @VisibleForTesting
+ protected void launchDisplaySettings(final int displayId) {
+ final Bundle args = new Bundle();
+ var context = getPrefContext();
+ args.putInt(DISPLAY_ID_ARG, displayId);
+ new SubSettingLauncher(context)
+ .setDestination(this.getClass().getName())
+ .setArguments(args)
+ .setSourceMetricsCategory(getMetricsCategory()).launch();
+ }
+
+ /**
+ * Returns the preference for the footer.
+ */
+ @NonNull
+ @VisibleForTesting
+ FooterPreference getFooterPreference(@NonNull Context context) {
+ if (mFooterPreference == null) {
+ mFooterPreference = new FooterPreference(context);
+ mFooterPreference.setPersistent(false);
+ }
+ return mFooterPreference;
+ }
+
+ @NonNull
+ @VisibleForTesting
+ ListPreference getRotationPreference(@NonNull Context context) {
+ if (mRotationPref == null) {
+ mRotationPref = new ListPreference(context);
+ mRotationPref.setPersistent(false);
+ }
+ return mRotationPref;
+ }
+
+ @NonNull
+ @VisibleForTesting
+ Preference getResolutionPreference(@NonNull Context context) {
+ if (mResolutionPreference == null) {
+ mResolutionPreference = new Preference(context);
+ mResolutionPreference.setPersistent(false);
+ }
+ return mResolutionPreference;
+ }
+
+ @NonNull
+ @VisibleForTesting
+ MainSwitchPreference getUseDisplayPreference(@NonNull Context context) {
+ if (mUseDisplayPref == null) {
+ mUseDisplayPref = new MainSwitchPreference(context);
+ mUseDisplayPref.setPersistent(false);
+ }
+ return mUseDisplayPref;
+ }
+
+ @NonNull
+ @VisibleForTesting
+ IllustrationPreference getIllustrationPreference(@NonNull Context context) {
+ if (mImagePreference == null) {
+ mImagePreference = new IllustrationPreference(context);
+ mImagePreference.setPersistent(false);
+ }
+ return mImagePreference;
+ }
+
+ /**
+ * @return return display id argument of this settings page.
+ */
+ @VisibleForTesting
+ protected int getDisplayIdArg() {
+ var args = getArguments();
+ return args != null ? args.getInt(DISPLAY_ID_ARG, INVALID_DISPLAY) : INVALID_DISPLAY;
+ }
+
+ @NonNull
+ private PreferenceCategory getDisplaysListPreference(@NonNull Context context) {
+ if (mDisplaysPreference == null) {
+ mDisplaysPreference = new PreferenceCategory(context);
+ mDisplaysPreference.setPersistent(false);
+ }
+ return mDisplaysPreference;
+ }
+
+ private void restoreState(@Nullable Bundle savedInstanceState) {
+ if (savedInstanceState == null) {
+ return;
+ }
+ mPreviouslyShownListOfDisplays = Boolean.TRUE.equals(savedInstanceState.getSerializable(
+ PREVIOUSLY_SHOWN_LIST_KEY, Boolean.class));
+ }
+
+ private void update() {
+ final var screen = getPreferenceScreen();
+ if (screen == null || mInjector == null || mInjector.getContext() == null) {
+ return;
+ }
+ screen.removeAll();
+ updateScreenForDisplayId(getDisplayIdArg(), screen, mInjector.getContext());
+ }
+
+ private void updateScreenForDisplayId(final int displayId,
+ @NonNull final PreferenceScreen screen, @NonNull Context context) {
+ final var displaysToShow = getDisplaysToShow(displayId);
+ if (displaysToShow.isEmpty() && displayId == INVALID_DISPLAY) {
+ showTextWhenNoDisplaysToShow(screen, context);
+ } else if (displaysToShow.size() == 1
+ && ((displayId == INVALID_DISPLAY && !mPreviouslyShownListOfDisplays)
+ || displaysToShow.get(0).getDisplayId() == displayId)) {
+ showDisplaySettings(displaysToShow.get(0), screen, context);
+ } else if (displayId == INVALID_DISPLAY) {
+ // If ever shown a list of displays - keep showing it for consistency after
+ // disconnecting one of the displays, and only one display is left.
+ mPreviouslyShownListOfDisplays = true;
+ showDisplaysList(displaysToShow, screen, context);
+ }
+ updateSettingsTitle(displaysToShow, displayId);
+ }
+
+ private void updateSettingsTitle(@NonNull final List<Display> displaysToShow, int displayId) {
+ final Activity activity = getCurrentActivity();
+ if (activity == null) {
+ return;
+ }
+ if (displaysToShow.size() == 1 && displaysToShow.get(0).getDisplayId() == displayId) {
+ var displayName = displaysToShow.get(0).getName();
+ if (!displayName.isEmpty()) {
+ activity.setTitle(displayName.substring(0, Math.min(displayName.length(), 40)));
+ return;
+ }
+ }
+ activity.setTitle(EXTERANAL_DISPLAY_TITLE_RESOURCE);
+ }
+
+ private void showTextWhenNoDisplaysToShow(@NonNull final PreferenceScreen screen,
+ @NonNull Context context) {
+ if (isUseDisplaySettingEnabled(mInjector)) {
+ screen.addPreference(updateUseDisplayPreferenceNoDisplaysFound(context));
+ }
+ screen.addPreference(updateFooterPreference(context,
+ EXTERNAL_DISPLAY_NOT_FOUND_FOOTER_RESOURCE));
+ }
+
+ private void showDisplaySettings(@NonNull Display display, @NonNull PreferenceScreen screen,
+ @NonNull Context context) {
+ final var isEnabled = mInjector != null && mInjector.isDisplayEnabled(display);
+ if (isUseDisplaySettingEnabled(mInjector)) {
+ screen.addPreference(updateUseDisplayPreference(context, display, isEnabled));
+ }
+ if (!isEnabled) {
+ // Skip all other settings
+ return;
+ }
+ final var displayRotation = getDisplayRotation(display.getDisplayId());
+ screen.addPreference(updateIllustrationImage(context, displayRotation));
+ screen.addPreference(updateResolutionPreference(context, display));
+ screen.addPreference(updateRotationPreference(context, display, displayRotation));
+ if (isResolutionSettingEnabled(mInjector)) {
+ screen.addPreference(updateFooterPreference(context,
+ EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE));
+ }
+ }
+
+ private void showDisplaysList(@NonNull List<Display> displaysToShow,
+ @NonNull PreferenceScreen screen, @NonNull Context context) {
+ var pref = getDisplaysListPreference(context);
+ pref.setKey(DISPLAYS_LIST_PREFERENCE_KEY);
+ pref.removeAll();
+ if (!displaysToShow.isEmpty()) {
+ screen.addPreference(pref);
+ }
+ for (var display : displaysToShow) {
+ pref.addPreference(new DisplayPreference(context, display));
+ }
+ }
+
+ private List<Display> getDisplaysToShow(int displayIdToShow) {
+ if (mInjector == null) {
+ return List.of();
+ }
+ if (displayIdToShow != INVALID_DISPLAY) {
+ var display = mInjector.getDisplay(displayIdToShow);
+ if (display != null && isDisplayAllowed(display, mInjector)) {
+ return List.of(display);
+ }
+ }
+ var displaysToShow = new ArrayList<Display>();
+ for (var display : mInjector.getAllDisplays()) {
+ if (display != null && isDisplayAllowed(display, mInjector)) {
+ displaysToShow.add(display);
+ }
+ }
+ return displaysToShow;
+ }
+
+ private Preference updateUseDisplayPreferenceNoDisplaysFound(@NonNull Context context) {
+ final var pref = getUseDisplayPreference(context);
+ pref.setKey(EXTERNAL_DISPLAY_USE_PREFERENCE_KEY);
+ pref.setTitle(EXTERNAL_DISPLAY_USE_TITLE_RESOURCE);
+ pref.setChecked(false);
+ pref.setEnabled(false);
+ pref.setOnPreferenceChangeListener(null);
+ return pref;
+ }
+
+ private Preference updateUseDisplayPreference(@NonNull final Context context,
+ @NonNull final Display display, boolean isEnabled) {
+ final var pref = getUseDisplayPreference(context);
+ pref.setKey(EXTERNAL_DISPLAY_USE_PREFERENCE_KEY);
+ pref.setTitle(EXTERNAL_DISPLAY_USE_TITLE_RESOURCE);
+ pref.setChecked(isEnabled);
+ pref.setEnabled(true);
+ pref.setOnPreferenceChangeListener((p, newValue) -> {
+ writePreferenceClickMetric(p);
+ final boolean result;
+ if (mInjector == null) {
+ return false;
+ }
+ if ((Boolean) newValue) {
+ result = mInjector.enableConnectedDisplay(display.getDisplayId());
+ } else {
+ result = mInjector.disableConnectedDisplay(display.getDisplayId());
+ }
+ if (result) {
+ pref.setChecked((Boolean) newValue);
+ }
+ return result;
+ });
+ return pref;
+ }
+
+ private Preference updateIllustrationImage(@NonNull final Context context,
+ final int displayRotation) {
+ var pref = getIllustrationPreference(context);
+ if (displayRotation % 2 == 0) {
+ pref.setLottieAnimationResId(EXTERNAL_DISPLAY_PORTRAIT_DRAWABLE);
+ } else {
+ pref.setLottieAnimationResId(EXTERNAL_DISPLAY_LANDSCAPE_DRAWABLE);
+ }
+ return pref;
+ }
+
+ private Preference updateFooterPreference(@NonNull final Context context, final int title) {
+ var pref = getFooterPreference(context);
+ pref.setTitle(title);
+ return pref;
+ }
+
+ private Preference updateRotationPreference(@NonNull final Context context,
+ @NonNull final Display display, final int displayRotation) {
+ var pref = getRotationPreference(context);
+ pref.setKey(EXTERNAL_DISPLAY_ROTATION_KEY);
+ pref.setTitle(EXTERNAL_DISPLAY_ROTATION_TITLE_RESOURCE);
+ if (mRotationEntries == null || mRotationEntriesValues == null) {
+ mRotationEntries = new String[] {
+ context.getString(R.string.external_display_standard_rotation),
+ context.getString(R.string.external_display_rotation_90),
+ context.getString(R.string.external_display_rotation_180),
+ context.getString(R.string.external_display_rotation_270)};
+ mRotationEntriesValues = new String[] {"0", "1", "2", "3"};
+ }
+ pref.setEntries(mRotationEntries);
+ pref.setEntryValues(mRotationEntriesValues);
+ pref.setValueIndex(displayRotation);
+ pref.setSummary(mRotationEntries[displayRotation]);
+ pref.setOnPreferenceChangeListener((p, newValue) -> {
+ writePreferenceClickMetric(p);
+ var rotation = Integer.parseInt((String) newValue);
+ var displayId = display.getDisplayId();
+ if (mInjector == null || !mInjector.freezeDisplayRotation(displayId, rotation)) {
+ return false;
+ }
+ pref.setValueIndex(rotation);
+ return true;
+ });
+ pref.setEnabled(isRotationSettingEnabled(mInjector));
+ return pref;
+ }
+
+ private Preference updateResolutionPreference(@NonNull final Context context,
+ @NonNull final Display display) {
+ var pref = getResolutionPreference(context);
+ pref.setKey(EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY);
+ pref.setTitle(EXTERNAL_DISPLAY_RESOLUTION_TITLE_RESOURCE);
+ pref.setSummary(display.getMode().getPhysicalWidth() + " x "
+ + display.getMode().getPhysicalHeight());
+ pref.setOnPreferenceClickListener((Preference p) -> {
+ writePreferenceClickMetric(p);
+ launchResolutionSelector(context, display.getDisplayId());
+ return true;
+ });
+ pref.setEnabled(isResolutionSettingEnabled(mInjector));
+ return pref;
+ }
+
+ private int getDisplayRotation(int displayId) {
+ if (mInjector == null) {
+ return 0;
+ }
+ return Math.min(3, Math.max(0, mInjector.getDisplayUserRotation(displayId)));
+ }
+
+ private void scheduleUpdate() {
+ if (mInjector == null || !mStarted) {
+ return;
+ }
+ unscheduleUpdate();
+ mInjector.getHandler().post(mUpdateRunnable);
+ }
+
+ private void unscheduleUpdate() {
+ if (mInjector == null || !mStarted) {
+ return;
+ }
+ mInjector.getHandler().removeCallbacks(mUpdateRunnable);
+ }
+
+ @VisibleForTesting
+ class DisplayPreference extends TwoTargetPreference
+ implements Preference.OnPreferenceClickListener {
+ private final int mDisplayId;
+
+ DisplayPreference(@NonNull final Context context, @NonNull final Display display) {
+ super(context);
+ mDisplayId = display.getDisplayId();
+ setPersistent(false);
+ setKey("display_id_" + mDisplayId);
+ setTitle(display.getName());
+ setSummary(display.getMode().getPhysicalWidth() + " x "
+ + display.getMode().getPhysicalHeight());
+ setOnPreferenceClickListener(this);
+ }
+
+ @Override
+ public boolean onPreferenceClick(@NonNull Preference preference) {
+ launchDisplaySettings(mDisplayId);
+ writePreferenceClickMetric(preference);
+ return true;
+ }
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/display/ExternalDisplaySettingsConfiguration.java b/src/com/android/settings/connecteddevice/display/ExternalDisplaySettingsConfiguration.java
new file mode 100644
index 0000000..89d464c
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/display/ExternalDisplaySettingsConfiguration.java
@@ -0,0 +1,341 @@
+/*
+ * 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.display;
+
+import static android.content.Context.DISPLAY_SERVICE;
+import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
+import static android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_ADDED;
+import static android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED;
+import static android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
+import static android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.view.Display;
+import android.view.Display.Mode;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settings.R;
+import com.android.settings.flags.FeatureFlags;
+import com.android.settings.flags.FeatureFlagsImpl;
+
+public class ExternalDisplaySettingsConfiguration {
+ static final String VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY =
+ "persist.demo.userrotation.package_name";
+ static final String DISPLAY_ID_ARG = "display_id";
+ static final int EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE = R.string.external_display_not_found;
+ static final int EXTERNAL_DISPLAY_HELP_URL = R.string.help_url_external_display;
+
+ public static class SystemServicesProvider {
+ @Nullable
+ private IWindowManager mWindowManager;
+ @Nullable
+ private DisplayManager mDisplayManager;
+ @Nullable
+ protected Context mContext;
+ /**
+ * @param name of a system property.
+ * @return the value of the system property.
+ */
+ @NonNull
+ public String getSystemProperty(@NonNull String name) {
+ return SystemProperties.get(name);
+ }
+
+ /**
+ * @return return public Display manager.
+ */
+ @Nullable
+ public DisplayManager getDisplayManager() {
+ if (mDisplayManager == null && getContext() != null) {
+ mDisplayManager = (DisplayManager) getContext().getSystemService(DISPLAY_SERVICE);
+ }
+ return mDisplayManager;
+ }
+
+ /**
+ * @return internal IWindowManager
+ */
+ @Nullable
+ public IWindowManager getWindowManager() {
+ if (mWindowManager == null) {
+ mWindowManager = WindowManagerGlobal.getWindowManagerService();
+ }
+ return mWindowManager;
+ }
+
+ /**
+ * @return context.
+ */
+ @Nullable
+ public Context getContext() {
+ return mContext;
+ }
+ }
+
+ public static class Injector extends SystemServicesProvider {
+ @NonNull
+ private final FeatureFlags mFlags;
+ @NonNull
+ private final Handler mHandler;
+
+ Injector(@Nullable Context context) {
+ this(context, new FeatureFlagsImpl(), new Handler(Looper.getMainLooper()));
+ }
+
+ Injector(@Nullable Context context, @NonNull FeatureFlags flags, @NonNull Handler handler) {
+ mContext = context;
+ mFlags = flags;
+ mHandler = handler;
+ }
+
+ /**
+ * @return all displays including disabled.
+ */
+ @NonNull
+ public Display[] getAllDisplays() {
+ var dm = getDisplayManager();
+ if (dm == null) {
+ return new Display[0];
+ }
+ return dm.getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+ }
+
+ /**
+ * @return enabled displays only.
+ */
+ @NonNull
+ public Display[] getEnabledDisplays() {
+ var dm = getDisplayManager();
+ if (dm == null) {
+ return new Display[0];
+ }
+ return dm.getDisplays();
+ }
+
+ /**
+ * @return true if the display is enabled
+ */
+ public boolean isDisplayEnabled(@NonNull Display display) {
+ for (var enabledDisplay : getEnabledDisplays()) {
+ if (enabledDisplay.getDisplayId() == display.getDisplayId()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Register display listener.
+ */
+ public void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener) {
+ var dm = getDisplayManager();
+ if (dm == null) {
+ return;
+ }
+ dm.registerDisplayListener(listener, mHandler, EVENT_FLAG_DISPLAY_ADDED
+ | EVENT_FLAG_DISPLAY_CHANGED | EVENT_FLAG_DISPLAY_REMOVED
+ | EVENT_FLAG_DISPLAY_CONNECTION_CHANGED);
+ }
+
+ /**
+ * Unregister display listener.
+ */
+ public void unregisterDisplayListener(@NonNull DisplayManager.DisplayListener listener) {
+ var dm = getDisplayManager();
+ if (dm == null) {
+ return;
+ }
+ dm.unregisterDisplayListener(listener);
+ }
+
+ /**
+ * @return feature flags.
+ */
+ @NonNull
+ public FeatureFlags getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Enable connected display.
+ */
+ public boolean enableConnectedDisplay(int displayId) {
+ var dm = getDisplayManager();
+ if (dm == null) {
+ return false;
+ }
+ dm.enableConnectedDisplay(displayId);
+ return true;
+ }
+
+ /**
+ * Disable connected display.
+ */
+ public boolean disableConnectedDisplay(int displayId) {
+ var dm = getDisplayManager();
+ if (dm == null) {
+ return false;
+ }
+ dm.disableConnectedDisplay(displayId);
+ return true;
+ }
+
+ /**
+ * @param displayId which must be returned
+ * @return display object for the displayId
+ */
+ @Nullable
+ public Display getDisplay(int displayId) {
+ if (displayId == INVALID_DISPLAY) {
+ return null;
+ }
+ var dm = getDisplayManager();
+ if (dm == null) {
+ return null;
+ }
+ return dm.getDisplay(displayId);
+ }
+
+ /**
+ * @return handler
+ */
+ @NonNull
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ /**
+ * Get display rotation
+ * @param displayId display identifier
+ * @return rotation
+ */
+ public int getDisplayUserRotation(int displayId) {
+ var wm = getWindowManager();
+ if (wm == null) {
+ return 0;
+ }
+ try {
+ return wm.getDisplayUserRotation(displayId);
+ } catch (RemoteException e) {
+ return 0;
+ }
+ }
+
+ /**
+ * Freeze rotation of the display in the specified rotation.
+ * @param displayId display identifier
+ * @param rotation [0, 1, 2, 3]
+ * @return true if successful
+ */
+ public boolean freezeDisplayRotation(int displayId, int rotation) {
+ var wm = getWindowManager();
+ if (wm == null) {
+ return false;
+ }
+ try {
+ wm.freezeDisplayRotation(displayId, rotation,
+ "ExternalDisplayPreferenceFragment");
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Enforce display mode on the given display.
+ */
+ public void setUserPreferredDisplayMode(int displayId, @NonNull Mode mode) {
+ DisplayManagerGlobal.getInstance().setUserPreferredDisplayMode(displayId, mode);
+ }
+ }
+
+ public abstract static class DisplayListener implements DisplayManager.DisplayListener {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ update(displayId);
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ update(displayId);
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ update(displayId);
+ }
+
+ @Override
+ public void onDisplayConnected(int displayId) {
+ update(displayId);
+ }
+
+ @Override
+ public void onDisplayDisconnected(int displayId) {
+ update(displayId);
+ }
+
+ /**
+ * Called from other listener methods to trigger update of the settings page.
+ */
+ public abstract void update(int displayId);
+ }
+
+ /**
+ * @return whether the settings page is enabled or not.
+ */
+ public static boolean isExternalDisplaySettingsPageEnabled(@NonNull FeatureFlags flags) {
+ return flags.rotationConnectedDisplaySetting()
+ || flags.resolutionAndEnableConnectedDisplaySetting();
+ }
+
+ static boolean isDisplayAllowed(@NonNull Display display,
+ @NonNull SystemServicesProvider props) {
+ return display.getType() == Display.TYPE_EXTERNAL
+ || display.getType() == Display.TYPE_OVERLAY
+ || isVirtualDisplayAllowed(display, props);
+ }
+
+ static boolean isVirtualDisplayAllowed(@NonNull Display display,
+ @NonNull SystemServicesProvider properties) {
+ var sysProp = properties.getSystemProperty(VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY);
+ return !sysProp.isEmpty() && display.getType() == Display.TYPE_VIRTUAL
+ && sysProp.equals(display.getOwnerPackageName());
+ }
+
+ static boolean isUseDisplaySettingEnabled(@Nullable Injector injector) {
+ return injector != null && injector.getFlags().resolutionAndEnableConnectedDisplaySetting();
+ }
+
+ static boolean isResolutionSettingEnabled(@Nullable Injector injector) {
+ return injector != null && injector.getFlags().resolutionAndEnableConnectedDisplaySetting();
+ }
+
+ static boolean isRotationSettingEnabled(@Nullable Injector injector) {
+ return injector != null && injector.getFlags().rotationConnectedDisplaySetting();
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdater.java b/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdater.java
new file mode 100644
index 0000000..64dd7bb
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdater.java
@@ -0,0 +1,181 @@
+/*
+ * 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.display;
+
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplayAllowed;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.connecteddevice.DevicePreferenceCallback;
+import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DisplayListener;
+import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.Injector;
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
+import com.android.settingslib.RestrictedPreference;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+public class ExternalDisplayUpdater {
+
+ private static final String PREF_KEY = "external_display_settings";
+ private final int mMetricsCategory;
+ @NonNull
+ private final MetricsFeatureProvider mMetricsFeatureProvider;
+ @NonNull
+ private final Runnable mUpdateRunnable = this::update;
+ @NonNull
+ private final DevicePreferenceCallback mDevicePreferenceCallback;
+ @Nullable
+ private RestrictedPreference mPreference;
+ @Nullable
+ private Injector mInjector;
+ private final DisplayListener mListener = new DisplayListener() {
+ @Override
+ public void update(int displayId) {
+ scheduleUpdate();
+ }
+ };
+
+ public ExternalDisplayUpdater(@NonNull DevicePreferenceCallback callback, int metricsCategory) {
+ mDevicePreferenceCallback = callback;
+ mMetricsCategory = metricsCategory;
+ mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
+ }
+
+ /**
+ * Set the context to generate the {@link Preference}, so it could get the correct theme.
+ */
+ public void initPreference(@NonNull Context context) {
+ initPreference(context, new Injector(context));
+ }
+
+ @VisibleForTesting
+ void initPreference(@NonNull Context context, Injector injector) {
+ mInjector = injector;
+ mPreference = new RestrictedPreference(context, null /* AttributeSet */);
+ mPreference.setTitle(R.string.external_display_settings_title);
+ mPreference.setSummary(getSummary());
+ mPreference.setIcon(getDrawable(context));
+ mPreference.setKey(PREF_KEY);
+ mPreference.setDisabledByAdmin(checkIfUsbDataSignalingIsDisabled(context));
+ mPreference.setOnPreferenceClickListener((Preference p) -> {
+ mMetricsFeatureProvider.logClickedPreference(p, mMetricsCategory);
+ // New version - uses a separate screen.
+ new SubSettingLauncher(context)
+ .setDestination(ExternalDisplayPreferenceFragment.class.getName())
+ .setTitleRes(R.string.external_display_settings_title)
+ .setSourceMetricsCategory(mMetricsCategory)
+ .launch();
+ return true;
+ });
+
+ scheduleUpdate();
+ }
+
+ /**
+ * Unregister the display listener.
+ */
+ public void unregisterCallback() {
+ if (mInjector != null) {
+ mInjector.unregisterDisplayListener(mListener);
+ }
+ }
+
+ /**
+ * Register the display listener.
+ */
+ public void registerCallback() {
+ if (mInjector != null) {
+ mInjector.registerDisplayListener(mListener);
+ }
+ }
+
+ @VisibleForTesting
+ @Nullable
+ protected RestrictedLockUtils.EnforcedAdmin checkIfUsbDataSignalingIsDisabled(Context context) {
+ return RestrictedLockUtilsInternal.checkIfUsbDataSignalingIsDisabled(context,
+ UserHandle.myUserId());
+ }
+
+ @VisibleForTesting
+ @Nullable
+ protected Drawable getDrawable(Context context) {
+ return context.getDrawable(R.drawable.ic_external_display_32dp);
+ }
+
+ @Nullable
+ protected CharSequence getSummary() {
+ if (mInjector == null) {
+ return null;
+ }
+ var context = mInjector.getContext();
+ if (context == null) {
+ return null;
+ }
+
+ for (var display : mInjector.getEnabledDisplays()) {
+ if (display != null && isDisplayAllowed(display, mInjector)) {
+ return context.getString(R.string.external_display_on);
+ }
+ }
+
+ for (var display : mInjector.getAllDisplays()) {
+ if (display != null && isDisplayAllowed(display, mInjector)) {
+ return context.getString(R.string.external_display_off);
+ }
+ }
+
+ return null;
+ }
+
+ private void scheduleUpdate() {
+ if (mInjector == null) {
+ return;
+ }
+ unscheduleUpdate();
+ mInjector.getHandler().post(mUpdateRunnable);
+ }
+
+ private void unscheduleUpdate() {
+ if (mInjector == null) {
+ return;
+ }
+ mInjector.getHandler().removeCallbacks(mUpdateRunnable);
+ }
+
+ private void update() {
+ var summary = getSummary();
+ if (mPreference == null) {
+ return;
+ }
+ mPreference.setSummary(summary);
+ if (summary != null) {
+ mDevicePreferenceCallback.onDeviceAdded(mPreference);
+ } else {
+ mDevicePreferenceCallback.onDeviceRemoved(mPreference);
+ }
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/display/OWNERS b/src/com/android/settings/connecteddevice/display/OWNERS
new file mode 100644
index 0000000..78aecb9
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/display/OWNERS
@@ -0,0 +1,7 @@
+# Default reviewers for this and subdirectories.
+santoscordon@google.com
+petsjonkin@google.com
+flc@google.com
+wilczynskip@google.com
+brup@google.com
+olb@google.com
diff --git a/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragment.java b/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragment.java
new file mode 100644
index 0000000..10314cb
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragment.java
@@ -0,0 +1,343 @@
+/*
+ * 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.display;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DISPLAY_ID_ARG;
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_HELP_URL;
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE;
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isDisplayAllowed;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.Pair;
+import android.view.Display;
+import android.view.Display.Mode;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+
+import com.android.internal.util.ToBooleanFunction;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragmentBase;
+import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DisplayListener;
+import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.Injector;
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+public class ResolutionPreferenceFragment extends SettingsPreferenceFragmentBase {
+ private static final String TAG = "ResolutionPreferenceFragment";
+ static final int DEFAULT_LOW_REFRESH_RATE = 60;
+ static final String MORE_OPTIONS_KEY = "more_options";
+ static final String TOP_OPTIONS_KEY = "top_options";
+ static final int MORE_OPTIONS_TITLE_RESOURCE =
+ R.string.external_display_more_options_title;
+ static final int EXTERNAL_DISPLAY_RESOLUTION_SETTINGS_RESOURCE =
+ R.xml.external_display_resolution_settings;
+ @Nullable
+ private Injector mInjector;
+ @Nullable
+ private PreferenceCategory mTopOptionsPreference;
+ @Nullable
+ private PreferenceCategory mMoreOptionsPreference;
+ private boolean mStarted;
+ private final HashSet<String> mResolutionPreferences = new HashSet<>();
+ private int mExternalDisplayPeakWidth;
+ private int mExternalDisplayPeakHeight;
+ private int mExternalDisplayPeakRefreshRate;
+ private boolean mRefreshRateSynchronizationEnabled;
+ private boolean mMoreOptionsExpanded;
+ private final Runnable mUpdateRunnable = this::update;
+ private final DisplayListener mListener = new DisplayListener() {
+ @Override
+ public void update(int displayId) {
+ scheduleUpdate();
+ }
+ };
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY;
+ }
+
+ @Override
+ public int getHelpResource() {
+ return EXTERNAL_DISPLAY_HELP_URL;
+ }
+
+ @Override
+ public void onCreateCallback(@Nullable Bundle icicle) {
+ if (mInjector == null) {
+ mInjector = new Injector(getPrefContext());
+ }
+ addPreferencesFromResource(EXTERNAL_DISPLAY_RESOLUTION_SETTINGS_RESOURCE);
+ updateDisplayModeLimits(mInjector.getContext());
+ }
+
+ @Override
+ public void onActivityCreatedCallback(@Nullable Bundle savedInstanceState) {
+ View view = getView();
+ TextView emptyView = null;
+ if (view != null) {
+ emptyView = (TextView) view.findViewById(android.R.id.empty);
+ }
+ if (emptyView != null) {
+ emptyView.setText(EXTERNAL_DISPLAY_NOT_FOUND_RESOURCE);
+ setEmptyView(emptyView);
+ }
+ }
+
+ @Override
+ public void onStartCallback() {
+ mStarted = true;
+ if (mInjector == null) {
+ return;
+ }
+ mInjector.registerDisplayListener(mListener);
+ scheduleUpdate();
+ }
+
+ @Override
+ public void onStopCallback() {
+ mStarted = false;
+ if (mInjector == null) {
+ return;
+ }
+ mInjector.unregisterDisplayListener(mListener);
+ unscheduleUpdate();
+ }
+
+ public ResolutionPreferenceFragment() {}
+
+ @VisibleForTesting
+ ResolutionPreferenceFragment(@NonNull Injector injector) {
+ mInjector = injector;
+ }
+
+ @VisibleForTesting
+ protected int getDisplayIdArg() {
+ var args = getArguments();
+ return args != null ? args.getInt(DISPLAY_ID_ARG, INVALID_DISPLAY) : INVALID_DISPLAY;
+ }
+
+ @VisibleForTesting
+ @NonNull
+ protected Resources getResources(@NonNull Context context) {
+ return context.getResources();
+ }
+
+ private void update() {
+ final PreferenceScreen screen = getPreferenceScreen();
+ if (screen == null || mInjector == null) {
+ return;
+ }
+ var context = mInjector.getContext();
+ if (context == null) {
+ return;
+ }
+ var display = mInjector.getDisplay(getDisplayIdArg());
+ if (display == null || !isDisplayAllowed(display, mInjector)) {
+ screen.removeAll();
+ mTopOptionsPreference = null;
+ mMoreOptionsPreference = null;
+ return;
+ }
+ mResolutionPreferences.clear();
+ var remainingModes = addModePreferences(context,
+ getTopPreference(context, screen),
+ display.getSupportedModes(), this::isTopMode, display);
+ addRemainingPreferences(context,
+ getMorePreference(context, screen),
+ display, remainingModes.first, remainingModes.second);
+ }
+
+ private PreferenceCategory getTopPreference(@NonNull Context context,
+ @NonNull PreferenceScreen screen) {
+ if (mTopOptionsPreference == null) {
+ mTopOptionsPreference = new PreferenceCategory(context);
+ mTopOptionsPreference.setPersistent(false);
+ mTopOptionsPreference.setKey(TOP_OPTIONS_KEY);
+ screen.addPreference(mTopOptionsPreference);
+ } else {
+ mTopOptionsPreference.removeAll();
+ }
+ return mTopOptionsPreference;
+ }
+
+ private PreferenceCategory getMorePreference(@NonNull Context context,
+ @NonNull PreferenceScreen screen) {
+ if (mMoreOptionsPreference == null) {
+ mMoreOptionsPreference = new PreferenceCategory(context);
+ mMoreOptionsPreference.setPersistent(false);
+ mMoreOptionsPreference.setTitle(MORE_OPTIONS_TITLE_RESOURCE);
+ mMoreOptionsPreference.setOnExpandButtonClickListener(() -> {
+ mMoreOptionsExpanded = true;
+ });
+ mMoreOptionsPreference.setKey(MORE_OPTIONS_KEY);
+ screen.addPreference(mMoreOptionsPreference);
+ } else {
+ mMoreOptionsPreference.removeAll();
+ }
+ return mMoreOptionsPreference;
+ }
+
+ private void addRemainingPreferences(@NonNull Context context,
+ @NonNull PreferenceCategory group, @NonNull Display display,
+ boolean isSelectedModeFound, @NonNull Mode[] moreModes) {
+ if (moreModes.length == 0) {
+ return;
+ }
+ mMoreOptionsExpanded |= !isSelectedModeFound;
+ group.setInitialExpandedChildrenCount(mMoreOptionsExpanded ? Integer.MAX_VALUE : 0);
+ addModePreferences(context, group, moreModes, /*checkMode=*/ null, display);
+ }
+
+ private Pair<Boolean, Mode[]> addModePreferences(@NonNull Context context,
+ @NonNull PreferenceGroup group,
+ @NonNull Mode[] modes,
+ @Nullable ToBooleanFunction<Mode> checkMode,
+ @NonNull Display display) {
+ Display.Mode curMode = display.getMode();
+ var currentResolution = curMode.getPhysicalWidth() + "x" + curMode.getPhysicalHeight();
+ var rotatedResolution = curMode.getPhysicalHeight() + "x" + curMode.getPhysicalWidth();
+ var skippedModes = new ArrayList<Mode>();
+ var isAnyOfModesSelected = false;
+ for (var mode : modes) {
+ var modeStr = mode.getPhysicalWidth() + "x" + mode.getPhysicalHeight();
+ SelectorWithWidgetPreference pref = group.findPreference(modeStr);
+ if (pref != null) {
+ continue;
+ }
+ if (checkMode != null && !checkMode.apply(mode)) {
+ skippedModes.add(mode);
+ continue;
+ }
+ var isCurrentMode =
+ currentResolution.equals(modeStr) || rotatedResolution.equals(modeStr);
+ if (!isCurrentMode && !isAllowedMode(mode)) {
+ continue;
+ }
+ if (mResolutionPreferences.contains(modeStr)) {
+ // Added to "Top modes" already.
+ continue;
+ }
+ mResolutionPreferences.add(modeStr);
+ pref = new SelectorWithWidgetPreference(context);
+ pref.setPersistent(false);
+ pref.setKey(modeStr);
+ pref.setTitle(mode.getPhysicalWidth() + " x " + mode.getPhysicalHeight());
+ pref.setSingleLineTitle(true);
+ pref.setOnClickListener(preference -> onDisplayModeClicked(preference, display));
+ pref.setChecked(isCurrentMode);
+ isAnyOfModesSelected |= isCurrentMode;
+ group.addPreference(pref);
+ }
+ return new Pair<>(isAnyOfModesSelected, skippedModes.toArray(Mode.EMPTY_ARRAY));
+ }
+
+ private boolean isTopMode(@NonNull Mode mode) {
+ return mTopOptionsPreference != null
+ && mTopOptionsPreference.getPreferenceCount() < 3;
+ }
+
+ private boolean isAllowedMode(@NonNull Mode mode) {
+ if (mRefreshRateSynchronizationEnabled
+ && (mode.getRefreshRate() < DEFAULT_LOW_REFRESH_RATE - 1
+ || mode.getRefreshRate() > DEFAULT_LOW_REFRESH_RATE + 1)) {
+ Log.d(TAG, mode + " refresh rate is out of synchronization range");
+ return false;
+ }
+ if (mExternalDisplayPeakHeight > 0
+ && mode.getPhysicalHeight() > mExternalDisplayPeakHeight) {
+ Log.d(TAG, mode + " height is above the allowed limit");
+ return false;
+ }
+ if (mExternalDisplayPeakWidth > 0
+ && mode.getPhysicalWidth() > mExternalDisplayPeakWidth) {
+ Log.d(TAG, mode + " width is above the allowed limit");
+ return false;
+ }
+ if (mExternalDisplayPeakRefreshRate > 0
+ && mode.getRefreshRate() > mExternalDisplayPeakRefreshRate) {
+ Log.d(TAG, mode + " refresh rate is above the allowed limit");
+ return false;
+ }
+ return true;
+ }
+
+ private void scheduleUpdate() {
+ if (mInjector == null || !mStarted) {
+ return;
+ }
+ unscheduleUpdate();
+ mInjector.getHandler().post(mUpdateRunnable);
+ }
+
+ private void unscheduleUpdate() {
+ if (mInjector == null || !mStarted) {
+ return;
+ }
+ mInjector.getHandler().removeCallbacks(mUpdateRunnable);
+ }
+
+ private void onDisplayModeClicked(@NonNull SelectorWithWidgetPreference preference,
+ @NonNull Display display) {
+ if (mInjector == null) {
+ return;
+ }
+ String[] modeResolution = preference.getKey().split("x");
+ int width = Integer.parseInt(modeResolution[0]);
+ int height = Integer.parseInt(modeResolution[1]);
+ for (var mode : display.getSupportedModes()) {
+ if (mode.getPhysicalWidth() == width && mode.getPhysicalHeight() == height
+ && isAllowedMode(mode)) {
+ mInjector.setUserPreferredDisplayMode(display.getDisplayId(), mode);
+ return;
+ }
+ }
+ }
+
+ private void updateDisplayModeLimits(@Nullable Context context) {
+ if (context == null) {
+ return;
+ }
+ mExternalDisplayPeakRefreshRate = getResources(context).getInteger(
+ com.android.internal.R.integer.config_externalDisplayPeakRefreshRate);
+ mExternalDisplayPeakWidth = getResources(context).getInteger(
+ com.android.internal.R.integer.config_externalDisplayPeakWidth);
+ mExternalDisplayPeakHeight = getResources(context).getInteger(
+ com.android.internal.R.integer.config_externalDisplayPeakHeight);
+ mRefreshRateSynchronizationEnabled = getResources(context).getBoolean(
+ com.android.internal.R.bool.config_refreshRateSynchronizationEnabled);
+ Log.d(TAG, "mExternalDisplayPeakRefreshRate=" + mExternalDisplayPeakRefreshRate);
+ Log.d(TAG, "mExternalDisplayPeakWidth=" + mExternalDisplayPeakWidth);
+ Log.d(TAG, "mExternalDisplayPeakHeight=" + mExternalDisplayPeakHeight);
+ Log.d(TAG, "mRefreshRateSynchronizationEnabled=" + mRefreshRateSynchronizationEnabled);
+ }
+}
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index 1c14712..11c05f3 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -162,6 +162,8 @@
import com.android.settings.notification.app.ChannelNotificationSettings;
import com.android.settings.notification.app.ConversationListSettings;
import com.android.settings.notification.history.NotificationStation;
+import com.android.settings.notification.modes.ZenModeFragment;
+import com.android.settings.notification.modes.ZenModesListFragment;
import com.android.settings.notification.zen.ZenAccessSettings;
import com.android.settings.notification.zen.ZenModeAutomationSettings;
import com.android.settings.notification.zen.ZenModeBlockedEffectsSettings;
@@ -396,6 +398,8 @@
CellularSecuritySettingsFragment.class.getName(),
AccessibilityHearingAidsFragment.class.getName(),
HearingDevicePairingFragment.class.getName(),
+ ZenModesListFragment.class.getName(),
+ ZenModeFragment.class.getName()
};
public static final String[] SETTINGS_FOR_RESTRICTED = {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java
index 7b5cd9f..b5d5099 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java
@@ -99,7 +99,8 @@
return mScreenOnTime;
}
- List<BatteryDiffEntry> getAppDiffEntryList() {
+ /** Gets the {@link BatteryDiffEntry} list for apps. */
+ public List<BatteryDiffEntry> getAppDiffEntryList() {
return mAppEntries;
}
@@ -298,8 +299,7 @@
* Sets total consume power, and adjusts the percentages to ensure the total round percentage
* could be 100%, and then sorts entries based on the sorting key.
*/
- @VisibleForTesting
- static void processAndSortEntries(final List<BatteryDiffEntry> batteryDiffEntries) {
+ public static void processAndSortEntries(final List<BatteryDiffEntry> batteryDiffEntries) {
if (batteryDiffEntries.isEmpty()) {
return;
}
diff --git a/src/com/android/settings/homepage/contextualcards/conditional/DndConditionCardController.java b/src/com/android/settings/homepage/contextualcards/conditional/DndConditionCardController.java
index e69a336..6362068 100644
--- a/src/com/android/settings/homepage/contextualcards/conditional/DndConditionCardController.java
+++ b/src/com/android/settings/homepage/contextualcards/conditional/DndConditionCardController.java
@@ -105,7 +105,8 @@
+ mAppContext.getText(R.string.condition_zen_title))
.setTitleText(mAppContext.getText(R.string.condition_zen_title).toString())
.setSummaryText(getSummary())
- .setIconDrawable(mAppContext.getDrawable(R.drawable.ic_do_not_disturb_on_24dp))
+ .setIconDrawable(mAppContext.getDrawable(
+ com.android.settingslib.R.drawable.ic_do_not_disturb_on_24dp))
.setViewType(ConditionContextualCardRenderer.VIEW_TYPE_HALF_WIDTH)
.build();
}
diff --git a/src/com/android/settings/inputmethod/PointerScaleSeekBarController.java b/src/com/android/settings/inputmethod/PointerScaleSeekBarController.java
new file mode 100644
index 0000000..06d5203
--- /dev/null
+++ b/src/com/android/settings/inputmethod/PointerScaleSeekBarController.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 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.inputmethod;
+
+import static android.view.PointerIcon.DEFAULT_POINTER_SCALE;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.widget.SeekBar;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.widget.LabeledSeekBarPreference;
+
+public class PointerScaleSeekBarController extends BasePreferenceController {
+
+ private final int mProgressMin;
+ private final int mProgressMax;
+ private final float mScaleMin;
+ private final float mScaleMax;
+
+ public PointerScaleSeekBarController(@NonNull Context context, @NonNull String key) {
+ super(context, key);
+
+ Resources res = context.getResources();
+ mProgressMin = res.getInteger(R.integer.pointer_scale_seek_bar_start);
+ mProgressMax = res.getInteger(R.integer.pointer_scale_seek_bar_end);
+ mScaleMin = res.getFloat(R.dimen.pointer_scale_size_start);
+ mScaleMax = res.getFloat(R.dimen.pointer_scale_size_end);
+ }
+
+ @AvailabilityStatus
+ public int getAvailabilityStatus() {
+ return android.view.flags.Flags.enableVectorCursorA11ySettings() ? AVAILABLE
+ : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ public void displayPreference(@NonNull PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ LabeledSeekBarPreference seekBarPreference = screen.findPreference(getPreferenceKey());
+ seekBarPreference.setMax(mProgressMax);
+ seekBarPreference.setContinuousUpdates(/* continuousUpdates= */ true);
+ seekBarPreference.setProgress(scaleToProgress(
+ Settings.System.getFloatForUser(mContext.getContentResolver(),
+ Settings.System.POINTER_SCALE, DEFAULT_POINTER_SCALE,
+ UserHandle.USER_CURRENT)));
+ seekBarPreference.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(@NonNull SeekBar seekBar, int progress,
+ boolean fromUser) {
+ Settings.System.putFloatForUser(mContext.getContentResolver(),
+ Settings.System.POINTER_SCALE, progressToScale(progress),
+ UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ public void onStartTrackingTouch(@NonNull SeekBar seekBar) {}
+
+ @Override
+ public void onStopTrackingTouch(@NonNull SeekBar seekBar) {}
+ });
+ }
+
+ private float progressToScale(int progress) {
+ return (((progress - mProgressMin) * (mScaleMax - mScaleMin)) / (mProgressMax
+ - mProgressMin)) + mScaleMin;
+ }
+
+ private int scaleToProgress(float scale) {
+ return (int) (
+ (((scale - mScaleMin) * (mProgressMax - mProgressMin)) / (mScaleMax - mScaleMin))
+ + mProgressMin);
+ }
+}
diff --git a/src/com/android/settings/network/MobileNetworkRepository.java b/src/com/android/settings/network/MobileNetworkRepository.java
index 8ee5389..ebb341e 100644
--- a/src/com/android/settings/network/MobileNetworkRepository.java
+++ b/src/com/android/settings/network/MobileNetworkRepository.java
@@ -49,7 +49,6 @@
import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity;
import com.android.settingslib.mobile.dataservice.SubscriptionInfoDao;
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
-import com.android.settingslib.mobile.dataservice.UiccInfoDao;
import com.android.settingslib.mobile.dataservice.UiccInfoEntity;
import java.util.ArrayList;
@@ -81,15 +80,11 @@
private SubscriptionManager mSubscriptionManager;
private MobileNetworkDatabase mMobileNetworkDatabase;
private SubscriptionInfoDao mSubscriptionInfoDao;
- private UiccInfoDao mUiccInfoDao;
private MobileNetworkInfoDao mMobileNetworkInfoDao;
private List<SubscriptionInfoEntity> mAvailableSubInfoEntityList = new ArrayList<>();
private List<SubscriptionInfoEntity> mActiveSubInfoEntityList = new ArrayList<>();
- private List<UiccInfoEntity> mUiccInfoEntityList = new ArrayList<>();
- private List<MobileNetworkInfoEntity> mMobileNetworkInfoEntityList = new ArrayList<>();
private Context mContext;
private AirplaneModeObserver mAirplaneModeObserver;
- private DataRoamingObserver mDataRoamingObserver;
private MetricsFeatureProvider mMetricsFeatureProvider;
private int mPhysicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
private int mLogicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
@@ -124,10 +119,8 @@
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_MOBILE_NETWORK_DB_CREATED);
mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
mSubscriptionInfoDao = mMobileNetworkDatabase.mSubscriptionInfoDao();
- mUiccInfoDao = mMobileNetworkDatabase.mUiccInfoDao();
mMobileNetworkInfoDao = mMobileNetworkDatabase.mMobileNetworkInfoDao();
mAirplaneModeObserver = new AirplaneModeObserver(new Handler(Looper.getMainLooper()));
- mDataRoamingObserver = new DataRoamingObserver(new Handler(Looper.getMainLooper()));
}
private class AirplaneModeObserver extends ContentObserver {
@@ -158,47 +151,6 @@
}
}
- private class DataRoamingObserver extends ContentObserver {
- private int mRegSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- private String mBaseField = Settings.Global.DATA_ROAMING;
-
- DataRoamingObserver(Handler handler) {
- super(handler);
- }
-
- public void register(Context context, int subId) {
- mRegSubId = subId;
- String lastField = mBaseField;
- createTelephonyManagerBySubId(subId);
- TelephonyManager tm = mTelephonyManagerMap.get(subId);
- if (tm.getSimCount() != 1) {
- lastField += subId;
- }
- context.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(lastField), false, this);
- }
-
- public void unRegister(Context context) {
- context.getContentResolver().unregisterContentObserver(this);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- TelephonyManager tm = mTelephonyManagerMap.get(mRegSubId);
- if (tm == null) {
- return;
- }
- sExecutor.execute(() -> {
- Log.d(TAG, "DataRoamingObserver changed");
- insertMobileNetworkInfo(mContext, mRegSubId, tm);
- });
- boolean isDataRoamingEnabled = tm.isDataRoamingEnabled();
- for (MobileNetworkCallback callback : sCallbacks) {
- callback.onDataRoamingChanged(mRegSubId, isDataRoamingEnabled);
- }
- }
- }
-
/**
* Register all callbacks and listener.
*
@@ -224,7 +176,6 @@
observeAllMobileNetworkInfo(lifecycleOwner);
if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
createTelephonyManagerBySubId(subId);
- mDataRoamingObserver.register(mContext, subId);
}
// When one client registers callback first time, convey the cached results to the client
// so that the client is aware of the content therein.
@@ -288,7 +239,6 @@
if (sCallbacks.isEmpty()) {
mSubscriptionManager.removeOnSubscriptionsChangedListener(this);
mAirplaneModeObserver.unRegister(mContext);
- mDataRoamingObserver.unRegister(mContext);
mTelephonyManagerMap.forEach((id, manager) -> {
TelephonyCallback callback = mTelephonyCallbackMap.get(id);
@@ -338,22 +288,6 @@
lifecycleOwner, this::onAllMobileNetworkInfoChanged);
}
- public List<SubscriptionInfoEntity> getAvailableSubInfoEntityList() {
- return mAvailableSubInfoEntityList;
- }
-
- public List<SubscriptionInfoEntity> getActiveSubscriptionInfoList() {
- return mActiveSubInfoEntityList;
- }
-
- public List<UiccInfoEntity> getUiccInfoEntityList() {
- return mUiccInfoEntityList;
- }
-
- public List<MobileNetworkInfoEntity> getMobileNetworkInfoEntityList() {
- return mMobileNetworkInfoEntityList;
- }
-
public SubscriptionInfoEntity getSubInfoById(String subId) {
return mSubscriptionInfoDao.querySubInfoById(subId);
}
@@ -464,7 +398,6 @@
}
private void onAllUiccInfoChanged(List<UiccInfoEntity> uiccInfoEntityList) {
- mUiccInfoEntityList = new ArrayList<>(uiccInfoEntityList);
for (MobileNetworkCallback callback : sCallbacks) {
callback.onAllUiccInfoChanged(uiccInfoEntityList);
}
@@ -474,7 +407,6 @@
private void onAllMobileNetworkInfoChanged(
List<MobileNetworkInfoEntity> mobileNetworkInfoEntityList) {
- mMobileNetworkInfoEntityList = new ArrayList<>(mobileNetworkInfoEntityList);
for (MobileNetworkCallback callback : sCallbacks) {
callback.onAllMobileNetworkInfoChanged(mobileNetworkInfoEntityList);
}
@@ -515,8 +447,6 @@
mMobileNetworkDatabase.deleteSubInfoBySubId(subId);
mMobileNetworkDatabase.deleteUiccInfoBySubId(subId);
mMobileNetworkDatabase.deleteMobileNetworkInfoBySubId(subId);
- mUiccInfoEntityList.removeIf(info -> info.subId.equals(subId));
- mMobileNetworkInfoEntityList.removeIf(info -> info.subId.equals(subId));
int id = Integer.parseInt(subId);
removerRegisterBySubId(id);
mSubscriptionInfoMap.remove(id);
@@ -613,10 +543,8 @@
private MobileNetworkInfoEntity convertToMobileNetworkInfoEntity(Context context, int subId,
TelephonyManager telephonyManager) {
boolean isDataEnabled = false;
- boolean isDataRoamingEnabled = false;
if (telephonyManager != null) {
isDataEnabled = telephonyManager.isDataEnabled();
- isDataRoamingEnabled = telephonyManager.isDataRoamingEnabled();
} else {
Log.d(TAG, "TelephonyManager is null, subId = " + subId);
}
@@ -632,7 +560,7 @@
MobileNetworkUtils.isTdscdmaSupported(context, subId),
MobileNetworkUtils.activeNetworkIsCellular(context),
SubscriptionUtil.showToggleForPhysicalSim(mSubscriptionManager),
- isDataRoamingEnabled
+ /* deprecated isDataRoamingEnabled = */ false
);
}
@@ -741,7 +669,6 @@
}
private class PhoneCallStateTelephonyCallback extends TelephonyCallback implements
- TelephonyCallback.CallStateListener,
TelephonyCallback.UserMobileDataStateListener {
private int mSubId;
@@ -751,13 +678,6 @@
}
@Override
- public void onCallStateChanged(int state) {
- for (MobileNetworkCallback callback : sCallbacks) {
- callback.onCallStateChanged(state);
- }
- }
-
- @Override
public void onUserMobileDataStateChanged(boolean enabled) {
Log.d(TAG, "onUserMobileDataStateChanged enabled " + enabled + " on SUB " + mSubId);
sExecutor.execute(() -> {
@@ -787,15 +707,6 @@
default void onAirplaneModeChanged(boolean enabled) {
}
-
- /**
- * Notify clients data roaming changed of subscription.
- */
- default void onDataRoamingChanged(int subId, boolean enabled) {
- }
-
- default void onCallStateChanged(int state) {
- }
}
public void dump(IndentingPrintWriter printwriter) {
@@ -803,8 +714,6 @@
printwriter.increaseIndent();
printwriter.println(" availableSubInfoEntityList= " + mAvailableSubInfoEntityList);
printwriter.println(" activeSubInfoEntityList=" + mActiveSubInfoEntityList);
- printwriter.println(" mobileNetworkInfoEntityList= " + mMobileNetworkInfoEntityList);
- printwriter.println(" uiccInfoEntityList= " + mUiccInfoEntityList);
printwriter.println(" CacheSubscriptionInfoEntityMap= " + sCacheSubscriptionInfoEntityMap);
printwriter.println(" SubscriptionInfoMap= " + mSubscriptionInfoMap);
printwriter.flush();
diff --git a/src/com/android/settings/network/apn/ApnPreference.java b/src/com/android/settings/network/apn/ApnPreference.java
index 879fcb6..55258c1 100644
--- a/src/com/android/settings/network/apn/ApnPreference.java
+++ b/src/com/android/settings/network/apn/ApnPreference.java
@@ -85,10 +85,11 @@
final RelativeLayout textArea = (RelativeLayout) view.findViewById(R.id.text_layout);
textArea.setOnClickListener(this);
+ final View radioButtonFrame = view.itemView.requireViewById(R.id.apn_radio_button_frame);
final RadioButton rb = view.itemView.requireViewById(R.id.apn_radiobutton);
mRadioButton = rb;
if (mDefaultSelectable) {
- view.itemView.requireViewById(R.id.apn_radio_button_frame).setOnClickListener((v) -> {
+ radioButtonFrame.setOnClickListener((v) -> {
rb.performClick();
});
rb.setOnCheckedChangeListener(this);
@@ -96,9 +97,9 @@
mProtectFromCheckedChange = true;
rb.setChecked(mIsChecked);
mProtectFromCheckedChange = false;
- rb.setVisibility(View.VISIBLE);
+ radioButtonFrame.setVisibility(View.VISIBLE);
} else {
- rb.setVisibility(View.GONE);
+ radioButtonFrame.setVisibility(View.GONE);
}
}
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSettings.java b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
index 34d2fbd..d70ef25 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSettings.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSettings.java
@@ -20,11 +20,14 @@
import android.app.Activity;
import android.app.settings.SettingsEnums;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Bundle;
import android.os.UserManager;
import android.provider.Settings;
+import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -106,6 +109,15 @@
private SubscriptionInfoEntity mSubscriptionInfoEntity;
private MobileNetworkInfoEntity mMobileNetworkInfoEntity;
+ private BroadcastReceiver mBrocastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
+ redrawPreferenceControllers();
+ }
+ }
+ };
+
public MobileNetworkSettings() {
super(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
}
@@ -351,6 +363,10 @@
mMobileNetworkRepository.updateEntity();
// TODO: remove log after fixing b/182326102
Log.d(LOG_TAG, "onResume() subId=" + mSubId);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ getContext().registerReceiver(mBrocastReceiver, intentFilter, Context.RECEIVER_EXPORTED);
}
private void onSubscriptionDetailChanged() {
@@ -370,6 +386,7 @@
@Override
public void onPause() {
mMobileNetworkRepository.removeRegister(this);
+ getContext().unregisterReceiver(mBrocastReceiver);
super.onPause();
}
diff --git a/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt b/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt
index a5d4ba8..b5cdeda 100644
--- a/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt
+++ b/src/com/android/settings/network/telephony/wificalling/WifiCallingRepository.kt
@@ -21,14 +21,16 @@
import android.telephony.CarrierConfigManager
import android.telephony.CarrierConfigManager.KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL
import android.telephony.SubscriptionManager
-import android.telephony.TelephonyManager
import android.telephony.ims.ImsMmTelManager.WiFiCallingMode
import android.telephony.ims.feature.MmTelFeature
import android.telephony.ims.stub.ImsRegistrationImplBase
+import androidx.lifecycle.LifecycleOwner
import com.android.settings.network.telephony.ims.ImsMmTelRepository
import com.android.settings.network.telephony.ims.ImsMmTelRepositoryImpl
import com.android.settings.network.telephony.ims.imsFeatureProvisionedFlow
import com.android.settings.network.telephony.subscriptionsChangedFlow
+import com.android.settings.network.telephony.telephonyManager
+import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -38,13 +40,19 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
-class WifiCallingRepository(
+interface IWifiCallingRepository {
+ /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */
+ fun collectIsWifiCallingReadyFlow(lifecycleOwner: LifecycleOwner, action: (Boolean) -> Unit)
+}
+
+class WifiCallingRepository
+@JvmOverloads
+constructor(
private val context: Context,
private val subId: Int,
private val imsMmTelRepository: ImsMmTelRepository = ImsMmTelRepositoryImpl(context, subId)
-) {
- private val telephonyManager = context.getSystemService(TelephonyManager::class.java)!!
- .createForSubscriptionId(subId)
+) : IWifiCallingRepository {
+ private val telephonyManager = context.telephonyManager(subId)
private val carrierConfigManager = context.getSystemService(CarrierConfigManager::class.java)!!
@@ -59,6 +67,14 @@
.getConfigForSubId(subId, KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
.getBoolean(KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL)
+ /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */
+ override fun collectIsWifiCallingReadyFlow(
+ lifecycleOwner: LifecycleOwner,
+ action: (Boolean) -> Unit,
+ ) {
+ wifiCallingReadyFlow().collectLatestWithLifecycle(lifecycleOwner, action = action)
+ }
+
@OptIn(ExperimentalCoroutinesApi::class)
fun wifiCallingReadyFlow(): Flow<Boolean> {
if (!SubscriptionManager.isValidSubscriptionId(subId)) return flowOf(false)
diff --git a/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java b/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java
index aebc4eb..9f819d1 100644
--- a/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java
+++ b/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java
@@ -26,6 +26,8 @@
import androidx.preference.Preference;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.google.common.base.Preconditions;
diff --git a/src/com/android/settings/notification/modes/IconLoader.java b/src/com/android/settings/notification/modes/IconLoader.java
deleted file mode 100644
index c590285..0000000
--- a/src/com/android/settings/notification/modes/IconLoader.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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.notification.modes;
-
-import static com.google.common.util.concurrent.Futures.immediateFuture;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.Nullable;
-import android.app.AutomaticZenRule;
-import android.content.Context;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
-import android.service.notification.SystemZenRules;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.LruCache;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.content.res.AppCompatResources;
-
-import com.google.common.util.concurrent.FluentFuture;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-class IconLoader {
-
- private static final String TAG = "ZenIconLoader";
-
- private static final Drawable MISSING = new ColorDrawable();
-
- @Nullable // Until first usage
- private static IconLoader sInstance;
-
- private final LruCache<String, Drawable> mCache;
- private final ListeningExecutorService mBackgroundExecutor;
-
- static IconLoader getInstance() {
- if (sInstance == null) {
- sInstance = new IconLoader();
- }
- return sInstance;
- }
-
- private IconLoader() {
- this(Executors.newFixedThreadPool(4));
- }
-
- @VisibleForTesting
- IconLoader(ExecutorService backgroundExecutor) {
- mCache = new LruCache<>(50);
- mBackgroundExecutor =
- MoreExecutors.listeningDecorator(backgroundExecutor);
- }
-
- @NonNull
- ListenableFuture<Drawable> getIcon(Context context, @NonNull AutomaticZenRule rule) {
- if (rule.getIconResId() == 0) {
- return Futures.immediateFuture(getFallbackIcon(context, rule.getType()));
- }
-
- return FluentFuture.from(loadIcon(context, rule.getPackageName(), rule.getIconResId()))
- .transform(icon ->
- icon != null ? icon : getFallbackIcon(context, rule.getType()),
- MoreExecutors.directExecutor());
- }
-
- @NonNull
- private ListenableFuture</* @Nullable */ Drawable> loadIcon(Context context, String pkg,
- int iconResId) {
- String cacheKey = pkg + ":" + iconResId;
- synchronized (mCache) {
- Drawable cachedValue = mCache.get(cacheKey);
- if (cachedValue != null) {
- return immediateFuture(cachedValue != MISSING ? cachedValue : null);
- }
- }
-
- return FluentFuture.from(mBackgroundExecutor.submit(() -> {
- if (TextUtils.isEmpty(pkg) || SystemZenRules.PACKAGE_ANDROID.equals(pkg)) {
- return context.getDrawable(iconResId);
- } else {
- Context appContext = context.createPackageContext(pkg, 0);
- Drawable appDrawable = AppCompatResources.getDrawable(appContext, iconResId);
- return getMonochromeIconIfPresent(appDrawable);
- }
- })).catching(Exception.class, ex -> {
- // If we cannot resolve the icon, then store MISSING in the cache below, so
- // we don't try again.
- Log.e(TAG, "Error while loading icon " + cacheKey, ex);
- return null;
- }, MoreExecutors.directExecutor()).transform(drawable -> {
- synchronized (mCache) {
- mCache.put(cacheKey, drawable != null ? drawable : MISSING);
- }
- return drawable;
- }, MoreExecutors.directExecutor());
- }
-
- private static Drawable getFallbackIcon(Context context, int ruleType) {
- int iconResIdFromType = switch (ruleType) {
- case AutomaticZenRule.TYPE_UNKNOWN ->
- com.android.internal.R.drawable.ic_zen_mode_type_unknown;
- case AutomaticZenRule.TYPE_OTHER ->
- com.android.internal.R.drawable.ic_zen_mode_type_other;
- case AutomaticZenRule.TYPE_SCHEDULE_TIME ->
- com.android.internal.R.drawable.ic_zen_mode_type_schedule_time;
- case AutomaticZenRule.TYPE_SCHEDULE_CALENDAR ->
- com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar;
- case AutomaticZenRule.TYPE_BEDTIME ->
- com.android.internal.R.drawable.ic_zen_mode_type_bedtime;
- case AutomaticZenRule.TYPE_DRIVING ->
- com.android.internal.R.drawable.ic_zen_mode_type_driving;
- case AutomaticZenRule.TYPE_IMMERSIVE ->
- com.android.internal.R.drawable.ic_zen_mode_type_immersive;
- case AutomaticZenRule.TYPE_THEATER ->
- com.android.internal.R.drawable.ic_zen_mode_type_theater;
- case AutomaticZenRule.TYPE_MANAGED ->
- com.android.internal.R.drawable.ic_zen_mode_type_managed;
- default ->
- com.android.internal.R.drawable.ic_zen_mode_type_unknown;
- };
- return requireNonNull(context.getDrawable(iconResIdFromType));
- }
-
- private static Drawable getMonochromeIconIfPresent(Drawable icon) {
- // For created rules, the app should've provided a monochrome Drawable. However, implicit
- // rules have the app's icon, which is not -- but might have a monochrome layer. Thus
- // we choose it, if present.
- if (icon instanceof AdaptiveIconDrawable adaptiveIcon) {
- if (adaptiveIcon.getMonochrome() != null) {
- // Wrap with negative inset => scale icon (inspired from BaseIconFactory)
- return new InsetDrawable(adaptiveIcon.getMonochrome(),
- -2.0f * AdaptiveIconDrawable.getExtraInsetFraction());
- }
- }
- return icon;
- }
-}
diff --git a/src/com/android/settings/notification/modes/IconUtil.java b/src/com/android/settings/notification/modes/IconUtil.java
index c6ecaa0..1e653bf 100644
--- a/src/com/android/settings/notification/modes/IconUtil.java
+++ b/src/com/android/settings/notification/modes/IconUtil.java
@@ -24,6 +24,7 @@
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
+import androidx.annotation.AttrRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
@@ -32,10 +33,18 @@
class IconUtil {
- static Drawable applyTint(@NonNull Context context, @NonNull Drawable icon) {
+ static Drawable applyNormalTint(@NonNull Context context, @NonNull Drawable icon) {
+ return applyTint(context, icon, android.R.attr.colorControlNormal);
+ }
+
+ static Drawable applyAccentTint(@NonNull Context context, @NonNull Drawable icon) {
+ return applyTint(context, icon, android.R.attr.colorAccent);
+ }
+
+ private static Drawable applyTint(@NonNull Context context, @NonNull Drawable icon,
+ @AttrRes int colorAttr) {
icon = icon.mutate();
- icon.setTintList(
- Utils.getColorAttr(context, android.R.attr.colorControlNormal));
+ icon.setTintList(Utils.getColorAttr(context, colorAttr));
return icon;
}
diff --git a/src/com/android/settings/notification/modes/InterruptionFilterPreferenceController.java b/src/com/android/settings/notification/modes/InterruptionFilterPreferenceController.java
index ee4c702..8bdeea4 100644
--- a/src/com/android/settings/notification/modes/InterruptionFilterPreferenceController.java
+++ b/src/com/android/settings/notification/modes/InterruptionFilterPreferenceController.java
@@ -26,6 +26,8 @@
import androidx.preference.TwoStatePreference;
import com.android.settings.R;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
class InterruptionFilterPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
diff --git a/src/com/android/settings/notification/modes/ZenMode.java b/src/com/android/settings/notification/modes/ZenMode.java
deleted file mode 100644
index 4ece2e3..0000000
--- a/src/com/android/settings/notification/modes/ZenMode.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * 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.notification.modes;
-
-import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent;
-import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
-import static android.service.notification.ZenModeConfig.tryParseEventConditionId;
-import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;
-
-import static com.google.common.base.Preconditions.checkState;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.SuppressLint;
-import android.app.AutomaticZenRule;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.service.notification.SystemZenRules;
-import android.service.notification.ZenDeviceEffects;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenPolicy;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.settings.R;
-
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.Objects;
-
-/**
- * Represents either an {@link AutomaticZenRule} or the manual DND rule in a unified way.
- *
- * <p>It also adapts other rule features that we don't want to expose in the UI, such as
- * interruption filters other than {@code PRIORITY}, rules without specific icons, etc.
- */
-class ZenMode {
-
- private static final String TAG = "ZenMode";
-
- static final String MANUAL_DND_MODE_ID = "manual_dnd";
-
- // Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy.
- private static final ZenPolicy POLICY_INTERRUPTION_FILTER_ALARMS =
- new ZenPolicy.Builder()
- .disallowAllSounds()
- .allowAlarms(true)
- .allowMedia(true)
- .allowPriorityChannels(false)
- .build();
-
- // Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy.
- private static final ZenPolicy POLICY_INTERRUPTION_FILTER_NONE =
- new ZenPolicy.Builder()
- .disallowAllSounds()
- .hideAllVisualEffects()
- .allowPriorityChannels(false)
- .build();
-
- private final String mId;
- private AutomaticZenRule mRule;
- private final boolean mIsActive;
- private final boolean mIsManualDnd;
-
- ZenMode(String id, AutomaticZenRule rule, boolean isActive) {
- this(id, rule, isActive, false);
- }
-
- private ZenMode(String id, AutomaticZenRule rule, boolean isActive, boolean isManualDnd) {
- mId = id;
- mRule = rule;
- mIsActive = isActive;
- mIsManualDnd = isManualDnd;
- }
-
- static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) {
- return new ZenMode(MANUAL_DND_MODE_ID, manualRule, isActive, true);
- }
-
- @NonNull
- public String getId() {
- return mId;
- }
-
- @NonNull
- public AutomaticZenRule getRule() {
- return mRule;
- }
-
- @NonNull
- public ListenableFuture<Drawable> getIcon(@NonNull Context context,
- @NonNull IconLoader iconLoader) {
- if (mIsManualDnd) {
- return Futures.immediateFuture(requireNonNull(
- context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
- }
-
- return iconLoader.getIcon(context, mRule);
- }
-
- @NonNull
- public ZenPolicy getPolicy() {
- switch (mRule.getInterruptionFilter()) {
- case INTERRUPTION_FILTER_PRIORITY:
- case NotificationManager.INTERRUPTION_FILTER_ALL:
- return requireNonNull(mRule.getZenPolicy());
-
- case NotificationManager.INTERRUPTION_FILTER_ALARMS:
- return POLICY_INTERRUPTION_FILTER_ALARMS;
-
- case NotificationManager.INTERRUPTION_FILTER_NONE:
- return POLICY_INTERRUPTION_FILTER_NONE;
-
- case NotificationManager.INTERRUPTION_FILTER_UNKNOWN:
- default:
- Log.wtf(TAG, "Rule " + mId + " with unexpected interruptionFilter "
- + mRule.getInterruptionFilter());
- return requireNonNull(mRule.getZenPolicy());
- }
- }
-
- /**
- * Updates the {@link ZenPolicy} of the associated {@link AutomaticZenRule} based on the
- * supplied policy. In some cases this involves conversions, so that the following call
- * to {@link #getPolicy} might return a different policy from the one supplied here.
- */
- @SuppressLint("WrongConstant")
- public void setPolicy(@NonNull ZenPolicy policy) {
- ZenPolicy currentPolicy = getPolicy();
- if (currentPolicy.equals(policy)) {
- return;
- }
-
- if (mRule.getInterruptionFilter() == INTERRUPTION_FILTER_ALL) {
- Log.wtf(TAG, "Able to change policy without filtering being enabled");
- }
-
- // If policy is customized from any of the "special" ones, make the rule PRIORITY.
- if (mRule.getInterruptionFilter() != INTERRUPTION_FILTER_PRIORITY) {
- mRule.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
- }
- mRule.setZenPolicy(policy);
- }
-
- @NonNull
- public ZenDeviceEffects getDeviceEffects() {
- return mRule.getDeviceEffects() != null
- ? mRule.getDeviceEffects()
- : new ZenDeviceEffects.Builder().build();
- }
-
- public void setCustomModeConditionId(Context context, Uri conditionId) {
- checkState(SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName()),
- "Trying to change condition of non-system-owned rule %s (to %s)",
- mRule, conditionId);
-
- Uri oldCondition = mRule.getConditionId();
- mRule.setConditionId(conditionId);
-
- ZenModeConfig.ScheduleInfo scheduleInfo = tryParseScheduleConditionId(conditionId);
- if (scheduleInfo != null) {
- mRule.setType(AutomaticZenRule.TYPE_SCHEDULE_TIME);
- mRule.setOwner(ZenModeConfig.getScheduleConditionProvider());
- mRule.setTriggerDescription(
- getTriggerDescriptionForScheduleTime(context, scheduleInfo));
- return;
- }
-
- ZenModeConfig.EventInfo eventInfo = tryParseEventConditionId(conditionId);
- if (eventInfo != null) {
- mRule.setType(AutomaticZenRule.TYPE_SCHEDULE_CALENDAR);
- mRule.setOwner(ZenModeConfig.getEventConditionProvider());
- mRule.setTriggerDescription(getTriggerDescriptionForScheduleEvent(context, eventInfo));
- return;
- }
-
- if (ZenModeConfig.isValidCustomManualConditionId(conditionId)) {
- mRule.setType(AutomaticZenRule.TYPE_OTHER);
- mRule.setOwner(ZenModeConfig.getCustomManualConditionProvider());
- mRule.setTriggerDescription("");
- return;
- }
-
- Log.wtf(TAG, String.format(
- "Changed condition of rule %s (%s -> %s) but cannot recognize which kind of "
- + "condition it was!",
- mRule, oldCondition, conditionId));
- }
-
- public boolean canEditName() {
- return !isManualDnd();
- }
-
- public boolean canEditIcon() {
- return !isManualDnd();
- }
-
- public boolean canBeDeleted() {
- return !mIsManualDnd;
- }
-
- public boolean isManualDnd() {
- return mIsManualDnd;
- }
-
- public boolean isActive() {
- return mIsActive;
- }
-
- public boolean isSystemOwned() {
- return SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName());
- }
-
- @AutomaticZenRule.Type
- public int getType() {
- return mRule.getType();
- }
-
- @Override
- public boolean equals(@Nullable Object obj) {
- return obj instanceof ZenMode other
- && mId.equals(other.mId)
- && mRule.equals(other.mRule)
- && mIsActive == other.mIsActive;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mId, mRule, mIsActive);
- }
-
- @Override
- public String toString() {
- return mId + "(" + (mIsActive ? "active" : "inactive") + ") -> " + mRule;
- }
-}
diff --git a/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java
index 8585234..914683f 100644
--- a/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeActionsPreferenceController.java
@@ -16,7 +16,7 @@
package com.android.settings.notification.modes;
-import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -27,6 +27,8 @@
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.ActionButtonsPreference;
class ZenModeActionsPreferenceController extends AbstractZenModePreferenceController {
@@ -50,7 +52,7 @@
buttonsPreference.setButton2Enabled(zenMode.canEditIcon());
buttonsPreference.setButton2OnClickListener(v -> {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, zenMode.getId());
+ bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
new SubSettingLauncher(mContext)
.setDestination(ZenModeIconPickerFragment.class.getName())
// TODO: b/332937635 - Update metrics category
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
index 6835e6c..f62dfdd 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceController.java
@@ -17,8 +17,7 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
-
-import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -32,6 +31,8 @@
import com.android.settings.core.SubSettingLauncher;
import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import java.util.ArrayList;
import java.util.HashMap;
@@ -72,7 +73,7 @@
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, zenMode.getId());
+ bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeAppsFragment.class.getName())
@@ -129,11 +130,13 @@
return appsBypassingDnd;
}
- @VisibleForTesting final ApplicationsState.Callbacks mAppSessionCallbacks =
+ @VisibleForTesting
+ final ApplicationsState.Callbacks mAppSessionCallbacks =
new ApplicationsState.Callbacks() {
@Override
- public void onRunningStateChanged(boolean running) { }
+ public void onRunningStateChanged(boolean running) {
+ }
@Override
public void onPackageListChanged() {
@@ -146,16 +149,20 @@
}
@Override
- public void onPackageIconChanged() { }
+ public void onPackageIconChanged() {
+ }
@Override
- public void onPackageSizeChanged(String packageName) { }
+ public void onPackageSizeChanged(String packageName) {
+ }
@Override
- public void onAllSizesComputed() { }
+ public void onAllSizesComputed() {
+ }
@Override
- public void onLauncherInfoChanged() { }
+ public void onLauncherInfoChanged() {
+ }
@Override
public void onLoadEntriesCompleted() {
diff --git a/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
index d5ef044..1d807ed 100644
--- a/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeAppsPreferenceController.java
@@ -16,7 +16,7 @@
package com.android.settings.notification.modes;
-import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.app.settings.SettingsEnums;
import android.content.Context;
@@ -31,6 +31,8 @@
import androidx.preference.TwoStatePreference;
import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
public class ZenModeAppsPreferenceController extends
@@ -103,7 +105,7 @@
private void launchPrioritySettings() {
Bundle bundle = new Bundle();
if (mModeId != null) {
- bundle.putString(MODE_ID, mModeId);
+ bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, mModeId);
}
// TODO(b/332937635): Update metrics category
new SubSettingLauncher(mContext)
diff --git a/src/com/android/settings/notification/modes/ZenModeButtonPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeButtonPreferenceController.java
index 1846dfc..4a99b33 100644
--- a/src/com/android/settings/notification/modes/ZenModeButtonPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeButtonPreferenceController.java
@@ -23,9 +23,11 @@
import androidx.preference.Preference;
import com.android.settings.R;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
-public class ZenModeButtonPreferenceController extends AbstractZenModePreferenceController {
+class ZenModeButtonPreferenceController extends AbstractZenModePreferenceController {
private Button mZenButton;
diff --git a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
index 8d27d4c..e5c1e48 100644
--- a/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceController.java
@@ -16,7 +16,7 @@
package com.android.settings.notification.modes;
-import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -25,6 +25,8 @@
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeCallsLinkPreferenceController extends AbstractZenModePreferenceController {
@@ -39,7 +41,7 @@
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, zenMode.getId());
+ bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeCallsFragment.class.getName())
diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java
index bca7b55..b0d3952 100644
--- a/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceController.java
@@ -23,7 +23,10 @@
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
-public class ZenModeDisplayEffectPreferenceController extends AbstractZenModePreferenceController
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
+class ZenModeDisplayEffectPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
public ZenModeDisplayEffectPreferenceController(Context context, String key,
@@ -34,24 +37,20 @@
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
TwoStatePreference pref = (TwoStatePreference) preference;
- ZenDeviceEffects effects = zenMode.getRule().getDeviceEffects();
- if (effects == null) {
- pref.setChecked(false);
- } else {
- switch (getPreferenceKey()) {
- case "effect_greyscale":
- pref.setChecked(effects.shouldDisplayGrayscale());
- break;
- case "effect_aod":
- pref.setChecked(effects.shouldSuppressAmbientDisplay());
- break;
- case "effect_wallpaper":
- pref.setChecked(effects.shouldDimWallpaper());
- break;
- case "effect_dark_theme":
- pref.setChecked(effects.shouldUseNightMode());
- break;
- }
+ ZenDeviceEffects effects = zenMode.getDeviceEffects();
+ switch (getPreferenceKey()) {
+ case "effect_greyscale":
+ pref.setChecked(effects.shouldDisplayGrayscale());
+ break;
+ case "effect_aod":
+ pref.setChecked(effects.shouldSuppressAmbientDisplay());
+ break;
+ case "effect_wallpaper":
+ pref.setChecked(effects.shouldDimWallpaper());
+ break;
+ case "effect_dark_theme":
+ pref.setChecked(effects.shouldUseNightMode());
+ break;
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
index 712c78a..d3559f1 100644
--- a/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceController.java
@@ -16,7 +16,7 @@
package com.android.settings.notification.modes;
-import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -25,6 +25,8 @@
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeDisplayLinkPreferenceController extends AbstractZenModePreferenceController {
@@ -39,7 +41,7 @@
@Override
void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, zenMode.getId());
+ bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeDisplayFragment.class.getName())
diff --git a/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java
index 8517af1..326bc97 100644
--- a/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceController.java
@@ -23,6 +23,9 @@
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
/**
* Preference controller controlling whether a time schedule-based mode ends at the next alarm.
*/
diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java
index f2f47b9..5897c4d 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragment.java
@@ -16,20 +16,28 @@
package com.android.settings.notification.modes;
+import android.app.AlertDialog;
import android.app.Application;
import android.app.AutomaticZenRule;
import android.app.settings.SettingsEnums;
import android.content.Context;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import com.android.settings.R;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.notification.modes.ZenMode;
import java.util.ArrayList;
import java.util.List;
public class ZenModeFragment extends ZenModeFragmentBase {
+ // for mode deletion menu
+ private static final int DELETE_MODE = 1;
+
@Override
protected int getPreferenceScreenResId() {
return R.xml.modes_rule_settings;
@@ -76,4 +84,43 @@
// TODO: b/332937635 - make this the correct metrics category
return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
}
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ menu.add(Menu.NONE, DELETE_MODE, Menu.NONE, R.string.zen_mode_menu_delete_mode);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ protected boolean onOptionsItemSelected(MenuItem item, ZenMode zenMode) {
+ switch (item.getItemId()) {
+ case DELETE_MODE:
+ new AlertDialog.Builder(mContext)
+ .setTitle(mContext.getString(R.string.zen_mode_delete_mode_confirmation,
+ zenMode.getRule().getName()))
+ .setPositiveButton(R.string.zen_mode_schedule_delete,
+ (dialog, which) -> {
+ // start finishing before calling removeMode() so that we don't
+ // try to update this activity with a nonexistent mode when the
+ // zen mode config is updated
+ finish();
+ mBackend.removeMode(zenMode);
+ })
+ .setNegativeButton(R.string.cancel, null)
+ .show();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ protected void updateZenModeState() {
+ // Because this fragment may be asked to finish by the delete menu but not be done doing
+ // so yet, ignore any attempts to update info in that case.
+ if (getActivity() != null && getActivity().isFinishing()) {
+ return;
+ }
+ super.updateZenModeState();
+ }
}
diff --git a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java
index 67cc13b..d08f7ea 100644
--- a/src/com/android/settings/notification/modes/ZenModeFragmentBase.java
+++ b/src/com/android/settings/notification/modes/ZenModeFragmentBase.java
@@ -16,10 +16,13 @@
package com.android.settings.notification.modes;
+import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
+
import android.app.AutomaticZenRule;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
+import android.view.MenuItem;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -29,6 +32,7 @@
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.notification.modes.ZenMode;
import java.util.List;
@@ -37,7 +41,6 @@
*/
abstract class ZenModeFragmentBase extends ZenModesFragmentBase {
static final String TAG = "ZenModeSettings";
- static final String MODE_ID = "MODE_ID";
@Nullable // only until reloadMode() is called
private ZenMode mZenMode;
@@ -46,17 +49,21 @@
public void onAttach(@NonNull Context context) {
super.onAttach(context);
- // TODO: b/322373473 - Update if modes page ends up using a different method of passing id
+ String id = null;
+ if (getActivity() != null && getActivity().getIntent() != null) {
+ id = getActivity().getIntent().getStringExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID);
+ }
Bundle bundle = getArguments();
- if (bundle != null && bundle.containsKey(MODE_ID)) {
- String id = bundle.getString(MODE_ID);
- if (!reloadMode(id)) {
- Log.e(TAG, "Mode id " + id + " not found");
- toastAndFinish();
- return;
- }
- } else {
- Log.e(TAG, "Mode id required to set mode config settings");
+ if (id == null && bundle != null && bundle.containsKey(EXTRA_AUTOMATIC_ZEN_RULE_ID)) {
+ id = bundle.getString(EXTRA_AUTOMATIC_ZEN_RULE_ID);
+ }
+ if (id == null) {
+ Log.d(TAG, "No id provided");
+ toastAndFinish();
+ return;
+ }
+ if (!reloadMode(id)) {
+ Log.d(TAG, "Mode id " + id + " not found");
toastAndFinish();
return;
}
@@ -108,6 +115,18 @@
updateControllers();
}
+ @Override
+ public final boolean onOptionsItemSelected(MenuItem item) {
+ if (mZenMode != null) {
+ return onOptionsItemSelected(item, mZenMode);
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ protected boolean onOptionsItemSelected(MenuItem item, @NonNull ZenMode zenMode) {
+ return true;
+ }
+
private void updateControllers() {
if (getPreferenceControllers() == null || mZenMode == null) {
return;
diff --git a/src/com/android/settings/notification/modes/ZenModeHeaderController.java b/src/com/android/settings/notification/modes/ZenModeHeaderController.java
index d8f0a67..1845ee8 100644
--- a/src/com/android/settings/notification/modes/ZenModeHeaderController.java
+++ b/src/com/android/settings/notification/modes/ZenModeHeaderController.java
@@ -25,6 +25,9 @@
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController;
+import com.android.settingslib.notification.modes.ZenIconLoader;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
class ZenModeHeaderController extends AbstractZenModePreferenceController {
@@ -62,8 +65,8 @@
}
FutureUtil.whenDone(
- zenMode.getIcon(mContext, IconLoader.getInstance()),
- icon -> mHeaderController.setIcon(IconUtil.applyTint(mContext, icon))
+ zenMode.getIcon(mContext, ZenIconLoader.getInstance()),
+ icon -> mHeaderController.setIcon(IconUtil.applyNormalTint(mContext, icon))
.done(/* rebindActions= */ false),
mContext.getMainExecutor());
}
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
index 9eaaa97..d1d53af 100644
--- a/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
@@ -25,6 +25,9 @@
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController;
+import com.android.settingslib.notification.modes.ZenIconLoader;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
class ZenModeIconPickerIconPreferenceController extends AbstractZenModePreferenceController {
@@ -51,8 +54,8 @@
}
FutureUtil.whenDone(
- zenMode.getIcon(mContext, IconLoader.getInstance()),
- icon -> mHeaderController.setIcon(IconUtil.applyTint(mContext, icon))
+ zenMode.getIcon(mContext, ZenIconLoader.getInstance()),
+ icon -> mHeaderController.setIcon(IconUtil.applyNormalTint(mContext, icon))
.done(/* rebindActions= */ false),
mContext.getMainExecutor());
}
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
index fc991dc..85ceafe 100644
--- a/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceController.java
@@ -33,6 +33,8 @@
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
import com.google.common.collect.ImmutableList;
diff --git a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
index 6e563c4..9b7c8a1 100644
--- a/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceController.java
@@ -16,7 +16,7 @@
package com.android.settings.notification.modes;
-import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -25,6 +25,8 @@
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeMessagesLinkPreferenceController extends AbstractZenModePreferenceController {
private final ZenModeSummaryHelper mSummaryHelper;
@@ -38,7 +40,7 @@
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, zenMode.getId());
+ bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeMessagesFragment.class.getName())
diff --git a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
index 1b1fec4..a2d9411 100644
--- a/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceController.java
@@ -17,8 +17,7 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
-
-import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -27,6 +26,8 @@
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeNotifVisLinkPreferenceController extends AbstractZenModePreferenceController {
@@ -46,7 +47,7 @@
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, zenMode.getId());
+ bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeNotifVisFragment.class.getName())
diff --git a/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceController.java
index f918b25..3d9f713 100644
--- a/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceController.java
@@ -21,19 +21,21 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import androidx.preference.CheckBoxPreference;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
-import com.android.settings.widget.DisabledCheckBoxPreference;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
public class ZenModeNotifVisPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
- @VisibleForTesting protected @ZenPolicy.VisualEffect int mEffect;
+ @VisibleForTesting
+ protected @ZenPolicy.VisualEffect int mEffect;
// if any of these effects are suppressed, this effect must be too
- @VisibleForTesting protected @ZenPolicy.VisualEffect int[] mParentSuppressedEffects;
+ @VisibleForTesting
+ protected @ZenPolicy.VisualEffect int[] mParentSuppressedEffects;
public ZenModeNotifVisPreferenceController(Context context, String key,
@ZenPolicy.VisualEffect int visualEffect,
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
index f6df9e3..99625eb 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceController.java
@@ -17,8 +17,7 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
-
-import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -27,6 +26,8 @@
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
/**
* Preference with a link and summary about what other sounds can break through the mode
@@ -49,7 +50,7 @@
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, zenMode.getId());
+ bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModeOtherFragment.class.getName())
.setSourceMetricsCategory(0)
diff --git a/src/com/android/settings/notification/modes/ZenModeOtherPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeOtherPreferenceController.java
index a770164e..ad5fa6a 100644
--- a/src/com/android/settings/notification/modes/ZenModeOtherPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeOtherPreferenceController.java
@@ -28,6 +28,9 @@
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
class ZenModeOtherPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
diff --git a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
index db8e135..1613a01 100644
--- a/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceController.java
@@ -17,8 +17,7 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
-
-import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import android.content.Context;
import android.os.Bundle;
@@ -27,6 +26,8 @@
import androidx.preference.Preference;
import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
/**
* Preference with a link and summary about what calls and messages can break through the mode
@@ -49,7 +50,7 @@
@Override
public void updateState(Preference preference, @NonNull ZenMode zenMode) {
Bundle bundle = new Bundle();
- bundle.putString(MODE_ID, zenMode.getId());
+ bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, zenMode.getId());
// TODO(b/332937635): Update metrics category
preference.setIntent(new SubSettingLauncher(mContext)
.setDestination(ZenModePeopleFragment.class.getName())
diff --git a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
index 31a8a0d..0f9323d 100644
--- a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java
@@ -46,6 +46,8 @@
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.app.ConversationListSettings;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import java.util.ArrayList;
diff --git a/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceController.java
index 7569051..ae62e35 100644
--- a/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceController.java
@@ -26,6 +26,8 @@
import androidx.preference.TwoStatePreference;
import com.android.settings.R;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
class ZenModeRepeatCallersPreferenceController extends AbstractZenModePreferenceController
implements Preference.OnPreferenceChangeListener {
diff --git a/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java
index e879076..4f45c5c8 100644
--- a/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceController.java
@@ -31,6 +31,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java
index 3432ed5..878a508 100644
--- a/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceController.java
@@ -31,6 +31,8 @@
import androidx.preference.Preference;
import com.android.settings.R;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
import java.text.SimpleDateFormat;
diff --git a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
index fd27958..1c96fee 100644
--- a/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceController.java
@@ -29,6 +29,8 @@
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.PrimarySwitchPreference;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
/**
* Preference controller for the link to an individual mode's configuration page.
@@ -63,7 +65,6 @@
}
switchPref.setChecked(zenMode.getRule().isEnabled());
switchPref.setOnPreferenceChangeListener(mSwitchChangeListener);
-
switchPref.setSummary(zenMode.getRule().getTriggerDescription());
switchPref.setIcon(null);
switchPref.setOnPreferenceClickListener(null);
diff --git a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
index bd0b798..48a4c36 100644
--- a/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
+++ b/src/com/android/settings/notification/modes/ZenModeSummaryHelper.java
@@ -48,6 +48,7 @@
import androidx.annotation.Nullable;
import com.android.settings.R;
+import com.android.settingslib.notification.modes.ZenMode;
import java.util.ArrayList;
import java.util.Arrays;
@@ -193,7 +194,7 @@
enabledEffects.add(getBlockedEffectsSummary(zenMode));
isFirst = false;
}
- ZenDeviceEffects currEffects = zenMode.getRule().getDeviceEffects();
+ ZenDeviceEffects currEffects = zenMode.getRule().getDeviceEffects();
if (currEffects != null) {
if (currEffects.shouldDisplayGrayscale()) {
if (isFirst) {
diff --git a/src/com/android/settings/notification/modes/ZenModesBackend.java b/src/com/android/settings/notification/modes/ZenModesBackend.java
deleted file mode 100644
index 4f86778..0000000
--- a/src/com/android/settings/notification/modes/ZenModesBackend.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * 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.notification.modes;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.AutomaticZenRule;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.net.Uri;
-import android.provider.Settings;
-import android.service.notification.Condition;
-import android.service.notification.ZenModeConfig;
-import android.util.Log;
-
-import com.android.settings.R;
-
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Class used for Settings-NMS interactions related to Mode management.
- *
- * <p>This class converts {@link AutomaticZenRule} instances, as well as the manual zen mode,
- * into the unified {@link ZenMode} format.
- */
-class ZenModesBackend {
-
- private static final String TAG = "ZenModeBackend";
-
- @Nullable // Until first usage
- private static ZenModesBackend sInstance;
-
- private final NotificationManager mNotificationManager;
-
- private final Context mContext;
-
- static ZenModesBackend getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new ZenModesBackend(context.getApplicationContext());
- }
- return sInstance;
- }
-
- ZenModesBackend(Context context) {
- mContext = context;
- mNotificationManager = context.getSystemService(NotificationManager.class);
- }
-
- List<ZenMode> getModes() {
- ArrayList<ZenMode> modes = new ArrayList<>();
- ZenModeConfig currentConfig = mNotificationManager.getZenModeConfig();
- modes.add(getManualDndMode(currentConfig));
-
- Map<String, AutomaticZenRule> zenRules = mNotificationManager.getAutomaticZenRules();
- for (Map.Entry<String, AutomaticZenRule> zenRuleEntry : zenRules.entrySet()) {
- String ruleId = zenRuleEntry.getKey();
- modes.add(new ZenMode(ruleId, zenRuleEntry.getValue(),
- isRuleActive(ruleId, currentConfig)));
- }
-
- modes.sort((l, r) -> {
- if (l.isManualDnd()) {
- return -1;
- } else if (r.isManualDnd()) {
- return 1;
- }
- return l.getRule().getName().compareTo(r.getRule().getName());
- });
-
- return modes;
- }
-
- @Nullable
- ZenMode getMode(String id) {
- ZenModeConfig currentConfig = mNotificationManager.getZenModeConfig();
- if (ZenMode.MANUAL_DND_MODE_ID.equals(id)) {
- return getManualDndMode(currentConfig);
- } else {
- AutomaticZenRule rule = mNotificationManager.getAutomaticZenRule(id);
- if (rule == null) {
- return null;
- }
- return new ZenMode(id, rule, isRuleActive(id, currentConfig));
- }
- }
-
- private ZenMode getManualDndMode(ZenModeConfig config) {
- ZenModeConfig.ZenRule manualRule = config.manualRule;
- // TODO: b/333682392 - Replace with final strings for name & trigger description
- AutomaticZenRule manualDndRule = new AutomaticZenRule.Builder(
- mContext.getString(R.string.zen_mode_settings_title), manualRule.conditionId)
- .setType(manualRule.type)
- .setZenPolicy(manualRule.zenPolicy)
- .setDeviceEffects(manualRule.zenDeviceEffects)
- .setManualInvocationAllowed(manualRule.allowManualInvocation)
- .setConfigurationActivity(null) // No further settings
- .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
- .build();
-
- return ZenMode.manualDndMode(manualDndRule, config != null && config.isManualActive());
- }
-
- private static boolean isRuleActive(String id, ZenModeConfig config) {
- if (config == null) {
- // shouldn't happen if the config is coming from NM, but be safe
- return false;
- }
- ZenModeConfig.ZenRule configRule = config.automaticRules.get(id);
- return configRule != null && configRule.isAutomaticActive();
- }
-
- void updateMode(ZenMode mode) {
- if (mode.isManualDnd()) {
- try {
- NotificationManager.Policy dndPolicy =
- new ZenModeConfig().toNotificationPolicy(mode.getPolicy());
- mNotificationManager.setNotificationPolicy(dndPolicy, /* fromUser= */ true);
-
- mNotificationManager.setManualZenRuleDeviceEffects(
- mode.getRule().getDeviceEffects());
- } catch (Exception e) {
- Log.w(TAG, "Error updating manual mode", e);
- }
- } else {
- mNotificationManager.updateAutomaticZenRule(mode.getId(), mode.getRule(),
- /* fromUser= */ true);
- }
- }
-
- void activateMode(ZenMode mode, @Nullable Duration forDuration) {
- if (mode.isManualDnd()) {
- Uri durationConditionId = null;
- if (forDuration != null) {
- durationConditionId = ZenModeConfig.toTimeCondition(mContext,
- (int) forDuration.toMinutes(), ActivityManager.getCurrentUser(), true).id;
- }
- mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- durationConditionId, TAG, /* fromUser= */ true);
-
- } else {
- if (forDuration != null) {
- throw new IllegalArgumentException(
- "Only the manual DND mode can be activated for a specific duration");
- }
- mNotificationManager.setAutomaticZenRuleState(mode.getId(),
- new Condition(mode.getRule().getConditionId(), "", Condition.STATE_TRUE,
- Condition.SOURCE_USER_ACTION));
- }
- }
-
- void deactivateMode(ZenMode mode) {
- if (mode.isManualDnd()) {
- // When calling with fromUser=true this will not snooze other modes.
- mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG,
- /* fromUser= */ true);
- } else {
- // TODO: b/333527800 - This should (potentially) snooze the rule if it was active.
- mNotificationManager.setAutomaticZenRuleState(mode.getId(),
- new Condition(mode.getRule().getConditionId(), "", Condition.STATE_FALSE,
- Condition.SOURCE_USER_ACTION));
- }
- }
-
- void removeMode(ZenMode mode) {
- if (!mode.canBeDeleted()) {
- throw new IllegalArgumentException("Mode " + mode + " cannot be deleted!");
- }
- mNotificationManager.removeAutomaticZenRule(mode.getId(), /* fromUser= */ true);
- }
-
- /**
- * Creates a new custom mode with the provided {@code name}. The mode will be "manual" (i.e.
- * not have a schedule), this can be later updated by the user in the mode settings page.
- *
- * @return the created mode. Only {@code null} if creation failed due to an internal error
- */
- @Nullable
- ZenMode addCustomMode(String name) {
- AutomaticZenRule rule = new AutomaticZenRule.Builder(name,
- ZenModeConfig.toCustomManualConditionId())
- .setPackage(ZenModeConfig.getCustomManualConditionProvider().getPackageName())
- .setType(AutomaticZenRule.TYPE_OTHER)
- .setOwner(ZenModeConfig.getCustomManualConditionProvider())
- .setManualInvocationAllowed(true)
- .build();
-
- String ruleId = mNotificationManager.addAutomaticZenRule(rule);
- return getMode(ruleId);
- }
-}
diff --git a/src/com/android/settings/notification/modes/ZenModesFragmentBase.java b/src/com/android/settings/notification/modes/ZenModesFragmentBase.java
index d99593a..e1156fe 100644
--- a/src/com/android/settings/notification/modes/ZenModesFragmentBase.java
+++ b/src/com/android/settings/notification/modes/ZenModesFragmentBase.java
@@ -27,6 +27,7 @@
import android.util.Log;
import com.android.settings.dashboard.RestrictedDashboardFragment;
+import com.android.settingslib.notification.modes.ZenModesBackend;
/**
* Base class for all Settings pages controlling Modes behavior.
diff --git a/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceController.java b/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceController.java
index c229fb1..ba74b93 100644
--- a/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModesListAddModePreferenceController.java
@@ -22,6 +22,8 @@
import com.android.settings.utils.ZenServiceListing;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import java.util.Random;
diff --git a/src/com/android/settings/notification/modes/ZenModesListFragment.java b/src/com/android/settings/notification/modes/ZenModesListFragment.java
index 80678f6..77107f8 100644
--- a/src/com/android/settings/notification/modes/ZenModesListFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModesListFragment.java
@@ -29,6 +29,7 @@
import com.android.settings.utils.ManagedServiceSettings;
import com.android.settings.utils.ZenServiceListing;
import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.search.SearchIndexable;
import com.google.common.collect.ImmutableList;
diff --git a/src/com/android/settings/notification/modes/ZenModesListItemPreference.java b/src/com/android/settings/notification/modes/ZenModesListItemPreference.java
index 7ecfb3a..1bc6e55 100644
--- a/src/com/android/settings/notification/modes/ZenModesListItemPreference.java
+++ b/src/com/android/settings/notification/modes/ZenModesListItemPreference.java
@@ -16,16 +16,31 @@
package com.android.settings.notification.modes;
import android.content.Context;
+import android.widget.TextView;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
import com.android.settingslib.RestrictedPreference;
+import com.android.settingslib.Utils;
+import com.android.settingslib.notification.modes.ZenIconLoader;
+import com.android.settingslib.notification.modes.ZenMode;
+
+import com.google.common.base.Strings;
/**
* Preference representing a single mode item on the modes aggregator page. Clicking on this
* preference leads to an individual mode's configuration page.
*/
class ZenModesListItemPreference extends RestrictedPreference {
- final Context mContext;
- ZenMode mZenMode;
+
+ private final Context mContext;
+ private ZenMode mZenMode;
+
+ private TextView mTitleView;
+ private TextView mSummaryView;
ZenModesListItemPreference(Context context, ZenMode zenMode) {
super(context);
@@ -35,19 +50,65 @@
}
@Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ if (holder.findViewById(android.R.id.title) instanceof TextView titleView) {
+ mTitleView = titleView;
+ }
+ if (holder.findViewById(android.R.id.summary) instanceof TextView summaryView) {
+ mSummaryView = summaryView;
+ }
+ updateTextColor(mZenMode);
+ }
+
+ @Override
public void onClick() {
ZenSubSettingLauncher.forMode(mContext, mZenMode.getId()).launch();
}
public void setZenMode(ZenMode zenMode) {
mZenMode = zenMode;
- setTitle(mZenMode.getRule().getName());
- setSummary(mZenMode.getRule().getTriggerDescription());
- setIconSize(ICON_SIZE_SMALL);
+ setTitle(mZenMode.getName());
+ CharSequence statusText = switch (mZenMode.getStatus()) {
+ case ENABLED_AND_ACTIVE ->
+ Strings.isNullOrEmpty(mZenMode.getTriggerDescription())
+ ? mContext.getString(R.string.zen_mode_active_text)
+ : mContext.getString(
+ R.string.zen_mode_format_status_and_trigger,
+ mContext.getString(R.string.zen_mode_active_text),
+ mZenMode.getRule().getTriggerDescription());
+ case ENABLED -> mZenMode.getRule().getTriggerDescription();
+ case DISABLED_BY_USER -> mContext.getString(R.string.zen_mode_disabled_by_user);
+ case DISABLED_BY_OTHER -> mContext.getString(R.string.zen_mode_disabled_tap_to_set_up);
+ };
+ setSummary(statusText);
+ setIconSize(ICON_SIZE_SMALL);
FutureUtil.whenDone(
- mZenMode.getIcon(mContext, IconLoader.getInstance()),
- icon -> setIcon(IconUtil.applyTint(mContext, icon)),
+ mZenMode.getIcon(mContext, ZenIconLoader.getInstance()),
+ icon -> setIcon(
+ zenMode.isActive()
+ ? IconUtil.applyAccentTint(mContext, icon)
+ : IconUtil.applyNormalTint(mContext, icon)),
mContext.getMainExecutor());
+
+ updateTextColor(zenMode);
+ }
+
+ private void updateTextColor(@Nullable ZenMode zenMode) {
+ boolean isActive = zenMode != null && zenMode.isActive();
+ if (mTitleView != null) {
+ mTitleView.setTextColor(Utils.getColorAttr(mContext,
+ isActive ? android.R.attr.colorAccent : android.R.attr.textColorPrimary));
+ }
+ if (mSummaryView != null) {
+ mSummaryView.setTextColor(Utils.getColorAttr(mContext,
+ isActive ? android.R.attr.colorAccent : android.R.attr.textColorSecondary));
+ }
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ ZenMode getZenMode() {
+ return mZenMode;
}
}
diff --git a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
index 5dcd9eb..fb07078 100644
--- a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
@@ -27,6 +27,8 @@
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.search.SearchIndexableRaw;
import java.util.HashMap;
diff --git a/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java b/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java
index aa66e6c..529f7fa 100644
--- a/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java
+++ b/src/com/android/settings/notification/modes/ZenSubSettingLauncher.java
@@ -16,6 +16,8 @@
package com.android.settings.notification.modes;
+import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
+
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
@@ -33,7 +35,7 @@
Class<? extends ZenModeFragmentBase> fragmentClass, String modeId,
int sourceMetricsCategory) {
Bundle bundle = new Bundle();
- bundle.putString(ZenModeFragmentBase.MODE_ID, modeId);
+ bundle.putString(EXTRA_AUTOMATIC_ZEN_RULE_ID, modeId);
return new SubSettingLauncher(context)
.setDestination(fragmentClass.getName())
diff --git a/src/com/android/settings/notification/zen/ZenModeBackend.java b/src/com/android/settings/notification/zen/ZenModeBackend.java
index 426f52d..de641c5 100644
--- a/src/com/android/settings/notification/zen/ZenModeBackend.java
+++ b/src/com/android/settings/notification/zen/ZenModeBackend.java
@@ -116,7 +116,7 @@
ActivityManager.getCurrentUser(), true).id;
if (android.app.Flags.modesApi()) {
mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
- conditionId, TAG, /* fromUser= */ true);
+ conditionId, TAG, /* fromUser= */ true);
} else {
mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
conditionId, TAG);
@@ -241,12 +241,14 @@
}
savePolicy(getNewDefaultPriorityCategories(allowSenders, category),
- priorityCallSenders, priorityMessagesSenders, mPolicy.suppressedVisualEffects,
+ priorityCallSenders, priorityMessagesSenders, mPolicy.suppressedVisualEffects,
mPolicy.priorityConversationSenders);
- if (ZenModeSettingsBase.DEBUG) Log.d(TAG, "onPrefChange allow" +
- stringCategory + "=" + allowSenders + " allow" + stringCategory + "From="
- + ZenModeConfig.sourceToString(allowSendersFrom));
+ if (ZenModeSettingsBase.DEBUG) {
+ Log.d(TAG, "onPrefChange allow"
+ + stringCategory + "=" + allowSenders + " allow" + stringCategory + "From="
+ + ZenModeConfig.sourceToString(allowSendersFrom));
+ }
}
protected void saveConversationSenders(int val) {
@@ -280,7 +282,7 @@
switch (contactType) {
case ZenPolicy.PEOPLE_TYPE_ANYONE:
return ZEN_MODE_FROM_ANYONE;
- case ZenPolicy.PEOPLE_TYPE_CONTACTS:
+ case ZenPolicy.PEOPLE_TYPE_CONTACTS:
return ZEN_MODE_FROM_CONTACTS;
case ZenPolicy.PEOPLE_TYPE_STARRED:
return ZEN_MODE_FROM_STARRED;
@@ -308,7 +310,7 @@
switch (setting) {
case ZenPolicy.PEOPLE_TYPE_ANYONE:
return NotificationManager.Policy.PRIORITY_SENDERS_ANY;
- case ZenPolicy.PEOPLE_TYPE_CONTACTS:
+ case ZenPolicy.PEOPLE_TYPE_CONTACTS:
return NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS;
case ZenPolicy.PEOPLE_TYPE_STARRED:
return NotificationManager.Policy.PRIORITY_SENDERS_STARRED;
@@ -321,7 +323,7 @@
protected int getAlarmsTotalSilencePeopleSummary(int category) {
if (category == NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) {
return R.string.zen_mode_none_messages;
- } else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CALLS){
+ } else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CALLS) {
return R.string.zen_mode_none_calls;
} else if (category == NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS) {
return R.string.zen_mode_from_no_conversations;
@@ -470,8 +472,8 @@
if (cursor != null && cursor.moveToFirst()) {
do {
String contact = cursor.getString(0);
- starredContacts.add(contact != null ? contact :
- mContext.getString(R.string.zen_mode_starred_contacts_empty_name));
+ int emptyNameId = R.string.zen_mode_starred_contacts_empty_name;
+ starredContacts.add(contact != null ? contact : mContext.getString(emptyNameId));
} while (cursor.moveToNext());
}
diff --git a/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java b/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java
index 4f6f058..d16b1e4 100644
--- a/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java
+++ b/src/com/android/settings/notification/zen/ZenModeSliceBuilder.java
@@ -132,8 +132,8 @@
final Uri contentUri = new Uri.Builder().appendPath(ZEN_MODE_SLICE_KEY).build();
final String screenTitle = context.getText(R.string.zen_mode_settings_title).toString();
return SliceBuilderUtils.buildSearchResultPageIntent(context,
- ZenModeSettings.class.getName(), ZEN_MODE_SLICE_KEY, screenTitle,
- SettingsEnums.NOTIFICATION_ZEN_MODE, R.string.menu_key_notifications)
+ ZenModeSettings.class.getName(), ZEN_MODE_SLICE_KEY, screenTitle,
+ SettingsEnums.NOTIFICATION_ZEN_MODE, R.string.menu_key_notifications)
.setClassName(context.getPackageName(), SubSettings.class.getName())
.setData(contentUri);
}
diff --git a/src/com/android/settings/system/FactoryResetPreferenceController.java b/src/com/android/settings/system/FactoryResetPreferenceController.java
index df7cc3d..54c97a3 100644
--- a/src/com/android/settings/system/FactoryResetPreferenceController.java
+++ b/src/com/android/settings/system/FactoryResetPreferenceController.java
@@ -16,6 +16,7 @@
package com.android.settings.system;
import android.Manifest;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
@@ -37,8 +38,8 @@
private static final String TAG = "FactoryResetPreference";
- @VisibleForTesting
- static final String ACTION_PREPARE_FACTORY_RESET =
+ @RequiresPermission(Manifest.permission.PREPARE_FACTORY_RESET)
+ public static final String ACTION_PREPARE_FACTORY_RESET =
"com.android.settings.ACTION_PREPARE_FACTORY_RESET";
private final UserManager mUm;
diff --git a/src/com/android/settings/users/AddUserWhenLockedPreferenceController.java b/src/com/android/settings/users/AddUserWhenLockedPreferenceController.java
index ce5533e..fe90a2a 100644
--- a/src/com/android/settings/users/AddUserWhenLockedPreferenceController.java
+++ b/src/com/android/settings/users/AddUserWhenLockedPreferenceController.java
@@ -42,9 +42,18 @@
if (!isAvailable()) {
restrictedSwitchPreference.setVisible(false);
} else {
- restrictedSwitchPreference.setDisabledByAdmin(
- mUserCaps.disallowAddUser() ? mUserCaps.getEnforcedAdmin() : null);
- restrictedSwitchPreference.setVisible(mUserCaps.mUserSwitcherEnabled);
+ if (android.multiuser.Flags.newMultiuserSettingsUx()) {
+ restrictedSwitchPreference.setVisible(true);
+ if (mUserCaps.mDisallowAddUserSetByAdmin) {
+ restrictedSwitchPreference.setDisabledByAdmin(mUserCaps.mEnforcedAdmin);
+ } else if (mUserCaps.mDisallowAddUser) {
+ restrictedSwitchPreference.setVisible(false);
+ }
+ } else {
+ restrictedSwitchPreference.setDisabledByAdmin(
+ mUserCaps.disallowAddUser() ? mUserCaps.getEnforcedAdmin() : null);
+ restrictedSwitchPreference.setVisible(mUserCaps.mUserSwitcherEnabled);
+ }
}
}
@@ -52,6 +61,8 @@
public int getAvailabilityStatus() {
if (!mUserCaps.isAdmin()) {
return DISABLED_FOR_USER;
+ } else if (android.multiuser.Flags.newMultiuserSettingsUx()) {
+ return AVAILABLE;
} else if (mUserCaps.disallowAddUser() || mUserCaps.disallowAddUserSetByAdmin()) {
return DISABLED_FOR_USER;
} else {
diff --git a/src/com/android/settings/users/GuestTelephonyPreferenceController.java b/src/com/android/settings/users/GuestTelephonyPreferenceController.java
index 4fbd449..e730cbf 100644
--- a/src/com/android/settings/users/GuestTelephonyPreferenceController.java
+++ b/src/com/android/settings/users/GuestTelephonyPreferenceController.java
@@ -19,12 +19,16 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
+import android.os.UserHandle;
import android.os.UserManager;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
+import com.android.settingslib.RestrictedSwitchPreference;
/**
* Controls the preference on the user settings screen which determines whether the guest user
@@ -43,10 +47,21 @@
@Override
public int getAvailabilityStatus() {
- if (!mUserCaps.isAdmin() || !mUserCaps.mCanAddGuest) {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+ || UserManager.isHeadlessSystemUserMode()) {
return DISABLED_FOR_USER;
+ }
+ if (android.multiuser.Flags.newMultiuserSettingsUx()) {
+ if (!mUserCaps.isAdmin()) {
+ return DISABLED_FOR_USER;
+ }
+ return AVAILABLE;
} else {
- return mUserCaps.mUserSwitcherEnabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ if (!mUserCaps.isAdmin() || !mUserCaps.mCanAddGuest) {
+ return DISABLED_FOR_USER;
+ } else {
+ return mUserCaps.mUserSwitcherEnabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
}
}
@@ -74,8 +89,31 @@
public void updateState(Preference preference) {
super.updateState(preference);
mUserCaps.updateAddUserCapabilities(mContext);
- preference.setVisible(isAvailable() && mUserCaps.mUserSwitcherEnabled
- && mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- && !UserManager.isHeadlessSystemUserMode());
+ final RestrictedSwitchPreference restrictedSwitchPreference =
+ (RestrictedSwitchPreference) preference;
+ restrictedSwitchPreference.setChecked(isChecked());
+ if (!isAvailable()) {
+ restrictedSwitchPreference.setVisible(false);
+ } else {
+ if (android.multiuser.Flags.newMultiuserSettingsUx()) {
+ restrictedSwitchPreference.setVisible(true);
+ final RestrictedLockUtils.EnforcedAdmin disallowRemoveUserAdmin =
+ RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
+ UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId());
+ if (disallowRemoveUserAdmin != null) {
+ restrictedSwitchPreference.setDisabledByAdmin(disallowRemoveUserAdmin);
+ } else if (mUserCaps.mDisallowAddUserSetByAdmin) {
+ restrictedSwitchPreference.setDisabledByAdmin(mUserCaps.mEnforcedAdmin);
+ } else if (mUserCaps.mDisallowAddUser) {
+ // Adding user is restricted by system
+ restrictedSwitchPreference.setVisible(false);
+ }
+ } else {
+ restrictedSwitchPreference.setDisabledByAdmin(
+ mUserCaps.disallowAddUser() ? mUserCaps.getEnforcedAdmin() : null);
+ restrictedSwitchPreference.setVisible(mUserCaps.mUserSwitcherEnabled
+ && isAvailable());
+ }
+ }
}
}
diff --git a/src/com/android/settings/users/MultiUserSwitchBarController.java b/src/com/android/settings/users/MultiUserSwitchBarController.java
index f57b795..641ae51 100644
--- a/src/com/android/settings/users/MultiUserSwitchBarController.java
+++ b/src/com/android/settings/users/MultiUserSwitchBarController.java
@@ -57,11 +57,6 @@
mSwitchBar.setDisabledByAdmin(RestrictedLockUtilsInternal
.checkIfRestrictionEnforced(mContext, UserManager.DISALLOW_USER_SWITCH,
UserHandle.myUserId()));
-
- } else if (mUserCapabilities.mDisallowAddUser) {
- mSwitchBar.setDisabledByAdmin(RestrictedLockUtilsInternal
- .checkIfRestrictionEnforced(mContext, UserManager.DISALLOW_ADD_USER,
- UserHandle.myUserId()));
} else {
mSwitchBar.setEnabled(mUserCapabilities.mIsMain);
}
diff --git a/src/com/android/settings/users/RemoveGuestOnExitPreferenceController.java b/src/com/android/settings/users/RemoveGuestOnExitPreferenceController.java
index 01df5fd..345b506 100644
--- a/src/com/android/settings/users/RemoveGuestOnExitPreferenceController.java
+++ b/src/com/android/settings/users/RemoveGuestOnExitPreferenceController.java
@@ -22,6 +22,7 @@
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.Handler;
+import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
@@ -33,6 +34,8 @@
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.RestrictedSwitchPreference;
/**
@@ -70,9 +73,24 @@
if (!isAvailable()) {
restrictedSwitchPreference.setVisible(false);
} else {
- restrictedSwitchPreference.setDisabledByAdmin(
- mUserCaps.disallowAddUser() ? mUserCaps.getEnforcedAdmin() : null);
- restrictedSwitchPreference.setVisible(mUserCaps.mUserSwitcherEnabled);
+ if (android.multiuser.Flags.newMultiuserSettingsUx()) {
+ restrictedSwitchPreference.setVisible(true);
+ final RestrictedLockUtils.EnforcedAdmin disallowRemoveUserAdmin =
+ RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
+ UserManager.DISALLOW_REMOVE_USER, UserHandle.myUserId());
+ if (disallowRemoveUserAdmin != null) {
+ restrictedSwitchPreference.setDisabledByAdmin(disallowRemoveUserAdmin);
+ } else if (mUserCaps.mDisallowAddUserSetByAdmin) {
+ restrictedSwitchPreference.setDisabledByAdmin(mUserCaps.mEnforcedAdmin);
+ } else if (mUserCaps.mDisallowAddUser) {
+ // Adding user is restricted by system
+ restrictedSwitchPreference.setVisible(false);
+ }
+ } else {
+ restrictedSwitchPreference.setDisabledByAdmin(
+ mUserCaps.disallowAddUser() ? mUserCaps.getEnforcedAdmin() : null);
+ restrictedSwitchPreference.setVisible(mUserCaps.mUserSwitcherEnabled);
+ }
}
}
@@ -82,14 +100,24 @@
// then disable this controller
// also disable this controller for non-admin users
// also disable when config_guestUserAllowEphemeralStateChange is false
- if (mUserManager.isGuestUserAlwaysEphemeral()
- || !UserManager.isGuestUserAllowEphemeralStateChange()
- || !mUserCaps.isAdmin()
- || mUserCaps.disallowAddUser()
- || mUserCaps.disallowAddUserSetByAdmin()) {
- return DISABLED_FOR_USER;
+ if (android.multiuser.Flags.newMultiuserSettingsUx()) {
+ if (mUserManager.isGuestUserAlwaysEphemeral()
+ || !UserManager.isGuestUserAllowEphemeralStateChange()
+ || !mUserCaps.isAdmin()) {
+ return DISABLED_FOR_USER;
+ } else {
+ return AVAILABLE;
+ }
} else {
- return mUserCaps.mUserSwitcherEnabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ if (mUserManager.isGuestUserAlwaysEphemeral()
+ || !UserManager.isGuestUserAllowEphemeralStateChange()
+ || !mUserCaps.isAdmin()
+ || mUserCaps.disallowAddUser()
+ || mUserCaps.disallowAddUserSetByAdmin()) {
+ return DISABLED_FOR_USER;
+ } else {
+ return mUserCaps.mUserSwitcherEnabled ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
}
}
diff --git a/src/com/android/settings/users/UserDetailsSettings.java b/src/com/android/settings/users/UserDetailsSettings.java
index 71dd43f..588f01a 100644
--- a/src/com/android/settings/users/UserDetailsSettings.java
+++ b/src/com/android/settings/users/UserDetailsSettings.java
@@ -126,7 +126,11 @@
@Override
public void onResume() {
super.onResume();
- mSwitchUserPref.setEnabled(canSwitchUserNow());
+ if (android.multiuser.Flags.newMultiuserSettingsUx()) {
+ mSwitchUserPref.setEnabled(canSwitchUserNow() && mUserCaps.mUserSwitcherEnabled);
+ } else {
+ mSwitchUserPref.setEnabled(canSwitchUserNow());
+ }
if (mUserInfo.isGuest() && mGuestUserAutoCreated) {
mRemoveUserPref.setEnabled((mUserInfo.flags & UserInfo.FLAG_INITIALIZED) != 0);
}
@@ -358,7 +362,12 @@
mSwitchUserPref.setDisabledByAdmin(RestrictedLockUtilsInternal.getDeviceOwner(context));
} else {
mSwitchUserPref.setDisabledByAdmin(null);
- mSwitchUserPref.setSelectable(true);
+ if (android.multiuser.Flags.newMultiuserSettingsUx()) {
+ mSwitchUserPref.setEnabled(mUserCaps.mUserSwitcherEnabled);
+ mSwitchUserPref.setSelectable(mUserCaps.mUserSwitcherEnabled);
+ } else {
+ mSwitchUserPref.setSelectable(true);
+ }
mSwitchUserPref.setOnPreferenceClickListener(this);
}
if (mUserInfo.isMain() || mUserInfo.isGuest() || !UserManager.isMultipleAdminEnabled()
diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java
index bf21c9b..0cf01e3 100644
--- a/src/com/android/settings/users/UserSettings.java
+++ b/src/com/android/settings/users/UserSettings.java
@@ -463,7 +463,8 @@
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
int pos = 0;
- if (!isCurrentUserAdmin() && canSwitchUserNow() && !isCurrentUserGuest()) {
+ if (!isCurrentUserAdmin() && (canSwitchUserNow() || Flags.newMultiuserSettingsUx())
+ && !isCurrentUserGuest()) {
String nickname = mUserManager.getUserName();
MenuItem removeThisUser = menu.add(0, MENU_REMOVE_USER, pos++,
getResources().getString(R.string.user_remove_user_menu, nickname));
@@ -1198,15 +1199,23 @@
}
List<UserInfo> users;
- if (mUserCaps.mUserSwitcherEnabled) {
+ if (Flags.newMultiuserSettingsUx()) {
// Only users that can be switched to should show up here.
// e.g. Managed profiles appear under Accounts Settings instead
users = mUserManager.getAliveUsers().stream()
.filter(UserInfo::supportsSwitchToByUser)
.collect(Collectors.toList());
} else {
- // Only current user will be displayed in case of multi-user switch is disabled
- users = List.of(mUserManager.getUserInfo(context.getUserId()));
+ if (mUserCaps.mUserSwitcherEnabled) {
+ // Only users that can be switched to should show up here.
+ // e.g. Managed profiles appear under Accounts Settings instead
+ users = mUserManager.getAliveUsers().stream()
+ .filter(UserInfo::supportsSwitchToByUser)
+ .collect(Collectors.toList());
+ } else {
+ // Only current user will be displayed in case of multi-user switch is disabled
+ users = List.of(mUserManager.getUserInfo(context.getUserId()));
+ }
}
final ArrayList<Integer> missingIcons = new ArrayList<>();
@@ -1257,7 +1266,10 @@
pref.setSummary(R.string.user_summary_not_set_up);
// Disallow setting up user which results in user switching when the
// restriction is set.
- pref.setEnabled(!mUserCaps.mDisallowSwitchUser && canSwitchUserNow());
+ // If newMultiuserSettingsUx flag is enabled, allow opening user details page
+ // since switch to user will be disabled
+ pref.setEnabled((!mUserCaps.mDisallowSwitchUser && canSwitchUserNow())
+ || Flags.newMultiuserSettingsUx());
}
} else if (user.isRestricted()) {
pref.setSummary(R.string.user_summary_restricted_profile);
@@ -1417,16 +1429,22 @@
getContext().getResources(), icon)));
pref.setKey(KEY_USER_GUEST);
pref.setOrder(Preference.DEFAULT_ORDER);
- if (mUserCaps.mDisallowSwitchUser) {
+ if (mUserCaps.mDisallowSwitchUser && !Flags.newMultiuserSettingsUx()) {
pref.setDisabledByAdmin(
RestrictedLockUtilsInternal.getDeviceOwner(context));
} else {
pref.setDisabledByAdmin(null);
}
- if (mUserCaps.mUserSwitcherEnabled) {
+ if (Flags.newMultiuserSettingsUx()) {
mGuestUserCategory.addPreference(pref);
// guest user preference is shown hence also make guest category visible
mGuestUserCategory.setVisible(true);
+ } else {
+ if (mUserCaps.mUserSwitcherEnabled) {
+ mGuestUserCategory.addPreference(pref);
+ // guest user preference is shown hence also make guest category visible
+ mGuestUserCategory.setVisible(true);
+ }
}
isGuestAlreadyCreated = true;
}
@@ -1450,10 +1468,11 @@
private boolean updateAddGuestPreference(Context context, boolean isGuestAlreadyCreated) {
boolean isVisible = false;
- if (!isGuestAlreadyCreated && mUserCaps.mCanAddGuest
+ if (!isGuestAlreadyCreated && (mUserCaps.mCanAddGuest
+ || (Flags.newMultiuserSettingsUx() && mUserCaps.mDisallowAddUser))
&& mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_GUEST)
&& WizardManagerHelper.isDeviceProvisioned(context)
- && mUserCaps.mUserSwitcherEnabled) {
+ && (mUserCaps.mUserSwitcherEnabled || Flags.newMultiuserSettingsUx())) {
Drawable icon = context.getDrawable(
com.android.settingslib.R.drawable.ic_account_circle);
mAddGuest.setIcon(centerAndTint(icon));
@@ -1466,7 +1485,25 @@
mAddGuest.setEnabled(false);
} else {
mAddGuest.setTitle(com.android.settingslib.R.string.guest_new_guest);
- mAddGuest.setEnabled(canSwitchUserNow());
+ if (Flags.newMultiuserSettingsUx()
+ && mUserCaps.mDisallowAddUserSetByAdmin) {
+ mAddGuest.setDisabledByAdmin(mUserCaps.mEnforcedAdmin);
+ } else if (Flags.newMultiuserSettingsUx() && mUserCaps.mDisallowAddUser) {
+ final List<UserManager.EnforcingUser> enforcingUsers =
+ mUserManager.getUserRestrictionSources(UserManager.DISALLOW_ADD_USER,
+ UserHandle.of(UserHandle.myUserId()));
+ if (!enforcingUsers.isEmpty()) {
+ final UserManager.EnforcingUser enforcingUser = enforcingUsers.get(0);
+ final int restrictionSource = enforcingUser.getUserRestrictionSource();
+ if (restrictionSource == UserManager.RESTRICTION_SOURCE_SYSTEM) {
+ mAddGuest.setEnabled(false);
+ } else {
+ mAddGuest.setVisible(false);
+ }
+ }
+ } else {
+ mAddGuest.setEnabled(canSwitchUserNow() || Flags.newMultiuserSettingsUx());
+ }
}
} else {
mAddGuest.setVisible(false);
@@ -1494,16 +1531,18 @@
private void updateAddUserCommon(Context context, RestrictedPreference addUser,
boolean canAddRestrictedProfile) {
- if ((mUserCaps.mCanAddUser && !mUserCaps.mDisallowAddUserSetByAdmin)
+ if ((mUserCaps.mCanAddUser
+ && !(mUserCaps.mDisallowAddUserSetByAdmin && Flags.newMultiuserSettingsUx()))
&& WizardManagerHelper.isDeviceProvisioned(context)
- && mUserCaps.mUserSwitcherEnabled) {
+ && (mUserCaps.mUserSwitcherEnabled || Flags.newMultiuserSettingsUx())) {
addUser.setVisible(true);
addUser.setSelectable(true);
final boolean canAddMoreUsers =
mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY)
|| (canAddRestrictedProfile
&& mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_RESTRICTED));
- addUser.setEnabled(canAddMoreUsers && !mAddingUser && canSwitchUserNow());
+ addUser.setEnabled(canAddMoreUsers && !mAddingUser
+ && (canSwitchUserNow() || Flags.newMultiuserSettingsUx()));
if (!canAddMoreUsers) {
addUser.setSummary(getString(R.string.user_add_max_count));
@@ -1514,6 +1553,23 @@
addUser.setDisabledByAdmin(
mUserCaps.mDisallowAddUser ? mUserCaps.mEnforcedAdmin : null);
}
+ } else if (Flags.newMultiuserSettingsUx() && mUserCaps.mDisallowAddUserSetByAdmin) {
+ addUser.setVisible(true);
+ addUser.setDisabledByAdmin(mUserCaps.mEnforcedAdmin);
+ } else if (Flags.newMultiuserSettingsUx() && mUserCaps.mDisallowAddUser) {
+ final List<UserManager.EnforcingUser> enforcingUsers =
+ mUserManager.getUserRestrictionSources(UserManager.DISALLOW_ADD_USER,
+ UserHandle.of(UserHandle.myUserId()));
+ if (!enforcingUsers.isEmpty()) {
+ final UserManager.EnforcingUser enforcingUser = enforcingUsers.get(0);
+ final int restrictionSource = enforcingUser.getUserRestrictionSource();
+ if (restrictionSource == UserManager.RESTRICTION_SOURCE_SYSTEM) {
+ addUser.setVisible(true);
+ addUser.setEnabled(false);
+ } else {
+ addUser.setVisible(false);
+ }
+ }
} else {
addUser.setVisible(false);
}
diff --git a/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java b/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java
index 82537d4..e5581d3 100644
--- a/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java
+++ b/src/com/android/settings/wifi/calling/WifiCallingSettingsForSub.java
@@ -33,7 +33,6 @@
import android.telephony.TelephonyManager;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsMmTelManager;
-import android.telephony.ims.ProvisioningManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
@@ -42,12 +41,14 @@
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
+import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener;
import androidx.preference.PreferenceScreen;
-import com.android.ims.ImsConfig;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.flags.Flags;
@@ -57,8 +58,12 @@
import com.android.settings.Utils;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.network.ims.WifiCallingQueryImsState;
+import com.android.settings.network.telephony.wificalling.IWifiCallingRepository;
+import com.android.settings.network.telephony.wificalling.WifiCallingRepository;
import com.android.settings.widget.SettingsMainSwitchPreference;
+import kotlin.Unit;
+
import java.util.List;
/**
@@ -103,7 +108,6 @@
private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private ImsMmTelManager mImsMmTelManager;
- private ProvisioningManager mProvisioningManager;
private TelephonyManager mTelephonyManager;
private PhoneTelephonyCallback mTelephonyCallback;
@@ -188,19 +192,6 @@
return true;
};
- private final ProvisioningManager.Callback mProvisioningCallback =
- new ProvisioningManager.Callback() {
- @Override
- public void onProvisioningIntChanged(int item, int value) {
- if (item == ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED
- || item == ImsConfig.ConfigConstants.VLT_SETTING_ENABLED) {
- // The provisioning policy might have changed. Update the body to make sure
- // this change takes effect if needed.
- updateBody();
- }
- }
- };
-
@VisibleForTesting
void showAlert(Intent intent) {
final Context context = getActivity();
@@ -264,14 +255,6 @@
}
@VisibleForTesting
- ProvisioningManager getImsProvisioningManager() {
- if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
- return null;
- }
- return ProvisioningManager.createForSubscriptionId(mSubId);
- }
-
- @VisibleForTesting
ImsMmTelManager getImsMmTelManager() {
if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
return null;
@@ -294,7 +277,6 @@
FRAGMENT_BUNDLE_SUBID, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
}
- mProvisioningManager = getImsProvisioningManager();
mImsMmTelManager = getImsMmTelManager();
mSwitchBar = (SettingsMainSwitchPreference) findPreference(SWITCH_BAR);
@@ -336,19 +318,33 @@
return view;
}
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ getWifiCallingRepository().collectIsWifiCallingReadyFlow(
+ getLifecycleOwner(), (isWifiWifiCallingReadyFlow) -> {
+ if (!isWifiWifiCallingReadyFlow) {
+ // This screen is not allowed to be shown due to provisioning policy and
+ // should therefore be closed.
+ finish();
+ }
+ return Unit.INSTANCE;
+ });
+ }
+
@VisibleForTesting
- boolean isWfcProvisionedOnDevice() {
- return queryImsState(mSubId).isWifiCallingProvisioned();
+ @NonNull
+ IWifiCallingRepository getWifiCallingRepository() {
+ return new WifiCallingRepository(requireContext(), mSubId);
+ }
+
+ @VisibleForTesting
+ @NonNull
+ LifecycleOwner getLifecycleOwner() {
+ return getViewLifecycleOwner();
}
private void updateBody() {
- if (!isWfcProvisionedOnDevice()) {
- // This screen is not allowed to be shown due to provisioning policy and should
- // therefore be closed.
- finish();
- return;
- }
-
final CarrierConfigManager configManager = (CarrierConfigManager)
getSystemService(Context.CARRIER_CONFIG_SERVICE);
boolean isWifiOnlySupported = true;
@@ -448,8 +444,6 @@
if (intent.getBooleanExtra(Phone.EXTRA_KEY_ALERT_SHOW, false)) {
showAlert(intent);
}
- // Register callback for provisioning changes.
- registerProvisioningChangedCallback();
}
@Override
@@ -462,8 +456,6 @@
mSwitchBar.removeOnSwitchChangeListener(this);
}
context.unregisterReceiver(mIntentReceiver);
- // Remove callback for provisioning changes.
- unregisterProvisioningChangedCallback();
}
/**
@@ -699,27 +691,6 @@
return SubscriptionManager.getResourcesForSubId(getContext(), mSubId);
}
- @VisibleForTesting
- void registerProvisioningChangedCallback() {
- if (mProvisioningManager == null) {
- return;
- }
- try {
- mProvisioningManager.registerProvisioningChangedCallback(getContext().getMainExecutor(),
- mProvisioningCallback);
- } catch (Exception ex) {
- Log.w(TAG, "onResume: Unable to register callback for provisioning changes.");
- }
- }
-
- @VisibleForTesting
- void unregisterProvisioningChangedCallback() {
- if (mProvisioningManager == null) {
- return;
- }
- mProvisioningManager.unregisterProvisioningChangedCallback(mProvisioningCallback);
- }
-
/**
* Determine whether to override roaming Wi-Fi calling preference when device is connected to
* non-terrestrial network.
diff --git a/tests/Enable16KbTests/Android.bp b/tests/Enable16KbTests/Android.bp
index 781ea8f..fa05d33 100644
--- a/tests/Enable16KbTests/Android.bp
+++ b/tests/Enable16KbTests/Android.bp
@@ -33,7 +33,6 @@
],
platform_apis: true,
certificate: "platform",
- test_suites: ["device-tests"],
libs: [
"android.test.runner",
"android.test.base",
@@ -57,6 +56,6 @@
data: [
":test_16kb_app",
],
- test_suites: ["device-tests"],
+ test_suites: ["general-tests"],
test_config: "AndroidTest.xml",
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
index d28ab3b..5a9f2bc 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceGroupControllerTest.java
@@ -17,6 +17,8 @@
import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+import static com.android.settings.flags.Flags.FLAG_RESOLUTION_AND_ENABLE_CONNECTED_DISPLAY_SETTING;
+import static com.android.settings.flags.Flags.FLAG_ROTATION_CONNECTED_DISPLAY_SETTING;
import static com.google.common.truth.Truth.assertThat;
@@ -30,6 +32,7 @@
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.hardware.input.InputManager;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -40,13 +43,16 @@
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
import com.android.settings.bluetooth.ConnectedBluetoothDeviceUpdater;
import com.android.settings.bluetooth.Utils;
+import com.android.settings.connecteddevice.display.ExternalDisplayUpdater;
import com.android.settings.connecteddevice.dock.DockUpdater;
import com.android.settings.connecteddevice.stylus.StylusDeviceUpdater;
import com.android.settings.connecteddevice.usb.ConnectedUsbDeviceUpdater;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.flags.FakeFeatureFlagsImpl;
import com.android.settings.flags.Flags;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
@@ -65,7 +71,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplicationPackageManager;
@@ -84,6 +89,8 @@
@Mock
private DashboardFragment mDashboardFragment;
@Mock
+ private ExternalDisplayUpdater mExternalDisplayUpdater;
+ @Mock
private ConnectedBluetoothDeviceUpdater mConnectedBluetoothDeviceUpdater;
@Mock
private ConnectedUsbDeviceUpdater mConnectedUsbDeviceUpdater;
@@ -105,6 +112,9 @@
private CachedBluetoothDevice mCachedDevice;
@Mock
private BluetoothDevice mDevice;
+ @Mock
+ private Resources mResources;
+ private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl();
private ShadowApplicationPackageManager mPackageManager;
private PreferenceGroup mPreferenceGroup;
@@ -118,8 +128,10 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mFakeFeatureFlags.setFlag(FLAG_ROTATION_CONNECTED_DISPLAY_SETTING, true);
+ mFakeFeatureFlags.setFlag(FLAG_RESOLUTION_AND_ENABLE_CONNECTED_DISPLAY_SETTING, true);
- mContext = spy(RuntimeEnvironment.application);
+ mContext = spy(ApplicationProvider.getApplicationContext());
mPreference = new Preference(mContext);
mPreference.setKey(PREFERENCE_KEY_1);
mPackageManager = (ShadowApplicationPackageManager) Shadows.shadowOf(
@@ -129,15 +141,19 @@
doReturn(mContext).when(mDashboardFragment).getContext();
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true);
when(mContext.getSystemService(InputManager.class)).thenReturn(mInputManager);
+ when(mContext.getResources()).thenReturn(mResources);
when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{});
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
- mConnectedDeviceGroupController = new ConnectedDeviceGroupController(mContext);
- mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
- mConnectedUsbDeviceUpdater, mConnectedDockUpdater, mStylusDeviceUpdater);
+ mConnectedDeviceGroupController = spy(new ConnectedDeviceGroupController(mContext));
+ when(mConnectedDeviceGroupController.getFeatureFlags()).thenReturn(mFakeFeatureFlags);
+
+ mConnectedDeviceGroupController.init(mExternalDisplayUpdater,
+ mConnectedBluetoothDeviceUpdater, mConnectedUsbDeviceUpdater, mConnectedDockUpdater,
+ mStylusDeviceUpdater);
mConnectedDeviceGroupController.mPreferenceGroup = mPreferenceGroup;
when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
@@ -147,6 +163,7 @@
FeatureFlagUtils.setEnabled(mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES,
true);
+ when(mPreferenceScreen.getContext()).thenReturn(mContext);
}
@Test
@@ -193,6 +210,7 @@
// register the callback in onStart()
mConnectedDeviceGroupController.onStart();
+ verify(mExternalDisplayUpdater).registerCallback();
verify(mConnectedBluetoothDeviceUpdater).registerCallback();
verify(mConnectedUsbDeviceUpdater).registerCallback();
verify(mConnectedDockUpdater).registerCallback();
@@ -204,6 +222,7 @@
public void onStop_shouldUnregisterUpdaters() {
// unregister the callback in onStop()
mConnectedDeviceGroupController.onStop();
+ verify(mExternalDisplayUpdater).unregisterCallback();
verify(mConnectedBluetoothDeviceUpdater).unregisterCallback();
verify(mConnectedUsbDeviceUpdater).unregisterCallback();
verify(mConnectedDockUpdater).unregisterCallback();
@@ -212,10 +231,12 @@
@Test
public void getAvailabilityStatus_noBluetoothUsbDockFeature_returnUnSupported() {
+ mFakeFeatureFlags.setFlag(FLAG_ROTATION_CONNECTED_DISPLAY_SETTING, false);
+ mFakeFeatureFlags.setFlag(FLAG_RESOLUTION_AND_ENABLE_CONNECTED_DISPLAY_SETTING, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
- mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
+ mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
mConnectedUsbDeviceUpdater, null, null);
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
@@ -223,11 +244,23 @@
}
@Test
+ public void getAvailabilityStatus_connectedDisplay_returnSupported() {
+ mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
+ mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
+ mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
+ mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
+ mConnectedUsbDeviceUpdater, null, null);
+
+ assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
+ AVAILABLE_UNSEARCHABLE);
+ }
+
+ @Test
public void getAvailabilityStatus_BluetoothFeature_returnSupported() {
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, true);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
- mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
+ mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
mConnectedUsbDeviceUpdater, null, null);
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
@@ -239,7 +272,7 @@
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, true);
- mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
+ mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
mConnectedUsbDeviceUpdater, null, null);
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
@@ -251,7 +284,7 @@
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
- mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
+ mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
mConnectedUsbDeviceUpdater, mConnectedDockUpdater, null);
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
@@ -261,6 +294,8 @@
@Test
public void getAvailabilityStatus_noUsiStylusFeature_returnUnSupported() {
+ mFakeFeatureFlags.setFlag(FLAG_ROTATION_CONNECTED_DISPLAY_SETTING, false);
+ mFakeFeatureFlags.setFlag(FLAG_RESOLUTION_AND_ENABLE_CONNECTED_DISPLAY_SETTING, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_BLUETOOTH, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_ACCESSORY, false);
mPackageManager.setSystemFeature(PackageManager.FEATURE_USB_HOST, false);
@@ -268,7 +303,7 @@
when(mInputManager.getInputDevice(0)).thenReturn(new InputDevice.Builder().setSources(
InputDevice.SOURCE_DPAD).setExternal(false).build());
- mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
+ mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
mConnectedUsbDeviceUpdater, null, mStylusDeviceUpdater);
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
@@ -284,7 +319,7 @@
when(mInputManager.getInputDevice(0)).thenReturn(new InputDevice.Builder().setSources(
InputDevice.SOURCE_STYLUS).setExternal(false).build());
- mConnectedDeviceGroupController.init(mConnectedBluetoothDeviceUpdater,
+ mConnectedDeviceGroupController.init(null, mConnectedBluetoothDeviceUpdater,
mConnectedUsbDeviceUpdater, mConnectedDockUpdater, mStylusDeviceUpdater);
assertThat(mConnectedDeviceGroupController.getAvailabilityStatus()).isEqualTo(
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceControllerTest.java
new file mode 100644
index 0000000..618e021
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceControllerTest.java
@@ -0,0 +1,319 @@
+/*
+ * 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;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.settings.SettingsEnums;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothStatusCodes;
+import android.content.Context;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
+import com.android.settingslib.bluetooth.BluetoothEventManager;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.bluetooth.VolumeControlProfile;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowLooper;
+
+import java.util.concurrent.Executor;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(
+ shadows = {
+ ShadowBluetoothAdapter.class,
+ ShadowBluetoothUtils.class,
+ })
+public class AudioSharingNamePreferenceControllerTest {
+ private static final String PREF_KEY = "audio_sharing_stream_name";
+ private static final String BROADCAST_NAME = "broadcast_name";
+ private static final CharSequence UPDATED_NAME = "updated_name";
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Spy Context mContext = ApplicationProvider.getApplicationContext();
+ @Mock private LocalBluetoothLeBroadcast mBroadcast;
+ @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
+ @Mock private VolumeControlProfile mVolumeControl;
+ @Mock private LocalBluetoothManager mLocalBtManager;
+ @Mock private BluetoothEventManager mEventManager;
+ @Mock private LocalBluetoothProfileManager mProfileManager;
+ @Mock private PreferenceScreen mScreen;
+ private AudioSharingNamePreferenceController mController;
+ private ShadowBluetoothAdapter mShadowBluetoothAdapter;
+ private Lifecycle mLifecycle;
+ private LifecycleOwner mLifecycleOwner;
+ private AudioSharingNamePreference mPreference;
+ private FakeFeatureFactory mFeatureFactory;
+
+ @Before
+ public void setUp() {
+ mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ mShadowBluetoothAdapter.setEnabled(true);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
+ mLocalBtManager = Utils.getLocalBtManager(mContext);
+ when(mLocalBtManager.getEventManager()).thenReturn(mEventManager);
+ when(mLocalBtManager.getProfileManager()).thenReturn(mProfileManager);
+ when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
+ when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
+ when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl);
+ when(mBroadcast.isProfileReady()).thenReturn(true);
+ when(mAssistant.isProfileReady()).thenReturn(true);
+ when(mVolumeControl.isProfileReady()).thenReturn(true);
+ when(mBroadcast.isProfileReady()).thenReturn(true);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ mLifecycleOwner = () -> mLifecycle;
+ mLifecycle = new Lifecycle(mLifecycleOwner);
+ mController = new AudioSharingNamePreferenceController(mContext, PREF_KEY);
+ mPreference = spy(new AudioSharingNamePreference(mContext));
+ when(mScreen.findPreference(PREF_KEY)).thenReturn(mPreference);
+ }
+
+ @Test
+ public void getAvailabilityStatus_flagOn_available() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_flagOff_unsupported() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+ }
+
+ @Test
+ public void onStart_flagOff_doNothing() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mController.onStart(mLifecycleOwner);
+ verify(mBroadcast, never())
+ .registerServiceCallBack(
+ any(Executor.class), any(BluetoothLeBroadcast.Callback.class));
+ }
+
+ @Test
+ public void onStart_flagOn_registerCallbacks() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mController.onStart(mLifecycleOwner);
+ verify(mBroadcast)
+ .registerServiceCallBack(
+ any(Executor.class), any(BluetoothLeBroadcast.Callback.class));
+ }
+
+ @Test
+ public void onStart_flagOn_serviceNotReady_registerCallbacks() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ when(mBroadcast.isProfileReady()).thenReturn(false);
+ mController.onStart(mLifecycleOwner);
+ verify(mProfileManager)
+ .addServiceListener(any(LocalBluetoothProfileManager.ServiceListener.class));
+ }
+
+ @Test
+ public void onServiceConnected_removeCallbacks() {
+ mController.onServiceConnected();
+ verify(mProfileManager)
+ .removeServiceListener(any(LocalBluetoothProfileManager.ServiceListener.class));
+ }
+
+ @Test
+ public void onStop_flagOff_doNothing() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mController.onStart(mLifecycleOwner);
+ mController.onStop(mLifecycleOwner);
+ verify(mBroadcast, never())
+ .unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class));
+ }
+
+ @Test
+ public void onStop_flagOn_unregisterCallbacks() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mController.onStart(mLifecycleOwner);
+ mController.onStop(mLifecycleOwner);
+ verify(mBroadcast).unregisterServiceCallBack(any(BluetoothLeBroadcast.Callback.class));
+ }
+
+ @Test
+ public void displayPreference_updateName_showIcon() {
+ when(mBroadcast.getBroadcastName()).thenReturn(BROADCAST_NAME);
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
+ mController.displayPreference(mScreen);
+ ShadowLooper.idleMainLooper();
+
+ assertThat(mPreference.getText()).isEqualTo(BROADCAST_NAME);
+ assertThat(mPreference.getSummary()).isEqualTo(BROADCAST_NAME);
+ verify(mPreference).setValidator(any());
+ verify(mPreference).setShowQrCodeIcon(true);
+ }
+
+ @Test
+ public void displayPreference_updateName_hideIcon() {
+ when(mBroadcast.getBroadcastName()).thenReturn(BROADCAST_NAME);
+ when(mBroadcast.isEnabled(any())).thenReturn(false);
+ mController.displayPreference(mScreen);
+ ShadowLooper.idleMainLooper();
+
+ assertThat(mPreference.getText()).isEqualTo(BROADCAST_NAME);
+ assertThat(mPreference.getSummary()).isEqualTo(BROADCAST_NAME);
+ verify(mPreference).setValidator(any());
+ verify(mPreference).setShowQrCodeIcon(false);
+ }
+
+ @Test
+ public void onPreferenceChange_noChange_doNothing() {
+ when(mPreference.getSummary()).thenReturn(BROADCAST_NAME);
+ mController.displayPreference(mScreen);
+ boolean changed = mController.onPreferenceChange(mPreference, BROADCAST_NAME);
+ ShadowLooper.idleMainLooper();
+
+ verify(mBroadcast, never()).setBroadcastName(anyString());
+ verify(mBroadcast, never()).setProgramInfo(anyString());
+ verify(mBroadcast, never()).updateBroadcast();
+ verify(mFeatureFactory.metricsFeatureProvider, never()).action(any(), anyInt(), anyInt());
+
+ assertThat(changed).isFalse();
+ }
+
+ @Test
+ public void onPreferenceChange_changed_updateName_broadcasting() {
+ when(mPreference.getSummary()).thenReturn(BROADCAST_NAME);
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
+ mController.displayPreference(mScreen);
+ boolean changed = mController.onPreferenceChange(mPreference, UPDATED_NAME);
+ ShadowLooper.idleMainLooper();
+
+ verify(mBroadcast).setBroadcastName(UPDATED_NAME.toString());
+ verify(mBroadcast).setProgramInfo(UPDATED_NAME.toString());
+ verify(mBroadcast).updateBroadcast();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(mContext, SettingsEnums.ACTION_AUDIO_STREAM_NAME_UPDATED, 1);
+ assertThat(changed).isTrue();
+ }
+
+ @Test
+ public void onPreferenceChange_changed_updateName_notBroadcasting() {
+ when(mPreference.getSummary()).thenReturn(BROADCAST_NAME);
+ when(mBroadcast.isEnabled(any())).thenReturn(false);
+ mController.displayPreference(mScreen);
+ boolean changed = mController.onPreferenceChange(mPreference, UPDATED_NAME);
+ ShadowLooper.idleMainLooper();
+
+ verify(mBroadcast).setBroadcastName(UPDATED_NAME.toString());
+ verify(mBroadcast).setProgramInfo(UPDATED_NAME.toString());
+ verify(mBroadcast, never()).updateBroadcast();
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(mContext, SettingsEnums.ACTION_AUDIO_STREAM_NAME_UPDATED, 0);
+ assertThat(changed).isTrue();
+ }
+
+ @Test
+ public void unrelatedCallbacks_doNotUpdateIcon() {
+ mController.displayPreference(mScreen);
+ mController.mBroadcastCallback.onBroadcastStartFailed(/* reason= */ 0);
+ mController.mBroadcastCallback.onBroadcastStarted(/* reason= */ 0, /* broadcastId= */ 0);
+ mController.mBroadcastCallback.onBroadcastStopFailed(/* reason= */ 0);
+ mController.mBroadcastCallback.onBroadcastUpdateFailed(
+ /* reason= */ 0, /* broadcastId= */ 0);
+ mController.mBroadcastCallback.onBroadcastUpdated(/* reason= */ 0, /* broadcastId= */ 0);
+ mController.mBroadcastCallback.onPlaybackStarted(/* reason= */ 0, /* broadcastId= */ 0);
+ mController.mBroadcastCallback.onPlaybackStopped(/* reason= */ 0, /* broadcastId= */ 0);
+
+ ShadowLooper.idleMainLooper();
+ // Should be called once in displayPreference, but not called after callbacks
+ verify(mPreference).setShowQrCodeIcon(anyBoolean());
+ }
+
+ @Test
+ public void broadcastOnCallback_updateIcon() {
+ mController.displayPreference(mScreen);
+ mController.mBroadcastCallback.onBroadcastMetadataChanged(
+ /* broadcastId= */ 0, mock(BluetoothLeBroadcastMetadata.class));
+
+ ShadowLooper.idleMainLooper();
+
+ // Should be called twice, in displayPreference and also after callback
+ verify(mPreference, times(2)).setShowQrCodeIcon(anyBoolean());
+ }
+
+ @Test
+ public void broadcastStopCallback_updateIcon() {
+ mController.displayPreference(mScreen);
+ mController.mBroadcastCallback.onBroadcastStopped(/* reason= */ 0, /* broadcastId= */ 0);
+
+ ShadowLooper.idleMainLooper();
+
+ // Should be called twice, in displayPreference and also after callback
+ verify(mPreference, times(2)).setShowQrCodeIcon(anyBoolean());
+ }
+
+ @Test
+ public void idTextValid_emptyString() {
+ boolean valid = mController.isTextValid("");
+
+ assertThat(valid).isFalse();
+ }
+
+ @Test
+ public void idTextValid_validName() {
+ boolean valid = mController.isTextValid("valid name");
+
+ assertThat(valid).isTrue();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceTest.java
new file mode 100644
index 0000000..13e2a9d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.content.Intent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import androidx.preference.PreferenceViewHolder;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsQrCodeFragment;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class AudioSharingNamePreferenceTest {
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ private Context mContext;
+ private AudioSharingNamePreference mPreference;
+
+ @Before
+ public void setup() {
+ mContext = ApplicationProvider.getApplicationContext();
+ mPreference = spy(new AudioSharingNamePreference(mContext, null));
+ }
+
+ @Test
+ public void initialize_correctLayout() {
+ assertThat(mPreference.getLayoutResource())
+ .isEqualTo(
+ com.android.settingslib.widget.preference.twotarget.R.layout
+ .preference_two_target);
+ assertThat(mPreference.getWidgetLayoutResource())
+ .isEqualTo(R.layout.preference_widget_qrcode);
+ }
+
+ @Test
+ public void onBindViewHolder_correctLayout_noQrCodeButton() {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ View view = inflater.inflate(mPreference.getLayoutResource(), null);
+ LinearLayout widgetView = view.findViewById(android.R.id.widget_frame);
+ assertThat(widgetView).isNotNull();
+ inflater.inflate(mPreference.getWidgetLayoutResource(), widgetView, true);
+
+ var holder = PreferenceViewHolder.createInstanceForTests(view);
+ mPreference.setShowQrCodeIcon(false);
+ mPreference.onBindViewHolder(holder);
+
+ ImageButton shareButton = (ImageButton) holder.findViewById(R.id.button_icon);
+ View divider =
+ holder.findViewById(
+ com.android.settingslib.widget.preference.twotarget.R.id
+ .two_target_divider);
+
+ assertThat(shareButton).isNotNull();
+ assertThat(shareButton.getVisibility()).isEqualTo(View.GONE);
+ assertThat(shareButton.hasOnClickListeners()).isFalse();
+ assertThat(divider).isNotNull();
+ assertThat(divider.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void onBindViewHolder_correctLayout_showQrCodeButton() {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ View view = inflater.inflate(mPreference.getLayoutResource(), null);
+ LinearLayout widgetView = view.findViewById(android.R.id.widget_frame);
+ assertThat(widgetView).isNotNull();
+ inflater.inflate(mPreference.getWidgetLayoutResource(), widgetView, true);
+
+ var holder = PreferenceViewHolder.createInstanceForTests(view);
+ mPreference.setShowQrCodeIcon(true);
+ mPreference.onBindViewHolder(holder);
+
+ ImageButton shareButton = (ImageButton) holder.findViewById(R.id.button_icon);
+ View divider =
+ holder.findViewById(
+ com.android.settingslib.widget.preference.twotarget.R.id
+ .two_target_divider);
+
+ assertThat(shareButton).isNotNull();
+ assertThat(shareButton.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(shareButton.getDrawable()).isNotNull();
+ assertThat(shareButton.hasOnClickListeners()).isTrue();
+ assertThat(divider).isNotNull();
+ assertThat(divider.getVisibility()).isEqualTo(View.VISIBLE);
+
+ // mContext is not an Activity context, calling startActivity() from outside of an Activity
+ // context requires the FLAG_ACTIVITY_NEW_TASK flag, create a mock to avoid this
+ // AndroidRuntimeException.
+ Context activityContext = mock(Context.class);
+ when(mPreference.getContext()).thenReturn(activityContext);
+ shareButton.callOnClick();
+
+ ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(activityContext).startActivity(argumentCaptor.capture());
+
+ Intent intent = argumentCaptor.getValue();
+ assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(AudioStreamsQrCodeFragment.class.getName());
+ assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0))
+ .isEqualTo(R.string.audio_streams_qr_code_page_title);
+ assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0))
+ .isEqualTo(SettingsEnums.AUDIO_SHARING_SETTINGS);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidatorTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidatorTest.java
new file mode 100644
index 0000000..ada6117
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNameTextValidatorTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class AudioSharingNameTextValidatorTest {
+ private AudioSharingNameTextValidator mValidator;
+
+ @Before
+ public void setUp() {
+ mValidator = new AudioSharingNameTextValidator();
+ }
+
+ @Test
+ public void testValidNames() {
+ assertThat(mValidator.isTextValid("ValidName")).isTrue();
+ assertThat(mValidator.isTextValid("12345678")).isTrue();
+ assertThat(mValidator.isTextValid("Name_With_Underscores")).isTrue();
+ assertThat(mValidator.isTextValid("ÄÖÜß")).isTrue();
+ assertThat(mValidator.isTextValid("ThisNameIsExactly32Characters!")).isTrue();
+ }
+
+ @Test
+ public void testInvalidNames() {
+ assertThat(mValidator.isTextValid(null)).isFalse();
+ assertThat(mValidator.isTextValid("")).isFalse();
+ assertThat(mValidator.isTextValid("abc")).isFalse();
+ assertThat(mValidator.isTextValid("ThisNameIsWayTooLongForAnAudioSharingName")).isFalse();
+ assertThat(mValidator.isTextValid("Invalid\uDC00")).isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceControllerTest.java
new file mode 100644
index 0000000..5bfb966
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceControllerTest.java
@@ -0,0 +1,335 @@
+/*
+ * 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;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.settings.SettingsEnums;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothStatusCodes;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.bluetooth.Utils;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowLooper;
+
+import java.nio.charset.StandardCharsets;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(
+ shadows = {
+ ShadowBluetoothAdapter.class,
+ ShadowBluetoothUtils.class,
+ })
+public class AudioSharingPasswordPreferenceControllerTest {
+ private static final String PREF_KEY = "audio_sharing_stream_password";
+ private static final String SHARED_PREF_KEY = "default_password";
+ private static final String BROADCAST_PASSWORD = "password";
+ private static final String EDITTEXT_PASSWORD = "edittext_password";
+ private static final String HIDDEN_PASSWORD = "********";
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Spy Context mContext = ApplicationProvider.getApplicationContext();
+ @Mock private LocalBluetoothLeBroadcast mBroadcast;
+ @Mock private LocalBluetoothManager mLocalBtManager;
+ @Mock private LocalBluetoothProfileManager mProfileManager;
+ @Mock private SharedPreferences mSharedPreferences;
+ @Mock private SharedPreferences.Editor mEditor;
+ @Mock private ContentResolver mContentResolver;
+ @Mock private PreferenceScreen mScreen;
+ private AudioSharingPasswordPreferenceController mController;
+ private ShadowBluetoothAdapter mShadowBluetoothAdapter;
+ private Lifecycle mLifecycle;
+ private LifecycleOwner mLifecycleOwner;
+ private AudioSharingPasswordPreference mPreference;
+ private FakeFeatureFactory mFeatureFactory;
+
+ @Before
+ public void setUp() {
+ mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ mShadowBluetoothAdapter.setEnabled(true);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mLocalBtManager = Utils.getLocalBtManager(mContext);
+ when(mLocalBtManager.getProfileManager()).thenReturn(mProfileManager);
+ when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ when(mContext.getSharedPreferences(anyString(), anyInt())).thenReturn(mSharedPreferences);
+ when(mSharedPreferences.edit()).thenReturn(mEditor);
+ when(mEditor.putString(anyString(), anyString())).thenReturn(mEditor);
+ mLifecycleOwner = () -> mLifecycle;
+ mLifecycle = new Lifecycle(mLifecycleOwner);
+ mController = new AudioSharingPasswordPreferenceController(mContext, PREF_KEY);
+ mPreference = spy(new AudioSharingPasswordPreference(mContext));
+ when(mScreen.findPreference(PREF_KEY)).thenReturn(mPreference);
+ }
+
+ @Test
+ public void getAvailabilityStatus_flagOn_available() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_flagOff_unsupported() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+ }
+
+ @Test
+ public void onStart_flagOff_doNothing() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mController.onStart(mLifecycleOwner);
+ verify(mContentResolver, never()).registerContentObserver(any(), anyBoolean(), any());
+ verify(mSharedPreferences, never()).registerOnSharedPreferenceChangeListener(any());
+ }
+
+ @Test
+ public void onStart_flagOn_registerCallbacks() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mController.onStart(mLifecycleOwner);
+ verify(mContentResolver).registerContentObserver(any(), anyBoolean(), any());
+ verify(mSharedPreferences).registerOnSharedPreferenceChangeListener(any());
+ }
+
+ @Test
+ public void onStop_flagOff_doNothing() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mController.onStop(mLifecycleOwner);
+ verify(mContentResolver, never()).unregisterContentObserver(any());
+ verify(mSharedPreferences, never()).unregisterOnSharedPreferenceChangeListener(any());
+ }
+
+ @Test
+ public void onStop_flagOn_registerCallbacks() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ mController.onStop(mLifecycleOwner);
+ verify(mContentResolver).unregisterContentObserver(any());
+ verify(mSharedPreferences).unregisterOnSharedPreferenceChangeListener(any());
+ }
+
+ @Test
+ public void displayPreference_setupPreference_noPassword() {
+ when(mSharedPreferences.getString(anyString(), anyString())).thenReturn(EDITTEXT_PASSWORD);
+ when(mBroadcast.getBroadcastCode()).thenReturn(new byte[] {});
+
+ mController.displayPreference(mScreen);
+ ShadowLooper.idleMainLooper();
+
+ assertThat(mPreference.isPassword()).isTrue();
+ assertThat(mPreference.getDialogLayoutResource())
+ .isEqualTo(R.layout.audio_sharing_password_dialog);
+ assertThat(mPreference.getText()).isEqualTo(EDITTEXT_PASSWORD);
+ assertThat(mPreference.getSummary())
+ .isEqualTo(mContext.getString(R.string.audio_streams_no_password_summary));
+ verify(mPreference).setValidator(any());
+ verify(mPreference).setOnDialogEventListener(any());
+ }
+
+ @Test
+ public void contentObserver_updatePreferenceOnChange() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ when(mBroadcast.getBroadcastCode())
+ .thenReturn(BROADCAST_PASSWORD.getBytes(StandardCharsets.UTF_8));
+ mController.onStart(mLifecycleOwner);
+ mController.displayPreference(mScreen);
+ ShadowLooper.idleMainLooper();
+
+ ArgumentCaptor<ContentObserver> observerCaptor =
+ ArgumentCaptor.forClass(ContentObserver.class);
+ verify(mContentResolver)
+ .registerContentObserver(any(), anyBoolean(), observerCaptor.capture());
+
+ var observer = observerCaptor.getValue();
+ assertThat(observer).isNotNull();
+ observer.onChange(true);
+ verify(mPreference).setText(anyString());
+ verify(mPreference).setSummary(anyString());
+ }
+
+ @Test
+ public void sharedPrefChangeListener_updatePreferenceOnChange() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ when(mBroadcast.getBroadcastCode())
+ .thenReturn(BROADCAST_PASSWORD.getBytes(StandardCharsets.UTF_8));
+ mController.onStart(mLifecycleOwner);
+ mController.displayPreference(mScreen);
+ ShadowLooper.idleMainLooper();
+
+ ArgumentCaptor<SharedPreferences.OnSharedPreferenceChangeListener> captor =
+ ArgumentCaptor.forClass(SharedPreferences.OnSharedPreferenceChangeListener.class);
+ verify(mSharedPreferences).registerOnSharedPreferenceChangeListener(captor.capture());
+
+ var observer = captor.getValue();
+ assertThat(captor).isNotNull();
+ observer.onSharedPreferenceChanged(mSharedPreferences, SHARED_PREF_KEY);
+ verify(mPreference).setText(anyString());
+ verify(mPreference).setSummary(anyString());
+ }
+
+ @Test
+ public void displayPreference_setupPreference_hasPassword() {
+ when(mBroadcast.getBroadcastCode())
+ .thenReturn(BROADCAST_PASSWORD.getBytes(StandardCharsets.UTF_8));
+ mController.displayPreference(mScreen);
+ ShadowLooper.idleMainLooper();
+
+ assertThat(mPreference.isPassword()).isTrue();
+ assertThat(mPreference.getDialogLayoutResource())
+ .isEqualTo(R.layout.audio_sharing_password_dialog);
+ assertThat(mPreference.getText()).isEqualTo(BROADCAST_PASSWORD);
+ assertThat(mPreference.getSummary()).isEqualTo(HIDDEN_PASSWORD);
+ verify(mPreference).setValidator(any());
+ verify(mPreference).setOnDialogEventListener(any());
+ }
+
+ @Test
+ public void onBindDialogView_updatePreference_isBroadcasting_noPassword() {
+ when(mBroadcast.getBroadcastCode()).thenReturn(new byte[] {});
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
+ mController.displayPreference(mScreen);
+ mController.onBindDialogView();
+ ShadowLooper.idleMainLooper();
+
+ verify(mPreference).setEditable(false);
+ verify(mPreference).setChecked(true);
+ }
+
+ @Test
+ public void onBindDialogView_updatePreference_isNotBroadcasting_hasPassword() {
+ when(mBroadcast.getBroadcastCode())
+ .thenReturn(BROADCAST_PASSWORD.getBytes(StandardCharsets.UTF_8));
+ mController.displayPreference(mScreen);
+ mController.onBindDialogView();
+ ShadowLooper.idleMainLooper();
+
+ verify(mPreference).setEditable(true);
+ verify(mPreference).setChecked(false);
+ }
+
+ @Test
+ public void onPreferenceDataChanged_isBroadcasting_doNothing() {
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
+ mController.displayPreference(mScreen);
+ mController.onPreferenceDataChanged(BROADCAST_PASSWORD, /* isPublicBroadcast= */ false);
+ ShadowLooper.idleMainLooper();
+
+ verify(mBroadcast, never()).setBroadcastCode(any());
+ verify(mFeatureFactory.metricsFeatureProvider, never()).action(any(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void onPreferenceDataChanged_noChange_doNothing() {
+ when(mSharedPreferences.getString(anyString(), anyString())).thenReturn(EDITTEXT_PASSWORD);
+ when(mBroadcast.getBroadcastCode()).thenReturn(new byte[] {});
+ mController.displayPreference(mScreen);
+ mController.onPreferenceDataChanged(EDITTEXT_PASSWORD, /* isPublicBroadcast= */ true);
+ ShadowLooper.idleMainLooper();
+
+ verify(mBroadcast, never()).setBroadcastCode(any());
+ verify(mFeatureFactory.metricsFeatureProvider, never()).action(any(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void onPreferenceDataChanged_updateToNonPublicBroadcast() {
+ when(mSharedPreferences.getString(anyString(), anyString())).thenReturn(EDITTEXT_PASSWORD);
+ when(mBroadcast.getBroadcastCode()).thenReturn(new byte[] {});
+ mController.displayPreference(mScreen);
+ mController.onPreferenceDataChanged(BROADCAST_PASSWORD, /* isPublicBroadcast= */ false);
+ ShadowLooper.idleMainLooper();
+
+ verify(mBroadcast).setBroadcastCode(BROADCAST_PASSWORD.getBytes(StandardCharsets.UTF_8));
+ verify(mEditor).putString(anyString(), eq(BROADCAST_PASSWORD));
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(mContext, SettingsEnums.ACTION_AUDIO_STREAM_PASSWORD_UPDATED, 0);
+ }
+
+ @Test
+ public void onPreferenceDataChanged_updateToPublicBroadcast() {
+ when(mSharedPreferences.getString(anyString(), anyString())).thenReturn(EDITTEXT_PASSWORD);
+ when(mBroadcast.getBroadcastCode())
+ .thenReturn(BROADCAST_PASSWORD.getBytes(StandardCharsets.UTF_8));
+ mController.displayPreference(mScreen);
+ mController.onPreferenceDataChanged(EDITTEXT_PASSWORD, /* isPublicBroadcast= */ true);
+ ShadowLooper.idleMainLooper();
+
+ verify(mBroadcast).setBroadcastCode("".getBytes(StandardCharsets.UTF_8));
+ verify(mEditor, never()).putString(anyString(), eq(EDITTEXT_PASSWORD));
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(mContext, SettingsEnums.ACTION_AUDIO_STREAM_PASSWORD_UPDATED, 1);
+ }
+
+ @Test
+ public void idTextValid_emptyString() {
+ boolean valid = mController.isTextValid("");
+
+ assertThat(valid).isFalse();
+ }
+
+ @Test
+ public void idTextValid_validPassword() {
+ boolean valid = mController.isTextValid(BROADCAST_PASSWORD);
+
+ assertThat(valid).isTrue();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceTest.java
new file mode 100644
index 0000000..0b87e8c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordPreferenceTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+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.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.EditText;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class AudioSharingPasswordPreferenceTest {
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ private static final String EDIT_TEXT_CONTENT = "text";
+ private Context mContext;
+ private AudioSharingPasswordPreference mPreference;
+
+ @Before
+ public void setup() {
+ mContext = ApplicationProvider.getApplicationContext();
+ mPreference = new AudioSharingPasswordPreference(mContext, null);
+ }
+
+ @Test
+ public void onBindDialogView_correctLayout() {
+ View view =
+ LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
+ mPreference.onBindDialogView(view);
+
+ var editText = view.findViewById(android.R.id.edit);
+ var checkBox = view.findViewById(R.id.audio_sharing_stream_password_checkbox);
+ var dialogMessage = view.findViewById(android.R.id.message);
+
+ assertThat(editText).isNotNull();
+ assertThat(checkBox).isNotNull();
+ assertThat(dialogMessage).isNotNull();
+ }
+
+ @Test
+ public void setEditable_true() {
+ View view =
+ LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
+ mPreference.onBindDialogView(view);
+
+ var editText = view.findViewById(android.R.id.edit);
+ var checkBox = view.findViewById(R.id.audio_sharing_stream_password_checkbox);
+ var dialogMessage = view.findViewById(android.R.id.message);
+
+ mPreference.setEditable(true);
+
+ assertThat(editText).isNotNull();
+ assertThat(editText.isEnabled()).isTrue();
+ assertThat(editText.getAlpha()).isEqualTo(1.0f);
+ assertThat(checkBox).isNotNull();
+ assertThat(checkBox.isEnabled()).isTrue();
+ assertThat(dialogMessage).isNotNull();
+ assertThat(dialogMessage.getVisibility()).isEqualTo(GONE);
+ }
+
+ @Test
+ public void setEditable_false() {
+ View view =
+ LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
+ mPreference.onBindDialogView(view);
+
+ var editText = view.findViewById(android.R.id.edit);
+ var checkBox = view.findViewById(R.id.audio_sharing_stream_password_checkbox);
+ var dialogMessage = view.findViewById(android.R.id.message);
+
+ mPreference.setEditable(false);
+
+ assertThat(editText).isNotNull();
+ assertThat(editText.isEnabled()).isFalse();
+ assertThat(editText.getAlpha()).isLessThan(1.0f);
+ assertThat(checkBox).isNotNull();
+ assertThat(checkBox.isEnabled()).isFalse();
+ assertThat(dialogMessage).isNotNull();
+ assertThat(dialogMessage.getVisibility()).isEqualTo(VISIBLE);
+ }
+
+ @Test
+ public void setChecked_true() {
+ View view =
+ LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
+ mPreference.onBindDialogView(view);
+
+ CheckBox checkBox = view.findViewById(R.id.audio_sharing_stream_password_checkbox);
+
+ mPreference.setChecked(true);
+
+ assertThat(checkBox).isNotNull();
+ assertThat(checkBox.isChecked()).isTrue();
+ }
+
+ @Test
+ public void setChecked_false() {
+ View view =
+ LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
+ mPreference.onBindDialogView(view);
+
+ CheckBox checkBox = view.findViewById(R.id.audio_sharing_stream_password_checkbox);
+
+ mPreference.setChecked(false);
+
+ assertThat(checkBox).isNotNull();
+ assertThat(checkBox.isChecked()).isFalse();
+ }
+
+ @Test
+ public void onDialogEventListener_onClick_positiveButton() {
+ AudioSharingPasswordPreference.OnDialogEventListener listener =
+ mock(AudioSharingPasswordPreference.OnDialogEventListener.class);
+ mPreference.setOnDialogEventListener(listener);
+ View view =
+ LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
+ mPreference.onBindDialogView(view);
+
+ EditText editText = view.findViewById(android.R.id.edit);
+ assertThat(editText).isNotNull();
+ editText.setText(EDIT_TEXT_CONTENT);
+
+ mPreference.onClick(mock(DialogInterface.class), DialogInterface.BUTTON_POSITIVE);
+
+ verify(listener).onBindDialogView();
+ verify(listener).onPreferenceDataChanged(eq(EDIT_TEXT_CONTENT), anyBoolean());
+ }
+
+ @Test
+ public void onDialogEventListener_onClick_negativeButton_doNothing() {
+ AudioSharingPasswordPreference.OnDialogEventListener listener =
+ mock(AudioSharingPasswordPreference.OnDialogEventListener.class);
+ mPreference.setOnDialogEventListener(listener);
+ View view =
+ LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
+ mPreference.onBindDialogView(view);
+
+ EditText editText = view.findViewById(android.R.id.edit);
+ assertThat(editText).isNotNull();
+ editText.setText(EDIT_TEXT_CONTENT);
+
+ mPreference.onClick(mock(DialogInterface.class), DialogInterface.BUTTON_NEGATIVE);
+
+ verify(listener).onBindDialogView();
+ verify(listener, never()).onPreferenceDataChanged(anyString(), anyBoolean());
+ }
+
+ @Test
+ public void onPrepareDialogBuilder_editable_doNothing() {
+ View view =
+ LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
+ mPreference.onBindDialogView(view);
+ mPreference.setEditable(true);
+
+ var dialogBuilder = mock(AlertDialog.Builder.class);
+ mPreference.onPrepareDialogBuilder(
+ dialogBuilder, mock(DialogInterface.OnClickListener.class));
+
+ verify(dialogBuilder, never()).setPositiveButton(any(), any());
+ }
+
+ @Test
+ public void onPrepareDialogBuilder_notEditable_disableButton() {
+ View view =
+ LayoutInflater.from(mContext).inflate(R.layout.audio_sharing_password_dialog, null);
+ mPreference.onBindDialogView(view);
+ mPreference.setEditable(false);
+
+ var dialogBuilder = mock(AlertDialog.Builder.class);
+ mPreference.onPrepareDialogBuilder(
+ dialogBuilder, mock(DialogInterface.OnClickListener.class));
+
+ verify(dialogBuilder).setPositiveButton(any(), any());
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordValidatorTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordValidatorTest.java
new file mode 100644
index 0000000..5c96fe1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingPasswordValidatorTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class AudioSharingPasswordValidatorTest {
+ private AudioSharingPasswordValidator mValidator;
+
+ @Before
+ public void setUp() {
+ mValidator = new AudioSharingPasswordValidator();
+ }
+
+ @Test
+ public void testValidPasswords() {
+ assertThat(mValidator.isTextValid("1234")).isTrue();
+ assertThat(mValidator.isTextValid("Password")).isTrue();
+ assertThat(mValidator.isTextValid("SecurePass123!")).isTrue();
+ assertThat(mValidator.isTextValid("ÄÖÜß")).isTrue();
+ assertThat(mValidator.isTextValid("1234567890abcdef")).isTrue();
+ }
+
+ @Test
+ public void testInvalidPasswords() {
+ assertThat(mValidator.isTextValid(null)).isFalse();
+ assertThat(mValidator.isTextValid("")).isFalse();
+ assertThat(mValidator.isTextValid("abc")).isFalse();
+ assertThat(mValidator.isTextValid("ThisIsAVeryLongPasswordThatExceedsSixteenOctets"))
+ .isFalse();
+ assertThat(mValidator.isTextValid("Invalid\uDC00")).isFalse();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceBadCodeStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceBadCodeStateTest.java
index 2fddff5..391a7b1 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceBadCodeStateTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceBadCodeStateTest.java
@@ -20,22 +20,45 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.testutils.FakeFeatureFactory;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class AddSourceBadCodeStateTest {
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ @Mock private AudioStreamPreference mPreference;
+ @Mock private AudioStreamsProgressCategoryController mController;
+ @Mock private AudioStreamsHelper mHelper;
+ private FakeFeatureFactory mFeatureFactory;
private AddSourceBadCodeState mInstance;
@Before
public void setUp() {
- mInstance = AddSourceBadCodeState.getInstance();
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ mInstance = new AddSourceBadCodeState();
}
@Test
public void testGetInstance() {
+ mInstance = AddSourceBadCodeState.getInstance();
assertThat(mInstance).isNotNull();
assertThat(mInstance).isInstanceOf(SyncedState.class);
}
@@ -55,4 +78,19 @@
AudioStreamsProgressCategoryController.AudioStreamState
.ADD_SOURCE_BAD_CODE);
}
+
+ @Test
+ public void testPerformAction() {
+ when(mPreference.getContext()).thenReturn(mContext);
+ when(mPreference.getSourceOriginForLogging())
+ .thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
+
+ mInstance.performAction(mPreference, mController, mHelper);
+
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ eq(mContext),
+ eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_FAILED_BAD_CODE),
+ eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceFailedStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceFailedStateTest.java
index d8b1fcf..917d8de 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceFailedStateTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceFailedStateTest.java
@@ -20,22 +20,45 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.testutils.FakeFeatureFactory;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class AddSourceFailedStateTest {
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ @Mock private AudioStreamPreference mPreference;
+ @Mock private AudioStreamsProgressCategoryController mController;
+ @Mock private AudioStreamsHelper mHelper;
+ private FakeFeatureFactory mFeatureFactory;
private AddSourceFailedState mInstance;
@Before
public void setUp() {
- mInstance = AddSourceFailedState.getInstance();
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ mInstance = new AddSourceFailedState();
}
@Test
public void testGetInstance() {
+ mInstance = AddSourceFailedState.getInstance();
assertThat(mInstance).isNotNull();
assertThat(mInstance).isInstanceOf(SyncedState.class);
}
@@ -54,4 +77,19 @@
.isEqualTo(
AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_FAILED);
}
+
+ @Test
+ public void testPerformAction() {
+ when(mPreference.getContext()).thenReturn(mContext);
+ when(mPreference.getSourceOriginForLogging())
+ .thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
+
+ mInstance.performAction(mPreference, mController, mHelper);
+
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ eq(mContext),
+ eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_FAILED_OTHER),
+ eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseStateTest.java
index 6e5342b..ce21658 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseStateTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AddSourceWaitForResponseStateTest.java
@@ -22,11 +22,21 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.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;
+import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
import org.junit.Rule;
@@ -36,27 +46,41 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowAlertDialog;
import org.robolectric.shadows.ShadowLooper;
import java.util.concurrent.TimeUnit;
@RunWith(RobolectricTestRunner.class)
+@Config(
+ shadows = {
+ ShadowAlertDialog.class,
+ })
public class AddSourceWaitForResponseStateTest {
- private static final int BROADCAST_ID = 1;
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ private static final int BROADCAST_ID = 1;
+ private final Context mContext = spy(ApplicationProvider.getApplicationContext());
@Mock private AudioStreamPreference mMockPreference;
@Mock private AudioStreamsProgressCategoryController mMockController;
@Mock private AudioStreamsHelper mMockHelper;
@Mock private BluetoothLeBroadcastMetadata mMockMetadata;
+ @Mock private AudioStreamsRepository mMockRepository;
+ private FakeFeatureFactory mFeatureFactory;
private AddSourceWaitForResponseState mInstance;
@Before
public void setUp() {
- mInstance = AddSourceWaitForResponseState.getInstance();
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ mInstance = new AddSourceWaitForResponseState();
+ when(mMockPreference.getContext()).thenReturn(mContext);
+ when(mMockPreference.getSourceOriginForLogging())
+ .thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
}
@Test
public void testGetInstance() {
+ mInstance = AddSourceWaitForResponseState.getInstance();
assertThat(mInstance).isNotNull();
assertThat(mInstance).isInstanceOf(AudioStreamStateHandler.class);
}
@@ -93,11 +117,18 @@
public void testPerformAction_metadataIsNotNull_addSource() {
when(mMockPreference.getAudioStreamMetadata()).thenReturn(mMockMetadata);
when(mMockPreference.getSourceOriginForLogging())
- .thenReturn(SourceOriginForLogging.UNKNOWN);
+ .thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
+ mInstance.setAudioStreamsRepositoryForTesting(mMockRepository);
mInstance.performAction(mMockPreference, mMockController, mMockHelper);
verify(mMockHelper).addSource(mMockMetadata);
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ eq(mContext),
+ eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN),
+ eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
+ verify(mMockRepository).cacheMetadata(mMockMetadata);
verify(mMockController, never()).handleSourceFailedToConnect(anyInt());
}
@@ -108,12 +139,28 @@
when(mMockPreference.getAudioStreamState()).thenReturn(mInstance.getStateEnum());
when(mMockPreference.getAudioStreamBroadcastId()).thenReturn(BROADCAST_ID);
when(mMockPreference.getSourceOriginForLogging())
- .thenReturn(SourceOriginForLogging.UNKNOWN);
+ .thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
+ when(mMockController.getFragment()).thenReturn(mock(AudioStreamsDashboardFragment.class));
+ mInstance.setAudioStreamsRepositoryForTesting(mMockRepository);
mInstance.performAction(mMockPreference, mMockController, mMockHelper);
ShadowLooper.idleMainLooper(ADD_SOURCE_WAIT_FOR_RESPONSE_TIMEOUT_MILLIS, TimeUnit.SECONDS);
verify(mMockHelper).addSource(mMockMetadata);
verify(mMockController).handleSourceFailedToConnect(BROADCAST_ID);
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ eq(mContext),
+ eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN),
+ eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
+ verify(mMockRepository).cacheMetadata(mMockMetadata);
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ eq(mContext),
+ eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_FAILED_TIMEOUT),
+ eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
+ verify(mContext).getString(R.string.audio_streams_dialog_stream_is_not_available);
+ verify(mContext).getString(R.string.audio_streams_is_not_playing);
+ verify(mContext).getString(R.string.audio_streams_dialog_close);
}
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java
new file mode 100644
index 0000000..adc77a1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamStateHandlerTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+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;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class AudioStreamStateHandlerTest {
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ private static final int SUMMARY_RES = 1;
+ private static final String SUMMARY = "summary";
+ private final Context mContext = spy(ApplicationProvider.getApplicationContext());
+ @Mock private AudioStreamsProgressCategoryController mController;
+ @Mock private AudioStreamsHelper mHelper;
+ @Mock private AudioStreamPreference mPreference;
+ private AudioStreamStateHandler mHandler;
+
+ @Before
+ public void setUp() {
+ mHandler = spy(new AudioStreamStateHandler());
+ }
+
+ @Test
+ public void testHandleStateChange_noChange_doNothing() {
+ when(mHandler.getStateEnum())
+ .thenReturn(
+ AudioStreamsProgressCategoryController.AudioStreamState
+ .ADD_SOURCE_BAD_CODE);
+ when(mPreference.getAudioStreamState())
+ .thenReturn(
+ AudioStreamsProgressCategoryController.AudioStreamState
+ .ADD_SOURCE_BAD_CODE);
+
+ mHandler.handleStateChange(mPreference, mController, mHelper);
+
+ verify(mPreference, never()).setAudioStreamState(any());
+ verify(mHandler, never()).performAction(any(), any(), any());
+ verify(mPreference, never()).setIsConnected(anyBoolean(), anyString(), any());
+ }
+
+ @Test
+ public void testHandleStateChange_setNewState() {
+ when(mHandler.getStateEnum())
+ .thenReturn(AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED);
+ when(mPreference.getAudioStreamState())
+ .thenReturn(
+ AudioStreamsProgressCategoryController.AudioStreamState
+ .ADD_SOURCE_BAD_CODE);
+
+ mHandler.handleStateChange(mPreference, mController, mHelper);
+
+ verify(mPreference)
+ .setAudioStreamState(
+ AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED);
+ verify(mHandler).performAction(any(), any(), any());
+ verify(mPreference).setIsConnected(eq(true), eq(""), eq(null));
+ }
+
+ @Test
+ public void testHandleStateChange_setNewState_newSummary_newListener() {
+ Preference.OnPreferenceClickListener listener =
+ mock(Preference.OnPreferenceClickListener.class);
+ when(mHandler.getStateEnum())
+ .thenReturn(
+ AudioStreamsProgressCategoryController.AudioStreamState
+ .ADD_SOURCE_BAD_CODE);
+ when(mHandler.getSummary()).thenReturn(SUMMARY_RES);
+ when(mHandler.getOnClickListener(any())).thenReturn(listener);
+ when(mPreference.getAudioStreamState())
+ .thenReturn(
+ AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_FAILED);
+ when(mPreference.getContext()).thenReturn(mContext);
+ doReturn(SUMMARY).when(mContext).getString(anyInt());
+
+ mHandler.handleStateChange(mPreference, mController, mHelper);
+
+ verify(mPreference)
+ .setAudioStreamState(
+ AudioStreamsProgressCategoryController.AudioStreamState
+ .ADD_SOURCE_BAD_CODE);
+ verify(mHandler).performAction(any(), any(), any());
+ verify(mPreference).setIsConnected(eq(false), eq(SUMMARY), eq(listener));
+ }
+
+ @Test
+ public void testGetSummary() {
+ int res = mHandler.getSummary();
+ assertThat(res).isEqualTo(AudioStreamStateHandler.EMPTY_STRING_RES);
+ }
+
+ @Test
+ public void testGetOnClickListener() {
+ Preference.OnPreferenceClickListener listener = mHandler.getOnClickListener(mController);
+ assertThat(listener).isNull();
+ }
+
+ @Test
+ public void testGetStateEnum() {
+ var state = mHandler.getStateEnum();
+ assertThat(state)
+ .isEqualTo(AudioStreamsProgressCategoryController.AudioStreamState.UNKNOWN);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdaterTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdaterTest.java
index 4403528..d6b99a1 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsActiveDeviceSummaryUpdaterTest.java
@@ -20,6 +20,7 @@
import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
@@ -76,25 +77,46 @@
}
@Test
- public void onActiveDeviceChanged_notLeProfile_doNothing() {
- mUpdater.onActiveDeviceChanged(mCachedBluetoothDevice, 0);
+ public void unregister_doNothing() {
+ mUpdater.register(false);
assertThat(mUpdatedSummary).isNull();
}
@Test
- public void onActiveDeviceChanged_leProfile_summaryUpdated() {
+ public void onProfileConnectionStateChanged_notLeAssistProfile_doNothing() {
+ mUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice, 0, 0);
+
+ assertThat(mUpdatedSummary).isNull();
+ }
+
+ @Test
+ public void onProfileConnectionStateChanged_leAssistantProfile_summaryUpdated() {
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(
mCachedBluetoothDevice);
when(mCachedBluetoothDevice.getName()).thenReturn(DEVICE_NAME);
- mUpdater.onActiveDeviceChanged(mCachedBluetoothDevice, BluetoothProfile.LE_AUDIO);
+ mUpdater.onProfileConnectionStateChanged(
+ mCachedBluetoothDevice,
+ BluetoothAdapter.STATE_CONNECTED,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
assertThat(mUpdatedSummary).isEqualTo(DEVICE_NAME);
}
@Test
- public void onActiveDeviceChanged_leProfile_noDevice_summaryUpdated() {
- mUpdater.onActiveDeviceChanged(mCachedBluetoothDevice, BluetoothProfile.LE_AUDIO);
+ public void onActiveDeviceChanged_leAssistantProfile_noDevice_summaryUpdated() {
+ mUpdater.onProfileConnectionStateChanged(
+ mCachedBluetoothDevice,
+ BluetoothAdapter.STATE_CONNECTED,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+
+ assertThat(mUpdatedSummary)
+ .isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_title));
+ }
+
+ @Test
+ public void onBluetoothStateOff_summaryUpdated() {
+ mUpdater.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
assertThat(mUpdatedSummary)
.isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_title));
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryControllerTest.java
index e4b6903..0e00309 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryControllerTest.java
@@ -23,11 +23,13 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.os.Looper;
@@ -42,6 +44,7 @@
import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
+import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
@@ -57,6 +60,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -116,7 +120,7 @@
when(mBroadcast.isProfileReady()).thenReturn(true);
when(mAssistant.isProfileReady()).thenReturn(true);
when(mVolumeControl.isProfileReady()).thenReturn(true);
- mController = new AudioStreamsCategoryController(mContext, KEY);
+ mController = spy(new AudioStreamsCategoryController(mContext, KEY));
mPreference = new Preference(mContext);
when(mScreen.findPreference(KEY)).thenReturn(mPreference);
mController.displayPreference(mScreen);
@@ -228,4 +232,21 @@
shadowOf(Looper.getMainLooper()).idle();
assertThat(mPreference.isVisible()).isTrue();
}
+
+ @Test
+ public void onProfileConnectionStateChanged_updateVisibility() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_QR_CODE_PRIVATE_BROADCAST_SHARING);
+ ArgumentCaptor<BluetoothCallback> argumentCaptor =
+ ArgumentCaptor.forClass(BluetoothCallback.class);
+ mController.onStart(mLifecycleOwner);
+ verify(mBluetoothEventManager).registerCallback(argumentCaptor.capture());
+
+ BluetoothCallback callback = argumentCaptor.getValue();
+ callback.onProfileConnectionStateChanged(
+ mCachedBluetoothDevice,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
+ BluetoothAdapter.STATE_DISCONNECTED);
+
+ verify(mController).updateVisibility();
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDialogFragmentTest.java
new file mode 100644
index 0000000..e83dade
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsDialogFragmentTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.test.core.app.ApplicationProvider;
+
+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 org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.shadows.androidx.fragment.FragmentController;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(
+ shadows = {
+ ShadowAlertDialog.class,
+ })
+public class AudioStreamsDialogFragmentTest {
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private AudioStreamsDialogFragment.DialogBuilder mDialogBuilder;
+ private AudioStreamsDialogFragment mFragment;
+
+ @Before
+ public void setUp() {
+ mDialogBuilder = spy(new AudioStreamsDialogFragment.DialogBuilder(mContext));
+ mFragment = new AudioStreamsDialogFragment(mDialogBuilder, SettingsEnums.PAGE_UNKNOWN);
+ }
+
+ @After
+ public void tearDown() {
+ ShadowAlertDialog.reset();
+ }
+
+ @Test
+ public void testGetMetricsCategory() {
+ int dialogId = mFragment.getMetricsCategory();
+
+ assertThat(dialogId).isEqualTo(SettingsEnums.PAGE_UNKNOWN);
+ }
+
+ @Test
+ public void testOnCreateDialog() {
+ mFragment.onCreateDialog(Bundle.EMPTY);
+
+ verify(mDialogBuilder).build();
+ }
+
+ @Test
+ public void testShowDialog() {
+ FragmentController.setupFragment(mFragment);
+ AudioStreamsDialogFragment.show(mFragment, mDialogBuilder, SettingsEnums.PAGE_UNKNOWN);
+ ShadowLooper.idleMainLooper();
+
+ var dialog = ShadowAlertDialog.getLatestAlertDialog();
+ assertThat(dialog).isNotNull();
+ assertThat(dialog.isShowing()).isTrue();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java
new file mode 100644
index 0000000..164c2f0
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallbackTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public class AudioStreamsProgressCategoryCallbackTest {
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Mock private AudioStreamsProgressCategoryController mController;
+ @Mock private BluetoothDevice mDevice;
+ @Mock private BluetoothLeBroadcastReceiveState mState;
+ @Mock private BluetoothLeBroadcastMetadata mMetadata;
+ private AudioStreamsProgressCategoryCallback mCallback;
+
+ @Before
+ public void setUp() {
+ mCallback = new AudioStreamsProgressCategoryCallback(mController);
+ }
+
+ @Test
+ public void testOnReceiveStateChanged_connected() {
+ List<Long> bisSyncState = new ArrayList<>();
+ bisSyncState.add(1L);
+ when(mState.getBisSyncState()).thenReturn(bisSyncState);
+ mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState);
+
+ verify(mController).handleSourceConnected(any());
+ }
+
+ @Test
+ public void testOnReceiveStateChanged_badCode() {
+ when(mState.getPaSyncState())
+ .thenReturn(BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED);
+ when(mState.getBigEncryptionState())
+ .thenReturn(BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE);
+ mCallback.onReceiveStateChanged(mDevice, /* sourceId= */ 0, mState);
+
+ verify(mController).handleSourceConnectBadCode(any());
+ }
+
+ @Test
+ public void testOnSearchStartFailed() {
+ mCallback.onSearchStartFailed(/* reason= */ 0);
+
+ verify(mController).showToast(anyString());
+ verify(mController).setScanning(anyBoolean());
+ }
+
+ @Test
+ public void testOnSearchStarted() {
+ mCallback.onSearchStarted(/* reason= */ 0);
+
+ verify(mController).setScanning(anyBoolean());
+ }
+
+ @Test
+ public void testOnSearchStopFailed() {
+ mCallback.onSearchStopFailed(/* reason= */ 0);
+
+ verify(mController).showToast(anyString());
+ }
+
+ @Test
+ public void testOnSearchStopped() {
+ mCallback.onSearchStopped(/* reason= */ 0);
+
+ verify(mController).setScanning(anyBoolean());
+ }
+
+ @Test
+ public void testOnSourceAddFailed() {
+ when(mMetadata.getBroadcastId()).thenReturn(1);
+ mCallback.onSourceAddFailed(mDevice, mMetadata, /* reason= */ 0);
+
+ verify(mController).handleSourceFailedToConnect(1);
+ }
+
+ @Test
+ public void testOnSourceFound() {
+ mCallback.onSourceFound(mMetadata);
+
+ verify(mController).handleSourceFound(mMetadata);
+ }
+
+ @Test
+ public void testOnSourceLost() {
+ mCallback.onSourceLost(/* broadcastId= */ 1);
+
+ verify(mController).handleSourceLost(1);
+ }
+
+ @Test
+ public void testOnSourceRemoveFailed() {
+ mCallback.onSourceRemoveFailed(mDevice, /* sourceId= */ 0, /* reason= */ 0);
+
+ verify(mController).showToast(anyString());
+ }
+
+ @Test
+ public void testOnSourceRemoved() {
+ mCallback.onSourceRemoved(mDevice, /* sourceId= */ 0, /* reason= */ 0);
+
+ verify(mController).handleSourceRemoved();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java
new file mode 100644
index 0000000..d43ec81
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryControllerTest.java
@@ -0,0 +1,671 @@
+/*
+ * 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 static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_BAD_CODE;
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_FAILED;
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.ADD_SOURCE_WAIT_FOR_RESPONSE;
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED;
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.SYNCED;
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.AudioStreamState.WAIT_FOR_SYNC;
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsProgressCategoryController.UNSET_BROADCAST_ID;
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import static java.util.Collections.emptyList;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Looper;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper;
+import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
+import com.android.settings.testutils.shadow.ShadowThreadUtils;
+import com.android.settingslib.bluetooth.BluetoothEventManager;
+import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.androidx.fragment.FragmentController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(
+ shadows = {
+ ShadowBluetoothUtils.class,
+ ShadowAudioStreamsHelper.class,
+ ShadowThreadUtils.class,
+ ShadowAlertDialog.class,
+ })
+public class AudioStreamsProgressCategoryControllerTest {
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ private static final String VALID_METADATA =
+ "BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;"
+ + "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;";
+ private static final String KEY = "audio_streams_nearby_category";
+ private static final int QR_CODE_BROADCAST_ID = 1;
+ private static final int ALREADY_CONNECTED_BROADCAST_ID = 2;
+ private static final int NEWLY_FOUND_BROADCAST_ID = 3;
+ private static final String BROADCAST_NAME_1 = "name_1";
+ private static final String BROADCAST_NAME_2 = "name_2";
+ private static final byte[] BROADCAST_CODE = new byte[] {1};
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ @Mock private LocalBluetoothManager mLocalBtManager;
+ @Mock private BluetoothEventManager mBluetoothEventManager;
+ @Mock private PreferenceScreen mScreen;
+ @Mock private AudioStreamsHelper mAudioStreamsHelper;
+ @Mock private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
+ @Mock private BluetoothLeBroadcastMetadata mMetadata;
+ @Mock private CachedBluetoothDevice mDevice;
+ @Mock private AudioStreamsProgressCategoryPreference mPreference;
+ private Lifecycle mLifecycle;
+ private LifecycleOwner mLifecycleOwner;
+ private Fragment mFragment;
+ private TestController mController;
+
+ @Before
+ public void setUp() {
+ ShadowAudioStreamsHelper.setUseMock(mAudioStreamsHelper);
+ when(mAudioStreamsHelper.getLeBroadcastAssistant()).thenReturn(mLeBroadcastAssistant);
+ when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(emptyList());
+
+ ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBtManager;
+ when(mLocalBtManager.getEventManager()).thenReturn(mBluetoothEventManager);
+ when(mLeBroadcastAssistant.isSearchInProgress()).thenReturn(false);
+
+ when(mScreen.findPreference(anyString())).thenReturn(mPreference);
+
+ mLifecycleOwner = () -> mLifecycle;
+ mLifecycle = new Lifecycle(mLifecycleOwner);
+
+ mFragment = new Fragment();
+ mController = spy(new TestController(mContext, KEY));
+ }
+
+ @After
+ public void tearDown() {
+ ShadowBluetoothUtils.reset();
+ ShadowAudioStreamsHelper.reset();
+ }
+
+ @Test
+ public void testGetAvailabilityStatus() {
+ int status = mController.getAvailabilityStatus();
+
+ assertThat(status).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void testDisplayPreference() {
+ mController.displayPreference(mScreen);
+
+ verify(mPreference).setVisible(true);
+ }
+
+ @Test
+ public void testSetScanning() {
+ mController.displayPreference(mScreen);
+ mController.setScanning(true);
+
+ verify(mPreference).setProgress(true);
+ }
+
+ @Test
+ public void testOnStart_initNoDevice_showDialog() {
+ when(mLeBroadcastAssistant.isSearchInProgress()).thenReturn(true);
+
+ FragmentController.setupFragment(mFragment);
+ mController.setFragment(mFragment);
+ mController.displayPreference(mScreen);
+ mController.onStart(mLifecycleOwner);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // Called twice, once in displayPreference, the other in init()
+ verify(mPreference, times(2)).setVisible(anyBoolean());
+ verify(mPreference).removeAudioStreamPreferences();
+ verify(mLeBroadcastAssistant).stopSearchingForSources();
+ verify(mLeBroadcastAssistant).unregisterServiceCallBack(any());
+
+ var dialog = ShadowAlertDialog.getLatestAlertDialog();
+ assertThat(dialog).isNotNull();
+ assertThat(dialog.isShowing()).isTrue();
+
+ TextView title = dialog.findViewById(R.id.dialog_title);
+ assertThat(title).isNotNull();
+ assertThat(title.getText())
+ .isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_title));
+ TextView subtitle1 = dialog.findViewById(R.id.dialog_subtitle);
+ assertThat(subtitle1).isNotNull();
+ assertThat(subtitle1.getVisibility()).isEqualTo(View.GONE);
+ TextView subtitle2 = dialog.findViewById(R.id.dialog_subtitle_2);
+ assertThat(subtitle2).isNotNull();
+ assertThat(subtitle2.getText())
+ .isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_subtitle));
+ View leftButton = dialog.findViewById(R.id.left_button);
+ assertThat(leftButton).isNotNull();
+ assertThat(leftButton.getVisibility()).isEqualTo(View.VISIBLE);
+ Button rightButton = dialog.findViewById(R.id.right_button);
+ assertThat(rightButton).isNotNull();
+ assertThat(rightButton.getText())
+ .isEqualTo(mContext.getString(R.string.audio_streams_dialog_no_le_device_button));
+ assertThat(rightButton.hasOnClickListeners()).isTrue();
+
+ dialog.cancel();
+ }
+
+ @Test
+ public void testBluetoothOff_triggerRunnable() {
+ mController.mBluetoothCallback.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
+
+ verify(mController.mExecutor).execute(any());
+ }
+
+ @Test
+ public void testDeviceConnectionStateChanged_triggerRunnable() {
+ mController.mBluetoothCallback.onProfileConnectionStateChanged(
+ mDevice,
+ BluetoothAdapter.STATE_DISCONNECTED,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+
+ verify(mController.mExecutor).execute(any());
+ }
+
+ @Test
+ public void testOnStart_initHasDevice_noPreference() {
+ // Setup a device
+ ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+ mController.onStart(mLifecycleOwner);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ verify(mLeBroadcastAssistant).registerServiceCallBack(any(), any());
+ verify(mLeBroadcastAssistant).startSearchingForSources(any());
+
+ var dialog = ShadowAlertDialog.getLatestAlertDialog();
+ assertThat(dialog).isNull();
+
+ verify(mController, never()).moveToState(any(), any());
+ }
+
+ @Test
+ public void testOnStart_handleSourceFromQrCode() {
+ // Setup a device
+ ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+ // Setup a source from qr code
+ mController.setSourceFromQrCode(mMetadata, SourceOriginForLogging.UNKNOWN);
+ when(mMetadata.getBroadcastId()).thenReturn(QR_CODE_BROADCAST_ID);
+
+ // Handle the source from qr code in onStart
+ mController.displayPreference(mScreen);
+ mController.onStart(mLifecycleOwner);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // Verify the connected source is created and moved to WAIT_FOR_SYNC
+ ArgumentCaptor<AudioStreamPreference> preference =
+ ArgumentCaptor.forClass(AudioStreamPreference.class);
+ ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
+ ArgumentCaptor.forClass(
+ AudioStreamsProgressCategoryController.AudioStreamState.class);
+
+ verify(mController).moveToState(preference.capture(), state.capture());
+ assertThat(preference.getValue()).isNotNull();
+ assertThat(preference.getValue().getAudioStreamBroadcastId())
+ .isEqualTo(QR_CODE_BROADCAST_ID);
+ assertThat(state.getValue()).isEqualTo(WAIT_FOR_SYNC);
+ }
+
+ @Test
+ public void testOnStart_handleSourceAlreadyConnected() {
+ // Setup a device
+ ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+ // Setup a connected source
+ BluetoothLeBroadcastReceiveState connected =
+ createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
+ List<BluetoothLeBroadcastReceiveState> list = new ArrayList<>();
+ list.add(connected);
+ when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(list);
+
+ // Handle already connected source in onStart
+ mController.displayPreference(mScreen);
+ mController.onStart(mLifecycleOwner);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ ArgumentCaptor<AudioStreamPreference> preference =
+ ArgumentCaptor.forClass(AudioStreamPreference.class);
+ ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
+ ArgumentCaptor.forClass(
+ AudioStreamsProgressCategoryController.AudioStreamState.class);
+
+ // Verify the connected source is created and moved to SOURCE_ADDED
+ verify(mController).moveToState(preference.capture(), state.capture());
+ assertThat(preference.getValue()).isNotNull();
+ assertThat(preference.getValue().getAudioStreamBroadcastId())
+ .isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
+ assertThat(state.getValue()).isEqualTo(SOURCE_ADDED);
+ }
+
+ @Test
+ public void testOnStart_sourceFromQrCodeNoId_sourceAlreadyConnected_sameName_updateId() {
+ // Setup a device
+ ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+ // Setup source from qr code with unset id and BROADCAST_NAME_1. Creating a real metadata
+ // for properly update its id.
+ var metadata =
+ BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(VALID_METADATA);
+ assertThat(metadata).isNotNull();
+ var metadataWithNoIdAndSameName =
+ new BluetoothLeBroadcastMetadata.Builder(metadata)
+ .setBroadcastId(UNSET_BROADCAST_ID)
+ .setBroadcastName(BROADCAST_NAME_1)
+ .build();
+ mController.setSourceFromQrCode(
+ metadataWithNoIdAndSameName, SourceOriginForLogging.UNKNOWN);
+
+ // Setup a connected source with name BROADCAST_NAME_1 and id
+ BluetoothLeBroadcastReceiveState connected =
+ createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
+ var data = mock(BluetoothLeAudioContentMetadata.class);
+ when(connected.getSubgroupMetadata()).thenReturn(ImmutableList.of(data));
+ when(data.getProgramInfo()).thenReturn(BROADCAST_NAME_1);
+ when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
+
+ // Handle both source from qr code and already connected source in onStart
+ mController.displayPreference(mScreen);
+ mController.onStart(mLifecycleOwner);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // Verify two preferences created, one moved to state WAIT_FOR_SYNC, one to SOURCE_ADDED.
+ // Both has ALREADY_CONNECTED_BROADCAST_ID as the UNSET_ID is updated to match.
+ ArgumentCaptor<AudioStreamPreference> preference =
+ ArgumentCaptor.forClass(AudioStreamPreference.class);
+ ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
+ ArgumentCaptor.forClass(
+ AudioStreamsProgressCategoryController.AudioStreamState.class);
+ verify(mController, times(2)).moveToState(preference.capture(), state.capture());
+
+ List<AudioStreamPreference> preferences = preference.getAllValues();
+ assertThat(preferences.size()).isEqualTo(2);
+ List<AudioStreamsProgressCategoryController.AudioStreamState> states = state.getAllValues();
+ assertThat(states.size()).isEqualTo(2);
+
+ // The preference contains source from qr code
+ assertThat(preferences.get(0).getAudioStreamBroadcastId())
+ .isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
+ assertThat(states.get(0)).isEqualTo(WAIT_FOR_SYNC);
+
+ // The preference contains already connected source
+ assertThat(preferences.get(1).getAudioStreamBroadcastId())
+ .isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
+ assertThat(states.get(1)).isEqualTo(SOURCE_ADDED);
+ }
+
+ @Test
+ public void testHandleSourceFound_addNew() {
+ // Setup a device
+ ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+ when(mMetadata.getBroadcastId()).thenReturn(NEWLY_FOUND_BROADCAST_ID);
+ // A new source is found
+ mController.handleSourceFound(mMetadata);
+
+ // Verify a preference is created with state SYNCED.
+ ArgumentCaptor<AudioStreamPreference> preference =
+ ArgumentCaptor.forClass(AudioStreamPreference.class);
+ ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
+ ArgumentCaptor.forClass(
+ AudioStreamsProgressCategoryController.AudioStreamState.class);
+
+ verify(mController).moveToState(preference.capture(), state.capture());
+ assertThat(preference.getValue()).isNotNull();
+ assertThat(preference.getValue().getAudioStreamBroadcastId())
+ .isEqualTo(NEWLY_FOUND_BROADCAST_ID);
+ assertThat(state.getValue()).isEqualTo(SYNCED);
+ }
+
+ @Test
+ public void testHandleSourceFound_sameIdWithSourceFromQrCode_updateMetadataAndState() {
+ // Setup a device
+ ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+ // Setup source from qr code with QR_CODE_BROADCAST_ID, BROADCAST_NAME_1 and BROADCAST_CODE.
+ var metadata =
+ BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(VALID_METADATA);
+ assertThat(metadata).isNotNull();
+ var metadataFromQrCode =
+ new BluetoothLeBroadcastMetadata.Builder(metadata)
+ .setBroadcastId(QR_CODE_BROADCAST_ID)
+ .setBroadcastName(BROADCAST_NAME_1)
+ .setBroadcastCode(BROADCAST_CODE)
+ .build();
+ mController.setSourceFromQrCode(metadataFromQrCode, SourceOriginForLogging.UNKNOWN);
+
+ // Handle the source from qr code in onStart
+ mController.displayPreference(mScreen);
+ mController.onStart(mLifecycleOwner);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // A new source is found
+ mController.handleSourceFound(
+ new BluetoothLeBroadcastMetadata.Builder(metadata)
+ .setBroadcastId(QR_CODE_BROADCAST_ID)
+ .setBroadcastName(BROADCAST_NAME_2)
+ .build());
+ shadowOf(Looper.getMainLooper()).idle();
+
+ ArgumentCaptor<AudioStreamPreference> preference =
+ ArgumentCaptor.forClass(AudioStreamPreference.class);
+ ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
+ ArgumentCaptor.forClass(
+ AudioStreamsProgressCategoryController.AudioStreamState.class);
+
+ verify(mController, times(2)).moveToState(preference.capture(), state.capture());
+ List<AudioStreamPreference> preferences = preference.getAllValues();
+ List<AudioStreamsProgressCategoryController.AudioStreamState> states = state.getAllValues();
+
+ // Verify the qr code source is created with WAIT_FOR_SYNC, broadcast name got updated to
+ // BROADCAST_NAME_2
+ var sourceFromQrCode = preferences.get(0);
+ assertThat(sourceFromQrCode.getAudioStreamBroadcastId()).isEqualTo(QR_CODE_BROADCAST_ID);
+ assertThat(sourceFromQrCode.getAudioStreamMetadata()).isNotNull();
+ assertThat(sourceFromQrCode.getAudioStreamMetadata().getBroadcastName())
+ .isEqualTo(BROADCAST_NAME_2);
+ assertThat(sourceFromQrCode.getAudioStreamMetadata().getBroadcastCode())
+ .isEqualTo(BROADCAST_CODE);
+ assertThat(states.get(0)).isEqualTo(WAIT_FOR_SYNC);
+
+ // Verify the newly found source is created, broadcast code is retrieved from the source
+ // from qr code, and state updated to ADD_SOURCE_WAIT_FOR_RESPONSE
+ var newlyFoundSource = preferences.get(1);
+ assertThat(newlyFoundSource.getAudioStreamBroadcastId()).isEqualTo(QR_CODE_BROADCAST_ID);
+ assertThat(newlyFoundSource.getAudioStreamMetadata()).isNotNull();
+ assertThat(newlyFoundSource.getAudioStreamMetadata().getBroadcastName())
+ .isEqualTo(BROADCAST_NAME_2);
+ assertThat(newlyFoundSource.getAudioStreamMetadata().getBroadcastCode())
+ .isEqualTo(BROADCAST_CODE);
+ assertThat(states.get(1)).isEqualTo(ADD_SOURCE_WAIT_FOR_RESPONSE);
+ }
+
+ @Test
+ public void testHandleSourceFound_sameIdWithOtherState_doNothing() {
+ // Setup a device
+ ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+ // Setup source already connected
+ BluetoothLeBroadcastReceiveState connected =
+ createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
+ when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
+
+ // Handle source already connected in onStart
+ mController.displayPreference(mScreen);
+ mController.onStart(mLifecycleOwner);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // A new source found
+ when(mMetadata.getBroadcastId()).thenReturn(ALREADY_CONNECTED_BROADCAST_ID);
+ mController.handleSourceFound(mMetadata);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // Verify only the connected source has created a preference, and its state remains as
+ // SOURCE_ADDED
+ ArgumentCaptor<AudioStreamPreference> preference =
+ ArgumentCaptor.forClass(AudioStreamPreference.class);
+ ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
+ ArgumentCaptor.forClass(
+ AudioStreamsProgressCategoryController.AudioStreamState.class);
+
+ verify(mController).moveToState(preference.capture(), state.capture());
+ assertThat(preference.getValue()).isNotNull();
+ assertThat(preference.getValue().getAudioStreamBroadcastId())
+ .isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
+ assertThat(preference.getValue().getAudioStreamState()).isEqualTo(SOURCE_ADDED);
+ }
+
+ @Test
+ public void testHandleSourceLost_removed() {
+ // Setup a device
+ ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+ // Setup mPreference so it's not null
+ mController.displayPreference(mScreen);
+
+ // A new source found
+ when(mMetadata.getBroadcastId()).thenReturn(NEWLY_FOUND_BROADCAST_ID);
+ mController.handleSourceFound(mMetadata);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // A new source found is lost
+ mController.handleSourceLost(NEWLY_FOUND_BROADCAST_ID);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ ArgumentCaptor<AudioStreamPreference> preferenceToAdd =
+ ArgumentCaptor.forClass(AudioStreamPreference.class);
+ ArgumentCaptor<AudioStreamPreference> preferenceToRemove =
+ ArgumentCaptor.forClass(AudioStreamPreference.class);
+ ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
+ ArgumentCaptor.forClass(
+ AudioStreamsProgressCategoryController.AudioStreamState.class);
+
+ // Verify a new preference is created with state SYNCED.
+ verify(mController).moveToState(preferenceToAdd.capture(), state.capture());
+ assertThat(preferenceToAdd.getValue()).isNotNull();
+ assertThat(preferenceToAdd.getValue().getAudioStreamBroadcastId())
+ .isEqualTo(NEWLY_FOUND_BROADCAST_ID);
+ assertThat(state.getValue()).isEqualTo(SYNCED);
+
+ // Verify the preference with NEWLY_FOUND_BROADCAST_ID is removed.
+ verify(mPreference).removePreference(preferenceToRemove.capture());
+ assertThat(preferenceToRemove.getValue().getAudioStreamBroadcastId())
+ .isEqualTo(NEWLY_FOUND_BROADCAST_ID);
+ }
+
+ @Test
+ public void testHandleSourceRemoved_removed() {
+ // Setup a device
+ ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+ // Setup already connected source
+ BluetoothLeBroadcastReceiveState connected =
+ createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
+ when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
+
+ // Handle connected source in onStart
+ mController.displayPreference(mScreen);
+ mController.onStart(mLifecycleOwner);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // The connect source is no longer connected
+ when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(emptyList());
+ mController.handleSourceRemoved();
+ shadowOf(Looper.getMainLooper()).idle();
+
+ ArgumentCaptor<AudioStreamPreference> preferenceToAdd =
+ ArgumentCaptor.forClass(AudioStreamPreference.class);
+ ArgumentCaptor<AudioStreamPreference> preferenceToRemove =
+ ArgumentCaptor.forClass(AudioStreamPreference.class);
+ ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
+ ArgumentCaptor.forClass(
+ AudioStreamsProgressCategoryController.AudioStreamState.class);
+
+ // Verify a new preference is created with state SOURCE_ADDED.
+ verify(mController).moveToState(preferenceToAdd.capture(), state.capture());
+ assertThat(preferenceToAdd.getValue()).isNotNull();
+ assertThat(preferenceToAdd.getValue().getAudioStreamBroadcastId())
+ .isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
+ assertThat(state.getValue()).isEqualTo(SOURCE_ADDED);
+
+ // Verify the preference with ALREADY_CONNECTED_BROADCAST_ID is removed.
+ verify(mPreference).removePreference(preferenceToRemove.capture());
+ assertThat(preferenceToRemove.getValue().getAudioStreamBroadcastId())
+ .isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
+ }
+
+ @Test
+ public void testHandleSourceRemoved_updateState() {
+ // Setup a device
+ ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+ // Setup a connected source
+ BluetoothLeBroadcastReceiveState connected =
+ createConnectedMock(ALREADY_CONNECTED_BROADCAST_ID);
+ when(mAudioStreamsHelper.getAllConnectedSources()).thenReturn(ImmutableList.of(connected));
+
+ // Handle connected source in onStart
+ mController.displayPreference(mScreen);
+ mController.onStart(mLifecycleOwner);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // The connected source is identified as having a bad code
+ BluetoothLeBroadcastReceiveState badCode = mock(BluetoothLeBroadcastReceiveState.class);
+ when(badCode.getBroadcastId()).thenReturn(ALREADY_CONNECTED_BROADCAST_ID);
+ when(badCode.getPaSyncState())
+ .thenReturn(BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED);
+ when(badCode.getBigEncryptionState())
+ .thenReturn(BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE);
+ mController.handleSourceConnectBadCode(badCode);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ ArgumentCaptor<AudioStreamPreference> preference =
+ ArgumentCaptor.forClass(AudioStreamPreference.class);
+ ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
+ ArgumentCaptor.forClass(
+ AudioStreamsProgressCategoryController.AudioStreamState.class);
+
+ verify(mController, times(2)).moveToState(preference.capture(), state.capture());
+ List<AudioStreamPreference> preferences = preference.getAllValues();
+ assertThat(preferences.size()).isEqualTo(2);
+ List<AudioStreamsProgressCategoryController.AudioStreamState> states = state.getAllValues();
+ assertThat(states.size()).isEqualTo(2);
+
+ // Verify the connected source is created state SOURCE_ADDED
+ assertThat(preferences.get(0).getAudioStreamBroadcastId())
+ .isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
+ assertThat(states.get(0)).isEqualTo(SOURCE_ADDED);
+
+ // Verify the connected source is updated to state ADD_SOURCE_BAD_CODE
+ assertThat(preferences.get(1).getAudioStreamBroadcastId())
+ .isEqualTo(ALREADY_CONNECTED_BROADCAST_ID);
+ assertThat(states.get(1)).isEqualTo(ADD_SOURCE_BAD_CODE);
+ }
+
+ @Test
+ public void testHandleSourceFailedToConnect_updateState() {
+ // Setup a device
+ ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
+
+ // Setup mPreference so it's not null
+ mController.displayPreference(mScreen);
+
+ // A new source found
+ when(mMetadata.getBroadcastId()).thenReturn(NEWLY_FOUND_BROADCAST_ID);
+ mController.handleSourceFound(mMetadata);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // The new found source is identified as failed to connect
+ mController.handleSourceFailedToConnect(NEWLY_FOUND_BROADCAST_ID);
+ shadowOf(Looper.getMainLooper()).idle();
+
+ ArgumentCaptor<AudioStreamPreference> preference =
+ ArgumentCaptor.forClass(AudioStreamPreference.class);
+ ArgumentCaptor<AudioStreamsProgressCategoryController.AudioStreamState> state =
+ ArgumentCaptor.forClass(
+ AudioStreamsProgressCategoryController.AudioStreamState.class);
+
+ verify(mController, times(2)).moveToState(preference.capture(), state.capture());
+ List<AudioStreamPreference> preferences = preference.getAllValues();
+ assertThat(preferences.size()).isEqualTo(2);
+ List<AudioStreamsProgressCategoryController.AudioStreamState> states = state.getAllValues();
+ assertThat(states.size()).isEqualTo(2);
+
+ // Verify one preference is created with SYNCED
+ assertThat(preferences.get(0).getAudioStreamBroadcastId())
+ .isEqualTo(NEWLY_FOUND_BROADCAST_ID);
+ assertThat(states.get(0)).isEqualTo(SYNCED);
+
+ // Verify the preference is updated to state ADD_SOURCE_FAILED
+ assertThat(preferences.get(1).getAudioStreamBroadcastId())
+ .isEqualTo(NEWLY_FOUND_BROADCAST_ID);
+ assertThat(states.get(1)).isEqualTo(ADD_SOURCE_FAILED);
+ }
+
+ private static BluetoothLeBroadcastReceiveState createConnectedMock(int id) {
+ var connected = mock(BluetoothLeBroadcastReceiveState.class);
+ List<Long> bisSyncState = new ArrayList<>();
+ bisSyncState.add(1L);
+ when(connected.getBroadcastId()).thenReturn(id);
+ when(connected.getBisSyncState()).thenReturn(bisSyncState);
+ return connected;
+ }
+
+ static class TestController extends AudioStreamsProgressCategoryController {
+ TestController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mExecutor = spy(mContext.getMainExecutor());
+ }
+
+ @Override
+ void moveToState(AudioStreamPreference preference, AudioStreamState state) {
+ preference.setAudioStreamState(state);
+ // Do nothing else to avoid side effect from AudioStreamStateHandler#performAction
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeControllerTest.java
index 4990f26..a83cbf0 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeControllerTest.java
@@ -16,29 +16,38 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static android.app.settings.SettingsEnums.AUDIO_STREAM_MAIN;
+
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController.REQUEST_SCAN_BT_BROADCAST_QR_CODE;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import android.content.Intent;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.connecteddevice.audiosharing.audiostreams.testshadows.ShadowAudioStreamsHelper;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.After;
@@ -46,6 +55,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -139,17 +149,46 @@
public void onPreferenceClick_hasFragment_launchSubSetting() {
mController.displayPreference(mScreen);
mController.setFragment(mFragment);
+ when(mFragment.getMetricsCategory()).thenReturn(AUDIO_STREAM_MAIN);
var listener = mPreference.getOnPreferenceClickListener();
assertThat(listener).isNotNull();
+
+ // mContext is not an Activity context, calling startActivity() from outside of an Activity
+ // context requires the FLAG_ACTIVITY_NEW_TASK flag, create a mock to avoid this
+ // AndroidRuntimeException.
+ Context activityContext = mock(Context.class);
+ when(mPreference.getContext()).thenReturn(activityContext);
+ when(mPreference.getKey()).thenReturn(AudioStreamsScanQrCodeController.KEY);
+
var clicked = listener.onPreferenceClick(mPreference);
+
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ ArgumentCaptor<Integer> requestCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mFragment)
+ .startActivityForResult(intentCaptor.capture(), requestCodeCaptor.capture());
+
+ Intent intent = intentCaptor.getValue();
+ assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(AudioStreamsQrCodeScanFragment.class.getName());
+ assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0))
+ .isEqualTo(R.string.audio_streams_main_page_scan_qr_code_title);
+ assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0))
+ .isEqualTo(AUDIO_STREAM_MAIN);
+
+ int requestCode = requestCodeCaptor.getValue();
+ assertThat(requestCode).isEqualTo(REQUEST_SCAN_BT_BROADCAST_QR_CODE);
+
assertThat(clicked).isTrue();
}
@Test
public void updateVisibility_noConnected_invisible() {
mController.displayPreference(mScreen);
- mController.mBluetoothCallback.onActiveDeviceChanged(mDevice, BluetoothProfile.LE_AUDIO);
+ mController.mBluetoothCallback.onProfileConnectionStateChanged(
+ mDevice,
+ BluetoothAdapter.STATE_DISCONNECTED,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
assertThat(mPreference.isVisible()).isFalse();
}
@@ -158,7 +197,10 @@
public void updateVisibility_hasConnected_visible() {
mController.displayPreference(mScreen);
ShadowAudioStreamsHelper.setCachedBluetoothDeviceInSharingOrLeConnected(mDevice);
- mController.mBluetoothCallback.onActiveDeviceChanged(mDevice, BluetoothProfile.LE_AUDIO);
+ mController.mBluetoothCallback.onProfileConnectionStateChanged(
+ mDevice,
+ BluetoothAdapter.STATE_CONNECTED,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
assertThat(mPreference.isVisible()).isTrue();
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedStateTest.java
index 0f0bafe..59a42a1 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedStateTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SourceAddedStateTest.java
@@ -16,31 +16,76 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static android.app.settings.SettingsEnums.AUDIO_STREAM_MAIN;
+
import static com.android.settings.connecteddevice.audiosharing.audiostreams.SourceAddedState.AUDIO_STREAM_SOURCE_ADDED_STATE_SUMMARY;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.settings.SettingsEnums;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.preference.Preference;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settings.testutils.shadow.ShadowFragment;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
+@Config(
+ shadows = {
+ ShadowFragment.class,
+ })
public class SourceAddedStateTest {
-
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ private static final int BROADCAST_ID = 1;
+ private static final String BROADCAST_TITLE = "title";
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ @Mock private AudioStreamPreference mPreference;
+ @Mock private AudioStreamsProgressCategoryController mController;
+ @Mock private AudioStreamsHelper mHelper;
+ @Mock private AudioStreamsRepository mRepository;
+ @Mock private AudioStreamsDashboardFragment mFragment;
+ @Mock private FragmentActivity mActivity;
+ private FakeFeatureFactory mFeatureFactory;
private SourceAddedState mInstance;
@Before
public void setUp() {
- mInstance = SourceAddedState.getInstance();
+ when(mFragment.getActivity()).thenReturn(mActivity);
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ mInstance = new SourceAddedState();
+ when(mPreference.getAudioStreamBroadcastId()).thenReturn(BROADCAST_ID);
+ when(mPreference.getTitle()).thenReturn(BROADCAST_TITLE);
}
@Test
public void testGetInstance() {
+ mInstance = SourceAddedState.getInstance();
assertThat(mInstance).isNotNull();
assertThat(mInstance).isInstanceOf(SourceAddedState.class);
}
@@ -58,4 +103,59 @@
assertThat(stateEnum)
.isEqualTo(AudioStreamsProgressCategoryController.AudioStreamState.SOURCE_ADDED);
}
+
+ @Test
+ public void testPerformAction() {
+ mInstance.setAudioStreamsRepositoryForTesting(mRepository);
+ BluetoothLeBroadcastMetadata mockMetadata = mock(BluetoothLeBroadcastMetadata.class);
+ when(mRepository.getCachedMetadata(anyInt())).thenReturn(mockMetadata);
+ when(mPreference.getContext()).thenReturn(mContext);
+ when(mPreference.getSourceOriginForLogging())
+ .thenReturn(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS);
+
+ mInstance.performAction(mPreference, mController, mHelper);
+
+ verify(mRepository).saveMetadata(eq(mContext), eq(mockMetadata));
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ eq(mContext),
+ eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED),
+ eq(SourceOriginForLogging.QR_CODE_SCAN_SETTINGS.ordinal()));
+ verify(mHelper).startMediaService(eq(mContext), eq(BROADCAST_ID), eq(BROADCAST_TITLE));
+ }
+
+ @Test
+ public void testGetOnClickListener_startSubSettings() {
+ when(mController.getFragment()).thenReturn(mFragment);
+ when(mFragment.getMetricsCategory()).thenReturn(AUDIO_STREAM_MAIN);
+
+ Preference.OnPreferenceClickListener listener = mInstance.getOnClickListener(mController);
+ assertThat(listener).isNotNull();
+
+ // mContext is not an Activity context, calling startActivity() from outside of an Activity
+ // context requires the FLAG_ACTIVITY_NEW_TASK flag, create a mock to avoid this
+ // AndroidRuntimeException.
+ Context activityContext = mock(Context.class);
+ when(mPreference.getContext()).thenReturn(activityContext);
+
+ listener.onPreferenceClick(mPreference);
+
+ ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(activityContext).startActivity(argumentCaptor.capture());
+
+ Intent intent = argumentCaptor.getValue();
+ assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(AudioStreamDetailsFragment.class.getName());
+ assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0))
+ .isEqualTo(R.string.audio_streams_detail_page_title);
+ assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0))
+ .isEqualTo(AUDIO_STREAM_MAIN);
+
+ Bundle bundle = intent.getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ assertThat(bundle).isNotNull();
+ assertThat(bundle.getString(AudioStreamDetailsFragment.BROADCAST_NAME_ARG))
+ .isEqualTo(BROADCAST_TITLE);
+ assertThat(bundle.getInt(AudioStreamDetailsFragment.BROADCAST_ID_ARG))
+ .isEqualTo(BROADCAST_ID);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java
index e9eab50..2b19e20 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/SyncedStateTest.java
@@ -20,7 +20,7 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.never;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
@@ -28,10 +28,16 @@
import android.app.AlertDialog;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.content.Context;
+import android.content.DialogInterface;
+import android.widget.Button;
+import android.widget.TextView;
import androidx.preference.Preference;
import androidx.test.core.app.ApplicationProvider;
+import com.android.settings.R;
+import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -42,7 +48,9 @@
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowLooper;
@RunWith(RobolectricTestRunner.class)
@Config(
@@ -51,6 +59,10 @@
})
public class SyncedStateTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ private static final String ENCRYPTED_METADATA =
+ "BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;"
+ + "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;";
+ private static final String BROADCAST_TITLE = "title";
@Mock private AudioStreamsProgressCategoryController mMockController;
@Mock private AudioStreamPreference mMockPreference;
@Mock private BluetoothLeBroadcastMetadata mMockMetadata;
@@ -105,18 +117,47 @@
@Test
public void testGetOnClickListener_isEncrypted_passwordDialogShowing() {
+ when(mMockPreference.getAudioStreamMetadata())
+ .thenReturn(
+ BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(
+ ENCRYPTED_METADATA));
+ when(mMockPreference.getContext()).thenReturn(mMockContext);
+ when(mMockPreference.getTitle()).thenReturn(BROADCAST_TITLE);
+
Preference.OnPreferenceClickListener listener =
mInstance.getOnClickListener(mMockController);
- when(mMockPreference.getAudioStreamMetadata()).thenReturn(mMockMetadata);
- when(mMockPreference.getContext()).thenReturn(mMockContext);
- when(mMockMetadata.isEncrypted()).thenReturn(true);
+ assertThat(listener).isNotNull();
listener.onPreferenceClick(mMockPreference);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialog.getLatestAlertDialog();
+
assertThat(dialog).isNotNull();
assertThat(dialog.isShowing()).isTrue();
- verify(mMockController, never()).handleSourceAddRequest(mMockPreference, mMockMetadata);
+
+ Button neutralButton = dialog.getButton(DialogInterface.BUTTON_NEUTRAL);
+ assertThat(neutralButton).isNotNull();
+ assertThat(neutralButton.getText().toString())
+ .isEqualTo(mMockContext.getString(android.R.string.cancel));
+
+ Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ assertThat(positiveButton).isNotNull();
+ assertThat(positiveButton.getText().toString())
+ .isEqualTo(
+ mMockContext.getString(R.string.bluetooth_connect_access_dialog_positive));
+
+ positiveButton.callOnClick();
+ ShadowLooper.idleMainLooper();
+ verify(mMockController).handleSourceAddRequest(any(), any());
+
+ ShadowAlertDialog shadowDialog = Shadow.extract(dialog);
+ TextView title = shadowDialog.getView().findViewById(R.id.broadcast_name_text);
+ assertThat(title).isNotNull();
+ assertThat(title.getText().toString()).isEqualTo(BROADCAST_TITLE);
+ assertThat(shadowDialog.getTitle().toString())
+ .isEqualTo(mMockContext.getString(R.string.find_broadcast_password_dialog_title));
+
+ dialog.cancel();
}
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncStateTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncStateTest.java
index 3eb07a4..813ed2b 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncStateTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/WaitForSyncStateTest.java
@@ -16,22 +16,39 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import static android.app.settings.SettingsEnums.DIALOG_AUDIO_STREAM_MAIN_WAIT_FOR_SYNC_TIMEOUT;
+
+import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsScanQrCodeController.REQUEST_SCAN_BT_BROADCAST_QR_CODE;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.WaitForSyncState.AUDIO_STREAM_WAIT_FOR_SYNC_STATE_SUMMARY;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.WaitForSyncState.WAIT_FOR_SYNC_TIMEOUT_MILLIS;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.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;
+import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -43,19 +60,23 @@
@RunWith(RobolectricTestRunner.class)
public class WaitForSyncStateTest {
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ private final Context mContext = spy(ApplicationProvider.getApplicationContext());
@Mock private AudioStreamPreference mMockPreference;
@Mock private AudioStreamsProgressCategoryController mMockController;
@Mock private AudioStreamsHelper mMockHelper;
@Mock private BluetoothLeBroadcastMetadata mMockMetadata;
+ private FakeFeatureFactory mFeatureFactory;
private WaitForSyncState mInstance;
@Before
public void setUp() {
- mInstance = WaitForSyncState.getInstance();
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
+ mInstance = new WaitForSyncState();
}
@Test
public void testGetInstance() {
+ mInstance = WaitForSyncState.getInstance();
assertThat(mInstance).isNotNull();
assertThat(mInstance).isInstanceOf(AudioStreamStateHandler.class);
}
@@ -93,12 +114,49 @@
.thenReturn(AudioStreamsProgressCategoryController.AudioStreamState.WAIT_FOR_SYNC);
when(mMockPreference.getAudioStreamBroadcastId()).thenReturn(1);
when(mMockPreference.getAudioStreamMetadata()).thenReturn(mMockMetadata);
+ when(mMockPreference.getContext()).thenReturn(mContext);
when(mMockPreference.getSourceOriginForLogging())
- .thenReturn(SourceOriginForLogging.UNKNOWN);
+ .thenReturn(SourceOriginForLogging.BROADCAST_SEARCH);
+ when(mMockController.getFragment()).thenReturn(mock(AudioStreamsDashboardFragment.class));
mInstance.performAction(mMockPreference, mMockController, mMockHelper);
ShadowLooper.idleMainLooper(WAIT_FOR_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
verify(mMockController).handleSourceLost(1);
+ verify(mFeatureFactory.metricsFeatureProvider)
+ .action(
+ eq(mContext),
+ eq(SettingsEnums.ACTION_AUDIO_STREAM_JOIN_FAILED_WAIT_FOR_SYNC_TIMEOUT),
+ eq(SourceOriginForLogging.BROADCAST_SEARCH.ordinal()));
+ verify(mContext).getString(R.string.audio_streams_dialog_stream_is_not_available);
+ verify(mContext).getString(R.string.audio_streams_is_not_playing);
+ verify(mContext).getString(R.string.audio_streams_dialog_close);
+ verify(mContext).getString(R.string.audio_streams_dialog_retry);
+ }
+
+ @Test
+ public void testLaunchQrCodeScanFragment() {
+ // mContext is not an Activity context, calling startActivity() from outside of an Activity
+ // context requires the FLAG_ACTIVITY_NEW_TASK flag, create a mock to avoid this
+ // AndroidRuntimeException.
+ Context activityContext = mock(Context.class);
+ AudioStreamsDashboardFragment fragment = mock(AudioStreamsDashboardFragment.class);
+ mInstance.launchQrCodeScanFragment(activityContext, fragment);
+
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ ArgumentCaptor<Integer> requestCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(fragment)
+ .startActivityForResult(intentCaptor.capture(), requestCodeCaptor.capture());
+
+ Intent intent = intentCaptor.getValue();
+ assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT))
+ .isEqualTo(AudioStreamsQrCodeScanFragment.class.getName());
+ assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0))
+ .isEqualTo(R.string.audio_streams_main_page_scan_qr_code_title);
+ assertThat(intent.getIntExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, 0))
+ .isEqualTo(DIALOG_AUDIO_STREAM_MAIN_WAIT_FOR_SYNC_TIMEOUT);
+
+ int requestCode = requestCodeCaptor.getValue();
+ assertThat(requestCode).isEqualTo(REQUEST_SCAN_BT_BROADCAST_QR_CODE);
}
}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java
index 3a0a6c4..13c19ca 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/audiostreams/testshadows/ShadowAudioStreamsHelper.java
@@ -32,7 +32,7 @@
import java.util.List;
import java.util.Optional;
-@Implements(value = AudioStreamsHelper.class, callThroughByDefault = false)
+@Implements(value = AudioStreamsHelper.class, callThroughByDefault = true)
public class ShadowAudioStreamsHelper {
private static AudioStreamsHelper sMockHelper;
@Nullable private static CachedBluetoothDevice sCachedBluetoothDevice;
diff --git a/tests/robotests/src/com/android/settings/inputmethod/PointerScaleSeekBarControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/PointerScaleSeekBarControllerTest.java
new file mode 100644
index 0000000..152649f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/inputmethod/PointerScaleSeekBarControllerTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.inputmethod;
+
+import static android.view.flags.Flags.enableVectorCursorA11ySettings;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.widget.SeekBar;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.testutils.shadow.ShadowSystemSettings;
+import com.android.settings.widget.LabeledSeekBarPreference;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link PointerScaleSeekBarController} */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {
+ ShadowSystemSettings.class,
+})
+public class PointerScaleSeekBarControllerTest {
+
+ private static final String PREFERENCE_KEY = "pointer_scale";
+
+ @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock private PreferenceScreen mPreferenceScreen;
+
+ private Context mContext;
+ private LabeledSeekBarPreference mPreference;
+ private PointerScaleSeekBarController mController;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mPreference = new LabeledSeekBarPreference(mContext, null);
+ mController = new PointerScaleSeekBarController(mContext, PREFERENCE_KEY);
+ }
+
+ @Test
+ public void getAvailabilityStatus_flagEnabled() {
+ assumeTrue(enableVectorCursorA11ySettings());
+
+ assertEquals(mController.getAvailabilityStatus(), AVAILABLE);
+ }
+
+ @Test
+ public void onProgressChanged_changeListenerUpdatesSetting() {
+ when(mPreferenceScreen.findPreference(anyString())).thenReturn(mPreference);
+ mController.displayPreference(mPreferenceScreen);
+ SeekBar seekBar = mPreference.getSeekbar();
+ int sliderValue = 1;
+
+ mPreference.onProgressChanged(seekBar, sliderValue, false);
+
+ float expectedScale = 1.5f;
+ float currentScale = Settings.System.getFloatForUser(mContext.getContentResolver(),
+ Settings.System.POINTER_SCALE, -1, UserHandle.USER_CURRENT);
+ assertEquals(expectedScale, currentScale, /* delta= */ 0.001f);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/IconLoaderTest.java b/tests/robotests/src/com/android/settings/notification/modes/IconLoaderTest.java
deleted file mode 100644
index 7d4a367..0000000
--- a/tests/robotests/src/com/android/settings/notification/modes/IconLoaderTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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.notification.modes;
-
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.AutomaticZenRule;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.service.notification.ZenPolicy;
-
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.MoreExecutors;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public class IconLoaderTest {
-
- private Context mContext;
- private IconLoader mLoader;
-
- @Before
- public void setUp() {
- mContext = RuntimeEnvironment.application;
- mLoader = new IconLoader(MoreExecutors.newDirectExecutorService());
- }
-
- @Test
- public void getIcon_systemOwnedRuleWithIcon_loads() throws Exception {
- AutomaticZenRule systemRule = newRuleBuilder()
- .setPackage("android")
- .setIconResId(android.R.drawable.ic_media_play)
- .build();
-
- ListenableFuture<Drawable> loadFuture = mLoader.getIcon(mContext, systemRule);
- assertThat(loadFuture.isDone()).isTrue();
- assertThat(loadFuture.get()).isNotNull();
- }
-
- @Test
- public void getIcon_ruleWithoutSpecificIcon_loadsFallback() throws Exception {
- AutomaticZenRule rule = newRuleBuilder()
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setPackage("com.blah")
- .build();
-
- ListenableFuture<Drawable> loadFuture = mLoader.getIcon(mContext, rule);
- assertThat(loadFuture.isDone()).isTrue();
- assertThat(loadFuture.get()).isNotNull();
- }
-
- @Test
- public void getIcon_ruleWithAppIconWithLoadFailure_loadsFallback() throws Exception {
- AutomaticZenRule rule = newRuleBuilder()
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setPackage("com.blah")
- .setIconResId(-123456)
- .build();
-
- ListenableFuture<Drawable> loadFuture = mLoader.getIcon(mContext, rule);
- assertThat(loadFuture.get()).isNotNull();
- }
-
- private static AutomaticZenRule.Builder newRuleBuilder() {
- return new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().build());
- }
-}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/InterruptionFilterPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/InterruptionFilterPreferenceControllerTest.java
index aeb1b8e..ff25322 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/InterruptionFilterPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/InterruptionFilterPreferenceControllerTest.java
@@ -18,27 +18,24 @@
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.STATE_DISALLOW;
-import static android.service.notification.ZenPolicy.STATE_UNSET;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
-import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -72,12 +69,9 @@
@Test
public void testUpdateState_all() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
- .setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
+ .build();
mController.updateZenMode(preference, zenMode);
verify(preference).setChecked(false);
@@ -86,12 +80,9 @@
@Test
public void testOnPreferenceChange_fromAll() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
- .setZenPolicy(new ZenPolicy.Builder().allowAlarms(false).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
+ .build();
mController.updateZenMode(preference, zenMode);
@@ -108,12 +99,10 @@
@Test
public void testUpdateState_priority() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build())
+ .build();
mController.updateZenMode(preference, zenMode);
verify(preference).setChecked(true);
@@ -122,12 +111,10 @@
@Test
public void testOnPreferenceChange_fromPriority() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAlarms(false).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder().allowAlarms(false).build())
+ .build();
mController.updateZenMode(preference, zenMode);
diff --git a/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java b/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java
new file mode 100644
index 0000000..fa12b30
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/TestModeBuilder.java
@@ -0,0 +1,149 @@
+/*
+ * 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.notification.modes;
+
+import android.app.AutomaticZenRule;
+import android.app.NotificationManager;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.ZenDeviceEffects;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenPolicy;
+
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.notification.modes.ZenMode;
+
+import java.util.Random;
+
+class TestModeBuilder {
+
+ private String mId;
+ private AutomaticZenRule mRule;
+ private ZenModeConfig.ZenRule mConfigZenRule;
+
+ public static final ZenMode EXAMPLE = new TestModeBuilder().build();
+
+ TestModeBuilder() {
+ // Reasonable defaults
+ int id = new Random().nextInt(1000);
+ mId = "rule_" + id;
+ mRule = new AutomaticZenRule.Builder("Test Rule #" + id, Uri.parse("rule://" + id))
+ .setPackage("some_package")
+ .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+ .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
+ .build();
+ mConfigZenRule = new ZenModeConfig.ZenRule();
+ mConfigZenRule.enabled = true;
+ mConfigZenRule.pkg = "some_package";
+ }
+
+ TestModeBuilder setId(String id) {
+ mId = id;
+ return this;
+ }
+
+ TestModeBuilder setAzr(AutomaticZenRule rule) {
+ mRule = rule;
+ mConfigZenRule.pkg = rule.getPackageName();
+ mConfigZenRule.conditionId = rule.getConditionId();
+ mConfigZenRule.enabled = rule.isEnabled();
+ return this;
+ }
+
+ TestModeBuilder setConfigZenRule(ZenModeConfig.ZenRule configZenRule) {
+ mConfigZenRule = configZenRule;
+ return this;
+ }
+
+ public TestModeBuilder setName(String name) {
+ mRule.setName(name);
+ mConfigZenRule.name = name;
+ return this;
+ }
+
+ public TestModeBuilder setPackage(String pkg) {
+ mRule.setPackageName(pkg);
+ mConfigZenRule.pkg = pkg;
+ return this;
+ }
+
+ TestModeBuilder setConditionId(Uri conditionId) {
+ mRule.setConditionId(conditionId);
+ mConfigZenRule.conditionId = conditionId;
+ return this;
+ }
+
+ TestModeBuilder setType(@AutomaticZenRule.Type int type) {
+ mRule.setType(type);
+ mConfigZenRule.type = type;
+ return this;
+ }
+
+ TestModeBuilder setInterruptionFilter(
+ @NotificationManager.InterruptionFilter int interruptionFilter) {
+ mRule.setInterruptionFilter(interruptionFilter);
+ mConfigZenRule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
+ interruptionFilter, NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+ return this;
+ }
+
+ TestModeBuilder setZenPolicy(@Nullable ZenPolicy policy) {
+ mRule.setZenPolicy(policy);
+ mConfigZenRule.zenPolicy = policy;
+ return this;
+ }
+
+ TestModeBuilder setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
+ mRule.setDeviceEffects(deviceEffects);
+ mConfigZenRule.zenDeviceEffects = deviceEffects;
+ return this;
+ }
+
+ public TestModeBuilder setEnabled(boolean enabled) {
+ mRule.setEnabled(enabled);
+ mConfigZenRule.enabled = enabled;
+ return this;
+ }
+
+ TestModeBuilder setManualInvocationAllowed(boolean allowed) {
+ mRule.setManualInvocationAllowed(allowed);
+ mConfigZenRule.allowManualInvocation = allowed;
+ return this;
+ }
+
+ public TestModeBuilder setTriggerDescription(@Nullable String triggerDescription) {
+ mRule.setTriggerDescription(triggerDescription);
+ mConfigZenRule.triggerDescription = triggerDescription;
+ return this;
+ }
+
+ TestModeBuilder setActive(boolean active) {
+ if (active) {
+ mConfigZenRule.enabled = true;
+ mConfigZenRule.condition = new Condition(mRule.getConditionId(), "...",
+ Condition.STATE_TRUE);
+ } else {
+ mConfigZenRule.condition = null;
+ }
+ return this;
+ }
+
+ ZenMode build() {
+ return new ZenMode(mId, mRule, mConfigZenRule);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
index b199a2b..83f8de0 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsLinkPreferenceControllerTest.java
@@ -17,8 +17,7 @@
package com.android.settings.notification.modes;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-
-import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+import static android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID;
import static com.google.common.truth.Truth.assertThat;
@@ -29,12 +28,10 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.net.Uri;
import android.os.Bundle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -46,6 +43,8 @@
import com.android.settings.SettingsActivity;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import org.junit.Before;
@@ -102,14 +101,13 @@
}
private ZenMode createPriorityChannelsZenMode() {
- return new ZenMode("id", new AutomaticZenRule.Builder("Bedtime",
- Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
+ return new TestModeBuilder()
+ .setId("id")
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder()
.allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
.build())
- .build(), true);
+ .build();
}
@Test
@@ -138,7 +136,7 @@
Bundle bundle = launcherIntent.getBundleExtra(
SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
assertThat(bundle).isNotNull();
- assertThat(bundle.getString(MODE_ID)).isEqualTo("id");
+ assertThat(bundle.getString(EXTRA_AUTOMATIC_ZEN_RULE_ID)).isEqualTo("id");
}
@Test
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsPreferenceControllerTest.java
index b67d332..c96dbb6 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAppsPreferenceControllerTest.java
@@ -16,8 +16,6 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static com.android.settings.notification.modes.ZenModeAppsPreferenceController.KEY_NONE;
@@ -28,10 +26,8 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
@@ -41,6 +37,8 @@
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import org.junit.Before;
@@ -107,13 +105,12 @@
@Test
public void testUpdateState_None() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowChannels(ZenPolicy.CHANNEL_POLICY_NONE)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_NONE)
+ .build())
+ .build();
+
mNoneController.updateZenMode(preference, zenMode);
verify(preference).setChecked(true);
@@ -122,13 +119,12 @@
@Test
public void testUpdateState_None_Unchecked() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
+ .build())
+ .build();
+
mNoneController.updateZenMode(preference, zenMode);
verify(preference).setChecked(false);
@@ -137,13 +133,12 @@
@Test
public void testUpdateState_Priority() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
+ .build())
+ .build();
+
mPriorityController.updateZenMode(preference, zenMode);
verify(preference).setChecked(true);
@@ -152,13 +147,12 @@
@Test
public void testUpdateState_Priority_Unchecked() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowChannels(ZenPolicy.CHANNEL_POLICY_NONE)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_NONE)
+ .build())
+ .build();
+
mPriorityController.updateZenMode(preference, zenMode);
verify(preference).setChecked(false);
@@ -166,21 +160,19 @@
@Test
public void testPreferenceClick_passesCorrectCheckedState_None() {
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
+ .build())
+ .build();
mNoneController.updateZenMode(mNonePref, zenMode);
mPriorityController.updateZenMode(mPriorityPref, zenMode);
- assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_NONE))
- .isChecked());
- assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
- .isChecked());
+ assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_NONE))
+ .isChecked()).isFalse();
+ assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
+ .isChecked()).isTrue();
// Click on NONE
mPrefCategory.findPreference(KEY_NONE).performClick();
@@ -192,30 +184,31 @@
// See AbstractZenModePreferenceController.
assertThat(captor.getValue().getRule().getInterruptionFilter())
.isEqualTo(INTERRUPTION_FILTER_PRIORITY);
- // NONE is now checked; others are unchecked.
+
+ // After screen is refreshed, NONE is now checked; others are unchecked.
+ mNoneController.updateZenMode(mNonePref, captor.getValue());
+ mPriorityController.updateZenMode(mPriorityPref, captor.getValue());
assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_NONE))
- .isChecked());
- assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
- .isChecked());
+ .isChecked()).isTrue();
+ assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
+ .isChecked()).isFalse();
}
@Test
public void testPreferenceClick_passesCorrectCheckedState_Priority() {
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowChannels(ZenPolicy.CHANNEL_POLICY_NONE)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowChannels(ZenPolicy.CHANNEL_POLICY_NONE)
+ .build())
+ .build();
mNoneController.updateZenMode(mNonePref, zenMode);
mPriorityController.updateZenMode(mPriorityPref, zenMode);
assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_NONE))
- .isChecked());
- assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
- .isChecked());
+ .isChecked()).isTrue();
+ assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
+ .isChecked()).isFalse();
// Click on PRIORITY
mPrefCategory.findPreference(KEY_PRIORITY).performClick();
@@ -225,11 +218,13 @@
// Checks the policy value for PRIORITY is propagated to the backend.
assertThat(captor.getValue().getRule().getInterruptionFilter())
.isEqualTo(INTERRUPTION_FILTER_PRIORITY);
- // PRIORITY is now checked; others are unchecked.
- assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
- .isChecked());
- assertThat(!((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_NONE))
- .isChecked());
- }
+ // After screen is refreshed, PRIORITY is now checked; others are unchecked.
+ mNoneController.updateZenMode(mNonePref, captor.getValue());
+ mPriorityController.updateZenMode(mPriorityPref, captor.getValue());
+ assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_PRIORITY))
+ .isChecked()).isTrue();
+ assertThat(((SelectorWithWidgetPreference) mPrefCategory.findPreference(KEY_NONE))
+ .isChecked()).isFalse();
+ }
}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeButtonPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeButtonPreferenceControllerTest.java
index bda3843..625f231 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeButtonPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeButtonPreferenceControllerTest.java
@@ -16,8 +16,6 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -25,15 +23,14 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.service.notification.ZenPolicy;
import android.widget.Button;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
import org.junit.Before;
@@ -71,43 +68,34 @@
@Test
public void isAvailable_notIfAppOptsOut() {
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
+ ZenMode zenMode = new TestModeBuilder()
.setManualInvocationAllowed(false)
- .setEnabled(true)
- .build(), false);
+ .build();
mController.setZenMode(zenMode);
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void isAvailable_notIfModeDisabled() {
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .setManualInvocationAllowed(true)
- .setEnabled(false)
- .build(), false);
+ ZenMode zenMode = new TestModeBuilder()
+ .setManualInvocationAllowed(true)
+ .setEnabled(false)
+ .build();
+
mController.setZenMode(zenMode);
+
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void isAvailable_appOptedIn_modeEnabled() {
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .setManualInvocationAllowed(true)
- .setEnabled(true)
- .build(), false);
+ ZenMode zenMode = new TestModeBuilder()
+ .setManualInvocationAllowed(true)
+ .setEnabled(true)
+ .build();
+
mController.setZenMode(zenMode);
+
assertThat(mController.isAvailable()).isTrue();
}
@@ -116,15 +104,13 @@
Button button = new Button(mContext);
LayoutPreference pref = mock(LayoutPreference.class);
when(pref.findViewById(anyInt())).thenReturn(button);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .setManualInvocationAllowed(true)
- .setEnabled(true)
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setManualInvocationAllowed(true)
+ .setActive(true)
+ .build();
+
mController.updateZenMode(pref, zenMode);
+
assertThat(button.getText().toString()).contains("off");
assertThat(button.hasOnClickListeners()).isTrue();
}
@@ -134,15 +120,13 @@
Button button = new Button(mContext);
LayoutPreference pref = mock(LayoutPreference.class);
when(pref.findViewById(anyInt())).thenReturn(button);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .setManualInvocationAllowed(true)
- .setEnabled(true)
- .build(), false);
+ ZenMode zenMode = new TestModeBuilder()
+ .setManualInvocationAllowed(true)
+ .setActive(false)
+ .build();
+
mController.updateZenMode(pref, zenMode);
+
assertThat(button.getText().toString()).contains("on");
assertThat(button.hasOnClickListeners()).isTrue();
}
@@ -152,14 +136,11 @@
Button button = new Button(mContext);
LayoutPreference pref = mock(LayoutPreference.class);
when(pref.findViewById(anyInt())).thenReturn(button);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .setManualInvocationAllowed(true)
- .setEnabled(true)
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setManualInvocationAllowed(true)
+ .setActive(true)
+ .build();
+
mController.updateZenMode(pref, zenMode);
button.callOnClick();
@@ -171,14 +152,11 @@
Button button = new Button(mContext);
LayoutPreference pref = mock(LayoutPreference.class);
when(pref.findViewById(anyInt())).thenReturn(button);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .setManualInvocationAllowed(true)
- .setEnabled(true)
- .build(), false);
+ ZenMode zenMode = new TestModeBuilder()
+ .setManualInvocationAllowed(true)
+ .setActive(false)
+ .build();
+
mController.updateZenMode(pref, zenMode);
button.callOnClick();
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceControllerTest.java
index 94c2d8a..058b2d7 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeCallsLinkPreferenceControllerTest.java
@@ -16,22 +16,19 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -67,13 +64,7 @@
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
Preference pref = mock(Preference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .build(), true);
- mController.updateZenMode(pref, zenMode);
+ mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
verify(pref).setSummary(any());
}
}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceControllerTest.java
index 1a62b75..a735cd9 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayEffectPreferenceControllerTest.java
@@ -16,22 +16,22 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-import static android.service.notification.ZenPolicy.STATE_ALLOW;
-import static android.service.notification.ZenPolicy.STATE_UNSET;
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenDeviceEffects;
-import android.service.notification.ZenPolicy;
+
import androidx.preference.TwoStatePreference;
+
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -62,15 +62,11 @@
@Test
public void testUpdateState_grayscale() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build())
- .setDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDisplayGrayscale(true)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build())
+ .build();
ZenModeDisplayEffectPreferenceController controller =
new ZenModeDisplayEffectPreferenceController(
@@ -84,15 +80,11 @@
@Test
public void testOnPreferenceChange_grayscale() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAlarms(false).build())
- .setDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDisplayGrayscale(true)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldDisplayGrayscale(true)
+ .build())
+ .build();
ZenModeDisplayEffectPreferenceController controller =
new ZenModeDisplayEffectPreferenceController(mContext, "effect_greyscale", mBackend);
@@ -103,22 +95,18 @@
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
verify(mBackend).updateMode(captor.capture());
- assertThat(captor.getValue().getRule().getDeviceEffects().shouldDisplayGrayscale())
+ assertThat(captor.getValue().getDeviceEffects().shouldDisplayGrayscale())
.isFalse();
}
@Test
public void testUpdateState_aod() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowMedia(true).build())
- .setDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldSuppressAmbientDisplay(true)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldSuppressAmbientDisplay(true)
+ .build())
+ .build();
ZenModeDisplayEffectPreferenceController controller =
new ZenModeDisplayEffectPreferenceController(mContext, "effect_aod", mBackend);
@@ -131,15 +119,11 @@
@Test
public void testOnPreferenceChange_aod() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowMedia(false).build())
- .setDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldSuppressAmbientDisplay(true)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldSuppressAmbientDisplay(true)
+ .build())
+ .build();
ZenModeDisplayEffectPreferenceController controller =
new ZenModeDisplayEffectPreferenceController(mContext, "effect_aod", mBackend);
@@ -150,22 +134,18 @@
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
verify(mBackend).updateMode(captor.capture());
- assertThat(captor.getValue().getRule().getDeviceEffects().shouldSuppressAmbientDisplay())
+ assertThat(captor.getValue().getDeviceEffects().shouldSuppressAmbientDisplay())
.isFalse();
}
@Test
public void testUpdateState_wallpaper() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowSystem(true).build())
- .setDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDimWallpaper(true)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .build())
+ .build();
ZenModeDisplayEffectPreferenceController controller =
new ZenModeDisplayEffectPreferenceController(
@@ -179,15 +159,11 @@
@Test
public void testOnPreferenceChange_wallpaper() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowSystem(false).build())
- .setDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDimWallpaper(true)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldDimWallpaper(true)
+ .build())
+ .build();
ZenModeDisplayEffectPreferenceController controller =
new ZenModeDisplayEffectPreferenceController(
@@ -199,21 +175,17 @@
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
verify(mBackend).updateMode(captor.capture());
- assertThat(captor.getValue().getRule().getDeviceEffects().shouldDimWallpaper()).isFalse();
+ assertThat(captor.getValue().getDeviceEffects().shouldDimWallpaper()).isFalse();
}
@Test
public void testUpdateState_darkTheme() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowReminders(true).build())
- .setDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldUseNightMode(true)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldUseNightMode(true)
+ .build())
+ .build();
ZenModeDisplayEffectPreferenceController controller =
new ZenModeDisplayEffectPreferenceController(mContext, "effect_dark_theme",
@@ -227,15 +199,11 @@
@Test
public void testOnPreferenceChange_darkTheme() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowReminders(false).build())
- .setDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldUseNightMode(true)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setDeviceEffects(new ZenDeviceEffects.Builder()
+ .setShouldUseNightMode(true)
+ .build())
+ .build();
ZenModeDisplayEffectPreferenceController controller =
new ZenModeDisplayEffectPreferenceController(mContext, "effect_dark_theme",
@@ -247,6 +215,6 @@
ArgumentCaptor<ZenMode> captor = ArgumentCaptor.forClass(ZenMode.class);
verify(mBackend).updateMode(captor.capture());
- assertThat(captor.getValue().getRule().getDeviceEffects().shouldUseNightMode()).isFalse();
+ assertThat(captor.getValue().getDeviceEffects().shouldUseNightMode()).isFalse();
}
}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceControllerTest.java
index 62aa046..3ccfb9f 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeDisplayLinkPreferenceControllerTest.java
@@ -16,22 +16,19 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -67,13 +64,7 @@
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
Preference pref = mock(Preference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .build(), true);
- mController.updateZenMode(pref, zenMode);
+ mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
verify(pref).setSummary(any());
}
}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java
index c1c4d61..03c75fb 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeExitAtAlarmPreferenceControllerTest.java
@@ -21,13 +21,15 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.app.AutomaticZenRule;
import android.content.Context;
import android.service.notification.ZenModeConfig;
import androidx.preference.TwoStatePreference;
import androidx.test.core.app.ApplicationProvider;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -65,10 +67,9 @@
scheduleInfo.endHour = 2;
scheduleInfo.exitAtAlarm = false;
- ZenMode mode = new ZenMode("id",
- new AutomaticZenRule.Builder("name",
- ZenModeConfig.toScheduleConditionId(scheduleInfo)).build(),
- true); // is active
+ ZenMode mode = new TestModeBuilder()
+ .setConditionId(ZenModeConfig.toScheduleConditionId(scheduleInfo))
+ .build();
// need to call updateZenMode for the first call
mPrefController.updateZenMode(preference, mode);
@@ -94,10 +95,9 @@
scheduleInfo.endHour = 2;
scheduleInfo.exitAtAlarm = true;
- ZenMode mode = new ZenMode("id",
- new AutomaticZenRule.Builder("name",
- ZenModeConfig.toScheduleConditionId(scheduleInfo)).build(),
- true); // is active
+ ZenMode mode = new TestModeBuilder()
+ .setConditionId(ZenModeConfig.toScheduleConditionId(scheduleInfo))
+ .build();
mPrefController.updateZenMode(preference, mode);
// turn off exit at alarm
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java
index ba9a6b8..5db7e92 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeIconPickerListPreferenceControllerTest.java
@@ -23,9 +23,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.AutomaticZenRule;
import android.content.Context;
-import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceScreen;
@@ -33,6 +31,8 @@
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.LayoutPreference;
import com.google.common.collect.ImmutableList;
@@ -47,10 +47,7 @@
@RunWith(RobolectricTestRunner.class)
public class ZenModeIconPickerListPreferenceControllerTest {
- private static final ZenMode ZEN_MODE = new ZenMode(
- "mode_id",
- new AutomaticZenRule.Builder("mode name", Uri.parse("mode")).build(),
- /* isActive= */ false);
+ private static final ZenMode ZEN_MODE = TestModeBuilder.EXAMPLE;
private ZenModesBackend mBackend;
private ZenModeIconPickerListPreferenceController mController;
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceControllerTest.java
index 9400f83..288359a 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeMessagesLinkPreferenceControllerTest.java
@@ -16,22 +16,19 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -67,13 +64,7 @@
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
Preference pref = mock(Preference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .build(), true);
- mController.updateZenMode(pref, zenMode);
+ mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
verify(pref).setSummary(any());
}
}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceControllerTest.java
index 00a9fbe..ee7340b 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisLinkPreferenceControllerTest.java
@@ -16,22 +16,19 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -67,13 +64,7 @@
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
Preference pref = mock(Preference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .build(), true);
- mController.updateZenMode(pref, zenMode);
+ mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
verify(pref).setSummary(any());
}
}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceControllerTest.java
index 54edaf4..b23d946 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeNotifVisPreferenceControllerTest.java
@@ -16,7 +16,6 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.STATE_DISALLOW;
import static android.service.notification.ZenPolicy.VISUAL_EFFECT_LIGHTS;
@@ -32,17 +31,18 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.content.res.Resources;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.TwoStatePreference;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -96,15 +96,12 @@
@Test
public void updateState_notChecked() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowAlarms(true)
- .showAllVisualEffects()
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowAlarms(true)
+ .showAllVisualEffects()
+ .build())
+ .build();
mController.updateZenMode(preference, zenMode);
@@ -115,15 +112,12 @@
@Test
public void updateState_checked() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowAlarms(true)
- .showVisualEffect(VISUAL_EFFECT_PEEK, false)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowAlarms(true)
+ .showVisualEffect(VISUAL_EFFECT_PEEK, false)
+ .build())
+ .build();
mController.updateZenMode(preference, zenMode);
@@ -138,16 +132,13 @@
"zen_effect_status", VISUAL_EFFECT_STATUS_BAR,
new int[]{VISUAL_EFFECT_NOTIFICATION_LIST}, mBackend);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowAlarms(true)
- .showVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, false)
- .showVisualEffect(VISUAL_EFFECT_STATUS_BAR, true)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowAlarms(true)
+ .showVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, false)
+ .showVisualEffect(VISUAL_EFFECT_STATUS_BAR, true)
+ .build())
+ .build();
mController.updateZenMode(preference, zenMode);
@@ -168,15 +159,12 @@
"zen_effect_status", VISUAL_EFFECT_STATUS_BAR,
new int[]{VISUAL_EFFECT_NOTIFICATION_LIST}, mBackend);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowAlarms(true)
- .showAllVisualEffects()
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowAlarms(true)
+ .showAllVisualEffects()
+ .build())
+ .build();
mController.updateZenMode(preference, zenMode);
@@ -188,15 +176,12 @@
@Test
public void onPreferenceChanged_checkedFalse() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowAlarms(true)
- .hideAllVisualEffects()
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowAlarms(true)
+ .hideAllVisualEffects()
+ .build())
+ .build();
mController.updateZenMode(preference, zenMode);
@@ -213,15 +198,12 @@
@Test
public void onPreferenceChanged_checkedTrue() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowAlarms(true)
- .showAllVisualEffects()
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowAlarms(true)
+ .showAllVisualEffects()
+ .build())
+ .build();
mController.updateZenMode(preference, zenMode);
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
index 699762e..c4d03fe 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherLinkPreferenceControllerTest.java
@@ -16,22 +16,19 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -68,13 +65,7 @@
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
Preference pref = mock(Preference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .build(), true);
- mController.updateZenMode(pref, zenMode);
+ mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
verify(pref).setSummary(any());
}
}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherPreferenceControllerTest.java
index 4a4a6e4..c69a8a0 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeOtherPreferenceControllerTest.java
@@ -16,7 +16,6 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.STATE_UNSET;
@@ -25,16 +24,17 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
import androidx.preference.TwoStatePreference;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -65,12 +65,9 @@
@Test
public void testUpdateState_alarms() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build())
+ .build();
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_alarm", mBackend);
@@ -83,12 +80,9 @@
@Test
public void testOnPreferenceChange_alarms() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAlarms(false).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowAlarms(false).build())
+ .build();
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_alarm", mBackend);
@@ -108,12 +102,9 @@
@Test
public void testUpdateState_media() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowMedia(true).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowMedia(true).build())
+ .build();
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_media", mBackend);
@@ -126,12 +117,9 @@
@Test
public void testOnPreferenceChange_media() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowMedia(false).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowMedia(false).build())
+ .build();
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_media", mBackend);
@@ -151,12 +139,9 @@
@Test
public void testUpdateState_system() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowSystem(true).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowSystem(true).build())
+ .build();
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_system", mBackend);
@@ -169,12 +154,9 @@
@Test
public void testOnPreferenceChange_system() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowSystem(false).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowSystem(false).build())
+ .build();
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_system", mBackend);
@@ -194,12 +176,9 @@
@Test
public void testUpdateState_reminders() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowReminders(true).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowReminders(true).build())
+ .build();
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_reminders",
@@ -213,12 +192,9 @@
@Test
public void testOnPreferenceChange_reminders() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowReminders(false).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowReminders(false).build())
+ .build();
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_reminders",
@@ -239,12 +215,9 @@
@Test
public void testUpdateState_events() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowEvents(true).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowEvents(true).build())
+ .build();
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_events", mBackend);
@@ -257,12 +230,9 @@
@Test
public void testOnPreferenceChange_events() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowEvents(false).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowEvents(false).build())
+ .build();
ZenModeOtherPreferenceController controller =
new ZenModeOtherPreferenceController(mContext, "modes_category_events", mBackend);
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
index a331318..6591b72 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java
@@ -16,22 +16,19 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.service.notification.ZenPolicy;
import androidx.preference.Preference;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -69,13 +66,7 @@
@EnableFlags(Flags.FLAG_MODES_UI)
public void testHasSummary() {
Preference pref = mock(Preference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .build(), true);
- mController.updateZenMode(pref, zenMode);
+ mController.updateZenMode(pref, TestModeBuilder.EXAMPLE);
verify(pref).setSummary(any());
}
}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java
index 709af43..04df27e 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java
@@ -16,7 +16,6 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_NONE;
@@ -40,11 +39,9 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
import android.database.Cursor;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
@@ -54,6 +51,8 @@
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import org.junit.Before;
@@ -439,20 +438,17 @@
@Test
public void testPreferenceClick_passesCorrectCheckedState_startingUnchecked_messages() {
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder()
- .disallowAllSounds()
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .disallowAllSounds()
+ .build())
+ .build();
mMessagesController.displayPreference(mPreferenceScreen);
mMessagesController.updateZenMode(mMessagesPrefCategory, zenMode);
assertThat(((SelectorWithWidgetPreference) mMessagesPrefCategory.findPreference(KEY_NONE))
- .isChecked());
+ .isChecked()).isTrue();
mMessagesPrefCategory.findPreference(KEY_STARRED).performClick();
@@ -464,14 +460,11 @@
@Test
public void testPreferenceClick_passesCorrectCheckedState_startingChecked_messages() {
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowAllSounds()
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowAllSounds()
+ .build())
+ .build();
mMessagesController.displayPreference(mPreferenceScreen);
mMessagesController.updateZenMode(mMessagesPrefCategory, zenMode);
@@ -490,14 +483,11 @@
@Test
public void testPreferenceClick_passesCorrectCheckedState_startingUnchecked_calls() {
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder()
- .disallowAllSounds()
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .disallowAllSounds()
+ .build())
+ .build();
mCallsController.displayPreference(mPreferenceScreen);
mCallsController.updateZenMode(mCallsPrefCategory, zenMode);
@@ -515,14 +505,11 @@
@Test
public void testPreferenceClick_passesCorrectCheckedState_startingChecked_calls() {
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder()
- .disallowAllSounds()
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .disallowAllSounds()
+ .build())
+ .build();
mCallsController.displayPreference(mPreferenceScreen);
mCallsController.updateZenMode(mCallsPrefCategory, zenMode);
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceControllerTest.java
index 7bbb042..c1b99e5 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeRepeatCallersPreferenceControllerTest.java
@@ -16,24 +16,27 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
-import static android.service.notification.ZenPolicy.STATE_ALLOW;
import static android.service.notification.ZenPolicy.STATE_DISALLOW;
import static android.service.notification.ZenPolicy.STATE_UNSET;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.ZenPolicy;
+
import androidx.preference.TwoStatePreference;
+
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -64,14 +67,11 @@
@Test
public void testUpdateState_allCalls() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowCalls(PEOPLE_TYPE_ANYONE)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowCalls(PEOPLE_TYPE_ANYONE)
+ .build())
+ .build();
ZenModeRepeatCallersPreferenceController controller =
new ZenModeRepeatCallersPreferenceController(mContext, "repeat", mBackend, 1);
@@ -85,15 +85,12 @@
@Test
public void testUpdateState_someCalls() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder()
- .allowCalls(PEOPLE_TYPE_STARRED)
- .allowRepeatCallers(true)
- .build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder()
+ .allowCalls(PEOPLE_TYPE_STARRED)
+ .allowRepeatCallers(true)
+ .build())
+ .build();
ZenModeRepeatCallersPreferenceController controller =
new ZenModeRepeatCallersPreferenceController(mContext, "repeat", mBackend, 1);
@@ -107,12 +104,9 @@
@Test
public void testOnPreferenceChange() {
TwoStatePreference preference = mock(TwoStatePreference.class);
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
- .build(), true);
+ ZenMode zenMode = new TestModeBuilder()
+ .setZenPolicy(new ZenPolicy.Builder().allowRepeatCallers(true).build())
+ .build();
ZenModeRepeatCallersPreferenceController controller =
new ZenModeRepeatCallersPreferenceController(mContext, "repeat", mBackend, 1);
@@ -130,4 +124,4 @@
assertThat(captor.getValue().getPolicy().getPriorityCallSenders())
.isEqualTo(STATE_UNSET);
}
-}
\ No newline at end of file
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java
index 0ede058..cc6a497 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetCalendarPreferenceControllerTest.java
@@ -28,10 +28,8 @@
import static org.mockito.Mockito.when;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.SystemZenRules;
@@ -41,6 +39,9 @@
import androidx.preference.PreferenceCategory;
import androidx.test.core.app.ApplicationProvider;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -85,11 +86,9 @@
@Test
@EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
public void updateEventMode_updatesConditionAndTriggerDescription() {
- ZenMode mode = new ZenMode("id",
- new AutomaticZenRule.Builder("name", Uri.parse("condition"))
- .setPackage(SystemZenRules.PACKAGE_ANDROID)
- .build(),
- true); // is active
+ ZenMode mode = new TestModeBuilder()
+ .setPackage(SystemZenRules.PACKAGE_ANDROID)
+ .build();
// Explicitly update preference controller with mode info first, which will also call
// updateState()
@@ -115,10 +114,9 @@
eventInfo.calName = "Definitely A Calendar";
eventInfo.reply = REPLY_YES;
- ZenMode mode = new ZenMode("id",
- new AutomaticZenRule.Builder("name",
- ZenModeConfig.toEventConditionId(eventInfo)).build(),
- true); // is active
+ ZenMode mode = new TestModeBuilder()
+ .setConditionId(ZenModeConfig.toEventConditionId(eventInfo))
+ .build();
mPrefController.updateZenMode(mPrefCategory, mode);
// We should see mCalendar, mReply have their values set
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java
index 5f492b9..7dbc802 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetSchedulePreferenceControllerTest.java
@@ -23,10 +23,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.AutomaticZenRule;
import android.app.Flags;
import android.content.Context;
-import android.net.Uri;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.SystemZenRules;
@@ -38,6 +36,8 @@
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
@@ -81,11 +81,9 @@
@Test
@EnableFlags({Flags.FLAG_MODES_API, Flags.FLAG_MODES_UI})
public void updateScheduleRule_updatesConditionAndTriggerDescription() {
- ZenMode mode = new ZenMode("id",
- new AutomaticZenRule.Builder("name", Uri.parse("condition"))
- .setPackage(SystemZenRules.PACKAGE_ANDROID)
- .build(),
- true); // is active
+ ZenMode mode = new TestModeBuilder()
+ .setPackage(SystemZenRules.PACKAGE_ANDROID)
+ .build();
ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo();
scheduleInfo.days = new int[] { Calendar.MONDAY };
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
index ff4d4a3..31959e5 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeSetTriggerLinkPreferenceControllerTest.java
@@ -37,7 +37,6 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.SystemZenRules;
import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenPolicy;
import androidx.preference.PreferenceCategory;
import androidx.test.core.app.ApplicationProvider;
@@ -46,6 +45,8 @@
import com.android.settings.SettingsActivity;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.PrimarySwitchPreference;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import org.junit.Before;
import org.junit.Rule;
@@ -100,26 +101,13 @@
assertThat(mPrefController.isAvailable()).isFalse();
// should be available for other modes
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build())
- .setEnabled(false)
- .build(), false);
- mPrefController.updateZenMode(mPrefCategory, zenMode);
+ mPrefController.updateZenMode(mPrefCategory, TestModeBuilder.EXAMPLE);
assertThat(mPrefController.isAvailable()).isTrue();
}
@Test
public void testUpdateState() {
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build())
- .setEnabled(false)
- .build(), false);
+ ZenMode zenMode = new TestModeBuilder().setEnabled(false).build();
// Update preference controller with a zen mode that is not enabled
mPrefController.updateZenMode(mPrefCategory, zenMode);
@@ -133,13 +121,7 @@
@Test
public void testOnPreferenceChange() {
- ZenMode zenMode = new ZenMode("id",
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build())
- .setEnabled(false)
- .build(), false);
+ ZenMode zenMode = new TestModeBuilder().setEnabled(false).build();
// start with disabled rule
mPrefController.updateZenMode(mPrefCategory, zenMode);
@@ -158,13 +140,12 @@
ZenModeConfig.EventInfo eventInfo = new ZenModeConfig.EventInfo();
eventInfo.calendarId = 1L;
eventInfo.calName = "My events";
- ZenMode mode = new ZenMode("id", new AutomaticZenRule.Builder("name",
- ZenModeConfig.toEventConditionId(eventInfo))
+ ZenMode mode = new TestModeBuilder()
.setPackage(SystemZenRules.PACKAGE_ANDROID)
+ .setConditionId(ZenModeConfig.toEventConditionId(eventInfo))
.setType(TYPE_SCHEDULE_CALENDAR)
.setTriggerDescription("My events")
- .build(),
- true); // is active
+ .build();
mPrefController.updateZenMode(mPrefCategory, mode);
assertThat(mPreference.getTitle()).isNotNull();
@@ -186,13 +167,12 @@
scheduleInfo.days = new int[] { Calendar.MONDAY, Calendar.TUESDAY, Calendar.THURSDAY };
scheduleInfo.startHour = 1;
scheduleInfo.endHour = 15;
- ZenMode mode = new ZenMode("id", new AutomaticZenRule.Builder("name",
- ZenModeConfig.toScheduleConditionId(scheduleInfo))
+ ZenMode mode = new TestModeBuilder()
+ .setConditionId(ZenModeConfig.toScheduleConditionId(scheduleInfo))
.setPackage(SystemZenRules.PACKAGE_ANDROID)
.setType(TYPE_SCHEDULE_TIME)
.setTriggerDescription("some schedule")
- .build(),
- true); // is active
+ .build();
mPrefController.updateZenMode(mPrefCategory, mode);
assertThat(mPreference.getTitle()).isNotNull();
@@ -210,13 +190,12 @@
@Test
public void testRuleLink_manual() {
- ZenMode mode = new ZenMode("id", new AutomaticZenRule.Builder("name",
- ZenModeConfig.toCustomManualConditionId())
+ ZenMode mode = new TestModeBuilder()
+ .setConditionId(ZenModeConfig.toCustomManualConditionId())
.setPackage(SystemZenRules.PACKAGE_ANDROID)
.setType(TYPE_OTHER)
.setTriggerDescription("Will not be shown")
- .build(),
- true); // is active
+ .build();
mPrefController.updateZenMode(mPrefCategory, mode);
assertThat(mPreference.getTitle()).isNotNull();
@@ -232,13 +211,12 @@
@Test
public void onScheduleChosen_updatesMode() {
- ZenMode originalMode = new ZenMode("id",
- new AutomaticZenRule.Builder("name", ZenModeConfig.toCustomManualConditionId())
- .setPackage(SystemZenRules.PACKAGE_ANDROID)
- .setType(TYPE_OTHER)
- .setTriggerDescription("")
- .build(),
- false);
+ ZenMode originalMode = new TestModeBuilder()
+ .setConditionId(ZenModeConfig.toCustomManualConditionId())
+ .setPackage(SystemZenRules.PACKAGE_ANDROID)
+ .setType(TYPE_OTHER)
+ .setTriggerDescription("")
+ .build();
mPrefController.updateZenMode(mPrefCategory, originalMode);
ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo();
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeTest.java
deleted file mode 100644
index 37b71a5..0000000
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeTest.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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.notification.modes;
-
-import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.AutomaticZenRule;
-import android.net.Uri;
-import android.service.notification.ZenPolicy;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-
-@RunWith(RobolectricTestRunner.class)
-public class ZenModeTest {
-
- private static final ZenPolicy ZEN_POLICY = new ZenPolicy.Builder().allowAllSounds().build();
-
- private static final AutomaticZenRule ZEN_RULE =
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(ZEN_POLICY)
- .build();
-
- @Test
- public void testBasicMethods() {
- ZenMode zenMode = new ZenMode("id", ZEN_RULE, true);
-
- assertThat(zenMode.getId()).isEqualTo("id");
- assertThat(zenMode.getRule()).isEqualTo(ZEN_RULE);
- assertThat(zenMode.isManualDnd()).isFalse();
- assertThat(zenMode.canBeDeleted()).isTrue();
- assertThat(zenMode.isActive()).isTrue();
-
- ZenMode manualMode = ZenMode.manualDndMode(ZEN_RULE, false);
- assertThat(manualMode.getId()).isEqualTo(ZenMode.MANUAL_DND_MODE_ID);
- assertThat(manualMode.isManualDnd()).isTrue();
- assertThat(manualMode.canBeDeleted()).isFalse();
- assertThat(manualMode.isActive()).isFalse();
- }
-
- @Test
- public void getPolicy_interruptionFilterPriority_returnsZenPolicy() {
- ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(ZEN_POLICY)
- .build(), false);
-
- assertThat(zenMode.getPolicy()).isEqualTo(ZEN_POLICY);
- }
-
- @Test
- public void getPolicy_interruptionFilterAlarms_returnsPolicyAllowingAlarms() {
- ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
- .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
- .setZenPolicy(ZEN_POLICY) // should be ignored
- .build(), false);
-
- assertThat(zenMode.getPolicy()).isEqualTo(
- new ZenPolicy.Builder()
- .disallowAllSounds()
- .allowAlarms(true)
- .allowMedia(true)
- .allowPriorityChannels(false)
- .build());
- }
-
- @Test
- public void getPolicy_interruptionFilterNone_returnsPolicyAllowingNothing() {
- ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
- .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
- .setZenPolicy(ZEN_POLICY) // should be ignored
- .build(), false);
-
- assertThat(zenMode.getPolicy()).isEqualTo(
- new ZenPolicy.Builder()
- .disallowAllSounds()
- .hideAllVisualEffects()
- .allowPriorityChannels(false)
- .build());
- }
-
- @Test
- public void setPolicy_setsInterruptionFilterPriority() {
- ZenMode zenMode = new ZenMode("id", new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
- .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
- .build(), false);
-
- zenMode.setPolicy(ZEN_POLICY);
-
- assertThat(zenMode.getRule().getInterruptionFilter()).isEqualTo(
- INTERRUPTION_FILTER_PRIORITY);
- assertThat(zenMode.getPolicy()).isEqualTo(ZEN_POLICY);
- assertThat(zenMode.getRule().getZenPolicy()).isEqualTo(ZEN_POLICY);
- }
-}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesBackendTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesBackendTest.java
deleted file mode 100644
index 9483683..0000000
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModesBackendTest.java
+++ /dev/null
@@ -1,363 +0,0 @@
-/*
- * 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.notification.modes;
-
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-import static android.provider.Settings.Global.ZEN_MODE_OFF;
-import static android.service.notification.Condition.SOURCE_UNKNOWN;
-import static android.service.notification.Condition.STATE_FALSE;
-import static android.service.notification.Condition.STATE_TRUE;
-import static android.service.notification.ZenPolicy.STATE_ALLOW;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.AutomaticZenRule;
-import android.app.Flags;
-import android.app.NotificationManager;
-import android.app.NotificationManager.Policy;
-import android.content.Context;
-import android.net.Uri;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
-import android.provider.Settings;
-import android.service.notification.Condition;
-import android.service.notification.ZenAdapters;
-import android.service.notification.ZenDeviceEffects;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenPolicy;
-
-import com.android.settings.R;
-
-import com.google.common.collect.ImmutableMap;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadows.ShadowApplication;
-
-import java.time.Duration;
-import java.util.List;
-
-@RunWith(RobolectricTestRunner.class)
-@EnableFlags(Flags.FLAG_MODES_UI)
-public class ZenModesBackendTest {
-
- private static final String ZEN_RULE_ID = "rule";
- private static final AutomaticZenRule ZEN_RULE =
- new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
- .setType(AutomaticZenRule.TYPE_DRIVING)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .build();
-
- private static final AutomaticZenRule MANUAL_DND_RULE =
- new AutomaticZenRule.Builder("Do Not Disturb", Uri.EMPTY)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .build();
-
- @Mock
- private NotificationManager mNm;
-
- private Context mContext;
- private ZenModesBackend mBackend;
-
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
- SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
-
- // Helper methods to add active/inactive rule state to a config. Returns a copy.
- private ZenModeConfig configWithManualRule(ZenModeConfig base, boolean active) {
- ZenModeConfig out = base.copy();
-
- if (active) {
- out.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- out.manualRule.condition =
- new Condition(out.manualRule.conditionId, "", STATE_TRUE, SOURCE_UNKNOWN);
- } else {
- out.manualRule.zenMode = ZEN_MODE_OFF;
- out.manualRule.condition =
- new Condition(out.manualRule.conditionId, "", STATE_FALSE, SOURCE_UNKNOWN);
- }
- return out;
- }
-
- private ZenModeConfig configWithRule(ZenModeConfig base, String ruleId, AutomaticZenRule rule,
- boolean active) {
- ZenModeConfig out = base.copy();
-
- // Note that there are many other fields of zenRule, but here we only set the ones
- // relevant to determining whether or not it is active.
- ZenModeConfig.ZenRule zenRule = new ZenModeConfig.ZenRule();
- zenRule.pkg = "package";
- zenRule.enabled = active;
- zenRule.snoozing = false;
- zenRule.condition = new Condition(rule.getConditionId(), "",
- active ? Condition.STATE_TRUE : Condition.STATE_FALSE,
- Condition.SOURCE_USER_ACTION);
- out.automaticRules.put(ruleId, zenRule);
-
- return out;
- }
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- ShadowApplication shadowApplication = ShadowApplication.getInstance();
- shadowApplication.setSystemService(Context.NOTIFICATION_SERVICE, mNm);
-
- mContext = RuntimeEnvironment.application;
- mBackend = new ZenModesBackend(mContext);
-
- // Default catch-all case with no data. This isn't realistic, but tests below that rely
- // on the config to get data on rules active will create those individually.
- when(mNm.getZenModeConfig()).thenReturn(new ZenModeConfig());
- }
-
- @Test
- public void getModes_containsManualDndAndZenRules() {
- AutomaticZenRule rule2 = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
- .build();
- Policy dndPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS,
- Policy.PRIORITY_SENDERS_CONTACTS, Policy.PRIORITY_SENDERS_CONTACTS);
- when(mNm.getAutomaticZenRules()).thenReturn(
- ImmutableMap.of("rule1", ZEN_RULE, "rule2", rule2));
- ZenModeConfig config = new ZenModeConfig();
- config.applyNotificationPolicy(dndPolicy);
- assertThat(config.manualRule.zenPolicy.getPriorityCategoryAlarms()).isEqualTo(STATE_ALLOW);
- when(mNm.getZenModeConfig()).thenReturn(config);
-
- List<ZenMode> modes = mBackend.getModes();
-
- // all modes exist, but none of them are currently active
- assertThat(modes).containsExactly(
- ZenMode.manualDndMode(
- new AutomaticZenRule.Builder(
- mContext.getString(R.string.zen_mode_settings_title), Uri.EMPTY)
- .setType(AutomaticZenRule.TYPE_OTHER)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(ZenAdapters.notificationPolicyToZenPolicy(dndPolicy))
- .setManualInvocationAllowed(true)
- .build(),
- false),
- new ZenMode("rule2", rule2, false),
- new ZenMode("rule1", ZEN_RULE, false))
- .inOrder();
- }
-
- @Test
- public void getMode_manualDnd_returnsMode() {
- Policy dndPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS,
- Policy.PRIORITY_SENDERS_CONTACTS, Policy.PRIORITY_SENDERS_CONTACTS);
- ZenModeConfig config = new ZenModeConfig();
- config.applyNotificationPolicy(dndPolicy);
- when(mNm.getZenModeConfig()).thenReturn(config);
-
- ZenMode mode = mBackend.getMode(ZenMode.MANUAL_DND_MODE_ID);
-
- assertThat(mode).isEqualTo(
- ZenMode.manualDndMode(
- new AutomaticZenRule.Builder(
- mContext.getString(R.string.zen_mode_settings_title), Uri.EMPTY)
- .setType(AutomaticZenRule.TYPE_OTHER)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(ZenAdapters.notificationPolicyToZenPolicy(dndPolicy))
- .setManualInvocationAllowed(true)
- .build(), false));
- }
-
- @Test
- public void getMode_zenRule_returnsMode() {
- when(mNm.getAutomaticZenRule(eq(ZEN_RULE_ID))).thenReturn(ZEN_RULE);
-
- ZenMode mode = mBackend.getMode(ZEN_RULE_ID);
-
- assertThat(mode).isEqualTo(new ZenMode(ZEN_RULE_ID, ZEN_RULE, false));
- }
-
- @Test
- public void getMode_missingRule_returnsNull() {
- when(mNm.getAutomaticZenRule(any())).thenReturn(null);
-
- ZenMode mode = mBackend.getMode(ZEN_RULE_ID);
-
- assertThat(mode).isNull();
- verify(mNm).getAutomaticZenRule(eq(ZEN_RULE_ID));
- }
-
- @Test
- public void getMode_manualDnd_returnsCorrectActiveState() {
- // Set up a base config with an active rule to make sure we're looking at the correct info
- ZenModeConfig configWithActiveRule = configWithRule(new ZenModeConfig(), ZEN_RULE_ID,
- ZEN_RULE, true);
-
- // Equivalent to disallowAllSounds()
- Policy dndPolicy = new Policy(0, 0, 0);
- configWithActiveRule.applyNotificationPolicy(dndPolicy);
- when(mNm.getZenModeConfig()).thenReturn(configWithActiveRule);
-
- ZenMode mode = mBackend.getMode(ZenMode.MANUAL_DND_MODE_ID);
-
- // By default, manual rule is inactive
- assertThat(mode.isActive()).isFalse();
-
- // Now the returned config will represent the manual rule being active
- when(mNm.getZenModeConfig()).thenReturn(configWithManualRule(configWithActiveRule, true));
- ZenMode activeMode = mBackend.getMode(ZenMode.MANUAL_DND_MODE_ID);
- assertThat(activeMode.isActive()).isTrue();
- }
-
- @Test
- public void getMode_zenRule_returnsCorrectActiveState() {
- // Set up a base config that has an active manual rule and "rule2", to make sure we're
- // looking at the correct rule's info.
- ZenModeConfig configWithActiveRules = configWithRule(
- configWithManualRule(new ZenModeConfig(), true), // active manual rule
- "rule2", ZEN_RULE, true); // active rule 2
-
- when(mNm.getAutomaticZenRule(eq(ZEN_RULE_ID))).thenReturn(ZEN_RULE);
- when(mNm.getZenModeConfig()).thenReturn(
- configWithRule(configWithActiveRules, ZEN_RULE_ID, ZEN_RULE, false));
-
- // Round 1: the current config should indicate that the rule is not active
- ZenMode mode = mBackend.getMode(ZEN_RULE_ID);
- assertThat(mode.isActive()).isFalse();
-
- when(mNm.getZenModeConfig()).thenReturn(
- configWithRule(configWithActiveRules, ZEN_RULE_ID, ZEN_RULE, true));
- ZenMode activeMode = mBackend.getMode(ZEN_RULE_ID);
- assertThat(activeMode.isActive()).isTrue();
- }
-
- @Test
- public void updateMode_manualDnd_setsDeviceEffects() throws Exception {
- ZenMode manualDnd = ZenMode.manualDndMode(
- new AutomaticZenRule.Builder("DND", Uri.EMPTY)
- .setZenPolicy(new ZenPolicy())
- .setDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDimWallpaper(true)
- .build())
- .build(), false);
-
- mBackend.updateMode(manualDnd);
-
- verify(mNm).setManualZenRuleDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDimWallpaper(true)
- .build());
- }
-
- @Test
- public void updateMode_manualDnd_setsNotificationPolicy() {
- ZenMode manualDnd = ZenMode.manualDndMode(
- new AutomaticZenRule.Builder("DND", Uri.EMPTY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .build(), false);
-
- mBackend.updateMode(manualDnd);
-
- verify(mNm).setNotificationPolicy(eq(new ZenModeConfig().toNotificationPolicy(
- new ZenPolicy.Builder().allowAllSounds().build())), eq(true));
- }
-
- @Test
- public void updateMode_zenRule_updatesRule() {
- ZenMode ruleMode = new ZenMode("rule", ZEN_RULE, false);
-
- mBackend.updateMode(ruleMode);
-
- verify(mNm).updateAutomaticZenRule(eq("rule"), eq(ZEN_RULE), eq(true));
- }
-
- @Test
- public void activateMode_manualDnd_setsZenModeImportant() {
- mBackend.activateMode(ZenMode.manualDndMode(MANUAL_DND_RULE, false), null);
-
- verify(mNm).setZenMode(eq(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
- any(), eq(true));
- }
-
- @Test
- public void activateMode_manualDndWithDuration_setsZenModeImportantWithCondition() {
- mBackend.activateMode(ZenMode.manualDndMode(MANUAL_DND_RULE, false),
- Duration.ofMinutes(30));
-
- verify(mNm).setZenMode(eq(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS),
- eq(ZenModeConfig.toTimeCondition(mContext, 30, 0, true).id),
- any(),
- eq(true));
- }
-
- @Test
- public void activateMode_zenRule_setsRuleStateActive() {
- mBackend.activateMode(new ZenMode(ZEN_RULE_ID, ZEN_RULE, false), null);
-
- verify(mNm).setAutomaticZenRuleState(eq(ZEN_RULE_ID),
- eq(new Condition(ZEN_RULE.getConditionId(), "", Condition.STATE_TRUE,
- Condition.SOURCE_USER_ACTION)));
- }
-
- @Test
- public void activateMode_zenRuleWithDuration_fails() {
- assertThrows(IllegalArgumentException.class,
- () -> mBackend.activateMode(new ZenMode(ZEN_RULE_ID, ZEN_RULE, false),
- Duration.ofMinutes(30)));
- }
-
- @Test
- public void deactivateMode_manualDnd_setsZenModeOff() {
- mBackend.deactivateMode(ZenMode.manualDndMode(MANUAL_DND_RULE, true));
-
- verify(mNm).setZenMode(eq(ZEN_MODE_OFF), eq(null), any(), eq(true));
- }
-
- @Test
- public void deactivateMode_zenRule_setsRuleStateInactive() {
- mBackend.deactivateMode(new ZenMode(ZEN_RULE_ID, ZEN_RULE, false));
-
- verify(mNm).setAutomaticZenRuleState(eq(ZEN_RULE_ID),
- eq(new Condition(ZEN_RULE.getConditionId(), "", Condition.STATE_FALSE,
- Condition.SOURCE_USER_ACTION)));
- }
-
- @Test
- public void removeMode_zenRule_deletesRule() {
- mBackend.removeMode(new ZenMode(ZEN_RULE_ID, ZEN_RULE, false));
-
- verify(mNm).removeAutomaticZenRule(ZEN_RULE_ID, true);
- }
-
- @Test
- public void removeMode_manualDnd_fails() {
- assertThrows(IllegalArgumentException.class,
- () -> mBackend.removeMode(ZenMode.manualDndMode(MANUAL_DND_RULE, false)));
- }
-}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesListItemPreferenceTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListItemPreferenceTest.java
new file mode 100644
index 0000000..495a24c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListItemPreferenceTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.notification.modes;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.service.notification.ZenModeConfig;
+
+import com.android.settingslib.notification.modes.ZenMode;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowLooper;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModesListItemPreferenceTest {
+
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ }
+
+ @Test
+ public void constructor_setsMode() {
+ ZenModesListItemPreference preference = new ZenModesListItemPreference(mContext,
+ TestModeBuilder.EXAMPLE);
+
+ assertThat(preference.getKey()).isEqualTo(TestModeBuilder.EXAMPLE.getId());
+ assertThat(preference.getZenMode()).isEqualTo(TestModeBuilder.EXAMPLE);
+ }
+
+ @Test
+ public void setZenMode_modeEnabled() {
+ ZenMode mode = new TestModeBuilder()
+ .setName("Enabled mode")
+ .setTriggerDescription("When the thrush knocks")
+ .setEnabled(true)
+ .build();
+
+ ZenModesListItemPreference preference = new ZenModesListItemPreference(mContext, mode);
+ ShadowLooper.idleMainLooper(); // To load icon.
+
+ assertThat(preference.getTitle()).isEqualTo("Enabled mode");
+ assertThat(preference.getSummary()).isEqualTo("When the thrush knocks");
+ assertThat(preference.getIcon()).isNotNull();
+ }
+
+ @Test
+ public void setZenMode_modeActive() {
+ ZenMode mode = new TestModeBuilder()
+ .setName("Active mode")
+ .setTriggerDescription("When Birnam forest comes to Dunsinane")
+ .setEnabled(true)
+ .setActive(true)
+ .build();
+
+ ZenModesListItemPreference preference = new ZenModesListItemPreference(mContext, mode);
+ ShadowLooper.idleMainLooper();
+
+ assertThat(preference.getTitle()).isEqualTo("Active mode");
+ assertThat(preference.getSummary()).isEqualTo("ON • When Birnam forest comes to Dunsinane");
+ assertThat(preference.getIcon()).isNotNull();
+ }
+
+ @Test
+ public void setZenMode_modeDisabledByApp() {
+ ZenModeConfig.ZenRule configRule = new ZenModeConfig.ZenRule();
+ configRule.enabled = false;
+ configRule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_APP;
+ ZenMode mode = new TestModeBuilder()
+ .setName("Mode disabled by app")
+ .setTriggerDescription("When the cat's away")
+ .setEnabled(false)
+ .setConfigZenRule(configRule)
+ .build();
+
+ ZenModesListItemPreference preference = new ZenModesListItemPreference(mContext, mode);
+ ShadowLooper.idleMainLooper();
+
+ assertThat(preference.getTitle()).isEqualTo("Mode disabled by app");
+ assertThat(preference.getSummary()).isEqualTo("Tap to set up");
+ assertThat(preference.getIcon()).isNotNull();
+ }
+
+ @Test
+ public void setZenMode_modeDisabledByUser() {
+ ZenModeConfig.ZenRule configRule = new ZenModeConfig.ZenRule();
+ configRule.enabled = false;
+ configRule.disabledOrigin = ZenModeConfig.UPDATE_ORIGIN_USER;
+ ZenMode mode = new TestModeBuilder()
+ .setName("Mode disabled by user")
+ .setTriggerDescription("When the Levee Breaks")
+ .setEnabled(false)
+ .setConfigZenRule(configRule)
+ .build();
+
+ ZenModesListItemPreference preference = new ZenModesListItemPreference(mContext, mode);
+ ShadowLooper.idleMainLooper();
+
+ assertThat(preference.getTitle()).isEqualTo("Mode disabled by user");
+ assertThat(preference.getSummary()).isEqualTo("Paused");
+ assertThat(preference.getIcon()).isNotNull();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java
index 9a4de60..f2624ac 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java
@@ -37,6 +37,8 @@
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
+import com.android.settingslib.notification.modes.ZenMode;
+import com.android.settingslib.notification.modes.ZenModesBackend;
import com.android.settingslib.search.SearchIndexableRaw;
import com.google.common.collect.ImmutableList;
@@ -58,14 +60,15 @@
public class ZenModesListPreferenceControllerTest {
private static final String TEST_MODE_ID = "test_mode";
private static final String TEST_MODE_NAME = "Test Mode";
- private static final ZenMode TEST_MODE = new ZenMode(
- TEST_MODE_ID,
- new AutomaticZenRule.Builder(TEST_MODE_NAME, Uri.parse("test_uri"))
+
+ private static final ZenMode TEST_MODE = new TestModeBuilder()
+ .setId(TEST_MODE_ID)
+ .setAzr(new AutomaticZenRule.Builder(TEST_MODE_NAME, Uri.parse("test_uri"))
.setType(AutomaticZenRule.TYPE_BEDTIME)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .build(),
- false);
+ .build())
+ .build();
private static final ZenMode TEST_MANUAL_MODE = ZenMode.manualDndMode(
new AutomaticZenRule.Builder("Do Not Disturb", Uri.EMPTY)
@@ -110,14 +113,9 @@
assertThat(mPreference.getPreferenceCount()).isEqualTo(5);
List<ZenModesListItemPreference> itemPreferences = getModeListItems(mPreference);
- assertThat(itemPreferences.stream().map(pref -> pref.mZenMode).toList())
+ assertThat(itemPreferences.stream().map(ZenModesListItemPreference::getZenMode).toList())
.containsExactlyElementsIn(modes)
.inOrder();
-
- for (int i = 0; i < modes.size(); i++) {
- assertThat(((ZenModesListItemPreference) (mPreference.getPreference(i))).mZenMode)
- .isEqualTo(modes.get(i));
- }
}
@Test
@@ -138,7 +136,7 @@
mPrefController.updateState(mPreference);
List<ZenModesListItemPreference> newPreferences = getModeListItems(mPreference);
- assertThat(newPreferences.stream().map(pref -> pref.mZenMode).toList())
+ assertThat(newPreferences.stream().map(ZenModesListItemPreference::getZenMode).toList())
.containsExactlyElementsIn(updatedModes)
.inOrder();
@@ -194,7 +192,7 @@
assertThat(newData).hasSize(1);
SearchIndexableRaw newItem = newData.get(0);
- assertThat(newItem.key).isEqualTo(ZenMode.MANUAL_DND_MODE_ID);
+ assertThat(newItem.key).isEqualTo(TEST_MANUAL_MODE.getId());
assertThat(newItem.title).isEqualTo("Do Not Disturb"); // set above
}
@@ -209,7 +207,7 @@
// Should keep the order presented by getModes()
SearchIndexableRaw item0 = data.get(0);
- assertThat(item0.key).isEqualTo(ZenMode.MANUAL_DND_MODE_ID);
+ assertThat(item0.key).isEqualTo(TEST_MANUAL_MODE.getId());
assertThat(item0.title).isEqualTo("Do Not Disturb"); // set above
SearchIndexableRaw item1 = data.get(1);
@@ -218,13 +216,7 @@
}
private static ZenMode newMode(String id) {
- return new ZenMode(
- id,
- new AutomaticZenRule.Builder("Mode " + id, Uri.parse("test_uri"))
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build())
- .build(),
- false);
+ return new TestModeBuilder().setId(id).setName("Mode " + id).build();
}
/**
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java
index 51368c5..62b5ee0 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModesSummaryHelperTest.java
@@ -16,7 +16,6 @@
package com.android.settings.notification.modes;
-import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_ANYONE;
import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
@@ -25,12 +24,12 @@
import static com.google.common.truth.Truth.assertThat;
-import android.app.AutomaticZenRule;
import android.content.Context;
-import android.net.Uri;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenPolicy;
+import com.android.settingslib.notification.modes.ZenMode;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,7 +39,6 @@
import java.util.LinkedHashSet;
import java.util.Set;
-
@RunWith(RobolectricTestRunner.class)
public class ZenModesSummaryHelperTest {
private Context mContext;
@@ -58,50 +56,38 @@
@Test
public void getPeopleSummary_noOne() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("No one can interrupt");
}
@Test
public void getPeopleSummary_some() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder().allowCalls(PEOPLE_TYPE_CONTACTS).build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("Some people can interrupt");
}
@Test
public void getPeopleSummary_all() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder().allowCalls(PEOPLE_TYPE_ANYONE).
allowConversations(CONVERSATION_SENDERS_ANYONE)
.allowMessages(PEOPLE_TYPE_ANYONE).build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getPeopleSummary(zenMode)).isEqualTo("All people can interrupt");
}
@Test
public void getOtherSoundCategoriesSummary_single() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode)).isEqualTo(
"Alarms can interrupt");
@@ -109,12 +95,9 @@
@Test
public void getOtherSoundCategoriesSummary_duo() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder().allowAlarms(true).allowMedia(true).build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode)).isEqualTo(
"Alarms and media can interrupt");
@@ -122,16 +105,13 @@
@Test
public void getOtherSoundCategoriesSummary_trio() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder()
.allowAlarms(true)
.allowMedia(true)
.allowSystem(true)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode)).isEqualTo(
"Alarms, media, and touch sounds can interrupt");
@@ -139,9 +119,7 @@
@Test
public void getOtherSoundCategoriesSummary_quad() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder()
.allowAlarms(true)
.allowMedia(true)
@@ -149,7 +127,6 @@
.allowReminders(true)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode)).isEqualTo(
"Alarms, media, and 2 more can interrupt");
@@ -157,9 +134,7 @@
@Test
public void getOtherSoundCategoriesSummary_all() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder()
.allowAlarms(true)
.allowMedia(true)
@@ -168,7 +143,6 @@
.allowEvents(true)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getOtherSoundCategoriesSummary(zenMode)).isEqualTo(
"Alarms, media, and 3 more can interrupt");
@@ -176,61 +150,52 @@
@Test
public void getBlockedEffectsSummary_none() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder()
.showAllVisualEffects()
.allowAlarms(true)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
+
assertThat(mSummaryHelper.getBlockedEffectsSummary(zenMode))
.isEqualTo("Notifications shown");
}
@Test
public void getBlockedEffectsSummary_some() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder()
.allowAlarms(true)
.showAllVisualEffects()
.showVisualEffect(VISUAL_EFFECT_AMBIENT, false)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
+
assertThat(mSummaryHelper.getBlockedEffectsSummary(zenMode))
.isEqualTo("Notifications partially hidden");
}
@Test
public void getBlockedEffectsSummary_all() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder()
.allowAlarms(true)
.hideAllVisualEffects()
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
+
assertThat(mSummaryHelper.getBlockedEffectsSummary(zenMode))
.isEqualTo("Notifications hidden");
}
@Test
public void getDisplayEffectsSummary_single_notifVis() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder()
.showAllVisualEffects()
.showVisualEffect(VISUAL_EFFECT_AMBIENT, false)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getDisplayEffectsSummary(zenMode)).isEqualTo(
"Notifications partially hidden");
@@ -238,15 +203,12 @@
@Test
public void getDisplayEffectsSummary_single_notifVis_unusedEffect() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder()
.showAllVisualEffects()
.showVisualEffect(VISUAL_EFFECT_LIGHTS, false)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getDisplayEffectsSummary(zenMode)).isEqualTo(
"Notifications shown");
@@ -254,15 +216,12 @@
@Test
public void getDisplayEffectsSummary_single_displayEffect() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder().showAllVisualEffects().build())
.setDeviceEffects(new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getDisplayEffectsSummary(zenMode)).isEqualTo(
"Dim the wallpaper");
@@ -270,16 +229,13 @@
@Test
public void getDisplayEffectsSummary_duo() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder().showAllVisualEffects().build())
.setDeviceEffects(new ZenDeviceEffects.Builder()
.setShouldDimWallpaper(true)
.setShouldDisplayGrayscale(true)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getDisplayEffectsSummary(zenMode)).isEqualTo(
"Grayscale and dim the wallpaper");
@@ -287,9 +243,7 @@
@Test
public void getDisplayEffectsSummary_trio() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder()
.hideAllVisualEffects()
.allowAlarms(true)
@@ -301,7 +255,6 @@
.setShouldDimWallpaper(true)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getDisplayEffectsSummary(zenMode)).isEqualTo(
"Notifications hidden, grayscale, and dim the wallpaper");
@@ -309,9 +262,7 @@
@Test
public void getDisplayEffectsSummary_quad() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder()
.showAllVisualEffects()
.showVisualEffect(VISUAL_EFFECT_AMBIENT, false)
@@ -325,7 +276,6 @@
.setShouldUseNightMode(true)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getDisplayEffectsSummary(zenMode)).isEqualTo(
"Notifications partially hidden, grayscale, and 2 more");
@@ -333,28 +283,22 @@
@Test
public void getAppsSummary_none() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder()
.allowChannels(ZenPolicy.CHANNEL_POLICY_NONE)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getAppsSummary(zenMode, new LinkedHashSet<>())).isEqualTo("None");
}
@Test
public void getAppsSummary_priorityAppsNoList() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder()
.allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
assertThat(mSummaryHelper.getAppsSummary(zenMode, null)).isEqualTo("Selected apps");
}
@@ -396,19 +340,15 @@
@Test
public void getAppsSummary_priorityApps() {
- AutomaticZenRule rule = new AutomaticZenRule.Builder("Bedtime", Uri.parse("bed"))
- .setType(AutomaticZenRule.TYPE_BEDTIME)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ ZenMode zenMode = new TestModeBuilder()
.setZenPolicy(new ZenPolicy.Builder()
.allowChannels(ZenPolicy.CHANNEL_POLICY_PRIORITY)
.build())
.build();
- ZenMode zenMode = new ZenMode("id", rule, true);
Set<String> apps = Set.of("My App", "SecondApp", "ThirdApp", "FourthApp",
"FifthApp", "SixthApp");
assertThat(mSummaryHelper.getAppsSummary(zenMode, apps)).isEqualTo("FifthApp, FourthApp, "
+ "and 4 more can interrupt");
}
-
}
diff --git a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java
index 44e1cc6..e035274 100644
--- a/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/users/UserDetailsSettingsTest.java
@@ -42,9 +42,13 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
+import android.multiuser.Flags;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.telephony.TelephonyManager;
import androidx.fragment.app.FragmentActivity;
@@ -63,6 +67,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -123,6 +128,8 @@
private Bundle mArguments;
private UserInfo mUserInfo;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -245,6 +252,19 @@
}
@Test
+ @RequiresFlagsEnabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
+ public void onResume_UserSwitcherDisabled_shouldDisableSwitchPref() {
+ setupSelectedUser();
+ mUserCapabilities.mUserSwitcherEnabled = false;
+ mFragment.mSwitchUserPref = mSwitchUserPref;
+ mFragment.onAttach(mContext);
+
+ mFragment.onResume();
+
+ verify(mSwitchUserPref).setEnabled(false);
+ }
+
+ @Test
public void onResume_switchDisallowed_shouldDisableSwitchPref() {
setupSelectedUser();
mUserManager.setSwitchabilityStatus(SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED);
diff --git a/tests/robotests/src/com/android/settings/users/UserSettingsTest.java b/tests/robotests/src/com/android/settings/users/UserSettingsTest.java
index 5826ca2..85db0bd 100644
--- a/tests/robotests/src/com/android/settings/users/UserSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/users/UserSettingsTest.java
@@ -34,6 +34,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.app.settings.SettingsEnums;
@@ -46,10 +47,15 @@
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
+import android.multiuser.Flags;
import android.os.Bundle;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
+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.provider.Settings;
import android.text.SpannableStringBuilder;
import android.view.Menu;
@@ -75,6 +81,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.AdditionalMatchers;
@@ -142,6 +149,9 @@
private UserSettings mFragment;
private UserCapabilities mUserCapabilities;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -359,6 +369,7 @@
}
@Test
+ @RequiresFlagsDisabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
public void updateUserList_cannotSwitchUser_shouldDisableAddUser() {
mUserCapabilities.mCanAddUser = true;
doReturn(true).when(mUserManager).canAddMoreUsers(anyString());
@@ -375,6 +386,20 @@
}
@Test
+ @RequiresFlagsEnabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
+ public void updateUserList_disallowAddUser_shouldDisableAddUserAndAddGuest() {
+ mUserCapabilities.mDisallowAddUserSetByAdmin = true;
+ doReturn(true).when(mUserManager).canAddMoreUsers(anyString());
+ doReturn(SWITCHABILITY_STATUS_OK)
+ .when(mUserManager).getUserSwitchability();
+
+ mFragment.updateUserList();
+
+ verify(mAddUserPreference).setVisible(true);
+ verify(mAddUserPreference).setDisabledByAdmin(any());
+ }
+
+ @Test
public void updateUserList_canNotAddMoreUsers_shouldDisableAddUserWithSummary() {
mUserCapabilities.mCanAddUser = true;
doReturn(false).when(mUserManager).canAddMoreUsers(anyString());
@@ -392,6 +417,7 @@
}
@Test
+ @RequiresFlagsDisabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
public void updateUserList_cannotSwitchUser_shouldDisableAddGuest() {
mUserCapabilities.mCanAddGuest = true;
doReturn(true)
@@ -406,6 +432,54 @@
}
@Test
+ @RequiresFlagsEnabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
+ public void updateUserList_cannotSwitchUser_shouldKeepPreferencesVisibleAndEnabled() {
+ givenUsers(getAdminUser(true));
+ mUserCapabilities.mCanAddGuest = true;
+ mUserCapabilities.mCanAddUser = true;
+ mUserCapabilities.mDisallowSwitchUser = true;
+ doReturn(true)
+ .when(mUserManager).canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_GUEST));
+ doReturn(true)
+ .when(mUserManager).canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY));
+
+ mFragment.updateUserList();
+
+ verify(mAddGuestPreference).setVisible(true);
+ verify(mAddGuestPreference).setEnabled(true);
+ verify(mAddUserPreference).setVisible(true);
+ verify(mAddUserPreference).setEnabled(true);
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
+ public void updateUserList_disallowAddUser_shouldShowButDisableAddActions() {
+ givenUsers(getAdminUser(true));
+ mUserCapabilities.mCanAddGuest = true;
+ mUserCapabilities.mCanAddUser = false;
+ mUserCapabilities.mDisallowAddUser = true;
+ mUserCapabilities.mDisallowAddUserSetByAdmin = false;
+ List<UserManager.EnforcingUser> enforcingUsers = new ArrayList<>();
+ enforcingUsers.add(new UserManager.EnforcingUser(UserHandle.myUserId(),
+ UserManager.RESTRICTION_SOURCE_SYSTEM));
+ when(mUserManager.getUserRestrictionSources(UserManager.DISALLOW_ADD_USER,
+ UserHandle.of(UserHandle.myUserId()))).thenReturn(enforcingUsers);
+
+ doReturn(true)
+ .when(mUserManager).canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_GUEST));
+ doReturn(true)
+ .when(mUserManager).canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY));
+
+ mFragment.updateUserList();
+
+ verify(mAddGuestPreference).setVisible(true);
+ verify(mAddGuestPreference).setEnabled(false);
+ verify(mAddUserPreference).setVisible(true);
+ verify(mAddUserPreference).setEnabled(false);
+ }
+
+ @Test
+ @RequiresFlagsDisabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
public void updateUserList_addUserDisallowedByAdmin_shouldNotShowAddUser() {
RestrictedLockUtils.EnforcedAdmin enforcedAdmin = mock(
RestrictedLockUtils.EnforcedAdmin.class);
@@ -421,6 +495,22 @@
}
@Test
+ @RequiresFlagsEnabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
+ public void updateUserList_addUserDisallowedByAdmin_shouldShowPrefDisabledByAdmin() {
+ RestrictedLockUtils.EnforcedAdmin enforcedAdmin = mock(
+ RestrictedLockUtils.EnforcedAdmin.class);
+
+ mUserCapabilities.mEnforcedAdmin = enforcedAdmin;
+ mUserCapabilities.mCanAddUser = false;
+ mUserCapabilities.mDisallowAddUser = true;
+ mUserCapabilities.mDisallowAddUserSetByAdmin = true;
+ doReturn(true).when(mAddUserPreference).isEnabled();
+
+ mFragment.updateUserList();
+
+ verify(mAddUserPreference).setDisabledByAdmin(enforcedAdmin);
+ }
+ @Test
public void updateUserList_cannotAddUserButCanSwitchUser_shouldNotShowAddUser() {
mUserCapabilities.mCanAddUser = false;
@@ -461,18 +551,31 @@
}
@Test
- public void updateUserList_userSwitcherDisabled_shouldNotShowAddUser() {
+ public void updateUserList_userSwitcherDisabled_shouldShowAddUser() {
givenUsers(getAdminUser(true));
mUserCapabilities.mCanAddUser = true;
mUserCapabilities.mUserSwitcherEnabled = false;
mFragment.updateUserList();
- verify(mAddUserPreference).setVisible(false);
+ verify(mAddUserPreference).setVisible(true);
}
@Test
- public void updateUserList_userSwitcherDisabled_shouldNotShowAddGuest() {
+ public void updateUserList_userSwitcherDisabled_shouldShowAddGuest() {
+ givenUsers(getAdminUser(true));
+ mUserCapabilities.mCanAddGuest = true;
+ mUserCapabilities.mUserSwitcherEnabled = false;
+ doReturn(true)
+ .when(mUserManager).canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_GUEST));
+
+ mFragment.updateUserList();
+
+ verify(mAddGuestPreference).setVisible(true);
+ }
+
+ @Test
+ public void updateUserList_userSwitcherDisabledCannotAddMoreGuests_shouldNotShowAddGuest() {
givenUsers(getAdminUser(true));
mUserCapabilities.mCanAddGuest = true;
mUserCapabilities.mUserSwitcherEnabled = false;
@@ -533,18 +636,18 @@
}
@Test
- public void updateUserList_existingSecondaryUser_shouldAddOnlyCurrUser_MultiUserIsDisabled() {
+ public void updateUserList_existingSecondaryUser_shouldAddAllUsers_MultiUserIsDisabled() {
givenUsers(getAdminUser(true), getSecondaryUser(false));
mUserCapabilities.mUserSwitcherEnabled = false;
mFragment.updateUserList();
ArgumentCaptor<UserPreference> captor = ArgumentCaptor.forClass(UserPreference.class);
- verify(mFragment.mUserListCategory, times(1))
+ verify(mFragment.mUserListCategory, times(2))
.addPreference(captor.capture());
List<UserPreference> userPrefs = captor.getAllValues();
- assertThat(userPrefs.size()).isEqualTo(1);
+ assertThat(userPrefs.size()).isEqualTo(2);
assertThat(userPrefs.get(0)).isSameInstanceAs(mMePreference);
}
@@ -631,6 +734,7 @@
}
@Test
+ @RequiresFlagsDisabled({Flags.FLAG_NEW_MULTIUSER_SETTINGS_UX})
public void updateUserList_uninitializedUserAndCanNotSwitchUser_shouldDisablePref() {
UserInfo uninitializedUser = getSecondaryUser(false);
removeFlag(uninitializedUser, UserInfo.FLAG_INITIALIZED);
diff --git a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java
index 4497a0a..4440bc9 100644
--- a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSettingsForSubTest.java
@@ -48,6 +48,8 @@
import android.telephony.ims.ImsMmTelManager;
import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -56,10 +58,14 @@
import com.android.settings.SettingsActivity;
import com.android.settings.network.ims.MockWifiCallingQueryImsState;
import com.android.settings.network.ims.WifiCallingQueryImsState;
+import com.android.settings.network.telephony.wificalling.IWifiCallingRepository;
import com.android.settings.testutils.shadow.ShadowFragment;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settings.widget.SettingsMainSwitchPreference;
+import kotlin.Unit;
+import kotlin.jvm.functions.Function1;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -183,36 +189,25 @@
}
@Test
- public void onResume_provisioningAllowed_shouldNotFinish() {
- // Call onResume while provisioning is allowed.
- mFragment.onResume();
+ public void onViewCreated_provisioningAllowed_shouldNotFinish() {
+ // Call onViewCreated while provisioning is allowed.
+ mFragment.onViewCreated(mView, null);
// Verify that finish() is not called.
verify(mFragment, never()).finish();
}
@Test
- public void onResume_provisioningDisallowed_shouldFinish() {
- // Call onResume while provisioning is disallowed.
- mQueryImsState.setIsProvisionedOnDevice(false);
- mFragment.onResume();
+ public void onViewCreated_provisioningDisallowed_shouldFinish() {
+ // Call onViewCreated while provisioning is disallowed.
+ mFragment.mIsWifiCallingReady = false;
+ mFragment.onViewCreated(mView, null);
// Verify that finish() is called
verify(mFragment).finish();
}
@Test
- public void onResumeOnPause_provisioningCallbackRegistration() throws Exception {
- // Verify that provisioning callback is registered after call to onResume().
- mFragment.onResume();
- verify(mFragment).registerProvisioningChangedCallback();
-
- // Verify that provisioning callback is unregistered after call to onPause.
- mFragment.onPause();
- verify(mFragment).unregisterProvisioningChangedCallback();
- }
-
- @Test
public void onResume_useWfcHomeModeConfigFalseAndEditable_shouldShowWfcRoaming() {
// Call onResume to update the WFC roaming preference.
mFragment.onResume();
@@ -377,6 +372,7 @@
protected class TestFragment extends WifiCallingSettingsForSub {
private SettingsMainSwitchPreference mSwitchPref;
+ protected boolean mIsWifiCallingReady = true;
protected void setSwitchBar(SettingsMainSwitchPreference switchPref) {
mSwitchPref = switchPref;
@@ -422,6 +418,25 @@
}
@Override
+ @NonNull
+ IWifiCallingRepository getWifiCallingRepository() {
+ return new IWifiCallingRepository() {
+ @Override
+ public void collectIsWifiCallingReadyFlow(
+ @NonNull LifecycleOwner lifecycleOwner,
+ @NonNull Function1<? super Boolean, Unit> action) {
+ action.invoke(mIsWifiCallingReady);
+ }
+ };
+ }
+
+ @NonNull
+ @Override
+ LifecycleOwner getLifecycleOwner() {
+ return this;
+ }
+
+ @Override
void showAlert(Intent intent) {
}
}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index bc5824f..55df480 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -33,6 +33,7 @@
"kotlinx_coroutines_test",
"Settings-testutils2",
"MediaDrmSettingsFlagsLib",
+ "servicestests-utils",
// 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/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java b/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java
new file mode 100644
index 0000000..019ade7
--- /dev/null
+++ b/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayPreferenceFragmentTest.java
@@ -0,0 +1,409 @@
+/*
+ * 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.display;
+
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.PREVIOUSLY_SHOWN_LIST_KEY;
+import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.DISPLAYS_LIST_PREFERENCE_KEY;
+import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE;
+import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_NOT_FOUND_FOOTER_RESOURCE;
+import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY;
+import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_RESOLUTION_TITLE_RESOURCE;
+import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_ROTATION_KEY;
+import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_ROTATION_TITLE_RESOURCE;
+import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_SETTINGS_RESOURCE;
+import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_USE_PREFERENCE_KEY;
+import static com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.EXTERNAL_DISPLAY_USE_TITLE_RESOURCE;
+import static com.android.settingslib.widget.FooterPreference.KEY_FOOTER;
+
+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.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Display;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.connecteddevice.display.ExternalDisplayPreferenceFragment.DisplayPreference;
+import com.android.settingslib.widget.FooterPreference;
+import com.android.settingslib.widget.MainSwitchPreference;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/** Unit tests for {@link ExternalDisplayPreferenceFragment}. */
+@RunWith(AndroidJUnit4.class)
+public class ExternalDisplayPreferenceFragmentTest extends ExternalDisplayTestBase {
+ @Nullable
+ private ExternalDisplayPreferenceFragment mFragment;
+ private int mPreferenceIdFromResource;
+ private int mDisplayIdArg = INVALID_DISPLAY;
+ private int mResolutionSelectorDisplayId = INVALID_DISPLAY;
+ @Mock
+ private MetricsLogger mMockedMetricsLogger;
+
+ @Test
+ @UiThreadTest
+ public void testCreateAndStart() {
+ initFragment();
+ assertThat(mPreferenceIdFromResource).isEqualTo(EXTERNAL_DISPLAY_SETTINGS_RESOURCE);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testShowDisplayList() {
+ var fragment = initFragment();
+ var outState = new Bundle();
+ fragment.onSaveInstanceStateCallback(outState);
+ assertThat(outState.getBoolean(PREVIOUSLY_SHOWN_LIST_KEY)).isFalse();
+ assertThat(mHandler.getPendingMessages().size()).isEqualTo(1);
+ PreferenceCategory pref = mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY);
+ assertThat(pref).isNull();
+ verify(mMockedInjector, never()).getAllDisplays();
+ mHandler.flush();
+ assertThat(mHandler.getPendingMessages().size()).isEqualTo(0);
+ verify(mMockedInjector).getAllDisplays();
+ pref = mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY);
+ assertThat(pref).isNotNull();
+ assertThat(pref.getPreferenceCount()).isEqualTo(2);
+ fragment.onSaveInstanceStateCallback(outState);
+ assertThat(outState.getBoolean(PREVIOUSLY_SHOWN_LIST_KEY)).isTrue();
+ }
+
+ @Test
+ @UiThreadTest
+ public void testLaunchDisplaySettingFromList() {
+ initFragment();
+ mHandler.flush();
+ PreferenceCategory pref = mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY);
+ assertThat(pref).isNotNull();
+ DisplayPreference display1Pref = (DisplayPreference) pref.getPreference(0);
+ DisplayPreference display2Pref = (DisplayPreference) pref.getPreference(1);
+ assertThat(display1Pref.getKey()).isEqualTo("display_id_" + 1);
+ assertThat("" + display1Pref.getTitle()).isEqualTo("HDMI");
+ assertThat("" + display1Pref.getSummary()).isEqualTo("1920 x 1080");
+ display1Pref.onPreferenceClick(display1Pref);
+ assertThat(mDisplayIdArg).isEqualTo(1);
+ verify(mMockedMetricsLogger).writePreferenceClickMetric(display1Pref);
+ assertThat(display2Pref.getKey()).isEqualTo("display_id_" + 2);
+ assertThat("" + display2Pref.getTitle()).isEqualTo("Overlay #1");
+ assertThat("" + display2Pref.getSummary()).isEqualTo("1240 x 780");
+ display2Pref.onPreferenceClick(display2Pref);
+ assertThat(mDisplayIdArg).isEqualTo(2);
+ verify(mMockedMetricsLogger).writePreferenceClickMetric(display2Pref);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testShowDisplayListForOnlyOneDisplay_PreviouslyShownList() {
+ var fragment = initFragment();
+ // Previously shown list of displays
+ fragment.onActivityCreatedCallback(createBundleForPreviouslyShownList());
+ // Only one display available
+ doReturn(new Display[] {mDisplays[1]}).when(mMockedInjector).getAllDisplays();
+ mHandler.flush();
+ PreferenceCategory pref = mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY);
+ assertThat(pref).isNotNull();
+ assertThat(pref.getPreferenceCount()).isEqualTo(1);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testShowEnabledDisplay_OnlyOneDisplayAvailable() {
+ doReturn(true).when(mMockedInjector).isDisplayEnabled(any());
+ // Only one display available
+ doReturn(new Display[] {mDisplays[1]}).when(mMockedInjector).getAllDisplays();
+ // Init
+ initFragment();
+ mHandler.flush();
+ PreferenceCategory list = mPreferenceScreen.findPreference(DISPLAYS_LIST_PREFERENCE_KEY);
+ assertThat(list).isNull();
+ var pref = mPreferenceScreen.findPreference(EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY);
+ assertThat(pref).isNotNull();
+ pref = mPreferenceScreen.findPreference(EXTERNAL_DISPLAY_ROTATION_KEY);
+ assertThat(pref).isNotNull();
+ var footerPref = (FooterPreference) mPreferenceScreen.findPreference(KEY_FOOTER);
+ assertThat(footerPref).isNotNull();
+ verify(footerPref).setTitle(EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testShowOneEnabledDisplay_FewAvailable() {
+ mDisplayIdArg = 1;
+ doReturn(true).when(mMockedInjector).isDisplayEnabled(any());
+ initFragment();
+ verify(mMockedInjector, never()).getDisplay(anyInt());
+ mHandler.flush();
+ verify(mMockedInjector).getDisplay(mDisplayIdArg);
+ var pref = mPreferenceScreen.findPreference(EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY);
+ assertThat(pref).isNotNull();
+ pref = mPreferenceScreen.findPreference(EXTERNAL_DISPLAY_ROTATION_KEY);
+ assertThat(pref).isNotNull();
+ var footerPref = (FooterPreference) mPreferenceScreen.findPreference(KEY_FOOTER);
+ assertThat(footerPref).isNotNull();
+ verify(footerPref).setTitle(EXTERNAL_DISPLAY_CHANGE_RESOLUTION_FOOTER_RESOURCE);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testShowDisabledDisplay() {
+ mDisplayIdArg = 1;
+ initFragment();
+ verify(mMockedInjector, never()).getDisplay(anyInt());
+ mHandler.flush();
+ verify(mMockedInjector).getDisplay(mDisplayIdArg);
+ var mainPref = (MainSwitchPreference) mPreferenceScreen.findPreference(
+ EXTERNAL_DISPLAY_USE_PREFERENCE_KEY);
+ assertThat(mainPref).isNotNull();
+ assertThat("" + mainPref.getTitle()).isEqualTo(
+ getText(EXTERNAL_DISPLAY_USE_TITLE_RESOURCE));
+ assertThat(mainPref.isChecked()).isFalse();
+ assertThat(mainPref.isEnabled()).isTrue();
+ assertThat(mainPref.getOnPreferenceChangeListener()).isNotNull();
+ var pref = mPreferenceScreen.findPreference(EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY);
+ assertThat(pref).isNull();
+ pref = mPreferenceScreen.findPreference(EXTERNAL_DISPLAY_ROTATION_KEY);
+ assertThat(pref).isNull();
+ var footerPref = (FooterPreference) mPreferenceScreen.findPreference(KEY_FOOTER);
+ assertThat(footerPref).isNull();
+ }
+
+ @Test
+ @UiThreadTest
+ public void testNoDisplays() {
+ doReturn(new Display[0]).when(mMockedInjector).getAllDisplays();
+ initFragment();
+ mHandler.flush();
+ var mainPref = (MainSwitchPreference) mPreferenceScreen.findPreference(
+ EXTERNAL_DISPLAY_USE_PREFERENCE_KEY);
+ assertThat(mainPref).isNotNull();
+ assertThat("" + mainPref.getTitle()).isEqualTo(
+ getText(EXTERNAL_DISPLAY_USE_TITLE_RESOURCE));
+ assertThat(mainPref.isChecked()).isFalse();
+ assertThat(mainPref.isEnabled()).isFalse();
+ assertThat(mainPref.getOnPreferenceChangeListener()).isNull();
+ var footerPref = (FooterPreference) mPreferenceScreen.findPreference(KEY_FOOTER);
+ assertThat(footerPref).isNotNull();
+ verify(footerPref).setTitle(EXTERNAL_DISPLAY_NOT_FOUND_FOOTER_RESOURCE);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testDisplayRotationPreference() {
+ mDisplayIdArg = 1;
+ doReturn(true).when(mMockedInjector).isDisplayEnabled(any());
+ var fragment = initFragment();
+ mHandler.flush();
+ var pref = fragment.getRotationPreference(mContext);
+ assertThat(pref.getKey()).isEqualTo(EXTERNAL_DISPLAY_ROTATION_KEY);
+ assertThat("" + pref.getTitle()).isEqualTo(
+ getText(EXTERNAL_DISPLAY_ROTATION_TITLE_RESOURCE));
+ assertThat(pref.getEntries().length).isEqualTo(4);
+ assertThat(pref.getEntryValues().length).isEqualTo(4);
+ assertThat(pref.getEntryValues()[0].toString()).isEqualTo("0");
+ assertThat(pref.getEntryValues()[1].toString()).isEqualTo("1");
+ assertThat(pref.getEntryValues()[2].toString()).isEqualTo("2");
+ assertThat(pref.getEntryValues()[3].toString()).isEqualTo("3");
+ assertThat(pref.getEntries()[0].length()).isGreaterThan(0);
+ assertThat(pref.getEntries()[1].length()).isGreaterThan(0);
+ assertThat("" + pref.getSummary()).isEqualTo(pref.getEntries()[0].toString());
+ assertThat(pref.getValue()).isEqualTo("0");
+ assertThat(pref.getOnPreferenceChangeListener()).isNotNull();
+ assertThat(pref.isEnabled()).isTrue();
+ var rotation = 1;
+ doReturn(true).when(mMockedInjector).freezeDisplayRotation(mDisplayIdArg, rotation);
+ assertThat(pref.getOnPreferenceChangeListener().onPreferenceChange(pref, rotation + ""))
+ .isTrue();
+ verify(mMockedInjector).freezeDisplayRotation(mDisplayIdArg, rotation);
+ assertThat(pref.getValue()).isEqualTo(rotation + "");
+ verify(mMockedMetricsLogger).writePreferenceClickMetric(pref);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testDisplayResolutionPreference() {
+ mDisplayIdArg = 1;
+ doReturn(true).when(mMockedInjector).isDisplayEnabled(any());
+ var fragment = initFragment();
+ mHandler.flush();
+ var pref = fragment.getResolutionPreference(mContext);
+ assertThat(pref.getKey()).isEqualTo(EXTERNAL_DISPLAY_RESOLUTION_PREFERENCE_KEY);
+ assertThat("" + pref.getTitle()).isEqualTo(
+ getText(EXTERNAL_DISPLAY_RESOLUTION_TITLE_RESOURCE));
+ assertThat("" + pref.getSummary()).isEqualTo("1920 x 1080");
+ assertThat(pref.isEnabled()).isTrue();
+ assertThat(pref.getOnPreferenceClickListener()).isNotNull();
+ assertThat(pref.getOnPreferenceClickListener().onPreferenceClick(pref)).isTrue();
+ assertThat(mResolutionSelectorDisplayId).isEqualTo(mDisplayIdArg);
+ verify(mMockedMetricsLogger).writePreferenceClickMetric(pref);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testUseDisplayPreference_EnabledDisplay() {
+ mDisplayIdArg = 1;
+ doReturn(true).when(mMockedInjector).isDisplayEnabled(any());
+ doReturn(true).when(mMockedInjector).enableConnectedDisplay(mDisplayIdArg);
+ doReturn(true).when(mMockedInjector).disableConnectedDisplay(mDisplayIdArg);
+ var fragment = initFragment();
+ mHandler.flush();
+ var pref = fragment.getUseDisplayPreference(mContext);
+ assertThat(pref.getKey()).isEqualTo(EXTERNAL_DISPLAY_USE_PREFERENCE_KEY);
+ assertThat("" + pref.getTitle()).isEqualTo(getText(EXTERNAL_DISPLAY_USE_TITLE_RESOURCE));
+ assertThat(pref.isEnabled()).isTrue();
+ assertThat(pref.isChecked()).isTrue();
+ assertThat(pref.getOnPreferenceChangeListener()).isNotNull();
+ assertThat(pref.getOnPreferenceChangeListener().onPreferenceChange(pref, false)).isTrue();
+ verify(mMockedInjector).disableConnectedDisplay(mDisplayIdArg);
+ assertThat(pref.isChecked()).isFalse();
+ assertThat(pref.getOnPreferenceChangeListener().onPreferenceChange(pref, true)).isTrue();
+ verify(mMockedInjector).enableConnectedDisplay(mDisplayIdArg);
+ assertThat(pref.isChecked()).isTrue();
+ verify(mMockedMetricsLogger, times(2)).writePreferenceClickMetric(pref);
+ }
+
+ @NonNull
+ private ExternalDisplayPreferenceFragment initFragment() {
+ if (mFragment != null) {
+ return mFragment;
+ }
+ mFragment = new TestableExternalDisplayPreferenceFragment();
+ mFragment.onCreateCallback(null);
+ mFragment.onActivityCreatedCallback(null);
+ mFragment.onStartCallback();
+ return mFragment;
+ }
+
+ @NonNull
+ private Bundle createBundleForPreviouslyShownList() {
+ var state = new Bundle();
+ state.putBoolean(PREVIOUSLY_SHOWN_LIST_KEY, true);
+ return state;
+ }
+
+ @NonNull
+ private String getText(int id) {
+ return mContext.getResources().getText(id).toString();
+ }
+
+ private class TestableExternalDisplayPreferenceFragment extends
+ ExternalDisplayPreferenceFragment {
+ private final View mMockedRootView;
+ private final TextView mEmptyView;
+ private final Activity mMockedActivity;
+ private final FooterPreference mMockedFooterPreference;
+ private final MetricsLogger mLogger;
+
+ TestableExternalDisplayPreferenceFragment() {
+ super(mMockedInjector);
+ mMockedActivity = mock(Activity.class);
+ mMockedRootView = mock(View.class);
+ mMockedFooterPreference = mock(FooterPreference.class);
+ doReturn(KEY_FOOTER).when(mMockedFooterPreference).getKey();
+ mEmptyView = new TextView(mContext);
+ doReturn(mEmptyView).when(mMockedRootView).findViewById(android.R.id.empty);
+ mLogger = mMockedMetricsLogger;
+ }
+
+ @Override
+ public PreferenceScreen getPreferenceScreen() {
+ return mPreferenceScreen;
+ }
+
+ @Override
+ protected Activity getCurrentActivity() {
+ return mMockedActivity;
+ }
+
+ @Override
+ public View getView() {
+ return mMockedRootView;
+ }
+
+ @Override
+ public void setEmptyView(View view) {
+ assertThat(view).isEqualTo(mEmptyView);
+ }
+
+ @Override
+ public View getEmptyView() {
+ return mEmptyView;
+ }
+
+ @Override
+ public void addPreferencesFromResource(int resource) {
+ mPreferenceIdFromResource = resource;
+ }
+
+ @Override
+ @NonNull
+ FooterPreference getFooterPreference(@NonNull Context context) {
+ return mMockedFooterPreference;
+ }
+
+ @Override
+ protected int getDisplayIdArg() {
+ return mDisplayIdArg;
+ }
+
+ @Override
+ protected void launchResolutionSelector(@NonNull Context context, int displayId) {
+ mResolutionSelectorDisplayId = displayId;
+ }
+
+ @Override
+ protected void launchDisplaySettings(final int displayId) {
+ mDisplayIdArg = displayId;
+ }
+
+ @Override
+ protected void writePreferenceClickMetric(Preference preference) {
+ mLogger.writePreferenceClickMetric(preference);
+ }
+ }
+
+ /**
+ * Interface allowing to mock and spy on log events.
+ */
+ public interface MetricsLogger {
+
+ /**
+ * On preference click metric
+ */
+ void writePreferenceClickMetric(Preference preference);
+ }
+}
diff --git a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayTestBase.java b/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayTestBase.java
new file mode 100644
index 0000000..60b0342
--- /dev/null
+++ b/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayTestBase.java
@@ -0,0 +1,149 @@
+/*
+ * 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.display;
+
+import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY;
+import static com.android.settings.flags.Flags.FLAG_ROTATION_CONNECTED_DISPLAY_SETTING;
+import static com.android.settings.flags.Flags.FLAG_RESOLUTION_AND_ENABLE_CONNECTED_DISPLAY_SETTING;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.IDisplayManager;
+import android.os.RemoteException;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.DisplayInfo;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.testutils.TestHandler;
+import com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.DisplayListener;
+import com.android.settings.flags.FakeFeatureFlagsImpl;
+
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class ExternalDisplayTestBase {
+ @Mock
+ ExternalDisplaySettingsConfiguration.Injector mMockedInjector;
+ @Mock
+ IDisplayManager mMockedIDisplayManager;
+ Resources mResources;
+ DisplayManagerGlobal mDisplayManagerGlobal;
+ FakeFeatureFlagsImpl mFlags = new FakeFeatureFlagsImpl();
+ Context mContext;
+ DisplayListener mListener;
+ TestHandler mHandler = new TestHandler(null);
+ PreferenceManager mPreferenceManager;
+ PreferenceScreen mPreferenceScreen;
+ Display[] mDisplays;
+
+ /**
+ * Setup.
+ */
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ mResources = spy(mContext.getResources());
+ doReturn(mResources).when(mContext).getResources();
+ mPreferenceManager = new PreferenceManager(mContext);
+ mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
+ doReturn(0).when(mMockedIDisplayManager).getPreferredWideGamutColorSpaceId();
+ mDisplayManagerGlobal = new DisplayManagerGlobal(mMockedIDisplayManager);
+ mFlags.setFlag(FLAG_ROTATION_CONNECTED_DISPLAY_SETTING, true);
+ mFlags.setFlag(FLAG_RESOLUTION_AND_ENABLE_CONNECTED_DISPLAY_SETTING, true);
+ mDisplays = new Display[] {
+ createDefaultDisplay(), createExternalDisplay(), createOverlayDisplay()};
+ doReturn(mDisplays).when(mMockedInjector).getAllDisplays();
+ doReturn(mDisplays).when(mMockedInjector).getEnabledDisplays();
+ for (var display : mDisplays) {
+ doReturn(display).when(mMockedInjector).getDisplay(display.getDisplayId());
+ }
+ doReturn(mFlags).when(mMockedInjector).getFlags();
+ doReturn(mHandler).when(mMockedInjector).getHandler();
+ doReturn("").when(mMockedInjector).getSystemProperty(
+ VIRTUAL_DISPLAY_PACKAGE_NAME_SYSTEM_PROPERTY);
+ doAnswer((arg) -> {
+ mListener = arg.getArgument(0);
+ return null;
+ }).when(mMockedInjector).registerDisplayListener(any());
+ doReturn(0).when(mMockedInjector).getDisplayUserRotation(anyInt());
+ doReturn(mContext).when(mMockedInjector).getContext();
+ }
+
+ Display createDefaultDisplay() throws RemoteException {
+ int displayId = 0;
+ var displayInfo = new DisplayInfo();
+ doReturn(displayInfo).when(mMockedIDisplayManager).getDisplayInfo(displayId);
+ displayInfo.displayId = displayId;
+ displayInfo.name = "Built-in";
+ displayInfo.type = Display.TYPE_INTERNAL;
+ displayInfo.supportedModes = new Display.Mode[]{
+ new Display.Mode(0, 2048, 1024, 60, 60, new float[0],
+ new int[0])};
+ displayInfo.appsSupportedModes = displayInfo.supportedModes;
+ return createDisplay(displayInfo);
+ }
+
+ Display createExternalDisplay() throws RemoteException {
+ int displayId = 1;
+ var displayInfo = new DisplayInfo();
+ doReturn(displayInfo).when(mMockedIDisplayManager).getDisplayInfo(displayId);
+ displayInfo.displayId = displayId;
+ displayInfo.name = "HDMI";
+ displayInfo.type = Display.TYPE_EXTERNAL;
+ displayInfo.supportedModes = new Display.Mode[]{
+ new Display.Mode(0, 1920, 1080, 60, 60, new float[0], new int[0]),
+ new Display.Mode(1, 800, 600, 60, 60, new float[0], new int[0]),
+ new Display.Mode(2, 320, 240, 70, 70, new float[0], new int[0]),
+ new Display.Mode(3, 640, 480, 60, 60, new float[0], new int[0]),
+ new Display.Mode(4, 640, 480, 50, 60, new float[0], new int[0]),
+ new Display.Mode(5, 2048, 1024, 60, 60, new float[0], new int[0]),
+ new Display.Mode(6, 720, 480, 60, 60, new float[0], new int[0])};
+ displayInfo.appsSupportedModes = displayInfo.supportedModes;
+ return createDisplay(displayInfo);
+ }
+
+ Display createOverlayDisplay() throws RemoteException {
+ int displayId = 2;
+ var displayInfo = new DisplayInfo();
+ doReturn(displayInfo).when(mMockedIDisplayManager).getDisplayInfo(displayId);
+ displayInfo.displayId = displayId;
+ displayInfo.name = "Overlay #1";
+ displayInfo.type = Display.TYPE_OVERLAY;
+ displayInfo.supportedModes = new Display.Mode[]{
+ new Display.Mode(0, 1240, 780, 60, 60, new float[0],
+ new int[0])};
+ displayInfo.appsSupportedModes = displayInfo.supportedModes;
+ return createDisplay(displayInfo);
+ }
+
+ Display createDisplay(DisplayInfo displayInfo) {
+ return new Display(mDisplayManagerGlobal, displayInfo.displayId, displayInfo,
+ (DisplayAdjustments) null);
+ }
+}
diff --git a/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdaterTest.java b/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdaterTest.java
new file mode 100644
index 0000000..824974a
--- /dev/null
+++ b/tests/unit/src/com/android/settings/connecteddevice/display/ExternalDisplayUpdaterTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.display;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
+import android.view.Display;
+
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.connecteddevice.DevicePreferenceCallback;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/** Unit tests for {@link ExternalDisplayUpdater}. */
+@RunWith(AndroidJUnit4.class)
+public class ExternalDisplayUpdaterTest extends ExternalDisplayTestBase {
+
+ private ExternalDisplayUpdater mUpdater;
+ @Mock
+ private DevicePreferenceCallback mMockedCallback;
+ @Mock
+ private Drawable mMockedDrawable;
+ private RestrictedPreference mPreferenceAdded;
+ private RestrictedPreference mPreferenceRemoved;
+
+ @Before
+ public void setUp() throws RemoteException {
+ super.setUp();
+ mUpdater = new TestableExternalDisplayUpdater(mMockedCallback, /*metricsCategory=*/ 0);
+ }
+
+ @Test
+ public void testPreferenceAdded() {
+ doAnswer((v) -> {
+ mPreferenceAdded = v.getArgument(0);
+ return null;
+ }).when(mMockedCallback).onDeviceAdded(any());
+ mUpdater.initPreference(mContext, mMockedInjector);
+ mUpdater.registerCallback();
+ mHandler.flush();
+ assertThat(mPreferenceAdded).isNotNull();
+ var summary = mPreferenceAdded.getSummary();
+ assertThat(summary).isNotNull();
+ assertThat(summary.length()).isGreaterThan(0);
+ var title = mPreferenceAdded.getTitle();
+ assertThat(title).isNotNull();
+ assertThat(title.length()).isGreaterThan(0);
+ }
+
+ @Test
+ public void testPreferenceRemoved() {
+ doAnswer((v) -> {
+ mPreferenceAdded = v.getArgument(0);
+ return null;
+ }).when(mMockedCallback).onDeviceAdded(any());
+ doAnswer((v) -> {
+ mPreferenceRemoved = v.getArgument(0);
+ return null;
+ }).when(mMockedCallback).onDeviceRemoved(any());
+ mUpdater.initPreference(mContext, mMockedInjector);
+ mUpdater.registerCallback();
+ mHandler.flush();
+ assertThat(mPreferenceAdded).isNotNull();
+ assertThat(mPreferenceRemoved).isNull();
+ // Remove display
+ doReturn(new Display[0]).when(mMockedInjector).getAllDisplays();
+ doReturn(new Display[0]).when(mMockedInjector).getEnabledDisplays();
+ mListener.onDisplayRemoved(1);
+ mHandler.flush();
+ assertThat(mPreferenceRemoved).isEqualTo(mPreferenceAdded);
+ }
+
+ class TestableExternalDisplayUpdater extends ExternalDisplayUpdater {
+ TestableExternalDisplayUpdater(
+ DevicePreferenceCallback callback,
+ int metricsCategory) {
+ super(callback, metricsCategory);
+ }
+
+ @Override
+ @Nullable
+ protected RestrictedLockUtils.EnforcedAdmin checkIfUsbDataSignalingIsDisabled(
+ Context context) {
+ // if null is returned - usb signalling is enabled
+ return null;
+ }
+
+ @Override
+ @Nullable
+ protected Drawable getDrawable(Context context) {
+ return mMockedDrawable;
+ }
+ }
+}
diff --git a/tests/unit/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragmentTest.java b/tests/unit/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragmentTest.java
new file mode 100644
index 0000000..ee38a1c
--- /dev/null
+++ b/tests/unit/src/com/android/settings/connecteddevice/display/ResolutionPreferenceFragmentTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.display;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.settings.connecteddevice.display.ResolutionPreferenceFragment.EXTERNAL_DISPLAY_RESOLUTION_SETTINGS_RESOURCE;
+import static com.android.settings.connecteddevice.display.ResolutionPreferenceFragment.MORE_OPTIONS_KEY;
+import static com.android.settings.connecteddevice.display.ResolutionPreferenceFragment.TOP_OPTIONS_KEY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settingslib.widget.SelectorWithWidgetPreference;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/** Unit tests for {@link ResolutionPreferenceFragment}. */
+@RunWith(AndroidJUnit4.class)
+public class ResolutionPreferenceFragmentTest extends ExternalDisplayTestBase {
+ @Nullable
+ private ResolutionPreferenceFragment mFragment;
+ private int mPreferenceIdFromResource;
+ private int mDisplayIdArg = INVALID_DISPLAY;
+ @Mock
+ private MetricsLogger mMockedMetricsLogger;
+
+ @Test
+ @UiThreadTest
+ public void testCreateAndStart() {
+ initFragment();
+ mHandler.flush();
+ assertThat(mPreferenceIdFromResource).isEqualTo(
+ EXTERNAL_DISPLAY_RESOLUTION_SETTINGS_RESOURCE);
+ var pref = mPreferenceScreen.findPreference(TOP_OPTIONS_KEY);
+ assertThat(pref).isNull();
+ pref = mPreferenceScreen.findPreference(MORE_OPTIONS_KEY);
+ assertThat(pref).isNull();
+ }
+
+ @Test
+ @UiThreadTest
+ public void testCreateAndStartDefaultDisplayNotAllowed() {
+ mDisplayIdArg = 0;
+ initFragment();
+ mHandler.flush();
+ var pref = mPreferenceScreen.findPreference(TOP_OPTIONS_KEY);
+ assertThat(pref).isNull();
+ pref = mPreferenceScreen.findPreference(MORE_OPTIONS_KEY);
+ assertThat(pref).isNull();
+ }
+
+ @Test
+ @UiThreadTest
+ public void testModePreferences() {
+ mDisplayIdArg = 1;
+ initFragment();
+ mHandler.flush();
+ PreferenceCategory topPref = mPreferenceScreen.findPreference(TOP_OPTIONS_KEY);
+ assertThat(topPref).isNotNull();
+ PreferenceCategory morePref = mPreferenceScreen.findPreference(MORE_OPTIONS_KEY);
+ assertThat(morePref).isNotNull();
+ assertThat(topPref.getPreferenceCount()).isEqualTo(3);
+ assertThat(morePref.getPreferenceCount()).isEqualTo(1);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testModeChange() {
+ mDisplayIdArg = 1;
+ initFragment();
+ mHandler.flush();
+ PreferenceCategory topPref = mPreferenceScreen.findPreference(TOP_OPTIONS_KEY);
+ assertThat(topPref).isNotNull();
+ var modePref = (SelectorWithWidgetPreference) topPref.getPreference(1);
+ modePref.onClick();
+ var mode = mDisplays[mDisplayIdArg].getSupportedModes()[1];
+ verify(mMockedInjector).setUserPreferredDisplayMode(mDisplayIdArg, mode);
+ }
+
+ private void initFragment() {
+ if (mFragment != null) {
+ return;
+ }
+ mFragment = new TestableResolutionPreferenceFragment();
+ mFragment.onCreateCallback(null);
+ mFragment.onActivityCreatedCallback(null);
+ mFragment.onStartCallback();
+ }
+
+ private class TestableResolutionPreferenceFragment extends ResolutionPreferenceFragment {
+ private final View mMockedRootView;
+ private final TextView mEmptyView;
+ private final Resources mMockedResources;
+ private final MetricsLogger mLogger;
+ TestableResolutionPreferenceFragment() {
+ super(mMockedInjector);
+ mMockedResources = mock(Resources.class);
+ doReturn(61).when(mMockedResources).getInteger(
+ com.android.internal.R.integer.config_externalDisplayPeakRefreshRate);
+ doReturn(1920).when(mMockedResources).getInteger(
+ com.android.internal.R.integer.config_externalDisplayPeakWidth);
+ doReturn(1080).when(mMockedResources).getInteger(
+ com.android.internal.R.integer.config_externalDisplayPeakHeight);
+ doReturn(true).when(mMockedResources).getBoolean(
+ com.android.internal.R.bool.config_refreshRateSynchronizationEnabled);
+ mMockedRootView = mock(View.class);
+ mEmptyView = new TextView(mContext);
+ doReturn(mEmptyView).when(mMockedRootView).findViewById(android.R.id.empty);
+ mLogger = mMockedMetricsLogger;
+ }
+
+ @Override
+ public PreferenceScreen getPreferenceScreen() {
+ return mPreferenceScreen;
+ }
+
+ @Override
+ public View getView() {
+ return mMockedRootView;
+ }
+
+ @Override
+ public void setEmptyView(View view) {
+ assertThat(view).isEqualTo(mEmptyView);
+ }
+
+ @Override
+ public View getEmptyView() {
+ return mEmptyView;
+ }
+
+ @Override
+ public void addPreferencesFromResource(int resource) {
+ mPreferenceIdFromResource = resource;
+ }
+
+ @Override
+ protected int getDisplayIdArg() {
+ return mDisplayIdArg;
+ }
+
+ @Override
+ protected void writePreferenceClickMetric(Preference preference) {
+ mLogger.writePreferenceClickMetric(preference);
+ }
+
+ @Override
+ @NonNull
+ protected Resources getResources(@NonNull Context context) {
+ return mMockedResources;
+ }
+ }
+
+ /**
+ * Interface allowing to mock and spy on log events.
+ */
+ public interface MetricsLogger {
+ /**
+ * On preference click metric
+ */
+ void writePreferenceClickMetric(Preference preference);
+ }
+}