Merge "Reduce Data Saver Settings launch time"
diff --git a/res/values-ky/arrays.xml b/res/values-ky/arrays.xml
index dbb925b..a0d7061 100644
--- a/res/values-ky/arrays.xml
+++ b/res/values-ky/arrays.xml
@@ -362,12 +362,12 @@
<string-array name="long_press_timeout_selector_titles">
<item msgid="6926391290986427331">"Кыска"</item>
<item msgid="5118829513010894576">"Орточо"</item>
- <item msgid="6740026006576843477">"Узун"</item>
+ <item msgid="6740026006576843477">"Узак"</item>
</string-array>
<string-array name="long_press_timeout_selector_list_titles">
<item msgid="8908021508913038488">"Кыска"</item>
<item msgid="7397961711906421599">"Орто"</item>
- <item msgid="4079132024502041928">"Узун"</item>
+ <item msgid="4079132024502041928">"Узак"</item>
</string-array>
<string-array name="captioning_typeface_selector_titles">
<item msgid="2166553138528640250">"Демейки"</item>
diff --git a/res/values-mk/arrays.xml b/res/values-mk/arrays.xml
index 5f6e3e3..767c02a 100644
--- a/res/values-mk/arrays.xml
+++ b/res/values-mk/arrays.xml
@@ -215,7 +215,7 @@
</string-array>
<string-array name="app_install_location_entries">
<item msgid="3771157789865587832">"Внатрешен капацитет"</item>
- <item msgid="5501345333507193420">"Пренослива СД картичка"</item>
+ <item msgid="5501345333507193420">"Пренослива SD-картичка"</item>
<item msgid="2362840341195111674">"Дозволи системот да одлучи"</item>
</string-array>
<string-array name="app_ops_categories">
diff --git a/res/values-ro/arrays.xml b/res/values-ro/arrays.xml
index 0ec751f..1327de5 100644
--- a/res/values-ro/arrays.xml
+++ b/res/values-ro/arrays.xml
@@ -99,7 +99,7 @@
<!-- no translation found for wifi_tether_security:2 (6851763638266088835) -->
<!-- no translation found for wifi_tether_security:3 (5512131148045414341) -->
<string-array name="eap_ocsp_type">
- <item msgid="8568170800958331461">"Nu verificați"</item>
+ <item msgid="8568170800958331461">"Nu verifica"</item>
<item msgid="5703177653586269306">"Solicitați starea certificatelor"</item>
<item msgid="326388247868439528">"Solicită obligatoriu starea certificatelor"</item>
</string-array>
@@ -122,7 +122,7 @@
<item msgid="4241913314075719627">"Fără timp limită"</item>
</string-array>
<string-array name="bluetooth_max_connected_audio_devices">
- <item msgid="4792793579224104167">"Utilizați setarea prestabilită a sistemului: <xliff:g id="DEFAULT_BLUETOOTH_MAX_CONNECTED_AUDIO_DEVICES">%1$d</xliff:g>"</item>
+ <item msgid="4792793579224104167">"Folosește setarea prestabilită a sistemului: <xliff:g id="DEFAULT_BLUETOOTH_MAX_CONNECTED_AUDIO_DEVICES">%1$d</xliff:g>"</item>
<item msgid="5818942631838356082">"1"</item>
<item msgid="4804155564025402919">"2"</item>
<item msgid="7123296338505723878">"3"</item>
@@ -138,7 +138,7 @@
</string-array>
<string-array name="data_usage_data_range">
<item msgid="3528385925116637939">"Ultimele 30 de zile"</item>
- <item msgid="3356834403629955571">"Setați ciclu utilizare..."</item>
+ <item msgid="3356834403629955571">"Setează ciclu utilizare..."</item>
</string-array>
<string-array name="usage_stats_display_order_types">
<item msgid="7527014644010884448">"Durată de utilizare"</item>
@@ -307,7 +307,7 @@
<item msgid="6657539556093198883">"Postează o notificare"</item>
<item msgid="8112680908829570200">"Locație"</item>
<item msgid="5019327268152480733">"Apelează un telefon"</item>
- <item msgid="8001855901083066554">"Citiți mesaje SMS/MMS"</item>
+ <item msgid="8001855901083066554">"Citește mesaje SMS/MMS"</item>
<item msgid="187744670643011148">"Scrie mesaje SMS/MMS"</item>
<item msgid="3324078624274013835">"Primește mesaje SMS/MMS"</item>
<item msgid="1924065490920451511">"Primește mesaje SMS/MMS"</item>
@@ -447,7 +447,7 @@
<string-array name="security_settings_premium_sms_values">
<item msgid="3985605994234635072">"Întreabă"</item>
<item msgid="2358187544264718285">"Nu permite niciodată"</item>
- <item msgid="7043782324123900484">"Permiteți întotdeauna"</item>
+ <item msgid="7043782324123900484">"Permite întotdeauna"</item>
</string-array>
<string-array name="ram_states">
<item msgid="335564863849202240">"Normală"</item>
@@ -504,7 +504,7 @@
</string-array>
<string-array name="wifi_privacy_entries">
<item msgid="3485945604919292489">"Folosiți o adresă MAC aleatorie (prestabilit)"</item>
- <item msgid="741680937828608749">"Folosiți adresa MAC a dispozitivului"</item>
+ <item msgid="741680937828608749">"Folosește adresa MAC a dispozitivului"</item>
</string-array>
<string-array name="wifi_hidden_entries">
<item msgid="342232116597649254">"Nu"</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c8208bb..5902387 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1740,16 +1740,10 @@
</plurals>
<!-- Bluetooth settings -->
- <!-- Bluetooth settings check box title on Main Settings screen -->
- <string name="bluetooth_quick_toggle_title">Bluetooth</string>
- <!-- Bluetooth settings check box summary for turning on bluetooth -->
- <string name="bluetooth_quick_toggle_summary">Turn on Bluetooth</string>
<!--Used as title on second screen after selecting Bluetooth settings -->
<string name="bluetooth_settings">Bluetooth</string>
<!--Wireless controls screen, settings title for the item to take you to the bluetooth settings screen -->
<string name="bluetooth_settings_title">Bluetooth</string>
- <!--Wireless controls screen, settings summary for the item tot ake you to the bluetooth settings screen -->
- <string name="bluetooth_settings_summary">Manage connections, set device name & discoverability</string>
<!-- ======================================================================================= -->
<!-- Note: The opening brackets of HTML style tags are escaped (e.g. "<b>" is "<b>") in -->
@@ -1780,18 +1774,9 @@
<!-- Pairing dialog text to remind user to enter the passkey on the other device. [CHAR LIMIT=NONE] -->
<string name="bluetooth_enter_passkey_other_device">You may also need to type this passkey on the other device.</string>
- <!-- Message for confirmation of passkey to complete pairing. [CHAR LIMIT=NONE] -->
- <string name="bluetooth_confirm_passkey_msg">To pair with:<br><b><xliff:g id="device_name">%1$s</xliff:g></b><br><br>Make sure it is showing this passkey:<br><b><xliff:g id="passkey">%2$s</xliff:g></b></string>
-
<!-- Pairing dialog text to remind user the pairing including all of the devices in a coordinated set. [CHAR LIMIT=NONE] -->
<string name="bluetooth_paring_group_msg">Confirm to pair with the coordinated set</string>
- <!-- Message when bluetooth incoming pairing request for (2.1 devices) dialog is showing -->
- <string name="bluetooth_incoming_pairing_msg">From:<br><b><xliff:g id="device_name">%1$s</xliff:g></b><br><br>Pair with this device?</string>
-
- <!-- Message when bluetooth dialog when passkey or pin needs to be displayed. -->
- <string name="bluetooth_display_passkey_pin_msg">To pair with:<xliff:g id="bold1"><br><b></xliff:g><xliff:g id="device_name">%1$s</xliff:g><xliff:g id="end_bold1"></b><br><br></xliff:g>Type on it:<xliff:g id="bold2"><br><b></xliff:g><xliff:g id="passkey">%2$s</xliff:g><xliff:g id="end_bold2"></b></xliff:g>, then press Return or Enter.</string>
-
<!-- Checkbox message in pairing dialogs. [CHAR LIMIT=NONE] -->
<string name="bluetooth_pairing_shares_phonebook">Allow access to your contacts and call history</string>
@@ -1801,63 +1786,18 @@
<!-- Message for the error dialog when BT connecting operation fails generically. -->
<string name="bluetooth_connecting_error_message">Couldn\u2019t connect to <xliff:g id="device_name">%1$s</xliff:g>.</string>
- <!-- Bluetooth settings: The title of the preference (list item) that initiates a scan for devices -->
- <string name="bluetooth_preference_scan_title">Scan for devices</string>
- <!-- Bluetooth settings: The title of the action button that initiates a search for nearby devices [CHAR LIMIT=20] -->
- <string name="bluetooth_search_for_devices">Refresh</string>
- <!-- Bluetooth settings: The title of the action button while a search for nearby devices is in progress [CHAR LIMIT=20] -->
- <string name="bluetooth_searching_for_devices">Searching\u2026</string>
- <!-- Bluetooth settings: The sub heading for device settings. [CHAR LIMIT=30] -->
- <string name="bluetooth_preference_device_settings">Device settings</string>
- <!-- Bluetooth settings: Paired dialog title [CHAR LIMIT=40] -->
- <string name="bluetooth_preference_paired_dialog_title">Paired device</string>
- <!-- Bluetooth settings: Checkbox label for enable/disable internet connection. [CHAR LIMIT=40] -->
- <string name="bluetooth_preference_paired_dialog_internet_option">Internet connection</string>
- <!-- Bluetooth settings: Checkbox label for enable/disable keyboard connection. [CHAR LIMIT=40] -->
- <string name="bluetooth_preference_paired_dialog_keyboard_option">Keyboard</string>
- <!-- Bluetooth settings: Checkbox label for enable/disable contacts connection. [CHAR LIMIT=40] -->
- <string name="bluetooth_preference_paired_dialog_contacts_option">Contacts and call history</string>
- <!-- Bluetooth settings: pairing dialog title. [CHAR LIMIT=40] -->
- <string name="bluetooth_pairing_dialog_title">Pair with this device?</string>
- <!-- Bluetooth settings: share phone book title. [CHAR LIMIT=40] -->
- <string name="bluetooth_pairing_dialog_sharing_phonebook_title">Share phone book?</string>
- <!-- Bluetooth settings: Message informing user that a bluetooth wants to access contant and call history. [CHAR LIMIT=100] -->
- <string name="bluetooth_pairing_dialog_contants_request"><xliff:g id="device_name">%1$s</xliff:g> wants to access your contacts and call history.</string>
- <!-- Bluetooth settings: paring permission message. [CHAR LIMIT=100] -->
- <string name="bluetooth_pairing_dialog_paring_request"><xliff:g id="device_name">%1$s</xliff:g> wants to pair with Bluetooth. When connected, it will have access to your contacts and call history.</string>
<!-- Bluetooth settings: The sub heading for available devices during and after scanning. [CHAR LIMIT=40] -->
<string name="bluetooth_preference_found_media_devices">Available devices</string>
- <!-- Bluetooth settings: The message displayed if no Bluetooth devices were found. [CHAR LIMIT=40] -->
- <string name="bluetooth_preference_no_found_devices">No devices available</string>
<!-- Bluetooth settings. Context menu item for a device. Action will connect to all profiles on the device. -->
<string name="bluetooth_device_context_connect">Connect</string>
<!-- Bluetooth settings. Context menu item for a device. Action will disconnect from all profiles on the device. -->
<string name="bluetooth_device_context_disconnect">Disconnect</string>
<!-- Bluetooth settings. Context menu item for a device. Action will first pair, and then connect to all profiles on the device. -->
<string name="bluetooth_device_context_pair_connect">Pair & connect</string>
- <!-- Bluetooth settings. Context menu item for a device. Action will remove pairing with the device. -->
- <string name="bluetooth_device_context_unpair">Unpair</string>
- <!-- Bluetooth settings. Context menu item for a device. Action will disconnect and remove pairing with the device. -->
- <string name="bluetooth_device_context_disconnect_unpair">Disconnect & unpair</string>
- <!-- Bluetooth settings. Context menu item for a device. Action will take the user to another screen where they can choose exactly which profiles to connect to. -->
- <string name="bluetooth_device_context_connect_advanced">Options\u2026</string>
- <!-- Bluetooth settings. Menu option to Bluetooth advanced settings [CHAR LIMIT=20]-->
- <string name="bluetooth_menu_advanced">Advanced</string>
- <!-- Bluetooth settings. Title of the advanced bluetooth settings screen [CHAR LIMIT=30]-->
- <string name="bluetooth_advanced_titlebar">Advanced Bluetooth</string>
<!-- Bluetooth settings. Text displayed when Bluetooth is off and device list is empty [CHAR LIMIT=50]-->
<string name="bluetooth_empty_list_bluetooth_off">When Bluetooth is turned on, your device can communicate with other nearby Bluetooth devices.</string>
<!-- Bluetooth settings. Text displayed when Bluetooth is off and bluetooth scanning is turned on [CHAR LIMIT=NONE] -->
<string name="bluetooth_scanning_on_info_message">When Bluetooth is turned on, your device can communicate with other nearby Bluetooth devices.\n\nTo improve device experience, apps and services can still scan for nearby devices at any time, even when Bluetooth is off. This can be used, for example, to improve location-based features and services. You can change this in <annotation id="link">Bluetooth scanning settings</annotation>.</string>
- <!-- Message to describe "BLE scan always available feature" when Bluetooth is off. The
- place-holders "LINK_BEGIN" and "LINK_END" must NOT be translated. They mark a link to bring
- the user to "scanning settings" screen. -->
- <string name="ble_scan_notify_text">To improve location accuracy, system apps and services can
- still detect Bluetooth devices. You can change this in
- <xliff:g id="link_begin">LINK_BEGIN</xliff:g>scanning
- settings<xliff:g id="link_end">LINK_END</xliff:g>.</string>
- <!-- Bluetooth connecting error message [CHAR LIMIT=NONE] -->
- <string name="bluetooth_connect_failed">Couldn\'t connect. Try again.</string>
<!-- Title of device details screen [CHAR LIMIT=28]-->
<string name="device_details_title">Device details</string>
@@ -1886,46 +1826,16 @@
<!-- Bluetooth device details companion apps. In the confirmation dialog for removing an associated app, this is the label on the button that will complete the disassociate action. [CHAR LIMIT=80] -->
<string name = "bluetooth_companion_app_remove_association_confirm_button">Disconnect app</string>
- <!-- Bluetooth settings. The title of the screen to pick which profiles to connect to on the device. For example, headphones may have both A2DP and headset, this allows the users to choose which one they want to connect to. -->
- <string name="bluetooth_connect_specific_profiles_title">Connect to\u2026</string>
-
- <!-- Bluetooth settings. Message for disconnecting from the A2DP profile. [CHAR LIMIT=80] -->
- <string name="bluetooth_disconnect_a2dp_profile"><xliff:g id="device_name">%1$s</xliff:g> will be disconnected from media audio.</string>
- <!-- Bluetooth settings. Message for disconnecting from the headset profile. [CHAR LIMIT=80] -->
- <string name="bluetooth_disconnect_headset_profile"><xliff:g id="device_name">%1$s</xliff:g> will be disconnected from handsfree audio.</string>
- <!-- Bluetooth settings. Message for disconnecting from the HID profile. [CHAR LIMIT=80] -->
- <string name="bluetooth_disconnect_hid_profile"><xliff:g id="device_name">%1$s</xliff:g> will be disconnected from input device.</string>
- <!-- Bluetooth settings. Message for disconnecting from the PAN profile (user role). [CHAR LIMIT=80] -->
- <string name="bluetooth_disconnect_pan_user_profile">Internet access via <xliff:g id="device_name">%1$s</xliff:g> will be disconnected.</string>
- <!-- Bluetooth settings. Message for disconnecting from the PAN profile (NAP role). [CHAR LIMIT=80] -->
- <string name="bluetooth_disconnect_pan_nap_profile" product="tablet"><xliff:g id="device_name">%1$s</xliff:g> will be disconnected from sharing this tablet\u2019s internet connection.</string>
- <!-- Bluetooth settings. Message for disconnecting from the PAN profile (NAP role). [CHAR LIMIT=80] -->
- <string name="bluetooth_disconnect_pan_nap_profile" product="default"><xliff:g id="device_name">%1$s</xliff:g> will be disconnected from sharing this phone\u2019s internet connection.</string>
-
<!-- Bluetooth settings. Connection options screen. The title of the screen. [CHAR LIMIT=40] -->
<string name="bluetooth_device_advanced_title">Paired Bluetooth device</string>
- <!-- Bluetooth settings. Connection options screen. The title of the checkbox that controls whether the device is in "online" mode or "offline" mode. This essentially is the checkbox that controls whether any checks / unchecks on a profile should be applied immediately, or next time the device is connected. -->
- <string name="bluetooth_device_advanced_online_mode_title">Connect</string>
- <!-- Bluetooth settings. Connection options screen. The summary of the online mode checkbox. This describes what the setting does in the context of the screen. -->
- <string name="bluetooth_device_advanced_online_mode_summary">Connect to Bluetooth device</string>
<!-- Bluetooth settings. Connection options screen. The title of the header that is above all of the profiles.
When a user decides what Bluetooth capabilities to use with the device. -->
<string name="bluetooth_device_advanced_profile_header_title">Use for</string>
- <!-- Bluetooth settings. Connection options screen. Title for option to rename the device. [CHAR LIMIT=30] -->
- <string name="bluetooth_device_advanced_rename_device">Rename</string>
- <!-- Bluetooth settings. Connection options screen. Title for checkbox to enable incoming file transfers [CHAR LIMIT=30] -->
- <string name="bluetooth_device_advanced_enable_opp_title">Allow incoming file transfers</string>
<!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (user role). [CHAR LIMIT=25]-->
<string name="bluetooth_pan_user_profile_summary_connected">Connected to device for internet access</string>
<!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (NAP role). [CHAR LIMIT=25]-->
<string name="bluetooth_pan_nap_profile_summary_connected">Sharing local internet connection with device</string>
- <!-- Bluetooth settings. Dock Setting Title -->
- <string name="bluetooth_dock_settings">Dock Settings</string>
- <!-- Bluetooth settings. Dock Setting Dialog Title -->
- <string name="bluetooth_dock_settings_title">Use dock for audio</string>
- <!-- Bluetooth settings. Dock Setting Dialog - Checkbox selection 1: Use dock as speaker phone -->
- <string name="bluetooth_dock_settings_headset">As speaker phone</string>
<!-- Bluetooth settings. Dock Setting Dialog - Checkbox selection 2: Use dock for media audio -->
<string name="bluetooth_dock_settings_a2dp">For music and media</string>
<!-- Bluetooth settings. Dock Setting Dialog - Remember setting and don't ask user again -->
@@ -3388,14 +3298,6 @@
<string name="status_prl_version">PRL version</string>
<!-- About phone screen, title for MEID for multi-sim devices -->
<string name="meid_multi_sim">MEID (sim slot %1$d)</string>
- <!-- The status text when both Wi-Fi scanning and Bluetooth scanning are on. [CHAR LIMIT=100] -->
- <string name="scanning_status_text_wifi_on_ble_on">Both Wi\u2011Fi and Bluetooth scanning are on</string>
- <!-- The status text when Wi-Fi scanning is on and Bluetooth scanning are off. [CHAR LIMIT=100] -->
- <string name="scanning_status_text_wifi_on_ble_off">Wi\u2011Fi scanning is on, Bluetooth scanning is off</string>
- <!-- The status text when Wi-Fi scanning is off and Bluetooth scanning are on. [CHAR LIMIT=100] -->
- <string name="scanning_status_text_wifi_off_ble_on">Bluetooth scanning is on, Wi\u2011Fi scanning is off</string>
- <!-- The status text when both Wi-Fi scanning and Bluetooth scanning are off. [CHAR LIMIT=100] -->
- <string name="scanning_status_text_wifi_off_ble_off">Both Wi\u2011Fi and Bluetooth scanning are off</string>
<!-- About phone, status item title. The phone MEID number of the current LTE/CDMA device. [CHAR LIMIT=30] -->
<string name="status_meid_number">MEID</string>
<!-- About phone, status item title. The ICCID of the current LTE device. [CHAR LIMIT=30] -->
@@ -3897,12 +3799,6 @@
<string name="bluetooth_tethering_subtext" product="tablet">Share tablet\u2019s internet connection via Bluetooth</string>
<!-- Bluetooth Tethering subtext [CHAR LIMIT=70]-->
<string name="bluetooth_tethering_subtext" product="default">Share phone\u2019s internet connection via Bluetooth</string>
- <!-- Bluetooth tethering off subtext - shown when Bluetooth Tethering is turned off [CHAR LIMIT=80]-->
- <string name="bluetooth_tethering_off_subtext_config">Sharing this <xliff:g id="device_name">%1$d</xliff:g>\u2019s internet connection via Bluetooth</string>
- <!-- Bluetooth Tethering settings. Error message shown when trying to connect an 8th device [CHAR LIMIT=50]-->
- <string name="bluetooth_tethering_overflow_error">Can\u2019t tether to more than <xliff:g id="maxConnection">%1$d</xliff:g> devices.</string>
- <!-- Bluetooth Tethering settings. Message for untethering from a bluetooth device [CHAR LIMIT=50]-->
- <string name="bluetooth_untether_blank"><xliff:g id="device_name">%1$s</xliff:g> will be untethered.</string>
<!-- Ethernet Tethering settings-->
<!-- Label for ethernet tether checkbox [CHAR LIMIT=NONE]-->
diff --git a/res/xml/accessibility_autoclick_settings.xml b/res/xml/accessibility_autoclick_settings.xml
index 943bc67..7a19928 100644
--- a/res/xml/accessibility_autoclick_settings.xml
+++ b/res/xml/accessibility_autoclick_settings.xml
@@ -21,50 +21,52 @@
<com.android.settingslib.widget.TopIntroPreference
android:key="accessibility_autoclick_intro"
- android:persistent="false"
- android:title="@string/accessibility_autoclick_intro_text" />
+ android:title="@string/accessibility_autoclick_intro_text"/>
<com.android.settingslib.widget.IllustrationPreference
android:key="accessibility_autoclick_banner"
- android:persistent="false"
android:selectable="false"
settings:searchable="false"
- settings:lottie_rawRes="@drawable/accessibility_dwell" />
+ settings:lottie_rawRes="@drawable/accessibility_dwell"/>
<com.android.settingslib.widget.SelectorWithWidgetPreference
android:key="accessibility_control_autoclick_default"
- android:title="@string/accessibility_autoclick_default_title" />
+ android:title="@string/accessibility_autoclick_default_title"
+ settings:controller="com.android.settings.accessibility.ToggleAutoclickPreferenceController"/>
<com.android.settingslib.widget.SelectorWithWidgetPreference
android:key="accessibility_control_autoclick_200ms"
android:title="@string/accessibility_autoclick_short_title"
- android:summary="@string/accessibility_autoclick_short_summary" />
+ android:summary="@string/accessibility_autoclick_short_summary"
+ settings:controller="com.android.settings.accessibility.ToggleAutoclickPreferenceController"/>
<com.android.settingslib.widget.SelectorWithWidgetPreference
android:key="accessibility_control_autoclick_600ms"
android:title="@string/accessibility_autoclick_medium_title"
- android:summary="@string/accessibility_autoclick_medium_summary" />
+ android:summary="@string/accessibility_autoclick_medium_summary"
+ settings:controller="com.android.settings.accessibility.ToggleAutoclickPreferenceController"/>
<com.android.settingslib.widget.SelectorWithWidgetPreference
android:key="accessibility_control_autoclick_1sec"
android:title="@string/accessibility_autoclick_long_title"
- android:summary="@string/accessibility_autoclick_long_summary" />
+ android:summary="@string/accessibility_autoclick_long_summary"
+ settings:controller="com.android.settings.accessibility.ToggleAutoclickPreferenceController"/>
<com.android.settingslib.widget.SelectorWithWidgetPreference
android:key="accessibility_control_autoclick_custom"
- android:title="@string/accessibility_autoclick_custom_title" />
+ android:title="@string/accessibility_autoclick_custom_title"
+ settings:controller="com.android.settings.accessibility.ToggleAutoclickPreferenceController"/>
<com.android.settingslib.widget.LayoutPreference
android:key="autoclick_custom_seekbar"
android:layout="@layout/accessibility_autoclick_custom_seekbar"
android:selectable="false"
android:visibility="gone"
- settings:controller="com.android.settings.accessibility.ToggleAutoclickCustomSeekbarController" />
+ settings:controller="com.android.settings.accessibility.ToggleAutoclickCustomSeekbarController"/>
<com.android.settings.accessibility.AccessibilityFooterPreference
android:key="accessibility_autoclick_footer"
android:title="@string/accessibility_autoclick_description"
- android:persistent="false"
android:selectable="false"
settings:searchable="false"
settings:controller="com.android.settings.accessibility.ToggleAutoclickFooterPreferenceController"/>
diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java
index 3dde687..4e93389 100644
--- a/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java
+++ b/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceController.java
@@ -43,6 +43,7 @@
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
@@ -55,7 +56,8 @@
* Controller that shows and updates the bluetooth device name
*/
public class AccessibilityHearingAidPreferenceController extends BasePreferenceController
- implements LifecycleObserver, OnStart, OnStop, BluetoothCallback {
+ implements LifecycleObserver, OnStart, OnStop, BluetoothCallback,
+ LocalBluetoothProfileManager.ServiceListener {
private static final String TAG = "AccessibilityHearingAidPreferenceController";
private Preference mHearingAidPreference;
@@ -84,12 +86,16 @@
private final LocalBluetoothManager mLocalBluetoothManager;
private final BluetoothAdapter mBluetoothAdapter;
+ private final LocalBluetoothProfileManager mProfileManager;
+ private final CachedBluetoothDeviceManager mCachedDeviceManager;
private FragmentManager mFragmentManager;
public AccessibilityHearingAidPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mLocalBluetoothManager = getLocalBluetoothManager();
+ mProfileManager = mLocalBluetoothManager.getProfileManager();
+ mCachedDeviceManager = mLocalBluetoothManager.getCachedDeviceManager();
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
}
@@ -111,12 +117,14 @@
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(mHearingAidChangedReceiver, filter);
mLocalBluetoothManager.getEventManager().registerCallback(this);
+ mProfileManager.addServiceListener(this);
}
@Override
public void onStop() {
mContext.unregisterReceiver(mHearingAidChangedReceiver);
mLocalBluetoothManager.getEventManager().unregisterCallback(this);
+ mProfileManager.removeServiceListener(this);
}
@Override
@@ -173,6 +181,22 @@
}
}
+ @Override
+ public void onServiceConnected() {
+ // Every registered ProfileService will callback. So we need to use isProfileReady() to
+ // check is the HearingAidService callback here, not other service.
+ // When hearing aids service connected, updating the UI.
+ if (mProfileManager.getHearingAidProfile().isProfileReady()) {
+ updateState(mHearingAidPreference);
+ mProfileManager.removeServiceListener(this);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected() {
+ // Do nothing
+ }
+
public void setFragmentManager(FragmentManager fragmentManager) {
mFragmentManager = fragmentManager;
}
@@ -183,14 +207,11 @@
return null;
}
- final CachedBluetoothDeviceManager deviceManager =
- mLocalBluetoothManager.getCachedDeviceManager();
- final HearingAidProfile hearingAidProfile =
- mLocalBluetoothManager.getProfileManager().getHearingAidProfile();
+ final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
final List<BluetoothDevice> deviceList = hearingAidProfile.getConnectedDevices();
for (BluetoothDevice obj : deviceList) {
- if (!deviceManager.isSubDevice(obj)) {
- return deviceManager.findDevice(obj);
+ if (!mCachedDeviceManager.isSubDevice(obj)) {
+ return mCachedDeviceManager.findDevice(obj);
}
}
return null;
@@ -201,13 +222,10 @@
return 0;
}
- final CachedBluetoothDeviceManager deviceManager =
- mLocalBluetoothManager.getCachedDeviceManager();
- final HearingAidProfile hearingAidProfile =
- mLocalBluetoothManager.getProfileManager().getHearingAidProfile();
+ final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
final List<BluetoothDevice> deviceList = hearingAidProfile.getConnectedDevices();
return (int) deviceList.stream()
- .filter(device -> !deviceManager.isSubDevice(device))
+ .filter(device -> !mCachedDeviceManager.isSubDevice(device))
.count();
}
diff --git a/src/com/android/settings/accessibility/AutoclickPreferenceController.java b/src/com/android/settings/accessibility/AutoclickPreferenceController.java
index f4df774..b118718 100644
--- a/src/com/android/settings/accessibility/AutoclickPreferenceController.java
+++ b/src/com/android/settings/accessibility/AutoclickPreferenceController.java
@@ -16,6 +16,9 @@
package com.android.settings.accessibility;
+import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
+import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
+
import android.content.Context;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;
@@ -23,8 +26,19 @@
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
+/** Preference controller for autoclick (dwell timing). */
public class AutoclickPreferenceController extends BasePreferenceController {
+ /**
+ * Resource ids from which autoclick preference summaries should be derived. The strings have
+ * placeholder for integer delay value.
+ */
+ private static final int[] AUTOCLICK_PREFERENCE_SUMMARIES = {
+ R.plurals.accessibilty_autoclick_preference_subtitle_short_delay,
+ R.plurals.accessibilty_autoclick_preference_subtitle_medium_delay,
+ R.plurals.accessibilty_autoclick_preference_subtitle_long_delay
+ };
+
public AutoclickPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@@ -37,14 +51,29 @@
@Override
public CharSequence getSummary() {
final boolean enabled = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, 0) == 1;
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF) == ON;
if (!enabled) {
return mContext.getResources().getText(R.string.off);
}
- final int delay = Settings.Secure.getInt(mContext.getContentResolver(),
+ final int delayMillis = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
AccessibilityManager.AUTOCLICK_DELAY_DEFAULT);
- return ToggleAutoclickPreferenceFragment.getAutoclickPreferenceSummary(
- mContext.getResources(), delay);
+ final int summaryIndex = getAutoclickPreferenceSummaryIndex(delayMillis);
+ return AutoclickUtils.getAutoclickDelaySummary(mContext.getResources(),
+ AUTOCLICK_PREFERENCE_SUMMARIES[summaryIndex], delayMillis);
}
-}
\ No newline at end of file
+
+ /** Finds index of the summary that should be used for the provided autoclick delay. */
+ private int getAutoclickPreferenceSummaryIndex(int delay) {
+ if (delay <= AutoclickUtils.MIN_AUTOCLICK_DELAY_MS) {
+ return 0;
+ }
+ if (delay >= AutoclickUtils.MAX_AUTOCLICK_DELAY_MS) {
+ return AUTOCLICK_PREFERENCE_SUMMARIES.length - 1;
+ }
+ int delayRange =
+ AutoclickUtils.MAX_AUTOCLICK_DELAY_MS - AutoclickUtils.MIN_AUTOCLICK_DELAY_MS;
+ int rangeSize = (delayRange) / (AUTOCLICK_PREFERENCE_SUMMARIES.length - 1);
+ return (delay - AutoclickUtils.MIN_AUTOCLICK_DELAY_MS) / rangeSize;
+ }
+}
diff --git a/src/com/android/settings/accessibility/AutoclickUtils.java b/src/com/android/settings/accessibility/AutoclickUtils.java
new file mode 100644
index 0000000..cd7d662
--- /dev/null
+++ b/src/com/android/settings/accessibility/AutoclickUtils.java
@@ -0,0 +1,78 @@
+/*
+ * 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.accessibility;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.PluralsRes;
+import android.content.res.Resources;
+
+import java.lang.annotation.Retention;
+
+/** Provides utility methods related auto click. */
+public final class AutoclickUtils {
+
+ /** Used for autoclick mode in the preferences editor. */
+ static final String KEY_DELAY_MODE = "delay_mode";
+
+ /** Used for autoclick custom delay in the preferences editor. */
+ static final String KEY_CUSTOM_DELAY_VALUE = "custom_delay_value";
+
+ /** Min allowed autoclick delay value. */
+ static final int MIN_AUTOCLICK_DELAY_MS = 200;
+
+ /** Max allowed autoclick delay value. */
+ static final int MAX_AUTOCLICK_DELAY_MS = 1000;
+
+ /**
+ * Allowed autoclick delay values are discrete. This is the difference between two allowed
+ * values.
+ */
+ static final int AUTOCLICK_DELAY_STEP = 100;
+
+ @Retention(SOURCE)
+ @IntDef({
+ Quantity.ONE,
+ Quantity.FEW
+ })
+ private @interface Quantity {
+ int ONE = 1;
+ int FEW = 3;
+ }
+
+ /**
+ * Gets string that should be used for provided autoclick delay.
+ *
+ * @param resources Resources from which string should be retrieved.
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ * @param delayMillis Delay for whose value summary should be retrieved.
+ */
+ public static CharSequence getAutoclickDelaySummary(Resources resources,
+ @PluralsRes int id, int delayMillis) {
+ final int quantity = (delayMillis == 1000) ? Quantity.ONE : Quantity.FEW;
+ final float delaySecond = (float) delayMillis / 1000;
+ // Only show integer when delay time is 1.
+ final String decimalFormat = (delaySecond == 1) ? "%.0f" : "%.1f";
+
+ return resources.getQuantityString(id, quantity, String.format(decimalFormat, delaySecond));
+ }
+
+ private AutoclickUtils(){}
+}
diff --git a/src/com/android/settings/accessibility/DisableAnimationsPreferenceController.java b/src/com/android/settings/accessibility/DisableAnimationsPreferenceController.java
index 5630dd9..2ec1d70 100644
--- a/src/com/android/settings/accessibility/DisableAnimationsPreferenceController.java
+++ b/src/com/android/settings/accessibility/DisableAnimationsPreferenceController.java
@@ -16,40 +16,66 @@
package com.android.settings.accessibility;
+import android.content.ContentResolver;
import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
import android.provider.Settings;
-import android.text.TextUtils;
import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
-public class DisableAnimationsPreferenceController extends TogglePreferenceController {
+import java.util.Arrays;
+import java.util.List;
+
+/** A toggle preference controller for disable animations. */
+public class DisableAnimationsPreferenceController extends TogglePreferenceController implements
+ LifecycleObserver, OnStart, OnStop {
@VisibleForTesting
- static final String ANIMATION_ON_VALUE = "1";
+ static final float ANIMATION_ON_VALUE = 1.0f;
@VisibleForTesting
- static final String ANIMATION_OFF_VALUE = "0";
+ static final float ANIMATION_OFF_VALUE = 0.0f;
// Settings that should be changed when toggling animations
@VisibleForTesting
- static final String[] TOGGLE_ANIMATION_TARGETS = {
+ static final List<String> TOGGLE_ANIMATION_TARGETS = Arrays.asList(
Settings.Global.WINDOW_ANIMATION_SCALE, Settings.Global.TRANSITION_ANIMATION_SCALE,
Settings.Global.ANIMATOR_DURATION_SCALE
+ );
+
+ private final ContentObserver mSettingsContentObserver = new ContentObserver(
+ new Handler(Looper.getMainLooper())){
+ @Override
+ public void onChange(boolean selfChange) {
+ updateState(mPreference);
+ }
};
+ private final ContentResolver mContentResolver;
+ private SwitchPreference mPreference;
+
public DisableAnimationsPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
+ mContentResolver = context.getContentResolver();
}
@Override
public boolean isChecked() {
boolean allAnimationsDisabled = true;
for (String animationSetting : TOGGLE_ANIMATION_TARGETS) {
- if (!TextUtils.equals(
- Settings.Global.getString(mContext.getContentResolver(), animationSetting),
- ANIMATION_OFF_VALUE)) {
+ final float value = Settings.Global.getFloat(mContentResolver, animationSetting,
+ ANIMATION_ON_VALUE);
+ if (value > ANIMATION_OFF_VALUE) {
allAnimationsDisabled = false;
break;
}
@@ -59,11 +85,11 @@
@Override
public boolean setChecked(boolean isChecked) {
- final String newAnimationValue = isChecked ? ANIMATION_OFF_VALUE : ANIMATION_ON_VALUE;
+ final float newAnimationValue = isChecked ? ANIMATION_OFF_VALUE : ANIMATION_ON_VALUE;
boolean allAnimationSet = true;
for (String animationPreference : TOGGLE_ANIMATION_TARGETS) {
- allAnimationSet &= Settings.Global.putString(mContext.getContentResolver(),
- animationPreference, newAnimationValue);
+ allAnimationSet &= Settings.Global.putFloat(mContentResolver, animationPreference,
+ newAnimationValue);
}
return allAnimationSet;
}
@@ -77,4 +103,23 @@
public int getSliceHighlightMenuRes() {
return R.string.menu_key_accessibility;
}
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @Override
+ public void onStart() {
+ for (String key : TOGGLE_ANIMATION_TARGETS) {
+ mContentResolver.registerContentObserver(Settings.Global.getUriFor(key),
+ false, mSettingsContentObserver, UserHandle.USER_ALL);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ mContentResolver.unregisterContentObserver(mSettingsContentObserver);
+ }
}
diff --git a/src/com/android/settings/accessibility/HearingAidUtils.java b/src/com/android/settings/accessibility/HearingAidUtils.java
index a3d2c93..dcbac5b 100644
--- a/src/com/android/settings/accessibility/HearingAidUtils.java
+++ b/src/com/android/settings/accessibility/HearingAidUtils.java
@@ -53,7 +53,7 @@
Log.w(TAG, "Can not launch hearing aid pairing dialog for invalid side");
return;
}
- HearingAidPairingDialogFragment.newInstance(device).show(fragmentManager,
+ HearingAidPairingDialogFragment.newInstance(device.getAddress()).show(fragmentManager,
HearingAidPairingDialogFragment.TAG);
}
}
diff --git a/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarController.java b/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarController.java
index 1a58b39..372c6f5 100644
--- a/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarController.java
+++ b/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarController.java
@@ -18,8 +18,11 @@
import static android.content.Context.MODE_PRIVATE;
-import static com.android.settings.accessibility.ToggleAutoclickPreferenceController.KEY_DELAY_MODE;
-import static com.android.settings.accessibility.ToggleAutoclickPreferenceFragment.Quantity;
+import static com.android.settings.accessibility.AutoclickUtils.AUTOCLICK_DELAY_STEP;
+import static com.android.settings.accessibility.AutoclickUtils.KEY_CUSTOM_DELAY_VALUE;
+import static com.android.settings.accessibility.AutoclickUtils.KEY_DELAY_MODE;
+import static com.android.settings.accessibility.AutoclickUtils.MAX_AUTOCLICK_DELAY_MS;
+import static com.android.settings.accessibility.AutoclickUtils.MIN_AUTOCLICK_DELAY_MS;
import android.content.ContentResolver;
import android.content.Context;
@@ -35,36 +38,16 @@
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
-import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
-import com.android.settingslib.core.lifecycle.events.OnPause;
-import com.android.settingslib.core.lifecycle.events.OnResume;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.widget.LayoutPreference;
-/**
- * Controller class that controls accessibility autoclick seekbar settings.
- */
+/** Controller class that controls accessibility autoclick seekbar settings. */
public class ToggleAutoclickCustomSeekbarController extends BasePreferenceController
- implements LifecycleObserver, OnResume, OnPause,
+ implements LifecycleObserver, OnStart, OnStop,
SharedPreferences.OnSharedPreferenceChangeListener {
- private static final String CONTROL_AUTOCLICK_DELAY_SECURE =
- Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY;
-
- @VisibleForTesting
- static final String KEY_CUSTOM_DELAY_VALUE = "custom_delay_value";
-
- // Min allowed autoclick delay value.
- static final int MIN_AUTOCLICK_DELAY_MS = 200;
-
- // Max allowed autoclick delay value.
- static final int MAX_AUTOCLICK_DELAY_MS = 1000;
-
- // Allowed autoclick delay values are discrete.
- // This is the difference between two allowed values.
- @VisibleForTesting
- static final int AUTOCLICK_DELAY_STEP = 100;
-
private final SharedPreferences mSharedPreferences;
private final ContentResolver mContentResolver;
private ImageView mShorter;
@@ -98,29 +81,20 @@
mContentResolver = context.getContentResolver();
}
- public ToggleAutoclickCustomSeekbarController(Context context, Lifecycle lifecycle,
- String preferenceKey) {
- this(context, preferenceKey);
-
- if (lifecycle != null) {
- lifecycle.addObserver(this);
- }
- }
-
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
- public void onResume() {
+ public void onStart() {
if (mSharedPreferences != null) {
mSharedPreferences.registerOnSharedPreferenceChangeListener(this);
}
}
@Override
- public void onPause() {
+ public void onStop() {
if (mSharedPreferences != null) {
mSharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
}
@@ -132,7 +106,7 @@
final LayoutPreference preference = screen.findPreference(getPreferenceKey());
if (isAvailable()) {
- int delayMillis = getSharedPreferenceForDelayValue();
+ final int delayMillis = getSharedPreferenceForDelayValue();
// Initialize seek bar preference. Sets seek bar size to the number of possible delay
// values.
mSeekBar = preference.findViewById(R.id.autoclick_delay);
@@ -144,14 +118,10 @@
mDelayLabel.setText(delayTimeToString(delayMillis));
mShorter = preference.findViewById(R.id.shorter);
- mShorter.setOnClickListener(v -> {
- minusDelayByImageView();
- });
+ mShorter.setOnClickListener(v -> minusDelayByImageView());
mLonger = preference.findViewById(R.id.longer);
- mLonger.setOnClickListener(v -> {
- plusDelayByImageView();
- });
+ mLonger.setOnClickListener(v -> plusDelayByImageView());
}
}
@@ -184,12 +154,9 @@
return mSharedPreferences.getInt(KEY_CUSTOM_DELAY_VALUE, delayMillis);
}
- private void putSecureInt(String name, int value) {
- Settings.Secure.putInt(mContentResolver, name, value);
- }
-
private void updateCustomDelayValue(int delayMillis) {
- putSecureInt(CONTROL_AUTOCLICK_DELAY_SECURE, delayMillis);
+ Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
+ delayMillis);
mSharedPreferences.edit().putInt(KEY_CUSTOM_DELAY_VALUE, delayMillis).apply();
mSeekBar.setProgress(delayToSeekBarProgress(delayMillis));
mDelayLabel.setText(delayTimeToString(delayMillis));
@@ -208,15 +175,8 @@
updateCustomDelayValue(delayMillis + AUTOCLICK_DELAY_STEP);
}
}
-
private CharSequence delayTimeToString(int delayMillis) {
- final int quantity = (delayMillis == 1000) ? Quantity.ONE : Quantity.FEW;
- final float delaySecond = (float) delayMillis / 1000;
- // Only show integer when delay time is 1.
- final String decimalFormat = (delaySecond == 1) ? "%.0f" : "%.1f";
-
- return mContext.getResources().getQuantityString(
- R.plurals.accessibilty_autoclick_delay_unit_second,
- quantity, String.format(decimalFormat, delaySecond));
+ return AutoclickUtils.getAutoclickDelaySummary(mContext.getResources(),
+ R.plurals.accessibilty_autoclick_delay_unit_second, delayMillis);
}
}
diff --git a/src/com/android/settings/accessibility/ToggleAutoclickPreferenceController.java b/src/com/android/settings/accessibility/ToggleAutoclickPreferenceController.java
index 2bf17fd..f5ce509 100644
--- a/src/com/android/settings/accessibility/ToggleAutoclickPreferenceController.java
+++ b/src/com/android/settings/accessibility/ToggleAutoclickPreferenceController.java
@@ -18,6 +18,10 @@
import static android.content.Context.MODE_PRIVATE;
+import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
+import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
+import static com.android.settings.accessibility.AutoclickUtils.KEY_DELAY_MODE;
+
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
@@ -25,49 +29,28 @@
import android.provider.Settings;
import android.util.ArrayMap;
-import androidx.annotation.VisibleForTesting;
-import androidx.lifecycle.LifecycleObserver;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
-import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.widget.LayoutPreference;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import java.util.Map;
-/**
- * Controller class that controls accessibility autoclick settings.
- */
+/** Controller class that controls accessibility autoclick settings. */
public class ToggleAutoclickPreferenceController extends BasePreferenceController implements
- LifecycleObserver, SelectorWithWidgetPreference.OnClickListener, PreferenceControllerMixin {
+ LifecycleObserver, OnStart, OnStop, SelectorWithWidgetPreference.OnClickListener,
+ SharedPreferences.OnSharedPreferenceChangeListener {
- @VisibleForTesting
- static final String CONTROL_AUTOCLICK_DELAY_SECURE =
- Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY;
-
- @VisibleForTesting
- static final String KEY_AUTOCLICK_CUSTOM_SEEKBAR = "autoclick_custom_seekbar";
- static final String KEY_DELAY_MODE = "delay_mode";
-
- @VisibleForTesting
- static final int AUTOCLICK_OFF_MODE = 0;
-
- @VisibleForTesting
- static final int AUTOCLICK_CUSTOM_MODE = 2000;
-
- // Pair the preference key and autoclick mode value.
- @VisibleForTesting
- Map<String, Integer> mAccessibilityAutoclickKeyToValueMap = new ArrayMap<>();
-
- private SharedPreferences mSharedPreferences;
- private final ContentResolver mContentResolver;
- private final Resources mResources;
- private OnChangeListener mOnChangeListener;
- private SelectorWithWidgetPreference mDelayModePref;
+ private static final String KEY_AUTOCLICK_CUSTOM_SEEKBAR = "autoclick_custom_seekbar";
+ private static final int AUTOCLICK_OFF_MODE = 0;
+ private static final int AUTOCLICK_CUSTOM_MODE = 2000;
/**
* Seek bar preference for autoclick delay value. The seek bar has values between 0 and
@@ -75,29 +58,33 @@
* delay values before saving them in settings.
*/
private LayoutPreference mSeekBerPreference;
- private int mCurrentUiAutoClickMode;
+ private SelectorWithWidgetPreference mDelayModePref;
+ private Map<String, Integer> mAccessibilityAutoclickKeyToValueMap = new ArrayMap<>();
+ private final SharedPreferences mSharedPreferences;
+ private final ContentResolver mContentResolver;
+ private final Resources mResources;
public ToggleAutoclickPreferenceController(Context context, String preferenceKey) {
- this(context, /* lifecycle= */ null, preferenceKey);
- }
-
- public ToggleAutoclickPreferenceController(Context context, Lifecycle lifecycle,
- String preferenceKey) {
super(context, preferenceKey);
-
mSharedPreferences = context.getSharedPreferences(context.getPackageName(), MODE_PRIVATE);
mContentResolver = context.getContentResolver();
mResources = context.getResources();
-
- setAutoclickModeToKeyMap();
-
- if (lifecycle != null) {
- lifecycle.addObserver(this);
- }
}
- public void setOnChangeListener(OnChangeListener listener) {
- mOnChangeListener = listener;
+ @Override
+ public void onStart() {
+ mSharedPreferences.registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onStop() {
+ mSharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+ @Nullable String key) {
+ updateState(mDelayModePref);
}
@Override
@@ -109,86 +96,51 @@
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
- mDelayModePref = (SelectorWithWidgetPreference)
- screen.findPreference(getPreferenceKey());
+ mDelayModePref = screen.findPreference(getPreferenceKey());
mDelayModePref.setOnClickListener(this);
- mSeekBerPreference = (LayoutPreference) screen.findPreference(KEY_AUTOCLICK_CUSTOM_SEEKBAR);
- updateState((Preference) mDelayModePref);
+ mSeekBerPreference = screen.findPreference(KEY_AUTOCLICK_CUSTOM_SEEKBAR);
+ updateState(mDelayModePref);
}
@Override
public void onRadioButtonClicked(SelectorWithWidgetPreference preference) {
- final int value = mAccessibilityAutoclickKeyToValueMap.get(mPreferenceKey);
- handleRadioButtonPreferenceChange(value);
- if (mOnChangeListener != null) {
- mOnChangeListener.onCheckedChanged(mDelayModePref);
+ final int mode = getAutoclickModeToKeyMap().get(mPreferenceKey);
+ Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
+ (mode != AUTOCLICK_OFF_MODE) ? ON : OFF);
+ mSharedPreferences.edit().putInt(KEY_DELAY_MODE, mode).apply();
+ if (mode != AUTOCLICK_CUSTOM_MODE) {
+ Settings.Secure.putInt(mContentResolver, Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
+ mode);
}
}
- private void updatePreferenceCheckedState(int mode) {
- if (mCurrentUiAutoClickMode == mode) {
- mDelayModePref.setChecked(true);
- }
- }
-
- private void updatePreferenceVisibleState(int mode) {
- mSeekBerPreference.setVisible(mCurrentUiAutoClickMode == mode);
- }
-
@Override
public void updateState(Preference preference) {
super.updateState(preference);
-
final boolean enabled = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, 0) == 1;
-
- mCurrentUiAutoClickMode =
- enabled ? getSharedPreferenceForAutoClickMode() : AUTOCLICK_OFF_MODE;
-
- // Reset RadioButton.
- mDelayModePref.setChecked(false);
- final int mode = mAccessibilityAutoclickKeyToValueMap.get(mDelayModePref.getKey());
- updatePreferenceCheckedState(mode);
- updatePreferenceVisibleState(mode);
- }
-
- /** Listener interface handles checked event. */
- public interface OnChangeListener {
- /**
- * A hook that is called when preference checked.
- */
- void onCheckedChanged(Preference preference);
- }
-
- private void setAutoclickModeToKeyMap() {
- final String[] autoclickKeys = mResources.getStringArray(
- R.array.accessibility_autoclick_control_selector_keys);
-
- final int[] autoclickValues = mResources.getIntArray(
- R.array.accessibility_autoclick_selector_values);
-
- final int autoclickValueCount = autoclickValues.length;
- for (int i = 0; i < autoclickValueCount; i++) {
- mAccessibilityAutoclickKeyToValueMap.put(autoclickKeys[i], autoclickValues[i]);
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF) == ON;
+ final int currentUiAutoClickMode = enabled
+ ? mSharedPreferences.getInt(KEY_DELAY_MODE, AUTOCLICK_CUSTOM_MODE)
+ : AUTOCLICK_OFF_MODE;
+ final int mode = getAutoclickModeToKeyMap().get(mDelayModePref.getKey());
+ mDelayModePref.setChecked(currentUiAutoClickMode == mode);
+ if (mode == AUTOCLICK_CUSTOM_MODE) {
+ mSeekBerPreference.setVisible(mDelayModePref.isChecked());
}
}
- private void handleRadioButtonPreferenceChange(int preference) {
- putSecureInt(Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
- (preference != AUTOCLICK_OFF_MODE) ? /* enabled */ 1 : /* disabled */ 0);
-
- mSharedPreferences.edit().putInt(KEY_DELAY_MODE, preference).apply();
-
- if (preference != AUTOCLICK_CUSTOM_MODE) {
- putSecureInt(CONTROL_AUTOCLICK_DELAY_SECURE, preference);
+ /** Returns the paring preference key and autoclick mode value listing. */
+ private Map<String, Integer> getAutoclickModeToKeyMap() {
+ if (mAccessibilityAutoclickKeyToValueMap.size() == 0) {
+ final String[] autoclickKeys = mResources.getStringArray(
+ R.array.accessibility_autoclick_control_selector_keys);
+ final int[] autoclickValues = mResources.getIntArray(
+ R.array.accessibility_autoclick_selector_values);
+ final int autoclickValueCount = autoclickValues.length;
+ for (int i = 0; i < autoclickValueCount; i++) {
+ mAccessibilityAutoclickKeyToValueMap.put(autoclickKeys[i], autoclickValues[i]);
+ }
}
- }
-
- private void putSecureInt(String name, int value) {
- Settings.Secure.putInt(mContentResolver, name, value);
- }
-
- private int getSharedPreferenceForAutoClickMode() {
- return mSharedPreferences.getInt(KEY_DELAY_MODE, AUTOCLICK_CUSTOM_MODE);
+ return mAccessibilityAutoclickKeyToValueMap;
}
}
diff --git a/src/com/android/settings/accessibility/ToggleAutoclickPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleAutoclickPreferenceFragment.java
index 03d4a4c..168e99d 100644
--- a/src/com/android/settings/accessibility/ToggleAutoclickPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleAutoclickPreferenceFragment.java
@@ -16,94 +16,21 @@
package com.android.settings.accessibility;
-import static com.android.settings.accessibility.ToggleAutoclickCustomSeekbarController.MAX_AUTOCLICK_DELAY_MS;
-import static com.android.settings.accessibility.ToggleAutoclickCustomSeekbarController.MIN_AUTOCLICK_DELAY_MS;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.content.res.Resources;
-
-import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settingslib.core.AbstractPreferenceController;
-import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
-import java.lang.annotation.Retention;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* Fragment for preference screen for settings related to Automatically click after mouse stops
* feature.
*/
@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC)
-public class ToggleAutoclickPreferenceFragment extends DashboardFragment
- implements ToggleAutoclickPreferenceController.OnChangeListener {
+public class ToggleAutoclickPreferenceFragment extends DashboardFragment {
private static final String TAG = "AutoclickPrefFragment";
- private static final List<AbstractPreferenceController> sControllers = new ArrayList<>();
-
- @Retention(SOURCE)
- @IntDef({
- Quantity.OTHER,
- Quantity.ONE,
- Quantity.FEW
- })
- @interface Quantity {
- int OTHER = 0;
- int ONE = 1;
- int FEW = 3;
- }
-
- /**
- * Resource ids from which autoclick preference summaries should be derived. The strings have
- * placeholder for integer delay value.
- */
- private static final int[] AUTOCLICK_PREFERENCE_SUMMARIES = {
- R.plurals.accessibilty_autoclick_preference_subtitle_short_delay,
- R.plurals.accessibilty_autoclick_preference_subtitle_medium_delay,
- R.plurals.accessibilty_autoclick_preference_subtitle_long_delay
- };
-
- /**
- * Gets string that should be used as a autoclick preference summary for provided autoclick
- * delay.
- *
- * @param resources Resources from which string should be retrieved.
- * @param delayMillis Delay for whose value summary should be retrieved.
- */
- static CharSequence getAutoclickPreferenceSummary(Resources resources, int delayMillis) {
- final int summaryIndex = getAutoclickPreferenceSummaryIndex(delayMillis);
- final int quantity = (delayMillis == 1000) ? Quantity.ONE : Quantity.FEW;
- final float delaySecond = (float) delayMillis / 1000;
- // Only show integer when delay time is 1.
- final String decimalFormat = (delaySecond == 1) ? "%.0f" : "%.1f";
-
- return resources.getQuantityString(AUTOCLICK_PREFERENCE_SUMMARIES[summaryIndex],
- quantity, String.format(decimalFormat, delaySecond));
- }
-
- /**
- * Finds index of the summary that should be used for the provided autoclick delay.
- */
- private static int getAutoclickPreferenceSummaryIndex(int delay) {
- if (delay <= MIN_AUTOCLICK_DELAY_MS) {
- return 0;
- }
- if (delay >= MAX_AUTOCLICK_DELAY_MS) {
- return AUTOCLICK_PREFERENCE_SUMMARIES.length - 1;
- }
- int delayRange = MAX_AUTOCLICK_DELAY_MS - MIN_AUTOCLICK_DELAY_MS;
- int rangeSize = (delayRange) / (AUTOCLICK_PREFERENCE_SUMMARIES.length - 1);
- return (delay - MIN_AUTOCLICK_DELAY_MS) / rangeSize;
- }
@Override
public int getMetricsCategory() {
@@ -125,58 +52,6 @@
return R.xml.accessibility_autoclick_settings;
}
- @Override
- public void onResume() {
- super.onResume();
-
- for (AbstractPreferenceController controller : sControllers) {
- ((ToggleAutoclickPreferenceController) controller).setOnChangeListener(this);
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- for (AbstractPreferenceController controller : sControllers) {
- ((ToggleAutoclickPreferenceController) controller).setOnChangeListener(null);
- }
- }
-
- @Override
- public void onCheckedChanged(Preference preference) {
- for (AbstractPreferenceController controller : sControllers) {
- controller.updateState(preference);
- }
- }
-
- @Override
- protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
- return buildPreferenceControllers(context, getSettingsLifecycle());
- }
-
- private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
- Lifecycle lifecycle) {
- Resources resources = context.getResources();
-
- String[] autoclickKeys = resources.getStringArray(
- R.array.accessibility_autoclick_control_selector_keys);
-
- final int length = autoclickKeys.length;
- for (int i = 0; i < length; i++) {
- sControllers.add(new ToggleAutoclickPreferenceController(
- context, lifecycle, autoclickKeys[i]));
- }
- return sControllers;
- }
-
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
- new BaseSearchIndexProvider(R.xml.accessibility_autoclick_settings) {
-
- @Override
- public List<AbstractPreferenceController> createPreferenceControllers(
- Context context) {
- return buildPreferenceControllers(context, null);
- }
- };
+ new BaseSearchIndexProvider(R.xml.accessibility_autoclick_settings);
}
diff --git a/src/com/android/settings/applications/AppInfoBase.java b/src/com/android/settings/applications/AppInfoBase.java
index 0f21097..155583e 100644
--- a/src/com/android/settings/applications/AppInfoBase.java
+++ b/src/com/android/settings/applications/AppInfoBase.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -235,6 +236,22 @@
.launch();
}
+ /** Starts app info fragment from SPA pages. */
+ public static void startAppInfoFragment(Class<?> fragment, String title, ApplicationInfo app,
+ Context context, int sourceMetricsCategory) {
+ final Bundle args = new Bundle();
+ args.putString(AppInfoBase.ARG_PACKAGE_NAME, app.packageName);
+ args.putInt(AppInfoBase.ARG_PACKAGE_UID, app.uid);
+
+ new SubSettingLauncher(context)
+ .setDestination(fragment.getName())
+ .setSourceMetricsCategory(sourceMetricsCategory)
+ .setTitleText(title)
+ .setArguments(args)
+ .setUserHandle(UserHandle.getUserHandleForUid(app.uid))
+ .launch();
+ }
+
public static class MyAlertDialogFragment extends InstrumentedDialogFragment {
private static final String ARG_ID = "id";
diff --git a/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java b/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java
index 26adb44..4251346 100644
--- a/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java
+++ b/src/com/android/settings/applications/appinfo/WriteSettingsDetails.java
@@ -18,11 +18,7 @@
import android.app.AppOpsManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.os.Bundle;
-import android.provider.Settings;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
@@ -41,20 +37,13 @@
public class WriteSettingsDetails extends AppInfoWithHeader implements OnPreferenceChangeListener,
OnPreferenceClickListener {
- private static final String KEY_APP_OPS_PREFERENCE_SCREEN = "app_ops_preference_screen";
private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch";
- private static final String LOG_TAG = "WriteSettingsDetails";
-
- private static final int [] APP_OPS_OP_CODE = {
- AppOpsManager.OP_WRITE_SETTINGS
- };
// Use a bridge to get the overlay details but don't initialize it to connect with all state.
// TODO: Break out this functionality into its own class.
private AppStateWriteSettingsBridge mAppBridge;
private AppOpsManager mAppOpsManager;
private SwitchPreference mSwitchPref;
- private Intent mSettingsIntent;
private WriteSettingsState mWriteSettingsState;
@Override
@@ -66,13 +55,10 @@
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
addPreferencesFromResource(R.xml.write_system_settings_permissions_details);
- mSwitchPref = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
+ mSwitchPref = findPreference(KEY_APP_OPS_SETTINGS_SWITCH);
mSwitchPref.setOnPreferenceChangeListener(this);
- mSettingsIntent = new Intent(Intent.ACTION_MAIN)
- .addCategory(Settings.INTENT_CATEGORY_USAGE_ACCESS_CONFIG)
- .setPackage(mPackageName);
}
@Override
@@ -107,16 +93,6 @@
logCategory, packageName);
}
- private boolean canWriteSettings(String pkgName) {
- int result = mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_WRITE_SETTINGS,
- mPackageInfo.applicationInfo.uid, pkgName);
- if (result == AppOpsManager.MODE_ALLOWED) {
- return true;
- }
-
- return false;
- }
-
@Override
protected boolean refreshUi() {
mWriteSettingsState = mAppBridge.getWriteSettingsInfo(mPackageName,
@@ -127,8 +103,6 @@
// you can't ask a user for a permission you didn't even declare!
mSwitchPref.setEnabled(mWriteSettingsState.permissionDeclared);
- ResolveInfo resolveInfo = mPm.resolveActivityAsUser(mSettingsIntent,
- PackageManager.GET_META_DATA, mUserId);
return true;
}
diff --git a/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java b/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java
index 60a100d..5ae7c17 100644
--- a/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java
+++ b/src/com/android/settings/bluetooth/HearingAidPairingDialogFragment.java
@@ -18,6 +18,9 @@
import android.app.Dialog;
import android.app.settings.SettingsEnums;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
@@ -29,30 +32,60 @@
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HearingAidProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
/**
* Provides a dialog to pair another side of hearing aid device.
*/
-public class HearingAidPairingDialogFragment extends InstrumentedDialogFragment {
+public class HearingAidPairingDialogFragment extends InstrumentedDialogFragment implements
+ CachedBluetoothDevice.Callback {
public static final String TAG = "HearingAidPairingDialogFragment";
- private static final String KEY_CACHED_DEVICE_SIDE = "cached_device_side";
+ private static final String KEY_DEVICE_ADDRESS = "device_address";
+ private LocalBluetoothManager mLocalBluetoothManager;
+ private CachedBluetoothDevice mDevice;
/**
* Creates a new {@link HearingAidPairingDialogFragment} and shows pair another side of hearing
- * aid device according to {@code CachedBluetoothDevice} side.
+ * aid device according to {@code deviceAddress}.
*
- * @param device The remote Bluetooth device, that needs to be hearing aid device.
+ * @param deviceAddress The remote Bluetooth device address, that needs to be a hearing aid
+ * device.
* @return a DialogFragment
*/
- public static HearingAidPairingDialogFragment newInstance(CachedBluetoothDevice device) {
+ public static HearingAidPairingDialogFragment newInstance(String deviceAddress) {
Bundle args = new Bundle(1);
- args.putInt(KEY_CACHED_DEVICE_SIDE, device.getDeviceSide());
+ args.putString(KEY_DEVICE_ADDRESS, deviceAddress);
final HearingAidPairingDialogFragment fragment = new HearingAidPairingDialogFragment();
fragment.setArguments(args);
return fragment;
}
@Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mLocalBluetoothManager = Utils.getLocalBtManager(context);
+ mDevice = getDevice();
+ if (mDevice != null) {
+ mDevice.registerCallback(this);
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ if (mDevice != null) {
+ mDevice.unregisterCallback(this);
+ }
+ }
+
+ private CachedBluetoothDevice getDevice() {
+ final String deviceAddress = getArguments().getString(KEY_DEVICE_ADDRESS);
+ final BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+ deviceAddress);
+ return mLocalBluetoothManager.getCachedDeviceManager().findDevice(device);
+ }
+
+ @Override
public int getMetricsCategory() {
return SettingsEnums.DIALOG_ACCESSIBILITY_HEARING_AID_PAIR_ANOTHER;
}
@@ -60,7 +93,7 @@
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
- final int deviceSide = getArguments().getInt(KEY_CACHED_DEVICE_SIDE);
+ final int deviceSide = mDevice.getDeviceSide();
final int titleId = R.string.bluetooth_pair_other_ear_dialog_title;
final int messageId = (deviceSide == HearingAidProfile.DeviceSide.SIDE_LEFT)
? R.string.bluetooth_pair_other_ear_dialog_left_ear_message
@@ -72,8 +105,7 @@
return new AlertDialog.Builder(getActivity())
.setTitle(titleId)
.setMessage(messageId)
- .setNegativeButton(
- android.R.string.cancel, /* listener= */ null)
+ .setNegativeButton(android.R.string.cancel, /* listener= */ null)
.setPositiveButton(pairBtnId, (dialog, which) -> positiveButtonListener())
.create();
}
@@ -84,4 +116,12 @@
.setSourceMetricsCategory(getMetricsCategory())
.launch();
}
+
+ @Override
+ public void onDeviceAttributesChanged() {
+ final CachedBluetoothDevice subDevice = mDevice.getSubDevice();
+ if (subDevice != null && subDevice.isConnectedHearingAidDevice()) {
+ this.dismiss();
+ }
+ }
}
diff --git a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java
index b58d9c3..e2d92c7 100644
--- a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java
@@ -69,8 +69,12 @@
public void onAttach(Context context) {
super.onAttach(context);
UwbPreferenceController uwbPreferenceController = use(UwbPreferenceController.class);
- if (uwbPreferenceController != null && getSettingsLifecycle() != null) {
- getSettingsLifecycle().addObserver(uwbPreferenceController);
+ // We only need the observer listen to the broadcast in the background for refreshing
+ // UI if the device supports UWB.
+ if (uwbPreferenceController != null && uwbPreferenceController.isUwbSupportedOnDevice()) {
+ if (getSettingsLifecycle() != null) {
+ getSettingsLifecycle().addObserver(uwbPreferenceController);
+ }
}
}
diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java
index 00fdf39..8b6fde7 100644
--- a/src/com/android/settings/homepage/SettingsHomepageActivity.java
+++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java
@@ -438,6 +438,8 @@
return;
}
+ targetIntent.setData(intent.getParcelableExtra(
+ SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA));
final ComponentName targetComponentName = targetIntent.resolveActivity(getPackageManager());
if (targetComponentName == null) {
Log.e(TAG, "No valid target for the deep link intent: " + targetIntent);
@@ -457,9 +459,6 @@
targetIntent.putExtra(EXTRA_IS_FROM_SETTINGS_HOMEPAGE, true);
targetIntent.putExtra(SettingsActivity.EXTRA_IS_FROM_SLICE, false);
- targetIntent.setData(intent.getParcelableExtra(
- SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA));
-
// Set 2-pane pair rule for the deep link page.
ActivityEmbeddingRulesController.registerTwoPanePairRule(this,
new ComponentName(getApplicationContext(), getClass()),
diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java
index b445123..21f026e 100644
--- a/src/com/android/settings/homepage/TopLevelSettings.java
+++ b/src/com/android/settings/homepage/TopLevelSettings.java
@@ -44,6 +44,7 @@
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.support.SupportPreferenceController;
import com.android.settings.widget.HomepagePreference;
@@ -165,6 +166,8 @@
public void onStart() {
if (mFirstStarted) {
mFirstStarted = false;
+ FeatureFactory.getFactory(getContext()).getSearchFeatureProvider().sendPreIndexIntent(
+ getContext());
} else if (mIsEmbeddingActivityEnabled && isOnlyOneActivityInTask()
&& !SplitController.getInstance().isActivityEmbedded(getActivity())) {
// Set default highlight menu key for 1-pane homepage since it will show the placeholder
@@ -354,7 +357,9 @@
}
private interface PreferenceJob {
- default void init() {}
+ default void init() {
+ }
+
void doForEach(Preference preference);
}
diff --git a/src/com/android/settings/search/SearchFeatureProvider.java b/src/com/android/settings/search/SearchFeatureProvider.java
index 1785361..b14a4d7 100644
--- a/src/com/android/settings/search/SearchFeatureProvider.java
+++ b/src/com/android/settings/search/SearchFeatureProvider.java
@@ -72,6 +72,12 @@
}
/**
+ * Send the pre-index intent.
+ */
+ default void sendPreIndexIntent(Context context){
+ }
+
+ /**
* Initializes the search toolbar.
*/
default void initSearchToolbar(FragmentActivity activity, Toolbar toolbar, int pageId) {
diff --git a/src/com/android/settings/search/SearchResultTrampoline.java b/src/com/android/settings/search/SearchResultTrampoline.java
index 7718ff3..6e7d911 100644
--- a/src/com/android/settings/search/SearchResultTrampoline.java
+++ b/src/com/android/settings/search/SearchResultTrampoline.java
@@ -22,6 +22,7 @@
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -86,8 +87,12 @@
return;
}
+ final Uri data = intent.getParcelableExtra(
+ SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA,
+ Uri.class);
try {
intent = Intent.parseUri(intentUriString, Intent.URI_INTENT_SCHEME);
+ intent.setData(data);
} catch (URISyntaxException e) {
Log.e(TAG, "Failed to parse deep link intent: " + e);
finish();
diff --git a/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceController.java b/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceController.java
index 49f94c8..9cc50f4 100644
--- a/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceController.java
+++ b/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceController.java
@@ -32,12 +32,14 @@
private static final int MY_USER_ID = UserHandle.myUserId();
private final LockPatternUtils mLockPatternUtils;
+ private TrustAgentManager mTrustAgentManager;
public ManageTrustAgentsPreferenceController(Context context, String key) {
super(context, key);
final SecurityFeatureProvider securityFeatureProvider = FeatureFactory.getFactory(context)
.getSecurityFeatureProvider();
mLockPatternUtils = securityFeatureProvider.getLockPatternUtils(context);
+ mTrustAgentManager = securityFeatureProvider.getTrustAgentManager();
}
@Override
@@ -64,6 +66,6 @@
}
private int getTrustAgentCount() {
- return mLockPatternUtils.getEnabledTrustAgents(MY_USER_ID).size();
+ return mTrustAgentManager.getActiveTrustAgents(mContext, mLockPatternUtils, false).size();
}
}
diff --git a/src/com/android/settings/security/trustagent/TrustAgentManager.java b/src/com/android/settings/security/trustagent/TrustAgentManager.java
index f5c693a..c8d403a 100644
--- a/src/com/android/settings/security/trustagent/TrustAgentManager.java
+++ b/src/com/android/settings/security/trustagent/TrustAgentManager.java
@@ -99,13 +99,27 @@
}
/**
- * Returns a list of trust agents.
+ * Returns a list of trust agents that have a android:settingsActivity set in their declaration.
*
* If {@link #ONLY_ONE_TRUST_AGENT} is set, the list will contain up to 1 agent instead of all
* available agents on device.
*/
public List<TrustAgentComponentInfo> getActiveTrustAgents(Context context,
LockPatternUtils utils) {
+ return getActiveTrustAgents(context, utils, true);
+ }
+
+ /**
+ * Returns a list of trust agents.
+ *
+ * If {@link #ONLY_ONE_TRUST_AGENT} is set, the list will contain up to 1 agent instead of all
+ * available agents on device.
+ *
+ * @param skipTrustAgentsWithNoActivity {@code false} to only include trustagents with
+ * android:settingsActivity set in their declaration, {@code true} otherwise.
+ */
+ public List<TrustAgentComponentInfo> getActiveTrustAgents(Context context,
+ LockPatternUtils utils, boolean skipTrustAgentsWithNoActivity) {
final int myUserId = UserHandle.myUserId();
final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
final PackageManager pm = context.getPackageManager();
@@ -125,9 +139,12 @@
}
final TrustAgentComponentInfo trustAgentComponentInfo =
getSettingsComponent(pm, resolveInfo);
- if (trustAgentComponentInfo.componentName == null ||
- !enabledTrustAgents.contains(getComponentName(resolveInfo)) ||
- TextUtils.isEmpty(trustAgentComponentInfo.title)) {
+ if (skipTrustAgentsWithNoActivity
+ && trustAgentComponentInfo.componentName == null) {
+ continue;
+ }
+ if (!enabledTrustAgents.contains(getComponentName(resolveInfo))
+ || TextUtils.isEmpty(trustAgentComponentInfo.title)) {
continue;
}
if (admin != null && dpm.getTrustAgentConfiguration(
diff --git a/src/com/android/settings/sim/PreferredSimDialogFragment.java b/src/com/android/settings/sim/PreferredSimDialogFragment.java
index 806e04b..2bfeafe 100644
--- a/src/com/android/settings/sim/PreferredSimDialogFragment.java
+++ b/src/com/android/settings/sim/PreferredSimDialogFragment.java
@@ -91,6 +91,7 @@
if (dialog == null) {
Log.d(TAG, "Dialog is null.");
dismiss();
+ return;
}
final SubscriptionInfo info = getPreferredSubscription();
diff --git a/src/com/android/settings/spa/SpaActivity.kt b/src/com/android/settings/spa/SpaActivity.kt
index 729668e..fab0e08 100644
--- a/src/com/android/settings/spa/SpaActivity.kt
+++ b/src/com/android/settings/spa/SpaActivity.kt
@@ -20,7 +20,7 @@
import android.content.Intent
import com.android.settingslib.spa.framework.BrowseActivity
-class SpaActivity : BrowseActivity(settingsPageProviders) {
+class SpaActivity : BrowseActivity(SpaEnvironment.settingsPageProviders) {
companion object {
@JvmStatic
fun startSpaActivity(context: Context, startDestination: String) {
diff --git a/src/com/android/settings/spa/SpaEnvironment.kt b/src/com/android/settings/spa/SpaEnvironment.kt
index fad7ef2..7da253a 100644
--- a/src/com/android/settings/spa/SpaEnvironment.kt
+++ b/src/com/android/settings/spa/SpaEnvironment.kt
@@ -18,16 +18,34 @@
import com.android.settings.spa.app.InstallUnknownAppsListProvider
import com.android.settings.spa.home.HomePageProvider
+import com.android.settingslib.spa.framework.common.SettingsEntryRepository
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settings.spa.notification.AppListNotificationsPageProvider
+import com.android.settings.spa.notification.NotificationMainPageProvider
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListTemplate
-private val togglePermissionAppListTemplate = TogglePermissionAppListTemplate(
- allProviders = listOf(InstallUnknownAppsListProvider),
-)
+object SpaEnvironment {
+ val settingsPageProviders: SettingsPageProviderRepository by
+ lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
+ val togglePermissionAppListTemplate = TogglePermissionAppListTemplate(
+ allProviders = listOf(InstallUnknownAppsListProvider),
+ )
+ SettingsPageProviderRepository(
+ allPageProviders = listOf(
+ HomePageProvider,
+ NotificationMainPageProvider,
+ AppListNotificationsPageProvider,
+ ) + togglePermissionAppListTemplate.createPageProviders(),
+ rootPages = listOf(
+ SettingsPage(HomePageProvider.name),
+ ),
+ )
+ }
+ val settingsEntryRepository: SettingsEntryRepository by
+ lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
+ SettingsEntryRepository(settingsPageProviders)
+ }
-val settingsPageProviders = SettingsPageProviderRepository(
- allPagesList = listOf(
- HomePageProvider,
- ) + togglePermissionAppListTemplate.createPageProviders(),
- rootPages = listOf(HomePageProvider.name),
-)
+ // TODO: add other environment setup here.
+}
diff --git a/src/com/android/settings/spa/home/HomePage.kt b/src/com/android/settings/spa/home/HomePage.kt
index 6cd6fe6..3b8fb84 100644
--- a/src/com/android/settings/spa/home/HomePage.kt
+++ b/src/com/android/settings/spa/home/HomePage.kt
@@ -17,10 +17,14 @@
package com.android.settings.spa.home
import android.os.Bundle
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.android.settings.R
+import com.android.settings.spa.SpaEnvironment
import com.android.settings.spa.app.InstallUnknownAppsListProvider
+import com.android.settings.spa.notification.NotificationMainPageProvider
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.widget.scaffold.HomeScaffold
@@ -37,5 +41,17 @@
private fun HomePage() {
HomeScaffold(title = stringResource(R.string.settings_label)) {
InstallUnknownAppsListProvider.EntryItem()
+ NotificationMainPageProvider.EntryItem()
+
+ /**
+ * A test button to generate hierarchy.
+ * TODO: remove it once the content provider is ready.
+ */
+ Button(onClick = {
+ SpaEnvironment.settingsEntryRepository.printAllPages()
+ SpaEnvironment.settingsEntryRepository.printAllEntries()
+ }) {
+ Text(text = "Generate Entry")
+ }
}
}
diff --git a/src/com/android/settings/spa/notification/AppListNotifications.kt b/src/com/android/settings/spa/notification/AppListNotifications.kt
new file mode 100644
index 0000000..da4ebb5
--- /dev/null
+++ b/src/com/android/settings/spa/notification/AppListNotifications.kt
@@ -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.spa.notification
+
+import android.app.settings.SettingsEnums
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.produceState
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settings.applications.AppInfoBase
+import com.android.settings.notification.app.AppNotificationSettings
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.rememberContext
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.template.app.AppListItemModel
+import com.android.settingslib.spaprivileged.template.app.AppListPage
+import com.android.settingslib.spaprivileged.template.app.AppListSwitchItem
+
+object AppListNotificationsPageProvider : SettingsPageProvider {
+ override val name = "AppListNotifications"
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ AppListPage(
+ title = stringResource(R.string.app_notifications_title),
+ listModel = rememberContext(::AppNotificationsListModel),
+ ) {
+ AppNotificationsItem(it)
+ }
+ }
+
+ @Composable
+ fun EntryItem() {
+ Preference(object : PreferenceModel {
+ override val title = stringResource(R.string.app_notifications_title)
+ override val summary = stringResource(R.string.app_notification_field_summary).toState()
+ override val onClick = navigator(name)
+ })
+ }
+}
+
+@Composable
+private fun AppNotificationsItem(
+ itemModel: AppListItemModel<AppNotificationsRecord>,
+) {
+ val appNotificationsRepository = rememberContext(::AppNotificationRepository)
+ val context = LocalContext.current
+ AppListSwitchItem(
+ itemModel = itemModel,
+ onClick = {
+ navigateToAppNotificationSettings(
+ context = context,
+ app = itemModel.record.app,
+ )
+ },
+ checked = itemModel.record.controller.isEnabled.observeAsState(),
+ changeable = produceState(initialValue = false) {
+ value = appNotificationsRepository.isChangeable(itemModel.record.app)
+ },
+ onCheckedChange = itemModel.record.controller::setEnabled,
+ )
+}
+
+private fun navigateToAppNotificationSettings(context: Context, app: ApplicationInfo) {
+ AppInfoBase.startAppInfoFragment(
+ AppNotificationSettings::class.java,
+ context.getString(R.string.notifications_title),
+ app,
+ context,
+ SettingsEnums.MANAGE_APPLICATIONS_NOTIFICATIONS,
+ )
+}
diff --git a/src/com/android/settings/spa/notification/AppNotificationController.kt b/src/com/android/settings/spa/notification/AppNotificationController.kt
new file mode 100644
index 0000000..1ce72e0
--- /dev/null
+++ b/src/com/android/settings/spa/notification/AppNotificationController.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.spa.notification
+
+import android.content.pm.ApplicationInfo
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+
+class AppNotificationController(
+ private val repository: AppNotificationRepository,
+ private val app: ApplicationInfo,
+) {
+ val isEnabled: LiveData<Boolean>
+ get() = _isEnabled
+
+ fun getEnabled() = _isEnabled.get()
+
+ fun setEnabled(enabled: Boolean) {
+ if (repository.setEnabled(app, enabled)) {
+ _isEnabled.postValue(enabled)
+ }
+ }
+
+ private val _isEnabled = object : MutableLiveData<Boolean>() {
+ override fun onActive() {
+ postValue(repository.isEnabled(app))
+ }
+
+ override fun onInactive() {
+ }
+
+ fun get(): Boolean = value ?: repository.isEnabled(app).also {
+ postValue(it)
+ }
+ }
+}
diff --git a/src/com/android/settings/spa/notification/AppNotificationRepository.kt b/src/com/android/settings/spa/notification/AppNotificationRepository.kt
new file mode 100644
index 0000000..c73aa00
--- /dev/null
+++ b/src/com/android/settings/spa/notification/AppNotificationRepository.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.spa.notification
+
+import android.Manifest
+import android.annotation.IntRange
+import android.app.INotificationManager
+import android.app.NotificationChannel
+import android.app.NotificationManager.IMPORTANCE_NONE
+import android.app.NotificationManager.IMPORTANCE_UNSPECIFIED
+import android.app.usage.IUsageStatsManager
+import android.app.usage.UsageEvents
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.os.Build
+import android.os.RemoteException
+import android.os.ServiceManager
+import android.util.Log
+import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasRequestPermission
+import java.util.concurrent.TimeUnit
+import kotlin.math.max
+import kotlin.math.roundToInt
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * This contains how often an app sends notifications and how recently it sent one.
+ */
+data class NotificationSentState(
+ @IntRange(from = 0)
+ var lastSent: Long = 0,
+
+ @IntRange(from = 0)
+ var sentCount: Int = 0,
+)
+
+class AppNotificationRepository(private val context: Context) {
+ fun getAggregatedUsageEvents(userIdFlow: Flow<Int>): Flow<Map<String, NotificationSentState>> =
+ userIdFlow.map { userId ->
+ val aggregatedStats = mutableMapOf<String, NotificationSentState>()
+ queryEventsForUser(userId)?.let { events ->
+ val event = UsageEvents.Event()
+ while (events.hasNextEvent()) {
+ events.getNextEvent(event)
+ if (event.eventType == UsageEvents.Event.NOTIFICATION_INTERRUPTION) {
+ aggregatedStats.getOrPut(event.packageName, ::NotificationSentState)
+ .apply {
+ lastSent = max(lastSent, event.timeStamp)
+ sentCount++
+ }
+ }
+ }
+ }
+ aggregatedStats
+ }
+
+ private fun queryEventsForUser(userId: Int): UsageEvents? {
+ val now = System.currentTimeMillis()
+ val startTime = now - TimeUnit.DAYS.toMillis(DAYS_TO_CHECK)
+ return try {
+ usageStatsManager.queryEventsForUser(startTime, now, userId, context.packageName)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed IUsageStatsManager.queryEventsForUser(): ", e)
+ null
+ }
+ }
+
+ fun isEnabled(app: ApplicationInfo): Boolean =
+ notificationManager.areNotificationsEnabledForPackage(app.packageName, app.uid)
+
+ fun isChangeable(app: ApplicationInfo): Boolean {
+ if (notificationManager.isImportanceLocked(app.packageName, app.uid)) {
+ return false
+ }
+
+ // If the app targets T but has not requested the permission, we cannot change the
+ // permission state.
+ return app.targetSdkVersion < Build.VERSION_CODES.TIRAMISU ||
+ hasRequestPermission(app, Manifest.permission.POST_NOTIFICATIONS)
+ }
+
+ fun setEnabled(app: ApplicationInfo, enabled: Boolean): Boolean {
+ if (onlyHasDefaultChannel(app)) {
+ getChannel(app, NotificationChannel.DEFAULT_CHANNEL_ID)?.let { channel ->
+ channel.importance = if (enabled) IMPORTANCE_UNSPECIFIED else IMPORTANCE_NONE
+ updateChannel(app, channel)
+ }
+ }
+ return try {
+ notificationManager.setNotificationsEnabledForPackage(app.packageName, app.uid, enabled)
+ true
+ } catch (e: Exception) {
+ Log.w(TAG, "Error calling INotificationManager", e)
+ false
+ }
+ }
+
+ private fun updateChannel(app: ApplicationInfo, channel: NotificationChannel) {
+ notificationManager.updateNotificationChannelForPackage(app.packageName, app.uid, channel)
+ }
+
+ private fun onlyHasDefaultChannel(app: ApplicationInfo): Boolean =
+ notificationManager.onlyHasDefaultChannel(app.packageName, app.uid)
+
+ private fun getChannel(app: ApplicationInfo, channelId: String): NotificationChannel? =
+ notificationManager.getNotificationChannelForPackage(
+ app.packageName, app.uid, channelId, null, true
+ )
+
+ companion object {
+ private const val TAG = "AppNotificationsRepo"
+
+ const val DAYS_TO_CHECK = 7L
+
+ private val usageStatsManager by lazy {
+ IUsageStatsManager.Stub.asInterface(
+ ServiceManager.getService(Context.USAGE_STATS_SERVICE)
+ )
+ }
+
+ private val notificationManager by lazy {
+ INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE)
+ )
+ }
+
+ fun calculateDailyFrequent(sentCount: Int): Int =
+ (sentCount.toFloat() / DAYS_TO_CHECK).roundToInt()
+ }
+}
diff --git a/src/com/android/settings/spa/notification/AppNotificationsListModel.kt b/src/com/android/settings/spa/notification/AppNotificationsListModel.kt
new file mode 100644
index 0000000..ff951fc
--- /dev/null
+++ b/src/com/android/settings/spa/notification/AppNotificationsListModel.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.spa.notification
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.icu.text.RelativeDateTimeFormatter
+import androidx.compose.runtime.Composable
+import com.android.settings.R
+import com.android.settings.spa.notification.SpinnerItem.Companion.toSpinnerItem
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.util.asyncFilter
+import com.android.settingslib.spa.framework.util.asyncForEach
+import com.android.settingslib.spaprivileged.model.app.AppEntry
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.utils.StringUtil
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+
+data class AppNotificationsRecord(
+ override val app: ApplicationInfo,
+ val sentState: NotificationSentState?,
+ val controller: AppNotificationController,
+) : AppRecord
+
+class AppNotificationsListModel(
+ private val context: Context,
+) : AppListModel<AppNotificationsRecord> {
+ private val repository = AppNotificationRepository(context)
+ private val now = System.currentTimeMillis()
+
+ override fun transform(
+ userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>,
+ ) = repository.getAggregatedUsageEvents(userIdFlow)
+ .combine(appListFlow) { usageEvents, appList ->
+ appList.map { app ->
+ AppNotificationsRecord(
+ app = app,
+ sentState = usageEvents[app.packageName],
+ controller = AppNotificationController(repository, app),
+ )
+ }
+ }
+
+ override fun filter(
+ userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<AppNotificationsRecord>>,
+ ) = recordListFlow.map { recordList ->
+ recordList.asyncFilter { record ->
+ when (option.toSpinnerItem()) {
+ SpinnerItem.MostRecent -> record.sentState != null
+ SpinnerItem.MostFrequent -> record.sentState != null
+ SpinnerItem.TurnedOff -> !record.controller.getEnabled()
+ else -> true
+ }
+ }
+ }
+
+ override suspend fun onFirstLoaded(recordList: List<AppNotificationsRecord>) {
+ recordList.asyncForEach { it.controller.getEnabled() }
+ }
+
+ override fun getComparator(option: Int) = when (option.toSpinnerItem()) {
+ SpinnerItem.MostRecent -> compareByDescending { it.record.sentState?.lastSent }
+ SpinnerItem.MostFrequent -> compareByDescending { it.record.sentState?.sentCount }
+ else -> compareBy<AppEntry<AppNotificationsRecord>> { 0 }
+ }.then(super.getComparator(option))
+
+ @Composable
+ override fun getSummary(option: Int, record: AppNotificationsRecord) = record.sentState?.let {
+ when (option.toSpinnerItem()) {
+ SpinnerItem.MostRecent -> stateOf(formatLastSent(it.lastSent))
+ SpinnerItem.MostFrequent -> stateOf(calculateFrequent(it.sentCount))
+ else -> null
+ }
+ }
+
+ override fun getSpinnerOptions() = SpinnerItem.values().map {
+ context.getString(it.stringResId)
+ }
+
+ private fun formatLastSent(lastSent: Long) =
+ StringUtil.formatRelativeTime(
+ context,
+ (now - lastSent).toDouble(),
+ true,
+ RelativeDateTimeFormatter.Style.LONG,
+ ).toString()
+
+ private fun calculateFrequent(sentCount: Int): String {
+ val dailyFrequent = AppNotificationRepository.calculateDailyFrequent(sentCount)
+ return if (dailyFrequent > 0) {
+ context.resources.getQuantityString(
+ R.plurals.notifications_sent_daily, dailyFrequent, dailyFrequent
+ )
+ } else {
+ context.resources.getQuantityString(
+ R.plurals.notifications_sent_weekly, sentCount, sentCount
+ )
+ }
+ }
+}
+
+private enum class SpinnerItem(val stringResId: Int) {
+ MostRecent(R.string.sort_order_recent_notification),
+ MostFrequent(R.string.sort_order_frequent_notification),
+ AllApps(R.string.filter_all_apps),
+ TurnedOff(R.string.filter_notif_blocked_apps);
+
+ companion object {
+ fun Int.toSpinnerItem(): SpinnerItem = values()[this]
+ }
+}
diff --git a/src/com/android/settings/spa/notification/NotificationMain.kt b/src/com/android/settings/spa/notification/NotificationMain.kt
new file mode 100644
index 0000000..46246b5
--- /dev/null
+++ b/src/com/android/settings/spa/notification/NotificationMain.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.spa.notification
+
+import android.os.Bundle
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Notifications
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.SettingsIcon
+
+object NotificationMainPageProvider : SettingsPageProvider {
+ override val name = "NotificationMain"
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ NotificationMain()
+ }
+
+ @Composable
+ fun EntryItem() {
+ Preference(object : PreferenceModel {
+ override val title = stringResource(R.string.configure_notification_settings)
+ override val summary = stringResource(R.string.notification_dashboard_summary).toState()
+ override val onClick = navigator(name)
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.Notifications)
+ }
+ })
+ }
+}
+
+@Composable
+private fun NotificationMain() {
+ RegularScaffold(title = stringResource(R.string.configure_notification_settings)) {
+ AppListNotificationsPageProvider.EntryItem()
+ }
+}
diff --git a/src/com/android/settings/uwb/UwbPreferenceController.java b/src/com/android/settings/uwb/UwbPreferenceController.java
index ad040ed..fb0836d 100644
--- a/src/com/android/settings/uwb/UwbPreferenceController.java
+++ b/src/com/android/settings/uwb/UwbPreferenceController.java
@@ -75,8 +75,7 @@
};
}
- @VisibleForTesting
- boolean isUwbSupportedOnDevice() {
+ public boolean isUwbSupportedOnDevice() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB);
}
@@ -99,6 +98,9 @@
@Override
public boolean isChecked() {
+ if (!isUwbSupportedOnDevice()) {
+ return false;
+ }
int state = mUwbManager.getAdapterState();
return state == STATE_ENABLED_ACTIVE || state == STATE_ENABLED_INACTIVE;
}
diff --git a/src/com/android/settings/wifi/WifiWakeupPreferenceController.java b/src/com/android/settings/wifi/WifiWakeupPreferenceController.java
index 066242c..df48129 100644
--- a/src/com/android/settings/wifi/WifiWakeupPreferenceController.java
+++ b/src/com/android/settings/wifi/WifiWakeupPreferenceController.java
@@ -109,11 +109,11 @@
@Override
public boolean setChecked(boolean isChecked) {
if (isChecked) {
- if (mFragment == null) {
- throw new IllegalStateException("No fragment to start activity");
- }
-
if (!getLocationEnabled()) {
+ if (mFragment == null) {
+ throw new IllegalStateException("No fragment to start activity");
+ }
+
final Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
mFragment.startActivityForResult(intent, WIFI_WAKEUP_REQUEST_CODE);
return false;
diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java
index affb28b..dc37d23 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityHearingAidPreferenceControllerTest.java
@@ -28,7 +28,6 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
-import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Intent;
@@ -74,7 +73,6 @@
private BluetoothAdapter mBluetoothAdapter;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
- private BluetoothManager mBluetoothManager;
private BluetoothDevice mBluetoothDevice;
private Activity mContext;
private Preference mHearingAidPreference;
@@ -101,8 +99,8 @@
MockitoAnnotations.initMocks(this);
mShadowApplication = ShadowApplication.getInstance();
mContext = spy(Robolectric.setupActivity(Activity.class));
- setupBluetoothEnvironment();
- setupHearingAidEnvironment();
+ setupEnvironment();
+
mHearingAidPreference = new Preference(mContext);
mHearingAidPreference.setKey(HEARING_AID_PREFERENCE);
mPreferenceController = new AccessibilityHearingAidPreferenceController(mContext,
@@ -247,25 +245,39 @@
assertThat(dialog.isShowing()).isTrue();
}
- private void setupBluetoothEnvironment() {
+ @Test
+ public void onServiceConnected_updateSummary() {
+ mPreferenceController.onStart();
+ when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getDeviceMode()).thenReturn(
+ HearingAidProfile.DeviceMode.MODE_BINAURAL);
+ when(mCachedBluetoothDevice.getDeviceSide()).thenReturn(
+ HearingAidProfile.DeviceSide.SIDE_LEFT);
+ when(mHearingAidProfile.getConnectedDevices()).thenReturn(generateHearingAidDeviceList());
+
+ mPreferenceController.onServiceConnected();
+
+ assertThat(mHearingAidPreference.getSummary().toString()).isEqualTo(
+ "TEST_HEARING_AID_BT_DEVICE_NAME, left only");
+ }
+
+ private void setupEnvironment() {
ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
- mBluetoothManager = mContext.getSystemService(BluetoothManager.class);
- mBluetoothAdapter = mBluetoothManager.getAdapter();
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ mShadowBluetoothAdapter = Shadow.extract(mBluetoothAdapter);
+ mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID);
+ mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
+ mBluetoothAdapter.enable();
+
+ doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager();
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
- doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager();
- }
-
- private void setupHearingAidEnvironment() {
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- mShadowBluetoothAdapter = Shadow.extract(mBluetoothAdapter);
- mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
- mBluetoothAdapter.enable();
- mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID);
when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
when(mCachedBluetoothDevice.getName()).thenReturn(TEST_DEVICE_NAME);
+ when(mHearingAidProfile.isProfileReady()).thenReturn(true);
}
private void sendIntent(Intent intent) {
diff --git a/tests/robotests/src/com/android/settings/accessibility/AutoclickPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/AutoclickPreferenceControllerTest.java
index b959483..fd6bedc 100644
--- a/tests/robotests/src/com/android/settings/accessibility/AutoclickPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/AutoclickPreferenceControllerTest.java
@@ -16,11 +16,17 @@
package com.android.settings.accessibility;
+import static android.view.accessibility.AccessibilityManager.AUTOCLICK_DELAY_DEFAULT;
+
+import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
+import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
+
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.provider.Settings;
-import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
@@ -29,17 +35,16 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
+/** Tests for {@link AutoclickPreferenceController}. */
@RunWith(RobolectricTestRunner.class)
public class AutoclickPreferenceControllerTest {
- private Context mContext;
+ private final Context mContext = ApplicationProvider.getApplicationContext();
private AutoclickPreferenceController mController;
@Before
public void setUp() {
- mContext = RuntimeEnvironment.application;
mController = new AutoclickPreferenceController(mContext, "auto_click");
}
@@ -52,7 +57,7 @@
@Test
public void getSummary_disabledAutoclick_shouldReturnOffSummary() {
Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, 0);
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF);
assertThat(mController.getSummary())
.isEqualTo(mContext.getText(R.string.off));
@@ -60,14 +65,15 @@
@Test
public void getSummary_enabledAutoclick_shouldReturnOnSummary() {
- final int autoclickDelayDefault = AccessibilityManager.AUTOCLICK_DELAY_DEFAULT;
Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, 1);
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, ON);
Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY, autoclickDelayDefault);
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY, AUTOCLICK_DELAY_DEFAULT);
- assertThat(mController.getSummary())
- .isEqualTo(ToggleAutoclickPreferenceFragment.getAutoclickPreferenceSummary(
- mContext.getResources(), autoclickDelayDefault));
+ assertThat(mController.getSummary().toString())
+ .isEqualTo(AutoclickUtils.getAutoclickDelaySummary(
+ mContext.getResources(),
+ R.plurals.accessibilty_autoclick_preference_subtitle_medium_delay,
+ AUTOCLICK_DELAY_DEFAULT).toString());
}
}
diff --git a/tests/robotests/src/com/android/settings/accessibility/AutoclickUtilsTest.java b/tests/robotests/src/com/android/settings/accessibility/AutoclickUtilsTest.java
new file mode 100644
index 0000000..46b1e2b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/AutoclickUtilsTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.accessibility;
+
+import static com.android.settings.accessibility.AutoclickUtils.MAX_AUTOCLICK_DELAY_MS;
+import static com.android.settings.accessibility.AutoclickUtils.MIN_AUTOCLICK_DELAY_MS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link AutoclickUtils}. */
+@RunWith(RobolectricTestRunner.class)
+public final class AutoclickUtilsTest {
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ @Test
+ public void getAutoclickDelaySummary_minDelay_shouldReturnOnSummary() {
+ final CharSequence summary = AutoclickUtils.getAutoclickDelaySummary(
+ mContext.getResources(), R.plurals.accessibilty_autoclick_delay_unit_second,
+ MIN_AUTOCLICK_DELAY_MS);
+ assertThat(summary.toString()).isEqualTo("0.2 seconds");
+ }
+
+ @Test
+ public void getAutoclickDelaySummary_maxDelay_shouldReturnOnSummary() {
+ final CharSequence summary = AutoclickUtils.getAutoclickDelaySummary(
+ mContext.getResources(), R.plurals.accessibilty_autoclick_delay_unit_second,
+ MAX_AUTOCLICK_DELAY_MS);
+ assertThat(summary.toString()).isEqualTo("1 second");
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java
index 5990a3d..bda60d4 100644
--- a/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/HearingAidPairingDialogFragmentTest.java
@@ -18,24 +18,38 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.app.settings.SettingsEnums;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.test.core.app.ApplicationProvider;
+import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.bluetooth.BluetoothPairingDetail;
import com.android.settings.bluetooth.HearingAidPairingDialogFragment;
+import com.android.settings.bluetooth.Utils;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HearingAidProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
import org.junit.Before;
import org.junit.Rule;
@@ -47,39 +61,57 @@
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
/** Tests for {@link HearingAidPairingDialogFragment}. */
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = ShadowAlertDialogCompat.class)
+@Config(shadows = {ShadowAlertDialogCompat.class, ShadowBluetoothAdapter.class,
+ ShadowBluetoothUtils.class})
public class HearingAidPairingDialogFragmentTest {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
- private static final String KEY_CACHED_DEVICE_SIDE = "cached_device_side";
+ private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
+ private final Context mContext = ApplicationProvider.getApplicationContext();
@Mock
private CachedBluetoothDevice mCachedBluetoothDevice;
+ @Mock
+ private CachedBluetoothDevice mCachedSubBluetoothDevice;
+ @Mock
+ private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock
+ private CachedBluetoothDeviceManager mCachedDeviceManager;
+ private BluetoothAdapter mBluetoothAdapter;
private FragmentActivity mActivity;
private HearingAidPairingDialogFragment mFragment;
+ private ShadowBluetoothAdapter mShadowBluetoothAdapter;
+ private BluetoothDevice mBluetoothDevice;
+ private FragmentManager mFragmentManager;
@Before
public void setUp() {
- mFragment = spy(HearingAidPairingDialogFragment.newInstance(mCachedBluetoothDevice));
+ setupEnvironment();
+ mFragment = spy(HearingAidPairingDialogFragment.newInstance(TEST_DEVICE_ADDRESS));
mActivity = Robolectric.setupActivity(FragmentActivity.class);
+ mFragmentManager = mActivity.getSupportFragmentManager();
when(mFragment.getActivity()).thenReturn(mActivity);
+ doReturn(mFragmentManager).when(mFragment).getParentFragmentManager();
+ mFragment.onAttach(mContext);
}
@Test
public void newInstance_deviceSideRight_argumentSideRight() {
when(mCachedBluetoothDevice.getDeviceSide()).thenReturn(
HearingAidProfile.DeviceSide.SIDE_RIGHT);
+ final AlertDialog dialog = (AlertDialog) mFragment.onCreateDialog(Bundle.EMPTY);
+ dialog.show();
- mFragment = HearingAidPairingDialogFragment.newInstance(mCachedBluetoothDevice);
-
- final Bundle bundle = mFragment.getArguments();
- assertThat(bundle.getInt(KEY_CACHED_DEVICE_SIDE)).isEqualTo(
- HearingAidProfile.DeviceSide.SIDE_RIGHT);
+ final String pairLeftString = mContext.getText(
+ R.string.bluetooth_pair_other_ear_dialog_left_ear_positive_button).toString();
+ assertThat(dialog.getButton(
+ DialogInterface.BUTTON_POSITIVE).getText().toString()).isEqualTo(pairLeftString);
}
@Test
@@ -109,4 +141,26 @@
assertThat(mFragment.getMetricsCategory()).isEqualTo(
SettingsEnums.DIALOG_ACCESSIBILITY_HEARING_AID_PAIR_ANOTHER);
}
+
+ @Test
+ public void onDeviceAttributesChanged_subHearingAidDeviceConnected_dialogDismiss() {
+ when(mCachedSubBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true);
+ when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mCachedSubBluetoothDevice);
+
+ mFragment.onDeviceAttributesChanged();
+
+ verify(mFragment).dismiss();
+ }
+
+ private void setupEnvironment() {
+ ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
+ mShadowBluetoothAdapter = Shadow.extract(mBluetoothAdapter);
+ mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
+ mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID);
+ when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
+ when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java b/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java
index efeb27f..6fdd210 100644
--- a/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/HearingAidUtilsTest.java
@@ -20,13 +20,24 @@
import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
+import androidx.test.core.app.ApplicationProvider;
+import com.android.settings.bluetooth.Utils;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HearingAidProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
import org.junit.Before;
import org.junit.Rule;
@@ -38,27 +49,40 @@
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
/** Tests for {@link HearingAidUtils}. */
@RunWith(RobolectricTestRunner.class)
-@Config(shadows = ShadowAlertDialogCompat.class)
+@Config(shadows = {ShadowAlertDialogCompat.class, ShadowBluetoothAdapter.class,
+ ShadowBluetoothUtils.class})
public class HearingAidUtilsTest {
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
@Mock
private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock
private CachedBluetoothDevice mSubCachedBluetoothDevice;
-
+ @Mock
+ private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock
+ private CachedBluetoothDeviceManager mCachedDeviceManager;
+ private BluetoothDevice mBluetoothDevice;
+ private BluetoothAdapter mBluetoothAdapter;
+ private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private FragmentManager mFragmentManager;
@Before
public void setUp() {
+ setupEnvironment();
final FragmentActivity mActivity = Robolectric.setupActivity(FragmentActivity.class);
mFragmentManager = mActivity.getSupportFragmentManager();
ShadowAlertDialogCompat.reset();
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
}
@Test
@@ -123,4 +147,16 @@
final AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog.isShowing()).isTrue();
}
+
+ private void setupEnvironment() {
+ ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
+ mShadowBluetoothAdapter = Shadow.extract(mBluetoothAdapter);
+ mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
+ mShadowBluetoothAdapter.addSupportedProfiles(BluetoothProfile.HEARING_AID);
+ when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
+ when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarControllerTest.java
index d6c66ad..6f0b157 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickCustomSeekbarControllerTest.java
@@ -18,13 +18,14 @@
import static android.content.Context.MODE_PRIVATE;
-import static com.android.settings.accessibility.ToggleAutoclickCustomSeekbarController.KEY_CUSTOM_DELAY_VALUE;
-import static com.android.settings.accessibility.ToggleAutoclickPreferenceController.KEY_DELAY_MODE;
+import static com.android.settings.accessibility.AutoclickUtils.KEY_CUSTOM_DELAY_VALUE;
+import static com.android.settings.accessibility.AutoclickUtils.KEY_DELAY_MODE;
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.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -37,57 +38,52 @@
import android.widget.SeekBar;
import android.widget.TextView;
-import androidx.lifecycle.LifecycleObserver;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
-import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.widget.LayoutPreference;
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.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
/** Tests for {@link ToggleAutoclickCustomSeekbarController}. */
@RunWith(RobolectricTestRunner.class)
public class ToggleAutoclickCustomSeekbarControllerTest {
+ private static final String KEY_CUSTOM_SEEKBAR = "autoclick_custom_seekbar";
+
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock
private PreferenceScreen mScreen;
-
@Mock
private LayoutPreference mLayoutPreference;
-
- @Mock
- private Lifecycle mLifecycle;
-
+ @Spy
+ private Context mContext = ApplicationProvider.getApplicationContext();
private SharedPreferences mSharedPreferences;
private TextView mDelayLabel;
private ImageView mShorter;
private ImageView mLonger;
private SeekBar mSeekBar;
private ToggleAutoclickCustomSeekbarController mController;
- private Context mContext;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- final String mPrefKey = "prefKey";
- mContext = ApplicationProvider.getApplicationContext();
mSharedPreferences = mContext.getSharedPreferences(mContext.getPackageName(), MODE_PRIVATE);
mDelayLabel = new TextView(mContext);
mShorter = new ImageView(mContext);
mLonger = new ImageView(mContext);
mSeekBar = new SeekBar(mContext);
- mController =
- new ToggleAutoclickCustomSeekbarController(mContext, mLifecycle, mPrefKey);
-
- doReturn(mLayoutPreference).when(mScreen).findPreference(mPrefKey);
+ mController = new ToggleAutoclickCustomSeekbarController(mContext, KEY_CUSTOM_SEEKBAR);
+ doReturn(mLayoutPreference).when(mScreen).findPreference(KEY_CUSTOM_SEEKBAR);
doReturn(mSeekBar).when(mLayoutPreference).findViewById(R.id.autoclick_delay);
doReturn(mDelayLabel).when(mLayoutPreference).findViewById(R.id.current_label);
doReturn(mShorter).when(mLayoutPreference).findViewById(R.id.shorter);
@@ -100,17 +96,12 @@
}
@Test
- public void constructor_hasLifecycle_addObserver() {
- verify(mLifecycle).addObserver(any(LifecycleObserver.class));
- }
-
- @Test
public void displayPreference_initSeekBar() {
mSharedPreferences.edit().putInt(KEY_CUSTOM_DELAY_VALUE, 700).apply();
- mController.onResume();
+ mController.onStart();
mController.displayPreference(mScreen);
- mController.onPause();
+ mController.onStop();
final SeekBar.OnSeekBarChangeListener mListener =
shadowOf(mSeekBar).getOnSeekBarChangeListener();
@@ -123,9 +114,9 @@
public void displayPreference_initDelayLabel() {
mSharedPreferences.edit().putInt(KEY_CUSTOM_DELAY_VALUE, 700).apply();
- mController.onResume();
+ mController.onStart();
mController.displayPreference(mScreen);
- mController.onPause();
+ mController.onStop();
assertThat(mDelayLabel.getText()).isEqualTo("0.7 seconds");
}
@@ -203,4 +194,28 @@
assertThat(actualDelayValue).isEqualTo(800);
assertThat(actualCustomDelayValue).isEqualTo(800);
}
+
+ @Test
+ public void onStart_registerOnSharedPreferenceChangeListener() {
+ final SharedPreferences prefs = mock(SharedPreferences.class);
+ doReturn(prefs).when(mContext).getSharedPreferences(anyString(), anyInt());
+ final ToggleAutoclickCustomSeekbarController controller =
+ new ToggleAutoclickCustomSeekbarController(mContext, KEY_CUSTOM_SEEKBAR);
+
+ controller.onStart();
+
+ verify(prefs).registerOnSharedPreferenceChangeListener(controller);
+ }
+
+ @Test
+ public void onStop_unregisterOnSharedPreferenceChangeListener() {
+ final SharedPreferences prefs = mock(SharedPreferences.class);
+ doReturn(prefs).when(mContext).getSharedPreferences(anyString(), anyInt());
+ final ToggleAutoclickCustomSeekbarController controller =
+ new ToggleAutoclickCustomSeekbarController(mContext, KEY_CUSTOM_SEEKBAR);
+
+ controller.onStop();
+
+ verify(prefs).unregisterOnSharedPreferenceChangeListener(controller);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickPreferenceControllerTest.java
index 1b7f326..3053eb2 100644
--- a/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickPreferenceControllerTest.java
@@ -16,18 +16,19 @@
package com.android.settings.accessibility;
-import static android.content.Context.MODE_PRIVATE;
-
-import static com.android.settings.accessibility.ToggleAutoclickPreferenceController.AUTOCLICK_CUSTOM_MODE;
-import static com.android.settings.accessibility.ToggleAutoclickPreferenceController.AUTOCLICK_OFF_MODE;
-import static com.android.settings.accessibility.ToggleAutoclickPreferenceController.KEY_AUTOCLICK_CUSTOM_SEEKBAR;
-import static com.android.settings.accessibility.ToggleAutoclickPreferenceController.KEY_DELAY_MODE;
+import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
+import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
+import static com.android.settings.accessibility.AutoclickUtils.KEY_DELAY_MODE;
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.Mockito.mock;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -35,127 +36,137 @@
import android.content.SharedPreferences;
import android.provider.Settings.Secure;
-import androidx.lifecycle.LifecycleObserver;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
-import com.android.settings.accessibility.ToggleAutoclickPreferenceController.OnChangeListener;
-import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.widget.LayoutPreference;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import com.android.settingslib.widget.SelectorWithWidgetPreference.OnClickListener;
-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.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
-import java.util.Map;
-
/** Tests for {@link ToggleAutoclickPreferenceController}. */
@RunWith(RobolectricTestRunner.class)
public class ToggleAutoclickPreferenceControllerTest {
+ private static final String KEY_PREF_DEFAULT = "accessibility_control_autoclick_default";
+ private static final String KEY_PREF_CUSTOM = "accessibility_control_autoclick_custom";
+ private static final String KEY_CUSTOM_SEEKBAR = "autoclick_custom_seekbar";
+
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock
private PreferenceScreen mScreen;
-
@Mock
private SelectorWithWidgetPreference mDelayModePref;
-
- @Mock
- private OnChangeListener mOnChangeListener;
-
@Mock
private LayoutPreference mSeekBarPref;
-
@Mock
- private Map<String, Integer> mAccessibilityAutoclickKeyToValueMap;
-
- private ToggleAutoclickPreferenceController mController;
private SharedPreferences mSharedPreferences;
- private final String mPrefKey = "prefKey";
- private final Context mContext = ApplicationProvider.getApplicationContext();
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mController = new ToggleAutoclickPreferenceController(mContext, mPrefKey);
- mController.mAccessibilityAutoclickKeyToValueMap = mAccessibilityAutoclickKeyToValueMap;
- mSharedPreferences =
- mContext.getSharedPreferences(mContext.getPackageName(), MODE_PRIVATE);
-
- when(mScreen.findPreference(mPrefKey)).thenReturn(mDelayModePref);
- when(mScreen.findPreference(KEY_AUTOCLICK_CUSTOM_SEEKBAR)).thenReturn(mSeekBarPref);
- when(mAccessibilityAutoclickKeyToValueMap.get(mDelayModePref.getKey())).thenReturn(
- AUTOCLICK_OFF_MODE);
- }
+ @Spy
+ private Context mContext = ApplicationProvider.getApplicationContext();
+ private ToggleAutoclickPreferenceController mController;
@Test
public void getAvailabilityStatus_available() {
+ setUpController(KEY_PREF_DEFAULT);
+
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
public void setClickListenerOnDelayModePref_whenDisplay_success() {
+ setUpController(KEY_PREF_DEFAULT);
+
mController.displayPreference(mScreen);
verify(mDelayModePref).setOnClickListener(any(OnClickListener.class));
}
@Test
- public void constructor_hasLifecycle_addObserver() {
- final Lifecycle lifecycle = mock(Lifecycle.class);
- mController = new ToggleAutoclickPreferenceController(mContext, lifecycle, mPrefKey);
+ public void onStart_registerOnSharedPreferenceChangeListener() {
+ doReturn(mSharedPreferences).when(mContext).getSharedPreferences(anyString(), anyInt());
+ setUpController(KEY_PREF_DEFAULT);
- verify(lifecycle).addObserver(any(LifecycleObserver.class));
+ mController.onStart();
+
+ verify(mSharedPreferences).registerOnSharedPreferenceChangeListener(mController);
+ }
+
+ @Test
+ public void onStop_unregisterOnSharedPreferenceChangeListener() {
+ doReturn(mSharedPreferences).when(mContext).getSharedPreferences(anyString(), anyInt());
+ setUpController(KEY_PREF_DEFAULT);
+
+ mController.onStop();
+
+ verify(mSharedPreferences).unregisterOnSharedPreferenceChangeListener(mController);
}
@Test
public void onRadioButtonClicked_offMode_disableAutoClick() {
- when(mAccessibilityAutoclickKeyToValueMap.get(mPrefKey)).thenReturn(AUTOCLICK_OFF_MODE);
-
+ setUpController(KEY_PREF_DEFAULT);
mController.displayPreference(mScreen);
- mController.onRadioButtonClicked(any(SelectorWithWidgetPreference.class));
- final boolean isEnabled = Secure.getInt(mContext.getContentResolver(),
- Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, /* def= */ 0) == 1;
- final int delayMs = Secure.getInt(mContext.getContentResolver(),
- Secure.ACCESSIBILITY_AUTOCLICK_DELAY, /* def= */ 0);
- final int keyDelayMode = mSharedPreferences.getInt(KEY_DELAY_MODE, AUTOCLICK_CUSTOM_MODE);
- assertThat(keyDelayMode).isEqualTo(AUTOCLICK_OFF_MODE);
- assertThat(delayMs).isEqualTo(/* expected= */ 0);
+ mController.onRadioButtonClicked(mDelayModePref);
+
+ final boolean isEnabled = Secure.getInt(mContext.getContentResolver(),
+ Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF) == ON;
+ final int delayMs = Secure.getInt(mContext.getContentResolver(),
+ Secure.ACCESSIBILITY_AUTOCLICK_DELAY, 0);
+ assertThat(delayMs).isEqualTo(0);
assertThat(isEnabled).isFalse();
}
+
@Test
public void onRadioButtonClicked_customMode_enableAutoClick() {
- when(mAccessibilityAutoclickKeyToValueMap.get(mDelayModePref.getKey())).thenReturn(
- AUTOCLICK_CUSTOM_MODE);
- when(mAccessibilityAutoclickKeyToValueMap.get(mPrefKey)).thenReturn(AUTOCLICK_CUSTOM_MODE);
-
+ setUpController(KEY_PREF_CUSTOM);
mController.displayPreference(mScreen);
- mController.onRadioButtonClicked(any(SelectorWithWidgetPreference.class));
- final boolean isEnabled = Secure.getInt(mContext.getContentResolver(),
- Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, /* def= */ 0) == 1;
- final int keyDelayMode = mSharedPreferences.getInt(KEY_DELAY_MODE, AUTOCLICK_CUSTOM_MODE);
- assertThat(keyDelayMode).isEqualTo(AUTOCLICK_CUSTOM_MODE);
+ mController.onRadioButtonClicked(mDelayModePref);
+
+ final boolean isEnabled = Secure.getInt(mContext.getContentResolver(),
+ Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, OFF) == ON;
assertThat(isEnabled).isTrue();
}
@Test
- public void onRadioButtonClicked_hasListener_runOnCheckedChanged() {
- when(mAccessibilityAutoclickKeyToValueMap.get(mDelayModePref.getKey())).thenReturn(
- AUTOCLICK_CUSTOM_MODE);
- when(mAccessibilityAutoclickKeyToValueMap.get(mPrefKey)).thenReturn(AUTOCLICK_CUSTOM_MODE);
-
- mController.setOnChangeListener(mOnChangeListener);
+ public void onSharedPreferenceChanged_customMode_shouldShowCustomSeekbar() {
+ setUpController(KEY_PREF_CUSTOM);
mController.displayPreference(mScreen);
- mController.onRadioButtonClicked(any(SelectorWithWidgetPreference.class));
+ mController.onRadioButtonClicked(mDelayModePref);
+ when(mDelayModePref.isChecked()).thenReturn(true);
+ reset(mSeekBarPref);
- verify(mOnChangeListener).onCheckedChanged(mDelayModePref);
+ mController.onSharedPreferenceChanged(mSharedPreferences, KEY_DELAY_MODE);
+
+ verify(mSeekBarPref).setVisible(true);
+ }
+
+ @Test
+ public void onSharedPreferenceChanged_offMode_shouldNotShowCustomSeekbar() {
+ setUpController(KEY_PREF_DEFAULT);
+ mController.displayPreference(mScreen);
+ mController.onRadioButtonClicked(mDelayModePref);
+ reset(mSeekBarPref);
+
+ mController.onSharedPreferenceChanged(mSharedPreferences, KEY_DELAY_MODE);
+
+ verify(mSeekBarPref, never()).setVisible(true);
+ }
+
+ private void setUpController(String preferenceKey) {
+ mController = new ToggleAutoclickPreferenceController(mContext, preferenceKey);
+ when(mScreen.findPreference(preferenceKey)).thenReturn(mDelayModePref);
+ when(mDelayModePref.getKey()).thenReturn(preferenceKey);
+ when(mScreen.findPreference(KEY_CUSTOM_SEEKBAR)).thenReturn(mSeekBarPref);
}
}
diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickPreferenceFragmentTest.java
new file mode 100644
index 0000000..4e8ce96
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/accessibility/ToggleAutoclickPreferenceFragmentTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settings.testutils.XmlTestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.List;
+
+/** Tests for {@link ToggleAutoclickPreferenceFragment}. */
+@RunWith(RobolectricTestRunner.class)
+public class ToggleAutoclickPreferenceFragmentTest {
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private ToggleAutoclickPreferenceFragment mFragment;
+
+ @Before
+ public void setUp() {
+ mFragment = new ToggleAutoclickPreferenceFragment();
+ }
+
+ @Test
+ public void getMetricsCategory_returnsCorrectCategory() {
+ assertThat(mFragment.getMetricsCategory()).isEqualTo(
+ SettingsEnums.ACCESSIBILITY_TOGGLE_AUTOCLICK);
+ }
+
+ @Test
+ public void getPreferenceScreenResId_returnsCorrectXml() {
+ assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(
+ R.xml.accessibility_autoclick_settings);
+ }
+
+ @Test
+ public void getHelpResource_returnsCorrectHelpResource() {
+ assertThat(mFragment.getHelpResource()).isEqualTo(R.string.help_url_autoclick);
+ }
+
+ @Test
+ public void getLogTag_returnsCorrectTag() {
+ assertThat(mFragment.getLogTag()).isEqualTo("AutoclickPrefFragment");
+ }
+
+ @Test
+ public void getNonIndexableKeys_existInXmlLayout() {
+ final List<String> niks = ToggleAutoclickPreferenceFragment.SEARCH_INDEX_DATA_PROVIDER
+ .getNonIndexableKeys(mContext);
+ final List<String> keys =
+ XmlTestUtils.getKeysFromPreferenceXml(mContext,
+ R.xml.accessibility_autoclick_settings);
+
+ assertThat(keys).containsAtLeastElementsIn(niks);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java
index e1a3da6..2b62503 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/AvailableMediaDeviceGroupControllerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -50,6 +51,7 @@
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -70,6 +72,7 @@
ShadowBluetoothUtils.class})
public class AvailableMediaDeviceGroupControllerTest {
+ private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
private static final String PREFERENCE_KEY_1 = "pref_key_1";
@Mock
@@ -85,7 +88,9 @@
@Mock
private BluetoothEventManager mEventManager;
@Mock
- private LocalBluetoothManager mLocalManager;
+ private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock
+ private CachedBluetoothDeviceManager mCachedDeviceManager;
@Mock
private CachedBluetoothDevice mCachedBluetoothDevice;
@@ -93,7 +98,6 @@
private Context mContext;
private Preference mPreference;
private AvailableMediaDeviceGroupController mAvailableMediaDeviceGroupController;
- private LocalBluetoothManager mLocalBluetoothManager;
private AudioManager mAudioManager;
@Before
@@ -112,10 +116,14 @@
when(mDashboardFragment.getParentFragmentManager()).thenReturn(
mActivity.getSupportFragmentManager());
- ShadowBluetoothUtils.sLocalBluetoothManager = mLocalManager;
+ ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
mLocalBluetoothManager = Utils.getLocalBtManager(mContext);
mAudioManager = mContext.getSystemService(AudioManager.class);
doReturn(mEventManager).when(mLocalBluetoothManager).getEventManager();
+ when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
+ when(mCachedDeviceManager.findDevice(any(BluetoothDevice.class))).thenReturn(
+ mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
mAvailableMediaDeviceGroupController = spy(
new AvailableMediaDeviceGroupController(mContext));
diff --git a/tests/robotests/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceControllerTest.java
index 72bdd39..dde2f5d 100644
--- a/tests/robotests/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/security/trustagent/ManageTrustAgentsPreferenceControllerTest.java
@@ -21,13 +21,13 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.when;
-import android.content.ComponentName;
import android.content.Context;
import androidx.preference.Preference;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
+import com.android.settings.security.trustagent.TrustAgentManager.TrustAgentComponentInfo;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
@@ -46,6 +46,8 @@
public class ManageTrustAgentsPreferenceControllerTest {
@Mock
+ private TrustAgentManager mTrustAgentManager;
+ @Mock
private LockPatternUtils mLockPatternUtils;
private FakeFeatureFactory mFeatureFactory;
@@ -60,6 +62,8 @@
mFeatureFactory = FakeFeatureFactory.setupForTest();
when(mFeatureFactory.securityFeatureProvider.getLockPatternUtils(mContext))
.thenReturn(mLockPatternUtils);
+ when(mFeatureFactory.securityFeatureProvider.getTrustAgentManager())
+ .thenReturn(mTrustAgentManager);
mController = new ManageTrustAgentsPreferenceController(mContext, "key");
mPreference = new Preference(mContext);
mPreference.setKey(mController.getPreferenceKey());
@@ -90,7 +94,8 @@
@Test
public void updateState_isSecure_noTrustAgent_shouldShowGenericSummary() {
when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
- when(mLockPatternUtils.getEnabledTrustAgents(anyInt())).thenReturn(new ArrayList<>());
+ when(mTrustAgentManager.getActiveTrustAgents(mContext, mLockPatternUtils, false))
+ .thenReturn(new ArrayList<>());
mController.updateState(mPreference);
@@ -102,8 +107,8 @@
@Test
public void updateState_isSecure_hasTrustAgent_shouldShowDetailedSummary() {
when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
- when(mLockPatternUtils.getEnabledTrustAgents(anyInt())).thenReturn(
- Collections.singletonList(new ComponentName("packageName", "className")));
+ when(mTrustAgentManager.getActiveTrustAgents(mContext, mLockPatternUtils, false))
+ .thenReturn(Collections.singletonList(new TrustAgentComponentInfo()));
mController.updateState(mPreference);
diff --git a/tests/robotests/src/com/android/settings/security/trustagent/TrustAgentManagerTest.java b/tests/robotests/src/com/android/settings/security/trustagent/TrustAgentManagerTest.java
index 7796dc3..8088c7c 100644
--- a/tests/robotests/src/com/android/settings/security/trustagent/TrustAgentManagerTest.java
+++ b/tests/robotests/src/com/android/settings/security/trustagent/TrustAgentManagerTest.java
@@ -18,11 +18,27 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.when;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.service.trust.TrustAgentService;
+
+import com.android.internal.widget.LockPatternUtils;
import org.junit.Before;
import org.junit.Test;
@@ -30,21 +46,55 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
@RunWith(RobolectricTestRunner.class)
public class TrustAgentManagerTest {
private static final String CANNED_PACKAGE_NAME = "com.test.package";
+ private static final String CANNED_CLASS_NAME = "TestTrustAgent";
+ private static final String CANNED_TRUST_AGENT_TITLE = "TestTrustAgentTitle";
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private Context mContext;
+ @Mock
+ private DevicePolicyManager mDevicePolicyManager;
+ @Mock
+ private LockPatternUtils mLockPatternUtils;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private UserInfo mUserInfo;
+ @Mock
+ private XmlResourceParser mXmlResourceParser;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private TypedArray mTypedArray;
private TrustAgentManager mTrustAgentManager;
@Before
- public void setUp() {
+ public void setUp() throws NameNotFoundException {
MockitoAnnotations.initMocks(this);
mTrustAgentManager = new TrustAgentManager();
+ when(mContext.getSystemService(DevicePolicyManager.class))
+ .thenReturn(mDevicePolicyManager);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mContext.getSystemService(Context.USER_SERVICE))
+ .thenReturn(mUserManager);
+ when(mResources.obtainAttributes(any(), any())).thenReturn(mTypedArray);
+ when(mPackageManager.getResourcesForApplication(any(ApplicationInfo.class)))
+ .thenReturn(mResources);
+ when(mPackageManager.getXml(any(), anyInt(), any())).thenReturn(mXmlResourceParser);
+ when(mUserManager.getUserInfo(anyInt())).thenReturn(mUserInfo);
}
@Test
@@ -72,4 +122,77 @@
assertThat(mTrustAgentManager.shouldProvideTrust(resolveInfo, mPackageManager)).isFalse();
}
+
+ @Test
+ public void getAllActiveTrustAgentsAndComponentSet_returnsTrustAgents()
+ throws XmlPullParserException, IOException {
+ setUpGetActiveTrustAgents(true);
+
+ assertThat(mTrustAgentManager.getActiveTrustAgents(mContext, mLockPatternUtils, false))
+ .isNotEmpty();
+ }
+
+ @Test
+ public void getActiveTrustAgentsAndComponentSet_componentSet_returnsTrustAgents()
+ throws XmlPullParserException, IOException {
+ setUpGetActiveTrustAgents(true);
+
+ assertThat(mTrustAgentManager.getActiveTrustAgents(mContext, mLockPatternUtils, true))
+ .isNotEmpty();
+ }
+
+ @Test
+ public void getAllActiveTrustAgentsAndComponentNotSet_returnsTrustAgents()
+ throws XmlPullParserException, IOException {
+ setUpGetActiveTrustAgents(false);
+
+ assertThat(mTrustAgentManager.getActiveTrustAgents(mContext, mLockPatternUtils, false))
+ .isNotEmpty();
+ }
+
+ @Test
+ public void getActiveTrustAgentsAndComponentSet_returnsEmpty()
+ throws XmlPullParserException, IOException {
+ setUpGetActiveTrustAgents(false);
+
+ assertThat(mTrustAgentManager.getActiveTrustAgents(mContext, mLockPatternUtils, true))
+ .isEmpty();
+ }
+
+ private void setUpGetActiveTrustAgents(boolean hasSettingsActivity)
+ throws XmlPullParserException, IOException {
+ String settingsActivity =
+ hasSettingsActivity ? CANNED_PACKAGE_NAME + "." + CANNED_CLASS_NAME : "";
+ when(mXmlResourceParser.next()).thenReturn(XmlPullParser.START_TAG);
+ when(mXmlResourceParser.getName()).thenReturn("trust-agent");
+ List<ResolveInfo> resolveInfos =
+ Collections.singletonList(createResolveInfo(hasSettingsActivity));
+ List<ComponentName> enabledTrustAgents =
+ Collections.singletonList(
+ new ComponentName(CANNED_PACKAGE_NAME, CANNED_CLASS_NAME));
+
+ when(mPackageManager.queryIntentServices(any(), anyInt())).thenReturn(resolveInfos);
+ when(mLockPatternUtils.getEnabledTrustAgents(anyInt())).thenReturn(enabledTrustAgents);
+ when(mUserInfo.isManagedProfile()).thenReturn(false);
+ when(mUserManager.getProfiles(anyInt())).thenReturn(null);
+ when(mPackageManager.checkPermission(TrustAgentManager.PERMISSION_PROVIDE_AGENT,
+ CANNED_PACKAGE_NAME)).thenReturn(PackageManager.PERMISSION_GRANTED);
+ when(mTypedArray.getString(com.android.internal.R.styleable.TrustAgent_title))
+ .thenReturn(CANNED_TRUST_AGENT_TITLE);
+ when(mTypedArray.getString(com.android.internal.R.styleable.TrustAgent_settingsActivity))
+ .thenReturn(settingsActivity);
+ }
+
+ private ResolveInfo createResolveInfo(boolean hasSettingsActivity) {
+ ServiceInfo serviceInfo = new ServiceInfo();
+ Bundle metaData = new Bundle();
+ metaData.putInt(TrustAgentService.TRUST_AGENT_META_DATA, 1);
+ serviceInfo.packageName = CANNED_PACKAGE_NAME;
+ serviceInfo.name = CANNED_CLASS_NAME;
+ serviceInfo.metaData = metaData;
+ serviceInfo.applicationInfo = new ApplicationInfo();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = serviceInfo;
+ return resolveInfo;
+ }
}
diff --git a/tests/robotests/src/com/android/settings/wifi/WifiWakeupPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/WifiWakeupPreferenceControllerTest.java
index ce4bfed..62d6fb8 100644
--- a/tests/robotests/src/com/android/settings/wifi/WifiWakeupPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/WifiWakeupPreferenceControllerTest.java
@@ -95,6 +95,18 @@
}
@Test
+ public void setChecked_mFragmentIsNullLocationEnable_wifiWakeupEnable() {
+ mController.setFragment(null);
+ when(mLocationManager.isLocationEnabled()).thenReturn(true);
+ when(mWifiManager.isScanAlwaysAvailable()).thenReturn(true);
+ when(mWifiManager.isAutoWakeupEnabled()).thenReturn(false);
+
+ mController.setChecked(true);
+
+ verify(mWifiManager).setAutoWakeupEnabled(true);
+ }
+
+ @Test
public void setChecked_scanEnableLocationEnable_wifiWakeupEnable() {
when(mWifiManager.isAutoWakeupEnabled()).thenReturn(false);
when(mWifiManager.isScanAlwaysAvailable()).thenReturn(true);
diff --git a/tests/unit/src/com/android/settings/accessibility/DisableAnimationsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/accessibility/DisableAnimationsPreferenceControllerTest.java
index 5706008..2933055 100644
--- a/tests/unit/src/com/android/settings/accessibility/DisableAnimationsPreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/accessibility/DisableAnimationsPreferenceControllerTest.java
@@ -23,8 +23,11 @@
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
+import android.os.Looper;
import android.provider.Settings;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,15 +41,27 @@
@RunWith(AndroidJUnit4.class)
public class DisableAnimationsPreferenceControllerTest {
- private Context mContext;
+ private static final String TEST_PREFERENCE_KEY = "disable_animation";
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ private PreferenceScreen mScreen;
private SwitchPreference mPreference;
private DisableAnimationsPreferenceController mController;
@Before
public void setUp() {
- mContext = ApplicationProvider.getApplicationContext();
- mPreference = new SwitchPreference(mContext);
- mController = new DisableAnimationsPreferenceController(mContext, "disable_animation");
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ PreferenceManager preferenceManager = new PreferenceManager(mContext);
+ mScreen = preferenceManager.createPreferenceScreen(mContext);
+ final SwitchPreference preference = new SwitchPreference(mContext);
+ preference.setKey(TEST_PREFERENCE_KEY);
+ mScreen.addPreference(preference);
+
+ mController = new DisableAnimationsPreferenceController(mContext, TEST_PREFERENCE_KEY);
+ mController.displayPreference(mScreen);
+ mPreference = mScreen.findPreference(TEST_PREFERENCE_KEY);
}
@Test
@@ -57,10 +72,7 @@
@Test
public void isChecked_enabledAnimation_shouldReturnFalse() {
- for (String animationPreference : TOGGLE_ANIMATION_TARGETS) {
- Settings.Global.putString(mContext.getContentResolver(), animationPreference,
- ANIMATION_ON_VALUE);
- }
+ setAnimationScale(ANIMATION_ON_VALUE);
mController.updateState(mPreference);
@@ -70,10 +82,7 @@
@Test
public void isChecked_disabledAnimation_shouldReturnTrue() {
- for (String animationPreference : TOGGLE_ANIMATION_TARGETS) {
- Settings.Global.putString(mContext.getContentResolver(), animationPreference,
- ANIMATION_OFF_VALUE);
- }
+ setAnimationScale(ANIMATION_OFF_VALUE);
mController.updateState(mPreference);
@@ -86,8 +95,9 @@
mController.setChecked(true);
for (String animationSetting : TOGGLE_ANIMATION_TARGETS) {
- assertThat(Settings.Global.getString(mContext.getContentResolver(), animationSetting))
- .isEqualTo(ANIMATION_OFF_VALUE);
+ final float value = Settings.Global.getFloat(mContext.getContentResolver(),
+ animationSetting, /* def= */ -1.0f);
+ assertThat(Float.compare(value, ANIMATION_OFF_VALUE)).isEqualTo(0);
}
}
@@ -96,8 +106,47 @@
mController.setChecked(false);
for (String animationSetting : TOGGLE_ANIMATION_TARGETS) {
- assertThat(Settings.Global.getString(mContext.getContentResolver(), animationSetting))
- .isEqualTo(ANIMATION_ON_VALUE);
+ final float value = Settings.Global.getFloat(mContext.getContentResolver(),
+ animationSetting, /* def= */ -1.0f);
+ assertThat(Float.compare(value, ANIMATION_ON_VALUE)).isEqualTo(0);
}
}
-}
+
+ @Test
+ public void onStart_enabledAnimation_shouldReturnFalse() {
+ mController.onStart();
+
+ setAnimationScale(ANIMATION_ON_VALUE);
+
+ assertThat(mController.isChecked()).isFalse();
+ assertThat(mPreference.isChecked()).isFalse();
+ }
+
+ @Test
+ public void onStart_disabledAnimation_shouldReturnTrue() {
+ mController.onStart();
+
+ setAnimationScale(ANIMATION_OFF_VALUE);
+
+ assertThat(mController.isChecked()).isTrue();
+ assertThat(mPreference.isChecked()).isTrue();
+ }
+
+ @Test
+ public void onStop_shouldNotUpdateTargets() {
+ mPreference.setChecked(true);
+ mController.onStart();
+ mController.onStop();
+
+ setAnimationScale(ANIMATION_ON_VALUE);
+
+ assertThat(mPreference.isChecked()).isTrue();
+ }
+
+ private void setAnimationScale(float newValue) {
+ for (String animationPreference : TOGGLE_ANIMATION_TARGETS) {
+ Settings.Global.putFloat(mContext.getContentResolver(), animationPreference,
+ newValue);
+ }
+ }
+}
\ No newline at end of file