Merge "Combine settings suggestion and condition."
diff --git a/res/layout-land/fingerprint_enroll_finish.xml b/res/layout-land/fingerprint_enroll_finish.xml
index b9e2a64..bb7e295 100644
--- a/res/layout-land/fingerprint_enroll_finish.xml
+++ b/res/layout-land/fingerprint_enroll_finish.xml
@@ -64,16 +64,6 @@
android:layout_height="wrap_content"
android:text="@string/security_settings_fingerprint_enroll_finish_message"/>
- <TextView
- android:id="@+id/message_secondary"
- style="@style/SuwDescription.Glif"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/suw_description_glif_margin_top"
- android:text="@string/setup_fingerprint_enroll_finish_message_secondary"
- android:textColor="?android:attr/textColorSecondary"
- android:visibility="gone" />
-
<Space
android:layout_width="0dp"
android:layout_height="0dp"
diff --git a/res/layout/fingerprint_enroll_finish_base.xml b/res/layout/fingerprint_enroll_finish_base.xml
index f0bd882..f666942 100644
--- a/res/layout/fingerprint_enroll_finish_base.xml
+++ b/res/layout/fingerprint_enroll_finish_base.xml
@@ -40,16 +40,6 @@
android:layout_marginTop="@dimen/suw_description_glif_margin_top"
android:text="@string/security_settings_fingerprint_enroll_finish_message"/>
- <TextView
- android:id="@+id/message_secondary"
- style="@style/SuwDescription.Glif"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/suw_description_glif_margin_top"
- android:text="@string/setup_fingerprint_enroll_finish_message_secondary"
- android:textColor="?android:attr/textColorSecondary"
- android:visibility="gone" />
-
<com.android.setupwizardlib.view.FillContentLayout
android:layout_width="match_parent"
android:layout_height="0dp"
diff --git a/res/layout/preference_progress_category.xml b/res/layout/preference_progress_category.xml
index d858697..2ac3dc1 100644
--- a/res/layout/preference_progress_category.xml
+++ b/res/layout/preference_progress_category.xml
@@ -20,9 +20,9 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
- android:layout_marginBottom="16dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="16dp"
+ android:paddingBottom="16dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<LinearLayout
@@ -56,6 +56,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="16dip"
+ android:minWidth="32dp"
android:text="@string/progress_scanning"/>
</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e00ac81..28252c5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -819,9 +819,9 @@
<!-- Introduction detail message shown in fingerprint enrollment dialog [CHAR LIMIT=NONE]-->
<string name="security_settings_fingerprint_enroll_introduction_message">Just touch the fingerprint sensor to unlock your phone, authorize purchases, or sign in to apps. Be careful whose fingerprints you add. Even one added print can do any of these things.\n\nNote: Your fingerprint may be less secure than a strong pattern or PIN.</string>
<!-- Introduction detail message shown in fingerprint enrollment dialog, when fingerprint unlock is disabled by device admin [CHAR LIMIT=NONE]-->
- <string name="security_settings_fingerprint_enroll_introduction_message_unlock_disabled">Just touch the fingerprint sensor to authorize purchases, or sign in to apps. Be careful whose fingerprints you add. Even one added print can do any of these things.\n\nNote: You can\u2019t use your fingerprint to unlock this device. For more information, contact your organization\u2019s admin.</string>
+ <string name="security_settings_fingerprint_enroll_introduction_message_unlock_disabled">Use your fingerprint to unlock your phone or approve purchases.\n\nNote: You can\u2019t use your fingerprint to unlock this device. For more information, contact your organization\u2019s admin.</string>
<!-- Introduction detail message shown in fingerprint enrollment screen in setup wizard. [CHAR LIMIT=NONE]-->
- <string name="security_settings_fingerprint_enroll_introduction_message_setup">Just touch the fingerprint sensor to unlock your phone, authorize purchases, or sign in to apps. Be careful whose fingerprints you add. Even one added print can do any of these things.\n\nNote: Your fingerprint may be less secure than a strong pattern or PIN.</string>
+ <string name="security_settings_fingerprint_enroll_introduction_message_setup">Use your fingerprint to unlock your phone or approve purchases.\n\nNote: Your fingerprint may be less secure than a strong pattern or PIN.</string>
<!-- Button text to cancel enrollment from the introduction [CHAR LIMIT=22] -->
<string name="security_settings_fingerprint_enroll_introduction_cancel">Cancel</string>
<!-- Button text to continue to the next screen from the introduction [CHAR LIMIT=22] -->
@@ -829,28 +829,27 @@
<!-- Button text to cancel enrollment from the introduction (this string variant is used while in setup wizard) [CHAR LIMIT=22] -->
<string name="security_settings_fingerprint_enroll_introduction_cancel_setup">Skip</string>
<!-- Button text to continue to the next screen from the introduction (this string variant is used while in setup wizard) [CHAR LIMIT=22] -->
- <string name="security_settings_fingerprint_enroll_introduction_continue_setup">Add fingerprint</string>
+ <string name="security_settings_fingerprint_enroll_introduction_continue_setup">Next</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning them of potential consequences of not doing so, including loss of factory reset protection. (tablet) [CHAR LIMIT=NONE] -->
- <string name="lock_screen_intro_skip_dialog_text_frp" product="tablet">Device protection features will not be activated. You won\u2019t be able to prevent others from using this tablet if it\u2019s lost, stolen or wiped.</string>
+ <string name="lock_screen_intro_skip_dialog_text_frp" product="tablet">Device protection features won\u2019t be turned on. You won\u2019t be able to prevent others from using this tablet if it\u2019s lost, stolen or reset.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning them of potential consequences of not doing so, including loss of factory reset protection. (device) [CHAR LIMIT=NONE] -->
- <string name="lock_screen_intro_skip_dialog_text_frp" product="device">Device protection features will not be activated. You won\u2019t be able to prevent others from using this device if it\u2019s lost, stolen or wiped.</string>
+ <string name="lock_screen_intro_skip_dialog_text_frp" product="device">Device protection features won\u2019t be turned on. You won\u2019t be able to prevent others from using this device if it\u2019s lost, stolen or reset.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning them of potential consequences of not doing so, including loss of factory reset protection. (phone) [CHAR LIMIT=NONE] -->
- <string name="lock_screen_intro_skip_dialog_text_frp" product="default">Device protection features will not be activated. You won\u2019t be able to prevent others from using this phone if it\u2019s lost, stolen or wiped.</string>
+ <string name="lock_screen_intro_skip_dialog_text_frp" product="default">Device protection features won\u2019t be turned on. You won\u2019t be able to prevent others from using this phone if it\u2019s lost, stolen or reset.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning them of potential consequences of not doing so. (tablet) [CHAR LIMIT=NONE] -->
- <string name="lock_screen_intro_skip_dialog_text" product="tablet">Device protection features will not be activated. You won\u2019t be able to prevent others from using this tablet if it\u2019s lost or stolen.</string>
+ <string name="lock_screen_intro_skip_dialog_text" product="tablet">Device protection features won\u2019t be turned on. You won\u2019t be able to prevent others from using this tablet if it\u2019s lost or stolen.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning them of potential consequences of not doing so. (device) [CHAR LIMIT=NONE] -->
- <string name="lock_screen_intro_skip_dialog_text" product="device">Device protection features will not be activated. You won\u2019t be able to prevent others from using this device if it\u2019s lost or stolen.</string>
+ <string name="lock_screen_intro_skip_dialog_text" product="device">Device protection features won\u2019t be turned on. You won\u2019t be able to prevent others from using this device if it\u2019s lost or stolen.</string>
<!-- Dialog text shown when the user tries to skip setting up a screen lock, warning them of potential consequences of not doing so. (phone) [CHAR LIMIT=NONE] -->
- <string name="lock_screen_intro_skip_dialog_text" product="default">Device protection features will not be activated. You won\u2019t be able to prevent others from using this phone if it\u2019s lost or stolen.</string>
+ <string name="lock_screen_intro_skip_dialog_text" product="default">Device protection features won\u2019t be turned on. You won\u2019t be able to prevent others from using this phone if it\u2019s lost or stolen.</string>
<!-- Button for skipping a step after having been warned of a potential concern [CHAR LIMIT=30] -->
<string name="skip_anyway_button_label">Skip anyway</string>
<!-- Button for going to the previous screen or step [CHAR LIMIT=20] -->
<string name="go_back_button_label">Go back</string>
<!-- Introduction title shown in fingerprint enrollment dialog to locate the sensor [CHAR LIMIT=29] -->
- <string name="security_settings_fingerprint_enroll_find_sensor_title">Find the sensor</string>
+ <string name="security_settings_fingerprint_enroll_find_sensor_title">Touch the sensor</string>
<!-- Message shown in fingerprint enrollment dialog to locate the sensor -->
- <string name="security_settings_fingerprint_enroll_find_sensor_message">
- Locate the fingerprint sensor on the back of your phone.</string>
+ <string name="security_settings_fingerprint_enroll_find_sensor_message">It\u2019s on the back of your phone. Use your index finger.</string>
<!-- Content description of graphic that shows where the fingerprint of the device is [CHAR LIMIT=NONE] -->
<string name="security_settings_fingerprint_enroll_find_sensor_content_description">Illustration with device and fingerprint sensor location</string>
<!-- Label text shown in fingerprint dialog for renaming a fingerprint template [CHAR LIMIT=22] -->
@@ -860,21 +859,17 @@
<!-- Button text shown in fingerprint dialog that allows the user to delete the fingerprint template [CHAR LIMIT=22] -->
<string name="security_settings_fingerprint_enroll_dialog_delete">Delete</string>
<!-- Title shown in fingerprint enrollment dialog to begin enrollment [CHAR LIMIT=29]-->
- <string name="security_settings_fingerprint_enroll_start_title">Let\u2019s start</string>
+ <string name="security_settings_fingerprint_enroll_start_title">Touch the sensor</string>
<!-- Message shown in fingerprint enrollment dialog to begin enrollment [CHAR LIMIT=NONE] -->
<string name="security_settings_fingerprint_enroll_start_message">Put your finger on the sensor and lift after you feel a vibration</string>
<!-- Title shown in fingerprint enrollment dialog to repeat touching the fingerprint sensor [CHAR LIMIT=29] -->
- <string name="security_settings_fingerprint_enroll_repeat_title">Keep going</string>
+ <string name="security_settings_fingerprint_enroll_repeat_title">Lift, then touch again</string>
<!-- Message shown in fingerprint enrollment dialog to repeat touching the fingerprint sensor [CHAR LIMIT=NONE] -->
- <string name="security_settings_fingerprint_enroll_repeat_message">Move your finger slightly to add all the different parts of your fingerprint</string>
+ <string name="security_settings_fingerprint_enroll_repeat_message">Keep lifting your finger to add the different parts of your fingerprint</string>
<!-- Title shown in fingerprint enrollment dialog once enrollment is completed [CHAR LIMIT=29] -->
- <string name="security_settings_fingerprint_enroll_finish_title">Fingerprint added!</string>
+ <string name="security_settings_fingerprint_enroll_finish_title">Fingerprint added</string>
<!-- Message shown in fingerprint enrollment dialog once enrollment is completed -->
- <string name="security_settings_fingerprint_enroll_finish_message">Whenever you see this icon, you can use your fingerprint for identification or to authorize a purchase.</string>
- <!-- Message shown when fingerprint enrollment is completed during setup wizard [CHAR LIMIT=NONE] -->
- <string name="setup_fingerprint_enroll_finish_message">Just touch the fingerprint sensor to wake and unlock your device.</string>
- <!-- Message shown when fingerprint enrollment is completed, telling user about the fingerprint icon that will be shown whenever they can use their fingerprint [CHAR LIMIT=NONE] -->
- <string name="setup_fingerprint_enroll_finish_message_secondary">When you see this icon, you can also authorize purchases or sign in to apps.</string>
+ <string name="security_settings_fingerprint_enroll_finish_message">When you see this icon, use your fingerprint for identification or to approve purchases.</string>
<!-- Title of the dialog shown when the user tries to skip fingerprint setup, asking them to confirm the action [CHAR LIMIT=40] -->
<string name="setup_fingerprint_enroll_enrolling_skip_title">Skip fingerprint setup?</string>
<!-- Content of the dialog shown when the user tries to skip fingerprint setup, asking them to confirm the action [CHAR LIMIT=NONE] -->
@@ -887,7 +882,7 @@
<!-- Dialog title for dialog which shows when user touches the icon on the screen, instead of the sensor at the back [CHAR LIMIT=45] -->
<string name="security_settings_fingerprint_enroll_touch_dialog_title">Whoops, that\u2019s not the sensor</string>
<!-- Dialog message for dialog which shows when user touches the icon on the screen, instead of the sensor at the back [CHAR LIMIT=45] -->
- <string name="security_settings_fingerprint_enroll_touch_dialog_message">Use the fingerprint sensor on your device.</string>
+ <string name="security_settings_fingerprint_enroll_touch_dialog_message">Touch the sensor on the back of your phone. Use your index finger.</string>
<!-- Dialog message for dialog which shows when finger cannot be enrolled. [CHAR LIMIT=45] -->
<string name="security_settings_fingerprint_enroll_error_dialog_title">Enrollment was not completed</string>
<!-- Dialog message for dialog which shows when finger cannot be enrolled due to being idle too long. -->
@@ -1527,6 +1522,18 @@
<string name="device_details_title">Device details</string>
<!-- Title of the item to show device MAC address -->
<string name="bluetooth_device_mac_address">Device\'s Bluetooth address: <xliff:g id="address">%1$s</xliff:g></string>
+ <!-- Bluetooth device details. The title of a confirmation dialog for unpairing a paired device. [CHAR LIMIT=60] -->
+ <string name="bluetooth_unpair_dialog_title">Forget device?</string>
+
+ <!-- Bluetooth device details. The body of a confirmation dialog for unpairing a paired device. -->
+ <string name="bluetooth_unpair_dialog_body" product="default">Your phone will no longer be paired with <xliff:g id="device_name">%1$s</xliff:g></string>
+ <!-- Bluetooth device details. The body of a confirmation dialog for unpairing a paired device. -->
+ <string name="bluetooth_unpair_dialog_body" product="tablet">Your tablet will no longer be paired with <xliff:g id="device_name">%1$s</xliff:g></string>
+ <!-- Bluetooth device details. The body of a confirmation dialog for unpairing a paired device. -->
+ <string name="bluetooth_unpair_dialog_body" product="device">Your device will no longer be paired with <xliff:g id="device_name">%1$s</xliff:g></string>
+
+ <!-- Bluetooth device details. In the confirmation dialog for unpairing a paired device, this is the label on the button that will complete the unpairing action. -->
+ <string name="bluetooth_unpair_dialog_forget_confirm_button">Forget device</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 user to choose which one he wants to connect to. -->
<string name="bluetooth_connect_specific_profiles_title">Connect to\u2026</string>
@@ -3540,9 +3547,11 @@
<!-- Applications settings screen, setting check box title. If checked, the system allows installation of applications that are downloaded from random places, such as web sites. [CHAR LIMIT=30] -->
<string name="install_applications_title">Allow all app sources</string>
<!-- Category title listing recently used apps [CHAR_LIMIT=50]-->
- <string name="recent_app_category_title">Recently opened</string>
+ <string name="recent_app_category_title">Recently opened apps</string>
<!-- Preference title for showing all apps on device [CHAR_LIMIT=50]-->
- <string name="see_all_apps_title">See all <xliff:g>%1$d</xliff:g> installed apps</string>
+ <string name="see_all_apps_title">See all <xliff:g>%1$d</xliff:g> apps</string>
+ <!-- Preference summary for each recently used app, which is the time since last used, i.e. "7 h 20 min ago". Note: ^1 should be used in all translations [CHAR_LIMIT=60] -->
+ <string name="recent_app_summary"><xliff:g id="time">^1</xliff:g> ago</string>
<!-- Warning that appears below the unknown sources switch in settings -->
<string name="install_all_warning" product="tablet">
@@ -3686,8 +3695,6 @@
<!-- Text for filter option in ManageApps screen to display list of
packages installed on sdcard. -->
<string name="filter_apps_onsdcard" product="default">On SD card</string>
- <!-- [CHAR LIMIT=25] Manage applications, text telling using an application is disabled. -->
- <string name="disabled">Disabled</string>
<!-- [CHAR LIMIT=30] Manage applications, text telling using an application is not installed for the current user. The key part is that it's not installed. -->
<string name="not_installed">Not installed for this user</string>
<!-- [CHAR LIMIT=30] App details, text telling an application is installed. -->
@@ -4625,7 +4632,51 @@
<string name="packages_subtitle">Included packages</string>
<!-- Activity title for battery abnormal details page [CHAR LIMIT=60] -->
- <string name="battery_abnormal_details_title">Abnormal app behavior</string>
+ <string name="battery_abnormal_details_title">Apps draining battery</string>
+
+ <!-- Summary for wakelock anomaly, means device been kept awake [CHAR LIMIT=60] -->
+ <string name="battery_abnormal_wakelock_summary">Keeping device awake</string>
+ <!-- Summary for wakeup alarm anomaly, meaning device been wakeup in the background [CHAR LIMIT=60] -->
+ <string name="battery_abnormal_wakeup_alarm_summary">Waking up device in background</string>
+ <!-- Summary for location anomaly, meaning device is requesting location too frequently [CHAR LIMIT=60] -->
+ <string name="battery_abnormal_location_summary">Requesting location frequently</string>
+ <!-- Summary for anomalies, meaning some apps behaving abnormally [CHAR LIMIT=80] -->
+ <string name="battery_abnormal_apps_summary"><xliff:g id="number">%1$d</xliff:g> apps misbehaving</string>
+
+ <!-- Title for force stop dialog [CHAR LIMIT=30] -->
+ <string name="dialog_stop_title">Stop app?</string>
+ <!-- Message body for force stop dialog [CHAR LIMIT=NONE] -->
+ <string name="dialog_stop_message" product="default">Your phone can\'t manage battery normally because <xliff:g id="app">%1$s</xliff:g> is keeping your phone awake.\n\nTo try to fix this issue, you can force stop the app.\n\nIf this keeps happening, you may need to uninstall the app to improve battery performance.</string>
+ <!-- Message body for force stop dialog [CHAR LIMIT=NONE] -->
+ <string name="dialog_stop_message" product="tablet">Your tablet can\'t manage battery normally because <xliff:g id="app">%1$s</xliff:g> is keeping your tablet awake.\n\nTo try to fix this issue, you can force stop the app.\n\nIf this keeps happening, you may need to uninstall the app to improve battery performance.</string>
+ <!-- Message body for force stop dialog [CHAR LIMIT=NONE] -->
+ <string name="dialog_stop_message" product="device">Your device can\'t manage battery normally because <xliff:g id="app">%1$s</xliff:g> is keeping your device awake.\n\nTo try to fix this issue, you can force stop the app.\n\nIf this keeps happening, you may need to uninstall the app to improve battery performance.</string>
+
+ <!-- Text for OK button in force stop dialog [CHAR LIMIT=30] -->
+ <string name="dialog_stop_ok">Stop app</string>
+
+ <!-- Title for background usage dialog [CHAR LIMIT=60] -->
+ <string name="dialog_background_check_title">Turn off background usage?</string>
+ <!-- Message body for background usage dialog [CHAR LIMIT=NONE] -->
+ <string name="dialog_background_check_message" product="default">Your phone can\'t manage battery normally because <xliff:g id="app">%1$s</xliff:g> keeps waking up your phone.\n\nTo address this issue, your phone can prevent this app from running in the background.</string>
+ <!-- Message body for background usage dialog [CHAR LIMIT=NONE] -->
+ <string name="dialog_background_check_message" product="tablet">Your tablet can\'t manage battery normally because <xliff:g id="app">%1$s</xliff:g> keeps waking up your tablet.\n\nTo address this issue, your tablet can prevent this app from running in the background.</string>
+ <!-- Message body for background usage dialog [CHAR LIMIT=NONE] -->
+ <string name="dialog_background_check_message" product="device">Your device can\'t manage battery normally because <xliff:g id="app">%1$s</xliff:g> keeps waking up your device.\n\nTo address this issue, your device can prevent this app from running in the background.</string>
+ <!-- Text for OK button in background usage dialog [CHAR LIMIT=30] -->
+ <string name="dialog_background_check_ok">Turn off</string>
+
+ <!-- Title for location dialog [CHAR LIMIT=60] -->
+ <string name="dialog_location_title">Turn off location?</string>
+ <!-- Message body for location dialog [CHAR LIMIT=NONE] -->
+ <string name="dialog_location_message" product="default">Your phone can’t manage battery normally because <xliff:g id="app">%1$s</xliff:g> keeps requesting your location when you're not using the app.\n\nTo fix this issue, you can turn off location for this app.</string>
+ <!-- Message body for location dialog [CHAR LIMIT=NONE] -->
+ <string name="dialog_location_message" product="tablet">Your tablet can’t manage battery normally because <xliff:g id="app">%1$s</xliff:g> keeps requesting your location when you're not using the app.\n\nTo fix this issue, you can turn off location for this app.</string>
+ <!-- Message body for location dialog [CHAR LIMIT=NONE] -->
+ <string name="dialog_location_message" product="device">Your device can’t manage battery normally because <xliff:g id="app">%1$s</xliff:g> keeps requesting your location when you're not using the app.\n\nTo fix this issue, you can turn off location for this app.</string>
+
+ <!-- Text for OK button in location dialog [CHAR LIMIT=30] -->
+ <string name="dialog_location_ok">Turn off</string>
<!-- Label for power consumed by the screen -->
<string name="power_screen">Screen</string>
@@ -4776,6 +4827,8 @@
<!-- Description for battery usage time for an app, i.e. Used for 30min. Note: ^1 should be used in all translations [CHAR LIMIT=60] -->
<string name="battery_used_for">Used for <xliff:g id="time">^1</xliff:g></string>
+ <!-- Description for battery usage background time for an app, i.e. Active for 30min. Note: ^1 should be used in all translations [CHAR LIMIT=60] -->
+ <string name="battery_active_for">Active for <xliff:g id="time">^1</xliff:g></string>
<!-- Description for battery screen usage time for an app, i.e. Screen usage 30min. Note: ^1 should be used in all translations [CHAR LIMIT=60] -->
<string name="battery_screen_usage">Screen usage <xliff:g id="time">^1</xliff:g></string>
<!-- Description for battery usage info for an app, i.e. 60% used by facebook. [CHAR LIMIT=60] -->
@@ -7504,6 +7557,12 @@
<item quantity="other"><xliff:g id="number">%2$d</xliff:g> apps behaving abnormally</item>
</plurals>
+ <!-- Title for high usage item, showing app/apps are draining battery [CHAR LIMIT=80] -->
+ <plurals name="power_high_usage_title">
+ <item quantity="one"><xliff:g id="app">%1$s</xliff:g> draining battery</item>
+ <item quantity="other">Apps draining battery</item>
+ </plurals>
+
<!-- Filter for apps allowed to use a lot of power [CHAR LIMIT=25] -->
<string name="high_power_filter_on">Not optimized</string>
@@ -7777,8 +7836,6 @@
<!-- Preference summary text for an app when it is disallowed for a permission. [CHAR LIMIT=45] -->
<string name="app_permission_summary_not_allowed">Not allowed</string>
- <!-- Title for settings screen for controlling apps that can install other apps on device [CHAR LIMIT=50] -->
- <string name="install_other_apps">Install unknown apps</string>
<!-- Keywords for setting screen for controlling apps that can install other apps on device -->
<string name="keywords_install_other_apps">install apps unknown sources</string>
@@ -7804,6 +7861,10 @@
<string name="permit_write_settings">Allow modify system settings</string>
<!-- Description of the write system settings [CHAR LIMIT=NONE] -->
<string name="write_settings_description">This permission allows an app to modify system settings.</string>
+ <!-- Summary of app allowed to write system settings [CHAR LIMIT=45] -->
+ <string name="write_settings_on">Yes</string>
+ <!-- Summary of app not allowed to write system settings [CHAR LIMIT=45] -->
+ <string name="write_settings_off">No</string>
<!-- Title of switch preference that controls whether an external app source is trusted or not [CHAR LIMIT=50] -->
<string name="external_source_switch_title">Allow from this source</string>
@@ -8848,4 +8909,8 @@
<!-- Summary label for new device suggestion, which is displayed in Settings homepage [CHAR LIMIT=100] -->
<string name="new_device_suggestion_summary">Check out the top 5 features</string>
+
+ <!-- The divider symbol between different parts of the notification header including spaces. not translatable [CHAR LIMIT=3] -->
+ <string name="notification_header_divider_symbol_with_spaces" translatable="false">" • "</string>
+
</resources>
diff --git a/res/xml/enterprise_privacy_settings.xml b/res/xml/enterprise_privacy_settings.xml
index 45c784e..2d07fa2 100644
--- a/res/xml/enterprise_privacy_settings.xml
+++ b/res/xml/enterprise_privacy_settings.xml
@@ -45,7 +45,8 @@
android:selectable="false"/>
</PreferenceCategory>
- <PreferenceCategory android:title="@string/enterprise_privacy_exposure_changes_category">
+ <PreferenceCategory android:title="@string/enterprise_privacy_exposure_changes_category"
+ android:key="exposure_changes_category">
<Preference android:fragment="com.android.settings.enterprise.ApplicationListFragment$EnterpriseInstalledPackages"
android:key="number_enterprise_installed_packages"
android:title="@string/enterprise_privacy_enterprise_installed_packages"/>
diff --git a/res/xml/wifi_network_details_fragment.xml b/res/xml/wifi_network_details_fragment.xml
index fafbbb0..f9926ca 100644
--- a/res/xml/wifi_network_details_fragment.xml
+++ b/res/xml/wifi_network_details_fragment.xml
@@ -82,8 +82,12 @@
<!-- IPv6 Details -->
<PreferenceCategory
- android:key="ipv6_details_category"
+ android:key="ipv6_category"
android:title="@string/wifi_details_ipv6_address_header"
- android:selectable="false"/>
+ android:selectable="false">
+ <Preference
+ android:key="ipv6_addresses"
+ android:selectable="false"/>
+ </PreferenceCategory>
</PreferenceScreen>
diff --git a/src/com/android/settings/ApnEditor.java b/src/com/android/settings/ApnEditor.java
index 11d8ea2..c8f9dd1 100644
--- a/src/com/android/settings/ApnEditor.java
+++ b/src/com/android/settings/ApnEditor.java
@@ -1056,7 +1056,7 @@
if (errorMsg == null) {
// if carrier does not allow editing certain apn types, make sure type does not include
// those
- if (mReadOnlyApnTypes.length > 0
+ if (!ArrayUtils.isEmpty(mReadOnlyApnTypes)
&& apnTypesMatch(mReadOnlyApnTypes, mApnType.getText())) {
StringBuilder stringBuilder = new StringBuilder();
for (String type : mReadOnlyApnTypes) {
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index f0d5107..15e4eb3 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -603,7 +603,8 @@
}
/**
- * Returns the managed profile of the current user or null if none found.
+ * Returns the managed profile of the current user or {@code null} if none is found or a profile
+ * exists but it is disabled.
*/
public static UserHandle getManagedProfile(UserManager userManager) {
List<UserHandle> userProfiles = userManager.getUserProfiles();
@@ -622,6 +623,29 @@
}
/**
+ * Returns the managed profile of the current user or {@code null} if none is found. Unlike
+ * {@link #getManagedProfile} this method returns enabled and disabled managed profiles.
+ */
+ public static UserHandle getManagedProfileWithDisabled(UserManager userManager) {
+ // TODO: Call getManagedProfileId from here once Robolectric supports
+ // API level 24 and UserManager.getProfileIdsWithDisabled can be Mocked (to avoid having
+ // yet another implementation that loops over user profiles in this method). In the meantime
+ // we need to use UserManager.getProfiles that is available on API 23 (the one currently
+ // used for Settings Robolectric tests).
+ final int myUserId = UserHandle.myUserId();
+ List<UserInfo> profiles = userManager.getProfiles(myUserId);
+ final int count = profiles.size();
+ for (int i = 0; i < count; i++) {
+ final UserInfo profile = profiles.get(i);
+ if (profile.isManagedProfile()
+ && profile.getUserHandle().getIdentifier() != myUserId) {
+ return profile.getUserHandle();
+ }
+ }
+ return null;
+ }
+
+ /**
* Retrieves the id for the given user's managed profile.
*
* @return the managed profile id or UserHandle.USER_NULL if there is none.
diff --git a/src/com/android/settings/accounts/AutoSyncWorkDataPreferenceController.java b/src/com/android/settings/accounts/AutoSyncWorkDataPreferenceController.java
index 1d08968..acf43aa 100644
--- a/src/com/android/settings/accounts/AutoSyncWorkDataPreferenceController.java
+++ b/src/com/android/settings/accounts/AutoSyncWorkDataPreferenceController.java
@@ -27,7 +27,7 @@
public AutoSyncWorkDataPreferenceController(Context context, Fragment parent) {
super(context, parent);
- mUserHandle = Utils.getManagedProfile(mUserManager);
+ mUserHandle = Utils.getManagedProfileWithDisabled(mUserManager);
}
@Override
diff --git a/src/com/android/settings/applications/ExternalSourcesDetails.java b/src/com/android/settings/applications/ExternalSourcesDetails.java
index 7158900..36ce8f8 100644
--- a/src/com/android/settings/applications/ExternalSourcesDetails.java
+++ b/src/com/android/settings/applications/ExternalSourcesDetails.java
@@ -89,13 +89,9 @@
return context.getString(R.string.disabled);
}
- final InstallAppsState appsState;
- if (entry.extraInfo instanceof InstallAppsState) {
- appsState = (InstallAppsState) entry.extraInfo;
- } else {
- appsState = new AppStateInstallAppsBridge(context, null, null)
- .createInstallAppsStateFor(entry.info.packageName, entry.info.uid);
- }
+ final InstallAppsState appsState = new AppStateInstallAppsBridge(context, null, null)
+ .createInstallAppsStateFor(entry.info.packageName, entry.info.uid);
+
return context.getString(appsState.canInstallApps()
? R.string.app_permission_summary_allowed
: R.string.app_permission_summary_not_allowed);
diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java
index 60a08ef..e66463d 100755
--- a/src/com/android/settings/applications/InstalledAppDetails.java
+++ b/src/com/android/settings/applications/InstalledAppDetails.java
@@ -139,7 +139,8 @@
private static final int LOADER_CHART_DATA = 2;
private static final int LOADER_STORAGE = 3;
- private static final int LOADER_BATTERY = 4;
+ @VisibleForTesting
+ static final int LOADER_BATTERY = 4;
private static final int DLG_FORCE_STOP = DLG_BASE + 1;
private static final int DLG_DISABLE = DLG_BASE + 2;
@@ -202,7 +203,8 @@
private AppStorageStats mLastResult;
private String mBatteryPercent;
- private final LoaderCallbacks<BatteryStatsHelper> mBatteryCallbacks =
+ @VisibleForTesting
+ final LoaderCallbacks<BatteryStatsHelper> mBatteryCallbacks =
new LoaderCallbacks<BatteryStatsHelper>() {
@Override
@@ -410,11 +412,16 @@
mDataCallbacks);
loaderManager.restartLoader(LOADER_STORAGE, Bundle.EMPTY, this);
}
- getLoaderManager().initLoader(LOADER_BATTERY, Bundle.EMPTY, mBatteryCallbacks);
+ restartBatteryStatsLoader();
new MemoryUpdater().execute();
updateDynamicPrefs();
}
+ @VisibleForTesting
+ public void restartBatteryStatsLoader() {
+ getLoaderManager().restartLoader(LOADER_BATTERY, Bundle.EMPTY, mBatteryCallbacks);
+ }
+
@Override
public void onPause() {
getLoaderManager().destroyLoader(LOADER_CHART_DATA);
diff --git a/src/com/android/settings/core/DynamicAvailabilityPreferenceController.java b/src/com/android/settings/core/DynamicAvailabilityPreferenceController.java
index 6db57e6..df6ccd3 100644
--- a/src/com/android/settings/core/DynamicAvailabilityPreferenceController.java
+++ b/src/com/android/settings/core/DynamicAvailabilityPreferenceController.java
@@ -29,6 +29,7 @@
private Preference mPreference;
private PreferenceScreen mScreen;
+ private PreferenceAvailabilityObserver mAvailabilityObserver = null;
public DynamicAvailabilityPreferenceController(Context context, Lifecycle lifecycle) {
super(context);
@@ -37,6 +38,14 @@
}
}
+ public void setAvailabilityObserver(PreferenceAvailabilityObserver observer) {
+ mAvailabilityObserver = observer;
+ }
+
+ public PreferenceAvailabilityObserver getAvailabilityObserver() {
+ return mAvailabilityObserver;
+ }
+
@Override
public void displayPreference(PreferenceScreen screen) {
mScreen = screen;
@@ -56,4 +65,10 @@
mScreen.addPreference(mPreference);
}
}
+
+ protected void notifyOnAvailabilityUpdate(boolean available) {
+ if (mAvailabilityObserver != null) {
+ mAvailabilityObserver.onPreferenceAvailabilityUpdated(getPreferenceKey(), available);
+ }
+ }
}
diff --git a/src/com/android/settings/core/PreferenceAvailabilityObserver.java b/src/com/android/settings/core/PreferenceAvailabilityObserver.java
new file mode 100644
index 0000000..46ff3ba
--- /dev/null
+++ b/src/com/android/settings/core/PreferenceAvailabilityObserver.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 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.core;
+
+/**
+ * @deprecated This interface allows a {@link android.support.v7.preference.PreferenceGroup}'s
+ * controller to observe the availability of the {@link android.support.v7.preference.Preference}s
+ * inside it, hiding the group when all preferences become unavailable. In the future,
+ * {@link android.support.v7.preference.PreferenceGroup} will have native support for that
+ * functionality, removing the need for this interface.
+ */
+public interface PreferenceAvailabilityObserver {
+
+ /**
+ * Notifies the observer that the availability of the preference identified by {@code key} has
+ * been updated.
+ */
+ void onPreferenceAvailabilityUpdated(String key, boolean available);
+}
diff --git a/src/com/android/settings/display/AutoBrightnessPreferenceController.java b/src/com/android/settings/display/AutoBrightnessPreferenceController.java
index 0cff755..f731264 100644
--- a/src/com/android/settings/display/AutoBrightnessPreferenceController.java
+++ b/src/com/android/settings/display/AutoBrightnessPreferenceController.java
@@ -19,15 +19,12 @@
import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.Preference;
-import android.util.ArrayMap;
import com.android.settings.core.PreferenceController;
import com.android.settings.search.DatabaseIndexingUtils;
import com.android.settings.search.InlineSwitchPayload;
import com.android.settings.search.ResultPayload;
import com.android.settings.R;
-import java.util.Map;
-
import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE;
import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
@@ -71,15 +68,12 @@
@Override
public ResultPayload getResultPayload() {
- final Map<Integer, Boolean> valueMap = new ArrayMap<>();
- valueMap.put(SCREEN_BRIGHTNESS_MODE_AUTOMATIC, true);
- valueMap.put(SCREEN_BRIGHTNESS_MODE_MANUAL, false);
-
final Intent intent = DatabaseIndexingUtils.buildSubsettingIntent(mContext,
getClass().getName(), mAutoBrightnessKey,
mContext.getString(R.string.display_settings));
return new InlineSwitchPayload(SCREEN_BRIGHTNESS_MODE,
- ResultPayload.SettingsSource.SYSTEM, valueMap, intent);
+ ResultPayload.SettingsSource.SYSTEM, SCREEN_BRIGHTNESS_MODE_AUTOMATIC, intent,
+ isAvailable());
}
}
diff --git a/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java b/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java
index 1fe35a3..9187cec 100644
--- a/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java
+++ b/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerBase.java
@@ -50,15 +50,15 @@
true /* async */,
(num) -> {
if (num == 0) {
- preference.setVisible(false);
mHasApps = false;
} else {
- preference.setVisible(true);
preference.setSummary(mContext.getResources().getQuantityString(
R.plurals.enterprise_privacy_number_packages_lower_bound,
num, num));
mHasApps = true;
}
+ preference.setVisible(mHasApps);
+ notifyOnAvailabilityUpdate(mHasApps);
});
}
@@ -80,6 +80,7 @@
mFeatureProvider.calculateNumberOfAppsWithAdminGrantedPermissions(mPermissions,
false /* async */, (num) -> haveAppsWithAdminGrantedPermissions[0] = num > 0);
mHasApps = haveAppsWithAdminGrantedPermissions[0];
+ notifyOnAvailabilityUpdate(mHasApps);
return mHasApps;
}
diff --git a/src/com/android/settings/enterprise/AlwaysOnVpnCurrentUserPreferenceController.java b/src/com/android/settings/enterprise/AlwaysOnVpnCurrentUserPreferenceController.java
index a2ff5b6..ed91fef 100644
--- a/src/com/android/settings/enterprise/AlwaysOnVpnCurrentUserPreferenceController.java
+++ b/src/com/android/settings/enterprise/AlwaysOnVpnCurrentUserPreferenceController.java
@@ -42,7 +42,9 @@
@Override
public boolean isAvailable() {
- return mFeatureProvider.isAlwaysOnVpnSetInCurrentUser();
+ final boolean available = mFeatureProvider.isAlwaysOnVpnSetInCurrentUser();
+ notifyOnAvailabilityUpdate(available);
+ return available;
}
@Override
diff --git a/src/com/android/settings/enterprise/AlwaysOnVpnManagedProfilePreferenceController.java b/src/com/android/settings/enterprise/AlwaysOnVpnManagedProfilePreferenceController.java
index 9851252..db6fc1d 100644
--- a/src/com/android/settings/enterprise/AlwaysOnVpnManagedProfilePreferenceController.java
+++ b/src/com/android/settings/enterprise/AlwaysOnVpnManagedProfilePreferenceController.java
@@ -33,7 +33,9 @@
@Override
public boolean isAvailable() {
- return mFeatureProvider.isAlwaysOnVpnSetInManagedProfile();
+ final boolean available = mFeatureProvider.isAlwaysOnVpnSetInManagedProfile();
+ notifyOnAvailabilityUpdate(available);
+ return available;
}
@Override
diff --git a/src/com/android/settings/enterprise/CaCertsPreferenceController.java b/src/com/android/settings/enterprise/CaCertsPreferenceController.java
index c5203c7..65c109c 100644
--- a/src/com/android/settings/enterprise/CaCertsPreferenceController.java
+++ b/src/com/android/settings/enterprise/CaCertsPreferenceController.java
@@ -44,8 +44,11 @@
@Override
public boolean isAvailable() {
- return mFeatureProvider.getNumberOfOwnerInstalledCaCertsForCurrentUserAndManagedProfile()
- > 0;
+ final boolean available =
+ mFeatureProvider.getNumberOfOwnerInstalledCaCertsForCurrentUserAndManagedProfile()
+ > 0;
+ notifyOnAvailabilityUpdate(available);
+ return available;
}
@Override
diff --git a/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceController.java b/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceController.java
index 9a64788..cced8e8 100644
--- a/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceController.java
+++ b/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceController.java
@@ -42,14 +42,18 @@
public void updateState(Preference preference) {
mFeatureProvider.calculateNumberOfPolicyInstalledApps(true /* async */,
(num) -> {
+ final boolean available;
if (num == 0) {
- preference.setVisible(false);
+ available = false;
} else {
- preference.setVisible(true);
+ available = true;
preference.setSummary(mContext.getResources().getQuantityString(
R.plurals.enterprise_privacy_number_packages_lower_bound, num,
num));
+
}
+ preference.setVisible(available);
+ notifyOnAvailabilityUpdate(available);
});
}
@@ -68,7 +72,9 @@
final Boolean[] haveEnterpriseInstalledPackages = { null };
mFeatureProvider.calculateNumberOfPolicyInstalledApps(false /* async */,
(num) -> haveEnterpriseInstalledPackages[0] = num > 0);
- return haveEnterpriseInstalledPackages[0];
+ final boolean available = haveEnterpriseInstalledPackages[0];
+ notifyOnAvailabilityUpdate(available);
+ return available;
}
@Override
diff --git a/src/com/android/settings/enterprise/EnterprisePrivacyPreferenceController.java b/src/com/android/settings/enterprise/EnterprisePrivacyPreferenceController.java
index e45373d..6ec091b 100644
--- a/src/com/android/settings/enterprise/EnterprisePrivacyPreferenceController.java
+++ b/src/com/android/settings/enterprise/EnterprisePrivacyPreferenceController.java
@@ -45,7 +45,9 @@
@Override
public boolean isAvailable() {
- return mFeatureProvider.hasDeviceOwner();
+ final boolean available = mFeatureProvider.hasDeviceOwner();
+ notifyOnAvailabilityUpdate(available);
+ return available;
}
@Override
diff --git a/src/com/android/settings/enterprise/EnterprisePrivacySettings.java b/src/com/android/settings/enterprise/EnterprisePrivacySettings.java
index b6860e3..54f0508 100644
--- a/src/com/android/settings/enterprise/EnterprisePrivacySettings.java
+++ b/src/com/android/settings/enterprise/EnterprisePrivacySettings.java
@@ -61,23 +61,32 @@
controllers.add(new NetworkLogsPreferenceController(context));
controllers.add(new BugReportsPreferenceController(context));
controllers.add(new SecurityLogsPreferenceController(context));
- controllers.add(new EnterpriseInstalledPackagesPreferenceController(context, lifecycle,
- async));
- controllers.add(new AdminGrantedLocationPermissionsPreferenceController(context, lifecycle,
- async));
- controllers.add(new AdminGrantedMicrophonePermissionPreferenceController(context, lifecycle,
- async));
- controllers.add(new AdminGrantedCameraPermissionPreferenceController(context, lifecycle,
- async));
- controllers.add(new EnterpriseSetDefaultAppsPreferenceController(context, lifecycle));
- controllers.add(new AlwaysOnVpnCurrentUserPreferenceController(context, lifecycle));
- controllers.add(new AlwaysOnVpnManagedProfilePreferenceController(context, lifecycle));
- controllers.add(new GlobalHttpProxyPreferenceController(context, lifecycle));
- controllers.add(new CaCertsPreferenceController(context, lifecycle));
+ final List exposureChangesCategoryControllers = new ArrayList<PreferenceController>();
+ exposureChangesCategoryControllers.add(new EnterpriseInstalledPackagesPreferenceController(
+ context, lifecycle, async));
+ exposureChangesCategoryControllers.add(
+ new AdminGrantedLocationPermissionsPreferenceController(context, lifecycle, async));
+ exposureChangesCategoryControllers.add(
+ new AdminGrantedMicrophonePermissionPreferenceController(context, lifecycle,
+ async));
+ exposureChangesCategoryControllers.add(new AdminGrantedCameraPermissionPreferenceController(
+ context, lifecycle, async));
+ exposureChangesCategoryControllers.add(new EnterpriseSetDefaultAppsPreferenceController(
+ context, lifecycle));
+ exposureChangesCategoryControllers.add(new AlwaysOnVpnCurrentUserPreferenceController(
+ context, lifecycle));
+ exposureChangesCategoryControllers.add(new AlwaysOnVpnManagedProfilePreferenceController(
+ context, lifecycle));
+ exposureChangesCategoryControllers.add(new ImePreferenceController(context, lifecycle));
+ exposureChangesCategoryControllers.add(new GlobalHttpProxyPreferenceController(context,
+ lifecycle));
+ exposureChangesCategoryControllers.add(new CaCertsPreferenceController(context, lifecycle));
+ controllers.addAll(exposureChangesCategoryControllers);
+ controllers.add(new ExposureChangesCategoryPreferenceController(context, lifecycle,
+ exposureChangesCategoryControllers, async));
controllers.add(new FailedPasswordWipeCurrentUserPreferenceController(context, lifecycle));
controllers.add(new FailedPasswordWipeManagedProfilePreferenceController(context,
lifecycle));
- controllers.add(new ImePreferenceController(context, lifecycle));
return controllers;
}
diff --git a/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceController.java b/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceController.java
index 946631e..537ef78 100644
--- a/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceController.java
+++ b/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceController.java
@@ -1,3 +1,4 @@
+
/*
* Copyright (C) 2017 The Android Open Source Project
*
@@ -48,7 +49,9 @@
@Override
public boolean isAvailable() {
- return getNumberOfEnterpriseSetDefaultApps() > 0;
+ final boolean available = getNumberOfEnterpriseSetDefaultApps() > 0;
+ notifyOnAvailabilityUpdate(available);
+ return available;
}
@Override
diff --git a/src/com/android/settings/enterprise/ExposureChangesCategoryPreferenceController.java b/src/com/android/settings/enterprise/ExposureChangesCategoryPreferenceController.java
new file mode 100644
index 0000000..7833325
--- /dev/null
+++ b/src/com/android/settings/enterprise/ExposureChangesCategoryPreferenceController.java
@@ -0,0 +1,111 @@
+
+/*
+ * Copyright (C) 2017 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.enterprise;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.core.DynamicAvailabilityPreferenceController;
+import com.android.settings.core.PreferenceAvailabilityObserver;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A controller that hides a {@link android.support.v7.preference.PreferenceGroup} when none of the
+ * {@link Preference}s inside it are visible.
+ *
+ * TODO(b/62051162): Use {@link android.support.v7.preference.PreferenceGroup}'s native ability to
+ * hide itself when all {@link Preference}s inside it are invisible when that functionality becomes
+ * available. This custom controller will still be needed to remove the
+ * {@link android.support.v7.preference.PreferenceGroup} from the search index as required (by
+ * having {@link #isAvailable()} return {@code false} if the method returns {@code false} for all
+ * {@link Preference}s in the {@link android.support.v7.preference.PreferenceGroup}).
+ */
+public class ExposureChangesCategoryPreferenceController
+ extends DynamicAvailabilityPreferenceController implements PreferenceAvailabilityObserver {
+
+ private static final String KEY_EXPOSURE_CHANGES_CATEGORY = "exposure_changes_category";
+ private final Set<String> mAvailablePrefs = new HashSet<String>();
+ private Preference mPreference = null;
+ private boolean mControllingUi;
+
+ /**
+ * When {@code controllingUi} is {@code true}, some of the preferences may have their visibility
+ * determined asynchronously. In this case, {@link #isAvailable()} must always return {@code
+ * true} and the group should be hidden using {@link Preference#setVisible()} if all preferences
+ * report that they are invisible.
+ * When {@code controllingUi} is {@code false}, we are running on the search indexer thread and
+ * visibility must be determined synchronously. {@link #isAvailable()} can rely on all
+ * preferences having their visibility determined already and should return whether the group is
+ * visible or not.
+ */
+ public ExposureChangesCategoryPreferenceController(Context context, Lifecycle lifecycle,
+ List<DynamicAvailabilityPreferenceController> controllers, boolean controllingUi) {
+ super(context, lifecycle);
+ mControllingUi = controllingUi;
+ for (final DynamicAvailabilityPreferenceController controller : controllers) {
+ controller.setAvailabilityObserver(this);
+ }
+ }
+
+ @Override
+ public void onPreferenceAvailabilityUpdated(String key, boolean available) {
+ if (available) {
+ mAvailablePrefs.add(key);
+ } else {
+ mAvailablePrefs.remove(key);
+ }
+ available = haveAnyVisiblePreferences();
+ if (mControllingUi) {
+ notifyOnAvailabilityUpdate(available);
+ }
+ if (mPreference != null) {
+ mPreference.setVisible(available);
+ }
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ mPreference = preference;
+ mPreference.setVisible(haveAnyVisiblePreferences());
+ }
+
+ @Override
+ public boolean isAvailable() {
+ if (mControllingUi) {
+ // When running on the main UI thread, some preferences determine their visibility
+ // asynchronously. Always return true here and determine the pref group's actual
+ // visibility as the other preferences report their visibility asynchronously via
+ // onPreferenceAvailabilityUpdated().
+ return true;
+ }
+ final boolean available = haveAnyVisiblePreferences();
+ notifyOnAvailabilityUpdate(available);
+ return available;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_EXPOSURE_CHANGES_CATEGORY;
+ }
+
+ private boolean haveAnyVisiblePreferences() {
+ return mAvailablePrefs.size() > 0;
+ }
+}
diff --git a/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerBase.java b/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerBase.java
index 530de1c..298f911 100644
--- a/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerBase.java
+++ b/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerBase.java
@@ -46,6 +46,8 @@
@Override
public boolean isAvailable() {
- return getMaximumFailedPasswordsBeforeWipe() > 0;
+ final boolean available = getMaximumFailedPasswordsBeforeWipe() > 0;
+ notifyOnAvailabilityUpdate(available);
+ return available;
}
}
diff --git a/src/com/android/settings/enterprise/GlobalHttpProxyPreferenceController.java b/src/com/android/settings/enterprise/GlobalHttpProxyPreferenceController.java
index 277b623..55552b6 100644
--- a/src/com/android/settings/enterprise/GlobalHttpProxyPreferenceController.java
+++ b/src/com/android/settings/enterprise/GlobalHttpProxyPreferenceController.java
@@ -32,7 +32,9 @@
@Override
public boolean isAvailable() {
- return mFeatureProvider.isGlobalHttpProxySet();
+ final boolean available = mFeatureProvider.isGlobalHttpProxySet();
+ notifyOnAvailabilityUpdate(available);
+ return available;
}
@Override
diff --git a/src/com/android/settings/enterprise/ImePreferenceController.java b/src/com/android/settings/enterprise/ImePreferenceController.java
index f2d5ddf..ca52fc0 100644
--- a/src/com/android/settings/enterprise/ImePreferenceController.java
+++ b/src/com/android/settings/enterprise/ImePreferenceController.java
@@ -43,7 +43,9 @@
@Override
public boolean isAvailable() {
- return mFeatureProvider.getImeLabelIfOwnerSet() != null;
+ final boolean available = mFeatureProvider.getImeLabelIfOwnerSet() != null;
+ notifyOnAvailabilityUpdate(available);
+ return available;
}
@Override
diff --git a/src/com/android/settings/fingerprint/SetupFingerprintEnrollFinish.java b/src/com/android/settings/fingerprint/SetupFingerprintEnrollFinish.java
index 4d7395d..43ff097 100644
--- a/src/com/android/settings/fingerprint/SetupFingerprintEnrollFinish.java
+++ b/src/com/android/settings/fingerprint/SetupFingerprintEnrollFinish.java
@@ -49,12 +49,6 @@
@Override
protected void initViews() {
super.initViews();
-
- final TextView message = (TextView) findViewById(R.id.message);
- message.setText(R.string.setup_fingerprint_enroll_finish_message);
-
- final TextView secondaryMessage = (TextView) findViewById(R.id.message_secondary);
- secondaryMessage.setVisibility(View.VISIBLE);
}
@Override
diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
index 8dbb183..72dc835 100644
--- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
@@ -28,6 +28,7 @@
import android.support.annotation.VisibleForTesting;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.Preference;
+import android.text.TextUtils;
import android.view.View;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -86,9 +87,12 @@
@VisibleForTesting
BatteryUtils mBatteryUtils;
- private Preference mForegroundPreference;
- private Preference mBackgroundPreference;
- private Preference mPowerUsagePreference;
+ @VisibleForTesting
+ Preference mForegroundPreference;
+ @VisibleForTesting
+ Preference mBackgroundPreference;
+ @VisibleForTesting
+ Preference mPowerUsagePreference;
private AppButtonsPreferenceController mAppButtonsPreferenceController;
private DevicePolicyManagerWrapper mDpm;
@@ -174,18 +178,7 @@
super.onResume();
initHeader();
-
- final Bundle bundle = getArguments();
- final Context context = getContext();
-
- final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME);
- final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME);
- final String usagePercent = bundle.getString(EXTRA_POWER_USAGE_PERCENT);
- final int powerMah = bundle.getInt(EXTRA_POWER_USAGE_AMOUNT);
- mForegroundPreference.setSummary(Utils.formatElapsedTime(context, foregroundTimeMs, false));
- mBackgroundPreference.setSummary(Utils.formatElapsedTime(context, backgroundTimeMs, false));
- mPowerUsagePreference.setSummary(
- getString(R.string.battery_detail_power_percentage, usagePercent, powerMah));
+ initPreference();
}
@VisibleForTesting
@@ -222,6 +215,25 @@
controller.done(context, true /* rebindActions */);
}
+ @VisibleForTesting
+ void initPreference() {
+ final Bundle bundle = getArguments();
+ final Context context = getContext();
+
+ final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME);
+ final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME);
+ final String usagePercent = bundle.getString(EXTRA_POWER_USAGE_PERCENT);
+ final int powerMah = bundle.getInt(EXTRA_POWER_USAGE_AMOUNT);
+ mForegroundPreference.setSummary(
+ TextUtils.expandTemplate(getText(R.string.battery_used_for),
+ Utils.formatElapsedTime(context, foregroundTimeMs, false)));
+ mBackgroundPreference.setSummary(
+ TextUtils.expandTemplate(getText(R.string.battery_active_for),
+ Utils.formatElapsedTime(context, backgroundTimeMs, false)));
+ mPowerUsagePreference.setSummary(
+ getString(R.string.battery_detail_power_percentage, usagePercent, powerMah));
+ }
+
@Override
public int getMetricsCategory() {
return MetricsEvent.FUELGAUGE_POWER_USAGE_DETAIL;
diff --git a/src/com/android/settings/fuelgauge/BatteryCellParser.java b/src/com/android/settings/fuelgauge/BatteryCellParser.java
index aaac024f..2b39877 100644
--- a/src/com/android/settings/fuelgauge/BatteryCellParser.java
+++ b/src/com/android/settings/fuelgauge/BatteryCellParser.java
@@ -19,7 +19,6 @@
import android.util.SparseIntArray;
import com.android.settings.Utils;
import com.android.settings.fuelgauge.BatteryActiveView.BatteryActiveProvider;
-import com.android.settingslib.BatteryInfo;
public class BatteryCellParser implements BatteryInfo.BatteryDataParser, BatteryActiveProvider {
diff --git a/src/com/android/settings/fuelgauge/BatteryFlagParser.java b/src/com/android/settings/fuelgauge/BatteryFlagParser.java
index cd5d89b..d4f3fb2 100644
--- a/src/com/android/settings/fuelgauge/BatteryFlagParser.java
+++ b/src/com/android/settings/fuelgauge/BatteryFlagParser.java
@@ -18,7 +18,6 @@
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.settings.fuelgauge.BatteryActiveView.BatteryActiveProvider;
-import com.android.settingslib.BatteryInfo;
public class BatteryFlagParser implements BatteryInfo.BatteryDataParser, BatteryActiveProvider {
diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
index 04e2c7a..70149c1 100644
--- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
@@ -27,7 +27,6 @@
import com.android.settings.R;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.core.PreferenceController;
-import com.android.settingslib.BatteryInfo;
import com.android.settingslib.Utils;
/**
diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java b/src/com/android/settings/fuelgauge/BatteryHistoryChart.java
index 8588f77..bbbcb06 100644
--- a/src/com/android/settings/fuelgauge/BatteryHistoryChart.java
+++ b/src/com/android/settings/fuelgauge/BatteryHistoryChart.java
@@ -40,7 +40,6 @@
import android.view.View;
import com.android.settings.R;
import com.android.settings.Utils;
-import com.android.settingslib.BatteryInfo;
import libcore.icu.LocaleData;
import java.util.ArrayList;
@@ -507,81 +506,82 @@
mMaxPercentLabelString = Utils.formatPercentage(100);
mMinPercentLabelString = Utils.formatPercentage(0);
- mInfo = BatteryInfo.getBatteryInfo(getContext(), mBatteryBroadcast, mStats,
- elapsedRealtimeUs);
- mDrainString = "";
- mChargeDurationString = "";
- setContentDescription(mInfo.chargeLabelString);
+ BatteryInfo.getBatteryInfo(getContext(), info -> {
+ mInfo = info;
+ mDrainString = "";
+ mChargeDurationString = "";
+ setContentDescription(mInfo.chargeLabelString);
- int pos = 0;
- int lastInteresting = 0;
- byte lastLevel = -1;
- mBatLow = 0;
- mBatHigh = 100;
- mStartWallTime = 0;
- mEndDataWallTime = 0;
- mEndWallTime = 0;
- mHistStart = 0;
- mHistEnd = 0;
- long lastWallTime = 0;
- long lastRealtime = 0;
- int aggrStates = 0;
- int aggrStates2 = 0;
- boolean first = true;
- if (stats.startIteratingHistoryLocked()) {
- final HistoryItem rec = new HistoryItem();
- while (stats.getNextHistoryLocked(rec)) {
- pos++;
- if (first) {
- first = false;
- mHistStart = rec.time;
- }
- if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
- || rec.cmd == HistoryItem.CMD_RESET) {
- // If there is a ridiculously large jump in time, then we won't be
- // able to create a good chart with that data, so just ignore the
- // times we got before and pretend like our data extends back from
- // the time we have now.
- // Also, if we are getting a time change and we are less than 5 minutes
- // since the start of the history real time, then also use this new
- // time to compute the base time, since whatever time we had before is
- // pretty much just noise.
- if (rec.currentTime > (lastWallTime+(180*24*60*60*1000L))
- || rec.time < (mHistStart+(5*60*1000L))) {
- mStartWallTime = 0;
+ int pos = 0;
+ int lastInteresting = 0;
+ byte lastLevel = -1;
+ mBatLow = 0;
+ mBatHigh = 100;
+ mStartWallTime = 0;
+ mEndDataWallTime = 0;
+ mEndWallTime = 0;
+ mHistStart = 0;
+ mHistEnd = 0;
+ long lastWallTime = 0;
+ long lastRealtime = 0;
+ int aggrStates = 0;
+ int aggrStates2 = 0;
+ boolean first = true;
+ if (stats.startIteratingHistoryLocked()) {
+ final HistoryItem rec = new HistoryItem();
+ while (stats.getNextHistoryLocked(rec)) {
+ pos++;
+ if (first) {
+ first = false;
+ mHistStart = rec.time;
}
- lastWallTime = rec.currentTime;
- lastRealtime = rec.time;
- if (mStartWallTime == 0) {
- mStartWallTime = lastWallTime - (lastRealtime-mHistStart);
+ if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
+ || rec.cmd == HistoryItem.CMD_RESET) {
+ // If there is a ridiculously large jump in time, then we won't be
+ // able to create a good chart with that data, so just ignore the
+ // times we got before and pretend like our data extends back from
+ // the time we have now.
+ // Also, if we are getting a time change and we are less than 5 minutes
+ // since the start of the history real time, then also use this new
+ // time to compute the base time, since whatever time we had before is
+ // pretty much just noise.
+ if (rec.currentTime > (lastWallTime+(180*24*60*60*1000L))
+ || rec.time < (mHistStart+(5*60*1000L))) {
+ mStartWallTime = 0;
+ }
+ lastWallTime = rec.currentTime;
+ lastRealtime = rec.time;
+ if (mStartWallTime == 0) {
+ mStartWallTime = lastWallTime - (lastRealtime-mHistStart);
+ }
}
- }
- if (rec.isDeltaData()) {
- if (rec.batteryLevel != lastLevel || pos == 1) {
- lastLevel = rec.batteryLevel;
+ if (rec.isDeltaData()) {
+ if (rec.batteryLevel != lastLevel || pos == 1) {
+ lastLevel = rec.batteryLevel;
+ }
+ lastInteresting = pos;
+ mHistDataEnd = rec.time;
+ aggrStates |= rec.states;
+ aggrStates2 |= rec.states2;
}
- lastInteresting = pos;
- mHistDataEnd = rec.time;
- aggrStates |= rec.states;
- aggrStates2 |= rec.states2;
}
}
- }
- mHistEnd = mHistDataEnd + (mInfo.remainingTimeUs/1000);
- mEndDataWallTime = lastWallTime + mHistDataEnd - lastRealtime;
- mEndWallTime = mEndDataWallTime + (mInfo.remainingTimeUs/1000);
- mNumHist = lastInteresting;
- mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0;
- mHaveFlashlight = (aggrStates2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0;
- mHaveCamera = (aggrStates2&HistoryItem.STATE2_CAMERA_FLAG) != 0;
- mHaveWifi = (aggrStates2&HistoryItem.STATE2_WIFI_RUNNING_FLAG) != 0
- || (aggrStates&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
- |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
- |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0;
- if (!com.android.settings.Utils.isWifiOnly(getContext())) {
- mHavePhoneSignal = true;
- }
- if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
+ mHistEnd = mHistDataEnd + (mInfo.remainingTimeUs/1000);
+ mEndDataWallTime = lastWallTime + mHistDataEnd - lastRealtime;
+ mEndWallTime = mEndDataWallTime + (mInfo.remainingTimeUs/1000);
+ mNumHist = lastInteresting;
+ mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0;
+ mHaveFlashlight = (aggrStates2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0;
+ mHaveCamera = (aggrStates2&HistoryItem.STATE2_CAMERA_FLAG) != 0;
+ mHaveWifi = (aggrStates2&HistoryItem.STATE2_WIFI_RUNNING_FLAG) != 0
+ || (aggrStates&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
+ |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
+ |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0;
+ if (!com.android.settings.Utils.isWifiOnly(getContext())) {
+ mHavePhoneSignal = true;
+ }
+ if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
+ }, mStats, false /* shortString */);
}
@Override
diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java b/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java
index 7d3e1d1..c6f326f 100644
--- a/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java
+++ b/src/com/android/settings/fuelgauge/BatteryHistoryDetail.java
@@ -20,7 +20,6 @@
import android.os.BatteryStats;
import android.os.BatteryStats.HistoryItem;
import android.os.Bundle;
-import android.os.SystemClock;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,7 +30,6 @@
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.fuelgauge.BatteryActiveView.BatteryActiveProvider;
-import com.android.settingslib.BatteryInfo;
import com.android.settingslib.graph.UsageView;
public class BatteryHistoryDetail extends SettingsPreferenceFragment {
@@ -92,24 +90,25 @@
}
private void updateEverything() {
- BatteryInfo info = BatteryInfo.getBatteryInfo(getContext(), mBatteryBroadcast, mStats,
- SystemClock.elapsedRealtime() * 1000);
- final View view = getView();
- info.bindHistory((UsageView) view.findViewById(R.id.battery_usage), mChargingParser,
- mScreenOn, mGpsParser, mFlashlightParser, mCameraParser, mWifiParser, mCpuParser,
- mPhoneParser);
- ((TextView) view.findViewById(R.id.charge)).setText(info.batteryPercentString);
- ((TextView) view.findViewById(R.id.estimation)).setText(info.remainingLabel);
+ BatteryInfo.getBatteryInfo(getContext(), info -> {
+ final View view = getView();
+ info.bindHistory((UsageView) view.findViewById(R.id.battery_usage), mChargingParser,
+ mScreenOn, mGpsParser, mFlashlightParser, mCameraParser, mWifiParser,
+ mCpuParser, mPhoneParser);
+ ((TextView) view.findViewById(R.id.charge)).setText(info.batteryPercentString);
+ ((TextView) view.findViewById(R.id.estimation)).setText(info.remainingLabel);
- bindData(mChargingParser, R.string.battery_stats_charging_label, R.id.charging_group);
- bindData(mScreenOn, R.string.battery_stats_screen_on_label, R.id.screen_on_group);
- bindData(mGpsParser, R.string.battery_stats_gps_on_label, R.id.gps_group);
- bindData(mFlashlightParser, R.string.battery_stats_flashlight_on_label,
- R.id.flashlight_group);
- bindData(mCameraParser, R.string.battery_stats_camera_on_label, R.id.camera_group);
- bindData(mWifiParser, R.string.battery_stats_wifi_running_label, R.id.wifi_group);
- bindData(mCpuParser, R.string.battery_stats_wake_lock_label, R.id.cpu_group);
- bindData(mPhoneParser, R.string.battery_stats_phone_signal_label, R.id.cell_network_group);
+ bindData(mChargingParser, R.string.battery_stats_charging_label, R.id.charging_group);
+ bindData(mScreenOn, R.string.battery_stats_screen_on_label, R.id.screen_on_group);
+ bindData(mGpsParser, R.string.battery_stats_gps_on_label, R.id.gps_group);
+ bindData(mFlashlightParser, R.string.battery_stats_flashlight_on_label,
+ R.id.flashlight_group);
+ bindData(mCameraParser, R.string.battery_stats_camera_on_label, R.id.camera_group);
+ bindData(mWifiParser, R.string.battery_stats_wifi_running_label, R.id.wifi_group);
+ bindData(mCpuParser, R.string.battery_stats_wake_lock_label, R.id.cpu_group);
+ bindData(mPhoneParser, R.string.battery_stats_phone_signal_label,
+ R.id.cell_network_group);
+ }, mStats, false /* shortString */);
}
private void bindData(BatteryActiveProvider provider, int label, int groupId) {
diff --git a/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java b/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java
index 1f6030e..0483534 100644
--- a/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java
+++ b/src/com/android/settings/fuelgauge/BatteryHistoryPreference.java
@@ -17,8 +17,6 @@
package com.android.settings.fuelgauge;
import android.content.Context;
-import android.os.Bundle;
-import android.os.SystemClock;
import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceViewHolder;
@@ -26,9 +24,6 @@
import android.widget.TextView;
import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.R;
-import com.android.settings.Utils;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settingslib.BatteryInfo;
import com.android.settingslib.graph.UsageView;
/**
@@ -47,10 +42,10 @@
}
public void setStats(BatteryStatsHelper batteryStats) {
- final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
- mBatteryInfo = BatteryInfo.getBatteryInfo(getContext(), batteryStats.getBatteryBroadcast(),
- batteryStats.getStats(), elapsedRealtimeUs);
- notifyChanged();
+ BatteryInfo.getBatteryInfo(getContext(), info -> {
+ mBatteryInfo = info;
+ notifyChanged();
+ }, batteryStats.getStats(), false);
}
@Override
diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java
new file mode 100644
index 0000000..5482c53
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/BatteryInfo.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settings.fuelgauge;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.os.AsyncTask;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.BatteryStats.HistoryItem;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.annotation.WorkerThread;
+import android.text.format.Formatter;
+import android.util.SparseIntArray;
+import com.android.internal.os.BatteryStatsHelper;
+import android.support.annotation.VisibleForTesting;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.R;
+import com.android.settingslib.Utils;
+import com.android.settingslib.graph.UsageView;
+
+public class BatteryInfo {
+
+ public String chargeLabelString;
+ public int batteryLevel;
+ public boolean discharging = true;
+ public long remainingTimeUs = 0;
+ public String batteryPercentString;
+ public String remainingLabel;
+ public String statusLabel;
+ private boolean mCharging;
+ private BatteryStats mStats;
+ private long timePeriod;
+
+ public interface Callback {
+ void onBatteryInfoLoaded(BatteryInfo info);
+ }
+
+ public void bindHistory(final UsageView view, BatteryDataParser... parsers) {
+ BatteryDataParser parser = new BatteryDataParser() {
+ SparseIntArray points = new SparseIntArray();
+
+ @Override
+ public void onParsingStarted(long startTime, long endTime) {
+ timePeriod = endTime - startTime - remainingTimeUs / 1000;
+ view.clearPaths();
+ view.configureGraph((int) (endTime - startTime), 100, remainingTimeUs != 0,
+ mCharging);
+ }
+
+ @Override
+ public void onDataPoint(long time, HistoryItem record) {
+ points.put((int) time, record.batteryLevel);
+ }
+
+ @Override
+ public void onDataGap() {
+ if (points.size() > 1) {
+ view.addPath(points);
+ }
+ points.clear();
+ }
+
+ @Override
+ public void onParsingDone() {
+ if (points.size() > 1) {
+ view.addPath(points);
+ }
+ }
+ };
+ BatteryDataParser[] parserList = new BatteryDataParser[parsers.length + 1];
+ for (int i = 0; i < parsers.length; i++) {
+ parserList[i] = parsers[i];
+ }
+ parserList[parsers.length] = parser;
+ parse(mStats, remainingTimeUs, parserList);
+ final Context context = view.getContext();
+ String timeString = context.getString(R.string.charge_length_format,
+ Formatter.formatShortElapsedTime(context, timePeriod));
+ String remaining = "";
+ if (remainingTimeUs != 0) {
+ remaining = context.getString(R.string.remaining_length_format,
+ Formatter.formatShortElapsedTime(context, remainingTimeUs / 1000));
+ }
+ view.setBottomLabels(new CharSequence[]{timeString, remaining});
+ }
+
+ public static void getBatteryInfo(final Context context, final Callback callback) {
+ BatteryInfo.getBatteryInfo(context, callback, false /* shortString */);
+ }
+
+ public static void getBatteryInfo(final Context context, final Callback callback,
+ boolean shortString) {
+ BatteryStatsHelper statsHelper = new BatteryStatsHelper(context, true);
+ statsHelper.create((Bundle) null);
+ BatteryInfo.getBatteryInfo(context, callback, statsHelper, shortString);
+ }
+
+ public static void getBatteryInfo(final Context context, final Callback callback,
+ BatteryStatsHelper statsHelper, boolean shortString) {
+ getBatteryInfo(context, callback, statsHelper.getStats(), shortString);
+ }
+
+ public static void getBatteryInfo(final Context context, final Callback callback,
+ BatteryStats stats, boolean shortString) {
+ new AsyncTask<Void, Void, BatteryInfo>() {
+ @Override
+ protected BatteryInfo doInBackground(Void... params) {
+ PowerUsageFeatureProvider provider =
+ FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context);
+ final BatteryUtils batteryUtils = BatteryUtils.getInstance(context);
+ final long elapsedRealtimeUs =
+ batteryUtils.convertMsToUs(SystemClock.elapsedRealtime());
+ Intent batteryBroadcast = context.registerReceiver(null,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ BatteryUtils utils = BatteryUtils.getInstance(context);
+
+ if (provider != null && provider.isEnhancedBatteryPredictionEnabled(context)) {
+ return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats,
+ elapsedRealtimeUs, shortString,
+ utils.convertMsToUs(provider.getEnhancedBatteryPrediction(context)),
+ true);
+ } else {
+ return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats,
+ elapsedRealtimeUs, shortString,
+ stats.computeBatteryTimeRemaining(elapsedRealtimeUs), false);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(BatteryInfo batteryInfo) {
+ callback.onBatteryInfoLoaded(batteryInfo);
+ }
+ }.execute();
+ }
+
+ @WorkerThread
+ public static BatteryInfo getBatteryInfoOld(Context context, Intent batteryBroadcast,
+ BatteryStats stats, long elapsedRealtimeUs, boolean shortString) {
+ return getBatteryInfo(context, batteryBroadcast, stats, elapsedRealtimeUs, shortString,
+ stats.computeBatteryTimeRemaining(elapsedRealtimeUs), false);
+ }
+
+ @WorkerThread
+ public static BatteryInfo getBatteryInfo(Context context, Intent batteryBroadcast,
+ BatteryStats stats, long elapsedRealtimeUs, boolean shortString, long drainTimeUs,
+ boolean basedOnUsage) {
+ BatteryInfo info = new BatteryInfo();
+ info.mStats = stats;
+ info.batteryLevel = Utils.getBatteryLevel(batteryBroadcast);
+ info.batteryPercentString = Utils.formatPercentage(info.batteryLevel);
+ info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
+ final Resources resources = context.getResources();
+ final BatteryUtils batteryUtils = BatteryUtils.getInstance(context);
+
+ info.statusLabel = Utils.getBatteryStatus(resources, batteryBroadcast);
+ if (!info.mCharging) {
+ if (drainTimeUs > 0) {
+ info.remainingTimeUs = drainTimeUs;
+ String timeString = Formatter.formatShortElapsedTime(context,
+ batteryUtils.convertUsToMs(drainTimeUs));
+ info.remainingLabel = resources.getString(
+ shortString ?
+ (basedOnUsage ?
+ R.string.power_remaining_duration_only_short_enhanced :
+ R.string.power_remaining_duration_only_short) :
+ (basedOnUsage ?
+ R.string.power_remaining_duration_only_enhanced :
+ R.string.power_remaining_duration_only),
+ timeString);
+ info.chargeLabelString = resources.getString(
+ shortString ?
+ R.string.power_discharging_duration_short :
+ basedOnUsage ?
+ R.string.power_discharging_duration_enhanced :
+ R.string.power_discharging_duration,
+ info.batteryPercentString, timeString);
+ } else {
+ info.remainingLabel = null;
+ info.chargeLabelString = info.batteryPercentString;
+ }
+ } else {
+ final long chargeTime = stats.computeChargeTimeRemaining(elapsedRealtimeUs);
+ final int status = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS,
+ BatteryManager.BATTERY_STATUS_UNKNOWN);
+ info.discharging = false;
+ if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
+ info.remainingTimeUs = chargeTime;
+ String timeString = Formatter.formatShortElapsedTime(context,
+ batteryUtils.convertUsToMs(chargeTime));
+ int resId = shortString ? R.string.power_charging_duration_short
+ : R.string.power_charging_duration;
+ info.remainingLabel = resources.getString(
+ R.string.power_remaining_charging_duration_only, timeString);
+ info.chargeLabelString = resources.getString(
+ resId, info.batteryPercentString, timeString);
+ } else {
+ final String chargeStatusLabel = resources.getString(
+ R.string.battery_info_status_charging_lower);
+ info.remainingLabel = null;
+ info.chargeLabelString = resources.getString(
+ R.string.power_charging, info.batteryPercentString, chargeStatusLabel);
+ }
+ }
+ return info;
+ }
+
+ public interface BatteryDataParser {
+ void onParsingStarted(long startTime, long endTime);
+
+ void onDataPoint(long time, HistoryItem record);
+
+ void onDataGap();
+
+ void onParsingDone();
+ }
+
+ private static void parse(BatteryStats stats, long remainingTimeUs,
+ BatteryDataParser... parsers) {
+ long startWalltime = 0;
+ long endDateWalltime = 0;
+ long endWalltime = 0;
+ long historyStart = 0;
+ long historyEnd = 0;
+ byte lastLevel = -1;
+ long curWalltime = startWalltime;
+ long lastWallTime = 0;
+ long lastRealtime = 0;
+ int lastInteresting = 0;
+ int pos = 0;
+ boolean first = true;
+ if (stats.startIteratingHistoryLocked()) {
+ final HistoryItem rec = new HistoryItem();
+ while (stats.getNextHistoryLocked(rec)) {
+ pos++;
+ if (first) {
+ first = false;
+ historyStart = rec.time;
+ }
+ if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
+ || rec.cmd == HistoryItem.CMD_RESET) {
+ // If there is a ridiculously large jump in time, then we won't be
+ // able to create a good chart with that data, so just ignore the
+ // times we got before and pretend like our data extends back from
+ // the time we have now.
+ // Also, if we are getting a time change and we are less than 5 minutes
+ // since the start of the history real time, then also use this new
+ // time to compute the base time, since whatever time we had before is
+ // pretty much just noise.
+ if (rec.currentTime > (lastWallTime + (180 * 24 * 60 * 60 * 1000L))
+ || rec.time < (historyStart + (5 * 60 * 1000L))) {
+ startWalltime = 0;
+ }
+ lastWallTime = rec.currentTime;
+ lastRealtime = rec.time;
+ if (startWalltime == 0) {
+ startWalltime = lastWallTime - (lastRealtime - historyStart);
+ }
+ }
+ if (rec.isDeltaData()) {
+ if (rec.batteryLevel != lastLevel || pos == 1) {
+ lastLevel = rec.batteryLevel;
+ }
+ lastInteresting = pos;
+ historyEnd = rec.time;
+ }
+ }
+ }
+ stats.finishIteratingHistoryLocked();
+ endDateWalltime = lastWallTime + historyEnd - lastRealtime;
+ endWalltime = endDateWalltime + (remainingTimeUs / 1000);
+
+ int i = 0;
+ final int N = lastInteresting;
+
+ for (int j = 0; j < parsers.length; j++) {
+ parsers[j].onParsingStarted(startWalltime, endWalltime);
+ }
+ if (endDateWalltime > startWalltime && stats.startIteratingHistoryLocked()) {
+ final HistoryItem rec = new HistoryItem();
+ while (stats.getNextHistoryLocked(rec) && i < N) {
+ if (rec.isDeltaData()) {
+ curWalltime += rec.time - lastRealtime;
+ lastRealtime = rec.time;
+ long x = (curWalltime - startWalltime);
+ if (x < 0) {
+ x = 0;
+ }
+ for (int j = 0; j < parsers.length; j++) {
+ parsers[j].onDataPoint(x, rec);
+ }
+ } else {
+ long lastWalltime = curWalltime;
+ if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
+ || rec.cmd == HistoryItem.CMD_RESET) {
+ if (rec.currentTime >= startWalltime) {
+ curWalltime = rec.currentTime;
+ } else {
+ curWalltime = startWalltime + (rec.time - historyStart);
+ }
+ lastRealtime = rec.time;
+ }
+
+ if (rec.cmd != HistoryItem.CMD_OVERFLOW
+ && (rec.cmd != HistoryItem.CMD_CURRENT_TIME
+ || Math.abs(lastWalltime - curWalltime) > (60 * 60 * 1000))) {
+ for (int j = 0; j < parsers.length; j++) {
+ parsers[j].onDataGap();
+ }
+ }
+ }
+ i++;
+ }
+ }
+
+ stats.finishIteratingHistoryLocked();
+
+ for (int j = 0; j < parsers.length; j++) {
+ parsers[j].onParsingDone();
+ }
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/BatteryInfoLoader.java b/src/com/android/settings/fuelgauge/BatteryInfoLoader.java
new file mode 100644
index 0000000..f1c2547
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/BatteryInfoLoader.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.fuelgauge;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.BatteryStats;
+import android.os.SystemClock;
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.utils.AsyncLoader;
+
+/**
+ * Loader that can be used by classes to load BatteryInfo in a background thread. This loader will
+ * automatically grab enhanced battery estimates if available or fall back to the system estimate
+ * when not available.
+ */
+public class BatteryInfoLoader extends AsyncLoader<BatteryInfo>{
+ BatteryStatsHelper mStatsHelper;
+
+ public BatteryInfoLoader(Context context, BatteryStatsHelper batteryStatsHelper) {
+ super(context);
+ mStatsHelper = batteryStatsHelper;
+ }
+
+ @Override
+ protected void onDiscardResult(BatteryInfo result) {
+
+ }
+
+ @Override
+ public BatteryInfo loadInBackground() {
+ Context context = getContext();
+ PowerUsageFeatureProvider powerUsageFeatureProvider =
+ FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context);
+
+ // Stuff we always need to get BatteryInfo
+ BatteryUtils batteryUtils = BatteryUtils.getInstance(context);
+ Intent batteryBroadcast = getContext().registerReceiver(null,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ final long elapsedRealtimeUs = batteryUtils.convertMsToUs(SystemClock.elapsedRealtime());
+ BatteryInfo batteryInfo;
+
+ // Get enhanced prediction if available, otherwise use the old prediction code
+ Cursor cursor = null;
+ if (powerUsageFeatureProvider.isEnhancedBatteryPredictionEnabled(context)) {
+ final Uri queryUri = powerUsageFeatureProvider.getEnhancedBatteryPredictionUri();
+ cursor = context.getContentResolver().query(queryUri, null, null, null, null);
+ }
+ if (cursor != null && cursor.moveToFirst()) {
+ long enhancedEstimate = powerUsageFeatureProvider.getTimeRemainingEstimate(cursor);
+ batteryInfo = BatteryInfo.getBatteryInfo(context, batteryBroadcast,
+ mStatsHelper.getStats(), elapsedRealtimeUs, false /* shortString */,
+ batteryUtils.convertMsToUs(enhancedEstimate), true /* basedOnUsage */);
+ } else {
+ BatteryStats stats = mStatsHelper.getStats();
+ batteryInfo = BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats,
+ elapsedRealtimeUs, false /* shortString */,
+ stats.computeBatteryTimeRemaining(elapsedRealtimeUs), false /* basedOnUsage */);
+ }
+
+ return batteryInfo;
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java
index a9792af..a515e06 100644
--- a/src/com/android/settings/fuelgauge/BatteryUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryUtils.java
@@ -274,7 +274,7 @@
}
- private long convertUsToMs(long timeUs) {
+ public long convertUsToMs(long timeUs) {
return timeUs / 1000;
}
diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
index adea925..291b2fe 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
@@ -19,21 +19,15 @@
import android.app.Activity;
import android.app.LoaderManager;
import android.content.Context;
-import android.content.CursorLoader;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.Loader;
import android.content.res.TypedArray;
-import android.database.Cursor;
import android.graphics.drawable.Drawable;
-import android.net.Uri;
import android.os.BatteryStats;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.SearchIndexableResource;
import android.support.annotation.VisibleForTesting;
@@ -69,7 +63,6 @@
import com.android.settings.fuelgauge.anomaly.AnomalySummaryPreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settingslib.BatteryInfo;
import com.android.settingslib.widget.FooterPreferenceMixin;
import java.util.ArrayList;
@@ -98,11 +91,12 @@
private static final String KEY_AUTO_BRIGHTNESS = "auto_brightness_battery";
private static final String KEY_SCREEN_TIMEOUT = "screen_timeout_battery";
private static final String KEY_BATTERY_SAVER_SUMMARY = "battery_saver_summary";
+ private static final String KEY_HIGH_USAGE = "high_usage";
@VisibleForTesting
static final int ANOMALY_LOADER = 1;
@VisibleForTesting
- static final int BATTERY_ESTIMATE_LOADER = 2;
+ static final int BATTERY_INFO_LOADER = 2;
private static final int MENU_STATS_TYPE = Menu.FIRST;
@VisibleForTesting
static final int MENU_HIGH_POWER_APPS = Menu.FIRST + 3;
@@ -125,8 +119,6 @@
PowerUsageFeatureProvider mPowerFeatureProvider;
@VisibleForTesting
BatteryUtils mBatteryUtils;
- @VisibleForTesting
- long mEnhancedEstimate = -1;
/**
* SparseArray that maps uid to {@link Anomaly}, so we could find {@link Anomaly} by uid
@@ -164,35 +156,21 @@
};
@VisibleForTesting
- LoaderManager.LoaderCallbacks<Cursor> mBatteryPredictionLoaderCallbacks =
- new LoaderManager.LoaderCallbacks<Cursor>() {
+ LoaderManager.LoaderCallbacks<BatteryInfo> BatteryInfoLoaderCallbacks =
+ new LoaderManager.LoaderCallbacks<BatteryInfo>() {
@Override
- public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
- final Uri queryUri = mPowerFeatureProvider.getEnhancedBatteryPredictionUri();
-
- return new CursorLoader(getContext(), queryUri, null, null, null, null);
+ public Loader<BatteryInfo> onCreateLoader(int i, Bundle bundle) {
+ return new BatteryInfoLoader(getContext(), mStatsHelper);
}
@Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- if (cursor == null) {
- return;
- }
- if (cursor.moveToFirst()) {
- mEnhancedEstimate =
- mPowerFeatureProvider.getTimeRemainingEstimate(cursor);
- }
- final long elapsedRealtimeUs =
- mBatteryUtils.convertMsToUs(SystemClock.elapsedRealtime());
- Intent batteryBroadcast = getContext().registerReceiver(null,
- new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
- BatteryInfo batteryInfo = getBatteryInfo(elapsedRealtimeUs, batteryBroadcast);
+ public void onLoadFinished(Loader<BatteryInfo> loader, BatteryInfo batteryInfo) {
mBatteryHeaderPreferenceController.updateHeaderPreference(batteryInfo);
}
@Override
- public void onLoaderReset(Loader<Cursor> loader) {
+ public void onLoaderReset(Loader<BatteryInfo> loader) {
// do nothing
}
};
@@ -214,7 +192,7 @@
mAnomalySparseArray = new SparseArray<>();
initFeatureProvider();
- initializeBatteryEstimateLoader();
+ restartBatteryInfoLoader();
}
@Override
@@ -481,12 +459,8 @@
initAnomalyDetectionIfPossible();
- final long elapsedRealtimeUs = mBatteryUtils.convertMsToUs(SystemClock.elapsedRealtime());
- Intent batteryBroadcast = context.registerReceiver(null,
- new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
- BatteryInfo batteryInfo = getBatteryInfo(elapsedRealtimeUs, batteryBroadcast);
- mBatteryHeaderPreferenceController.updateHeaderPreference(batteryInfo);
-
+ // reload BatteryInfo and updateUI
+ restartBatteryInfoLoader();
final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime(mStatsHelper,
System.currentTimeMillis());
updateScreenPreference();
@@ -702,28 +676,12 @@
}
}
- private BatteryInfo getBatteryInfo(long elapsedRealtimeUs, Intent batteryBroadcast) {
- BatteryInfo batteryInfo;
- if (mEnhancedEstimate > 0 &&
- mPowerFeatureProvider.isEnhancedBatteryPredictionEnabled(
- getContext())) {
- // Drain time is in micro-seconds so we have to multiply by 1000
- batteryInfo = BatteryInfo.getBatteryInfo(getContext(), batteryBroadcast,
- mStatsHelper.getStats(), elapsedRealtimeUs, false,
- mBatteryUtils.convertMsToUs(mEnhancedEstimate), true);
- } else {
- batteryInfo = BatteryInfo.getBatteryInfo(getContext(), batteryBroadcast,
- mStatsHelper.getStats(), elapsedRealtimeUs, false);
- }
- return batteryInfo;
- }
-
@VisibleForTesting
- void initializeBatteryEstimateLoader() {
+ void restartBatteryInfoLoader() {
if (mPowerFeatureProvider != null
&& mPowerFeatureProvider.isEnhancedBatteryPredictionEnabled(getContext())) {
- getLoaderManager().initLoader(BATTERY_ESTIMATE_LOADER, Bundle.EMPTY,
- mBatteryPredictionLoaderCallbacks);
+ getLoaderManager().restartLoader(BATTERY_INFO_LOADER, Bundle.EMPTY,
+ BatteryInfoLoaderCallbacks);
}
}
@@ -844,6 +802,7 @@
niks.add(KEY_AUTO_BRIGHTNESS);
niks.add(KEY_SCREEN_TIMEOUT);
niks.add(KEY_BATTERY_SAVER_SUMMARY);
+ niks.add(KEY_HIGH_USAGE);
return niks;
}
};
diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java
index 4cbe692..96cef01 100644
--- a/src/com/android/settings/notification/AppNotificationSettings.java
+++ b/src/com/android/settings/notification/AppNotificationSettings.java
@@ -93,7 +93,6 @@
getPreferenceScreen().setOrderingAsAdded(true);
setupBlock();
addHeaderPref();
- addAppLinkPref();
mShowLegacyChannelConfig = mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid);
if (mShowLegacyChannelConfig) {
@@ -118,6 +117,7 @@
return;
}
populateChannelList();
+ addAppLinkPref();
}
}.execute();
}
diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java
index 6b4129c..63434b4 100644
--- a/src/com/android/settings/notification/ChannelNotificationSettings.java
+++ b/src/com/android/settings/notification/ChannelNotificationSettings.java
@@ -18,13 +18,17 @@
import android.app.Activity;
import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.os.AsyncTask;
import android.provider.Settings;
import android.support.v7.preference.Preference;
import android.text.TextUtils;
+import android.text.BidiFormatter;
+import android.text.SpannableStringBuilder;
import android.util.ArrayMap;
import android.util.Log;
import android.view.LayoutInflater;
@@ -60,6 +64,8 @@
private RestrictedSwitchPreference mVibrate;
private NotificationSoundPreference mRingtone;
private FooterPreference mFooter;
+ private NotificationChannelGroup mChannelGroup;
+ private EntityHeaderController mHeaderPref;
@Override
public int getMetricsCategory() {
@@ -89,6 +95,27 @@
mShowLegacyChannelConfig = true;
} else {
populateUpgradedChannelPrefs();
+
+ if (mChannel.getGroup() != null) {
+ // Go look up group name
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ if (mChannel.getGroup() != null) {
+ mChannelGroup = mBackend.getGroup(mChannel.getGroup(), mPkg, mUid);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void unused) {
+ if (getHost() == null || mChannelGroup == null) {
+ return;
+ }
+ setChannelGroupLabel(mChannelGroup.getName());
+ }
+ }.execute();
+ }
}
updateDependents(mChannel.getImportance() == IMPORTANCE_NONE);
@@ -110,9 +137,10 @@
rows.put(mAppRow.pkg, mAppRow);
collectConfigActivities(rows);
final Activity activity = getActivity();
- final Preference pref = EntityHeaderController
+ mHeaderPref = EntityHeaderController
.newInstance(activity, this /* fragment */, null /* header */)
- .setRecyclerView(getListView(), getLifecycle())
+ .setRecyclerView(getListView(), getLifecycle());
+ final Preference pref = mHeaderPref
.setIcon(mAppRow.icon)
.setLabel(mChannel.getName())
.setSummary(mAppRow.label)
@@ -124,6 +152,20 @@
getPreferenceScreen().addPreference(pref);
}
+ private void setChannelGroupLabel(CharSequence groupName) {
+ final SpannableStringBuilder summary = new SpannableStringBuilder();
+ BidiFormatter bidi = BidiFormatter.getInstance();
+ summary.append(bidi.unicodeWrap(mAppRow.label.toString()));
+ if (groupName != null) {
+ summary.append(bidi.unicodeWrap(mContext.getText(
+ R.string.notification_header_divider_symbol_with_spaces)));
+ summary.append(bidi.unicodeWrap(groupName.toString()));
+ }
+ final Activity activity = getActivity();
+ mHeaderPref.setSummary(summary.toString());
+ mHeaderPref.done(activity, getPrefContext());
+ }
+
private void addFooterPref() {
if (!TextUtils.isEmpty(mChannel.getDescription())) {
FooterPreference descPref = new FooterPreference(getPrefContext());
@@ -171,6 +213,7 @@
private void setupVibrate() {
mVibrate = (RestrictedSwitchPreference) findPreference(KEY_VIBRATE);
mVibrate.setDisabledByAdmin(mSuspendedAppsAdmin);
+ mVibrate.setEnabled(!(mAppRow.lockedImportance || mVibrate.isDisabledByAdmin()));
mVibrate.setChecked(mChannel.shouldVibrate());
mVibrate.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
@@ -187,6 +230,7 @@
private void setupRingtone() {
mRingtone = (NotificationSoundPreference) findPreference(KEY_RINGTONE);
mRingtone.setRingtone(mChannel.getSound());
+ mRingtone.setEnabled(!(mAppRow.lockedImportance));
mRingtone.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
@@ -242,12 +286,15 @@
channelArgs.putBoolean(AppHeader.EXTRA_HIDE_INFO_BUTTON, true);
channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mPkg);
channelArgs.putString(Settings.EXTRA_CHANNEL_ID, mChannel.getId());
- Intent channelIntent = Utils.onBuildStartFragmentIntent(getActivity(),
- ChannelImportanceSettings.class.getName(),
- channelArgs, null, R.string.notification_importance_title, null,
- false, getMetricsCategory());
- mImportance.setIntent(channelIntent);
- mImportance.setEnabled(mSuspendedAppsAdmin == null);
+ mImportance.setEnabled(mSuspendedAppsAdmin == null && !mAppRow.lockedImportance);
+ // Set up intent to show importance selection only if this setting is enabled.
+ if (mImportance.isEnabled()) {
+ Intent channelIntent = Utils.onBuildStartFragmentIntent(getActivity(),
+ ChannelImportanceSettings.class.getName(),
+ channelArgs, null, R.string.notification_importance_title, null,
+ false, getMetricsCategory());
+ mImportance.setIntent(channelIntent);
+ }
mImportance.setSummary(getImportanceSummary(mChannel.getImportance()));
}
@@ -341,7 +388,7 @@
if (mAppLink != null) {
setVisible(mAppLink, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
}
- if (mFooter !=null) {
+ if (mFooter != null) {
setVisible(mFooter, checkCanBeVisible(NotificationManager.IMPORTANCE_MIN));
}
}
diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java
index 84d7e31..96737db 100644
--- a/src/com/android/settings/notification/NotificationBackend.java
+++ b/src/com/android/settings/notification/NotificationBackend.java
@@ -122,6 +122,19 @@
}
}
+
+ public NotificationChannelGroup getGroup(String groupId, String pkg, int uid) {
+ if (groupId == null) {
+ return null;
+ }
+ try {
+ return sINM.getNotificationChannelGroupForPackage(groupId, pkg, uid);
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return null;
+ }
+ }
+
public ParceledListSlice<NotificationChannelGroup> getChannelGroups(String pkg, int uid) {
try {
return sINM.getNotificationChannelGroupsForPackage(pkg, uid, false);
diff --git a/src/com/android/settings/notification/NotificationSettingsBase.java b/src/com/android/settings/notification/NotificationSettingsBase.java
index fe426e7..911c827 100644
--- a/src/com/android/settings/notification/NotificationSettingsBase.java
+++ b/src/com/android/settings/notification/NotificationSettingsBase.java
@@ -263,7 +263,7 @@
}
protected void addAppLinkPref() {
- if (mAppRow.settingsIntent != null) {
+ if (mAppRow.settingsIntent != null && mAppLink == null) {
mAppLink = new Preference(getPrefContext());
mAppLink.setKey(KEY_APP_LINK);
mAppLink.setOrder(500);
@@ -293,6 +293,8 @@
private void setupImportanceToggle() {
mImportanceToggle = (RestrictedSwitchPreference) findPreference(KEY_ALLOW_SOUND);
mImportanceToggle.setDisabledByAdmin(mSuspendedAppsAdmin);
+ mImportanceToggle.setEnabled(!(mAppRow.lockedImportance
+ || mImportanceToggle.isDisabledByAdmin()));
mImportanceToggle.setChecked(mChannel.getImportance() >= IMPORTANCE_DEFAULT
|| mChannel.getImportance() == IMPORTANCE_UNSPECIFIED);
mImportanceToggle.setOnPreferenceChangeListener(
@@ -313,6 +315,7 @@
protected void setupPriorityPref(boolean priority) {
mPriority = (RestrictedSwitchPreference) findPreference(KEY_BYPASS_DND);
mPriority.setDisabledByAdmin(mSuspendedAppsAdmin);
+ mPriority.setEnabled(!(mAppRow.lockedImportance || mPriority.isDisabledByAdmin()));
mPriority.setChecked(priority);
mPriority.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
diff --git a/src/com/android/settings/notification/VolumeSeekBarPreference.java b/src/com/android/settings/notification/VolumeSeekBarPreference.java
index 6339008..1428116 100644
--- a/src/com/android/settings/notification/VolumeSeekBarPreference.java
+++ b/src/com/android/settings/notification/VolumeSeekBarPreference.java
@@ -91,9 +91,10 @@
mStopped = true;
if (mVolumizer != null) {
mVolumizer.stop();
+ mVolumizer = null;
}
}
-
+
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
diff --git a/src/com/android/settings/search/CursorToSearchResultConverter.java b/src/com/android/settings/search/CursorToSearchResultConverter.java
index 1a0f856..26b46ff 100644
--- a/src/com/android/settings/search/CursorToSearchResultConverter.java
+++ b/src/com/android/settings/search/CursorToSearchResultConverter.java
@@ -58,9 +58,9 @@
* - {@link Drawable} icon
* - {@link ResultPayload} payload
*/
-class CursorToSearchResultConverter {
+public class CursorToSearchResultConverter {
- private final String TAG = "CursorConverter";
+ private static final String TAG = "CursorConverter";
private final Context mContext;
@@ -103,6 +103,23 @@
return results;
}
+ public static ResultPayload getUnmarshalledPayload(byte[] marshalledPayload,
+ int payloadType) {
+ try {
+ switch (payloadType) {
+ case ResultPayload.PayloadType.INTENT:
+ return ResultPayloadUtils.unmarshall(marshalledPayload,
+ ResultPayload.CREATOR);
+ case ResultPayload.PayloadType.INLINE_SWITCH:
+ return ResultPayloadUtils.unmarshall(marshalledPayload,
+ InlineSwitchPayload.CREATOR);
+ }
+ } catch (BadParcelableException e) {
+ Log.w(TAG, "Error creating parcelable: " + e);
+ }
+ return null;
+ }
+
private SearchResult buildSingleSearchResultFromCursor(SiteMapManager sitemapManager,
Map<String, Context> contextMap, Cursor cursor, int baseRank) {
final int docId = cursor.getInt(COLUMN_INDEX_ID);
@@ -162,22 +179,6 @@
return icon;
}
- private ResultPayload getUnmarshalledPayload(byte[] unmarshalledPayload, int payloadType) {
- try {
- switch (payloadType) {
- case ResultPayload.PayloadType.INTENT:
- return ResultPayloadUtils.unmarshall(unmarshalledPayload,
- ResultPayload.CREATOR);
- case ResultPayload.PayloadType.INLINE_SWITCH:
- return ResultPayloadUtils.unmarshall(unmarshalledPayload,
- InlineSwitchPayload.CREATOR);
- }
- } catch (BadParcelableException e) {
- Log.w(TAG, "Error creating parcelable: " + e);
- }
- return null;
- }
-
private List<String> getBreadcrumbs(SiteMapManager siteMapManager, Cursor cursor) {
final String screenTitle = cursor.getString(COLUMN_INDEX_SCREEN_TITLE);
final String screenClass = cursor.getString(COLUMN_INDEX_CLASS_NAME);
@@ -209,4 +210,4 @@
}
return baseRank;
}
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/search/DatabaseIndexingManager.java b/src/com/android/settings/search/DatabaseIndexingManager.java
index 746ab8a..c681912 100644
--- a/src/com/android/settings/search/DatabaseIndexingManager.java
+++ b/src/com/android/settings/search/DatabaseIndexingManager.java
@@ -803,6 +803,7 @@
entries = XmlParserUtils.getDataEntries(context, attrs);
}
+ // TODO (b/62254931) index primitives instead of payload
payload = DatabaseIndexingUtils.getPayloadFromUriMap(controllerUriMap, key);
childFragment = XmlParserUtils.getDataChildFragment(context, attrs);
diff --git a/src/com/android/settings/search/InlinePayload.java b/src/com/android/settings/search/InlinePayload.java
index b2bb59d..439f111 100644
--- a/src/com/android/settings/search/InlinePayload.java
+++ b/src/com/android/settings/search/InlinePayload.java
@@ -19,30 +19,93 @@
import android.content.Intent;
+import android.content.Context;
+import android.os.Parcel;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Abstract Payload for inline settings results.
*/
public abstract class InlinePayload extends ResultPayload {
+
+ public static final int FALSE = 0;
+ public static final int TRUE = 1;
+
/**
- * Defines the URI to access and store the Setting the inline result represents
+ * Defines the key to access and store the Setting the inline result represents.
*/
- public String settingsUri;
+ @VisibleForTesting
+ final String mSettingKey;
/**
* The UI type for the inline result.
*/
- @PayloadType public int inlineType;
+ @PayloadType final int mInlineType;
/**
* Defines where the Setting is stored.
*/
- @SettingsSource public int settingSource;
+ @SettingsSource final int mSettingSource;
- public InlinePayload(String uri, @PayloadType int type, @SettingsSource int source,
- Intent intent) {
+ /**
+ * True when the setting is available for the device.
+ */
+ final boolean mIsDeviceSupported;
+
+ /**
+ * @param key uniquely identifies the stored setting.
+ * @param payloadType of the setting being stored.
+ * @param source of the setting. Used to determine where to get and set the setting.
+ * @param intent to the setting page.
+ * @param isDeviceSupported is true when the setting is valid for the given device.
+ */
+ public InlinePayload(String key, @PayloadType int payloadType, @SettingsSource int source,
+ Intent intent, boolean isDeviceSupported) {
super(intent);
- settingsUri = uri;
- inlineType = type;
- settingSource = source;
+ mSettingKey = key;
+ mInlineType = payloadType;
+ mSettingSource = source;
+ mIsDeviceSupported = isDeviceSupported;
}
-}
+
+ InlinePayload(Parcel parcel) {
+ super((Intent) parcel.readParcelable(Intent.class.getClassLoader()));
+ mSettingKey = parcel.readString();
+ mInlineType = parcel.readInt();
+ mSettingSource = parcel.readInt();
+ mIsDeviceSupported = parcel.readInt() == TRUE;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mSettingKey);
+ dest.writeInt(mInlineType);
+ dest.writeInt(mSettingSource);
+ dest.writeInt(mIsDeviceSupported ? TRUE : FALSE);
+ }
+
+ /**
+ * @returns the status of the underlying setting. See {@link ResultPayload.Availability} for
+ * possible values.
+ */
+ @Availability public int getAvailability() {
+ if (mIsDeviceSupported) {
+ return Availability.AVAILABLE;
+ }
+ return Availability.DISABLED_UNSUPPORTED;
+ }
+
+ /**
+ * @returns the current value of the setting.
+ */
+ public abstract int getValue(Context context);
+
+ /**
+ * Attempts to set the setting value.
+ *
+ * @param newValue is the requested new value for the setting.
+ * @returns true when the setting was changed, and false otherwise.
+ */
+ public abstract boolean setValue(Context context, int newValue);
+}
\ No newline at end of file
diff --git a/src/com/android/settings/search/InlineSwitchPayload.java b/src/com/android/settings/search/InlineSwitchPayload.java
index 8046b72..a232054 100644
--- a/src/com/android/settings/search/InlineSwitchPayload.java
+++ b/src/com/android/settings/search/InlineSwitchPayload.java
@@ -23,37 +23,41 @@
import android.os.Parcelable;
import android.provider.Settings;
-import java.util.Map;
-
/**
* Payload for inline Switch results. Mappings from integer to boolean.
*/
public class InlineSwitchPayload extends InlinePayload {
- /**
- * Maps Inline values to UI-consumable Values.
- * For example, if you have a switch preference whose values are stored as ints, the two valid
- * list of mappings would be:
- * < (0,True), (1, false) >
- * < (1,True), (0, false) >
- */
- public final Map<Integer, Boolean> valueMap;
- public InlineSwitchPayload(String newUri, @SettingsSource int settingsSource,
- Map<Integer, Boolean> map, Intent intent) {
- super(newUri, PayloadType.INLINE_SWITCH, settingsSource, intent);
- valueMap = map;
+ /**
+ * Provides a mapping for how switches are stored.
+ * If mIsStandard is true, then (0 == false) and (1 == true)
+ * If mIsStandard is false, then (1 == false) and (0 == true)
+ */
+ private boolean mIsStandard;
+
+ /**
+ *
+ * @param key uniquely identifies the stored setting.
+ * @param source of the setting. Used to determine where to get and set the setting.
+ * @param onValue is the value stored as on for the switch. Should be 0 or 1.
+ * @param intent to the setting page.
+ * @param isDeviceSupported is true when the setting is valid for the given device.
+ */
+ public InlineSwitchPayload(String key, @SettingsSource int source,
+ int onValue, Intent intent, boolean isDeviceSupported) {
+ super(key, PayloadType.INLINE_SWITCH, source, intent, isDeviceSupported);
+ // If on is stored as TRUE then the switch is standard.
+ mIsStandard = onValue == TRUE;
}
private InlineSwitchPayload(Parcel in) {
- super(in.readString() /* Uri */ , in.readInt() /* Payload Type */,
- in.readInt() /* Settings Source */,
- (Intent) in.readParcelable(Intent.class.getClassLoader()) /* Intent */);
- valueMap = in.readHashMap(Integer.class.getClassLoader());
+ super(in);
+ mIsStandard = in.readInt() == TRUE;
}
@Override
public int getType() {
- return inlineType;
+ return mInlineType;
}
@Override
@@ -63,11 +67,8 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(settingsUri);
- dest.writeInt(inlineType);
- dest.writeInt(settingSource);
- dest.writeParcelable(mIntent, flags);
- dest.writeMap(valueMap);
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mIsStandard ? TRUE : FALSE);
}
public static final Parcelable.Creator<InlineSwitchPayload> CREATOR =
@@ -83,71 +84,65 @@
}
};
- public boolean getSwitchValue(Context context) {
- if (valueMap == null) {
- throw new IllegalStateException("Value map is null");
- }
-
+ @Override
+ public int getValue(Context context) {
int settingsValue = -1;
- switch(settingSource) {
+ switch(mSettingSource) {
case SettingsSource.SECURE:
settingsValue = Settings.Secure.getInt(context.getContentResolver(),
- settingsUri, 0);
+ mSettingKey, -1);
break;
case SettingsSource.SYSTEM:
settingsValue = Settings.System.getInt(context.getContentResolver(),
- settingsUri, 0);
+ mSettingKey, -1);
break;
case SettingsSource.GLOBAL:
settingsValue = Settings.Global.getInt(context.getContentResolver(),
- settingsUri, 0);
+ mSettingKey, -1);
break;
}
if (settingsValue == -1) {
throw new IllegalStateException("Unable to find setting from uri: "
- + settingsUri.toString());
+ + mSettingKey.toString());
}
- for (Integer key : valueMap.keySet()) {
- if ((key == settingsValue)) {
- return valueMap.get(key);
- }
- }
+ settingsValue = standardizeInput(settingsValue);
- throw new IllegalStateException("No results matched the key: " + settingsValue);
+ return settingsValue;
}
- public void setSwitchValue(Context context, boolean isChecked) {
- if (valueMap == null) {
- throw new IllegalStateException("Value map is null");
- }
- int switchValue = -1;
-
- for (Map.Entry<Integer, Boolean> pair : valueMap.entrySet()) {
- if (pair.getValue() == isChecked) {
- switchValue = pair.getKey();
- break;
- }
+ @Override
+ public boolean setValue(Context context, int newValue) {
+ if (newValue != 0 && newValue != 1) {
+ throw new IllegalArgumentException("newValue should be 0 for off and 1 for on."
+ + "The passed value was: " + newValue);
}
- if (switchValue == -1) {
- throw new IllegalStateException("Switch value is not set");
- }
+ newValue = standardizeInput(newValue);
- switch(settingSource) {
+ switch(mSettingSource) {
case SettingsSource.GLOBAL:
- Settings.Global.putInt(context.getContentResolver(), settingsUri, switchValue);
- return;
+ return Settings.Global.putInt(context.getContentResolver(), mSettingKey, newValue);
case SettingsSource.SECURE:
- Settings.Secure.putInt(context.getContentResolver(), settingsUri, switchValue);
- return;
+ return Settings.Secure.putInt(context.getContentResolver(), mSettingKey, newValue);
case SettingsSource.SYSTEM:
- Settings.System.putInt(context.getContentResolver(), settingsUri, switchValue);
- return;
+ return Settings.System.putInt(context.getContentResolver(), mSettingKey, newValue);
case SettingsSource.UNKNOWN:
- return;
+ return false;
}
+
+ return false;
+ }
+
+ public boolean isStandard() {
+ return mIsStandard;
+ }
+
+ private int standardizeInput(int value) {
+ return mIsStandard
+ ? value
+ : 1 - value;
}
}
diff --git a/src/com/android/settings/search/InlineSwitchViewHolder.java b/src/com/android/settings/search/InlineSwitchViewHolder.java
index 82d33d8..bb8320d 100644
--- a/src/com/android/settings/search/InlineSwitchViewHolder.java
+++ b/src/com/android/settings/search/InlineSwitchViewHolder.java
@@ -46,19 +46,20 @@
}
@Override
- public void onBind(SearchFragment fragment, SearchResult result) {
+ public void onBind(SearchFragment fragment, final SearchResult result) {
super.onBind(fragment, result);
if (mContext == null) {
return;
}
final InlineSwitchPayload payload = (InlineSwitchPayload) result.payload;
- switchView.setChecked(payload.getSwitchValue(mContext));
+ switchView.setChecked(payload.getValue(mContext) == InlineSwitchPayload.TRUE);
switchView.setOnCheckedChangeListener((buttonView, isChecked) -> {
final Pair<Integer, Object> value = Pair.create(
MetricsEvent.FIELD_SETTINGS_SEARCH_INLINE_RESULT_VALUE, isChecked
? 1L : 0L);
- fragment.onSearchResultClicked(this, payload.settingsUri, value);
- payload.setSwitchValue(mContext, isChecked);
+ fragment.onSearchResultClicked(this, result, value);
+ int newValue = isChecked ? InlineSwitchPayload.TRUE : InlineSwitchPayload.FALSE;
+ payload.setValue(mContext, newValue);
});
}
}
diff --git a/src/com/android/settings/search/IntentSearchViewHolder.java b/src/com/android/settings/search/IntentSearchViewHolder.java
index 17ff981..11adaef 100644
--- a/src/com/android/settings/search/IntentSearchViewHolder.java
+++ b/src/com/android/settings/search/IntentSearchViewHolder.java
@@ -16,13 +16,9 @@
*/
package com.android.settings.search;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.text.TextUtils;
import android.view.View;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settings.SettingsActivity;
/**
* ViewHolder for intent based search results.
@@ -44,14 +40,8 @@
super.onBind(fragment, result);
itemView.setOnClickListener(v -> {
- final Intent intent = result.payload.getIntent();
- final ComponentName cn = intent.getComponent();
- String resultName = intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT);
- if (TextUtils.isEmpty(resultName) && cn != null) {
- resultName = cn.flattenToString();
- }
- fragment.onSearchResultClicked(this, resultName);
- fragment.startActivity(intent);
+ fragment.onSearchResultClicked(this, result);
+ fragment.startActivity(result.payload.getIntent());
});
}
}
diff --git a/src/com/android/settings/search/ResultPayload.java b/src/com/android/settings/search/ResultPayload.java
index 2352ffd..39688ac 100644
--- a/src/com/android/settings/search/ResultPayload.java
+++ b/src/com/android/settings/search/ResultPayload.java
@@ -57,6 +57,37 @@
int SAVED_QUERY = 3;
}
+ /**
+ * Enumerates the possible values for the Availability of a setting.
+ */
+ @IntDef({Availability.AVAILABLE,
+ Availability.DISABLED_DEPENDENCY,
+ Availability.DISABLED_UNSUPPORTED,
+ Availability.RESOURCE_CONTENTION})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Availability {
+ /**
+ * The setting is available.
+ */
+ int AVAILABLE = 0;
+
+ /**
+ * The setting has a dependency which is currently disabled, blocking access.
+ */
+ int DISABLED_DEPENDENCY = 1;
+
+ /**
+ * The setting is not supported by the device.
+ */
+ int DISABLED_UNSUPPORTED = 2;
+
+ /**
+ * The setting you are trying to change is being used by another application and cannot
+ * be changed until it is released by said application.
+ */
+ int RESOURCE_CONTENTION = 3;
+ }
+
@IntDef({SettingsSource.UNKNOWN, SettingsSource.SYSTEM, SettingsSource.SECURE,
SettingsSource.GLOBAL})
@Retention(RetentionPolicy.SOURCE)
diff --git a/src/com/android/settings/search/SearchFeatureProvider.java b/src/com/android/settings/search/SearchFeatureProvider.java
index 67d4ef1..50edae7 100644
--- a/src/com/android/settings/search/SearchFeatureProvider.java
+++ b/src/com/android/settings/search/SearchFeatureProvider.java
@@ -22,6 +22,7 @@
import android.view.View;
import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.search.ranking.SearchResultsRankerCallback;
import java.util.List;
@@ -98,21 +99,30 @@
}
/**
- * Ranks search results based on the input query.
+ * Query search results based on the input query.
*
+ * @param context application context
* @param query input user query
- * @param searchResults list of search results to be ranked
+ * @param searchResultsRankerCallback {@link SearchResultsRankerCallback}
*/
- default void rankSearchResults(String query, List<SearchResult> searchResults) {
+ default void querySearchResults(Context context, String query,
+ SearchResultsRankerCallback searchResultsRankerCallback) {
+ }
+
+ /**
+ * Cancel pending search query
+ */
+ default void cancelPendingSearchQuery(Context context) {
}
/**
* Notify that a search result is clicked.
*
+ * @param context application context
* @param query input user query
* @param searchResult clicked result
*/
- default void searchResultClicked(String query, SearchResult searchResult) {
+ default void searchResultClicked(Context context, String query, SearchResult searchResult) {
}
/**
diff --git a/src/com/android/settings/search/SearchFragment.java b/src/com/android/settings/search/SearchFragment.java
index 6b07f2d..87df62e 100644
--- a/src/com/android/settings/search/SearchFragment.java
+++ b/src/com/android/settings/search/SearchFragment.java
@@ -19,7 +19,9 @@
import android.app.Activity;
import android.app.LoaderManager;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.Loader;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
@@ -37,6 +39,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.core.InstrumentedFragment;
import com.android.settings.core.instrumentation.MetricsFeatureProvider;
@@ -234,6 +237,7 @@
mSavedQueryController.loadSavedQueries();
mSearchFeatureProvider.hideFeedbackButton();
} else {
+ mSearchAdapter.initializeSearch(mQuery);
restartLoaders();
}
@@ -270,15 +274,7 @@
return;
}
- final int resultCount = mSearchAdapter.displaySearchResults(mQuery);
-
- if (resultCount == 0) {
- mNoResultsView.setVisibility(View.VISIBLE);
- } else {
- mNoResultsView.setVisibility(View.GONE);
- mResultsRecyclerView.scrollToPosition(0);
- }
- mSearchFeatureProvider.showFeedbackButton(this, getView());
+ mSearchAdapter.notifyResultsLoaded();
}
@Override
@@ -304,30 +300,24 @@
requery();
}
- public void onSearchResultClicked(SearchViewHolder result, String settingName,
+ public void onSearchResultClicked(SearchViewHolder resultViewHolder, SearchResult result,
Pair<Integer, Object>... logTaggedData) {
- final List<Pair<Integer, Object>> taggedData = new ArrayList<>();
- if (logTaggedData != null) {
- taggedData.addAll(Arrays.asList(logTaggedData));
- }
- taggedData.add(Pair.create(
- MetricsEvent.FIELD_SETTINGS_SERACH_RESULT_COUNT,
- mSearchAdapter.getItemCount()));
- taggedData.add(Pair.create(
- MetricsEvent.FIELD_SETTINGS_SERACH_RESULT_RANK,
- result.getAdapterPosition()));
- taggedData.add(Pair.create(
- MetricsEvent.FIELD_SETTINGS_SERACH_QUERY_LENGTH,
- TextUtils.isEmpty(mQuery) ? 0 : mQuery.length()));
+ logSearchResultClicked(resultViewHolder, result, logTaggedData);
- mMetricsFeatureProvider.action(getContext(),
- MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_RESULT,
- settingName,
- taggedData.toArray(new Pair[0]));
mSavedQueryController.saveQuery(mQuery);
mResultClickCount++;
}
+ public void onSearchResultsDisplayed(int resultCount) {
+ if (resultCount == 0) {
+ mNoResultsView.setVisibility(View.VISIBLE);
+ } else {
+ mNoResultsView.setVisibility(View.GONE);
+ mResultsRecyclerView.scrollToPosition(0);
+ }
+ mSearchFeatureProvider.showFeedbackButton(this, getView());
+ }
+
public void onSavedQueryClicked(CharSequence query) {
final String queryString = query.toString();
mMetricsFeatureProvider.action(getContext(),
@@ -378,4 +368,38 @@
mResultsRecyclerView.requestFocus();
}
}
-}
\ No newline at end of file
+
+ private void logSearchResultClicked(SearchViewHolder resultViewHolder, SearchResult result,
+ Pair<Integer, Object>... logTaggedData) {
+ final Intent intent = result.payload.getIntent();
+ if (intent == null) {
+ Log.w(TAG, "Skipped logging click on search result because of null intent, which can " +
+ "happen on saved query results.");
+ return;
+ }
+ final ComponentName cn = intent.getComponent();
+ String resultName = intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT);
+ if (TextUtils.isEmpty(resultName) && cn != null) {
+ resultName = cn.flattenToString();
+ }
+ final List<Pair<Integer, Object>> taggedData = new ArrayList<>();
+ if (logTaggedData != null) {
+ taggedData.addAll(Arrays.asList(logTaggedData));
+ }
+ taggedData.add(Pair.create(
+ MetricsEvent.FIELD_SETTINGS_SERACH_RESULT_COUNT,
+ mSearchAdapter.getItemCount()));
+ taggedData.add(Pair.create(
+ MetricsEvent.FIELD_SETTINGS_SERACH_RESULT_RANK,
+ resultViewHolder.getAdapterPosition()));
+ taggedData.add(Pair.create(
+ MetricsEvent.FIELD_SETTINGS_SERACH_QUERY_LENGTH,
+ TextUtils.isEmpty(mQuery) ? 0 : mQuery.length()));
+
+ mMetricsFeatureProvider.action(getContext(),
+ MetricsEvent.ACTION_CLICK_SETTINGS_SEARCH_RESULT,
+ resultName,
+ taggedData.toArray(new Pair[0]));
+ mSearchFeatureProvider.searchResultClicked(getContext(), mQuery, result);
+ }
+}
diff --git a/src/com/android/settings/search/SearchResultsAdapter.java b/src/com/android/settings/search/SearchResultsAdapter.java
index 31e0793..199930f 100644
--- a/src/com/android/settings/search/SearchResultsAdapter.java
+++ b/src/com/android/settings/search/SearchResultsAdapter.java
@@ -18,36 +18,79 @@
package com.android.settings.search;
import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.IntDef;
import android.support.annotation.MainThread;
import android.support.annotation.VisibleForTesting;
import android.support.v7.util.DiffUtil;
import android.support.v7.widget.RecyclerView;
import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.android.settings.R;
+import com.android.settings.search.ranking.SearchResultsRankerCallback;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder> {
+public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder>
+ implements SearchResultsRankerCallback {
+ private static final String TAG = "SearchResultsAdapter";
+ @VisibleForTesting
+ static final String DB_RESULTS_LOADER_KEY = DatabaseResultLoader.class.getName();
+
+ @VisibleForTesting
+ static final String APP_RESULTS_LOADER_KEY = InstalledAppResultLoader.class.getName();
+
+ @VisibleForTesting
+ static final int MSG_RANKING_TIMED_OUT = 1;
+
+ // TODO(b/38197948): Tune this timeout based on latency of static and async rankings. Also, we
+ // should add a gservices flag to control this.
+ private static final long RANKING_TIMEOUT_MS = 300;
private final SearchFragment mFragment;
-
- private List<SearchResult> mSearchResults;
+ private final Context mContext;
+ private final List<SearchResult> mSearchResults;
+ private final List<SearchResult> mStaticallyRankedSearchResults;
private Map<String, Set<? extends SearchResult>> mResultsMap;
private final SearchFeatureProvider mSearchFeatureProvider;
+ private List<Pair<String, Float>> mSearchRankingScores;
+ private Handler mHandler;
+ private boolean mSearchResultsLoaded;
+ private boolean mSearchResultsUpdated;
+
+ @IntDef({DISABLED, PENDING_RESULTS, SUCCEEDED, FAILED, TIMED_OUT})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface AsyncRankingState {}
+ private static final int DISABLED = 0;
+ private static final int PENDING_RESULTS = 1;
+ private static final int SUCCEEDED = 2;
+ private static final int FAILED = 3;
+ private static final int TIMED_OUT = 4;
+ private @AsyncRankingState int mAsyncRankingState;
public SearchResultsAdapter(SearchFragment fragment,
SearchFeatureProvider searchFeatureProvider) {
mFragment = fragment;
+ mContext = fragment.getContext().getApplicationContext();
mSearchResults = new ArrayList<>();
mResultsMap = new ArrayMap<>();
+ mSearchRankingScores = new ArrayList<>();
+ mStaticallyRankedSearchResults = new ArrayList<>();
mSearchFeatureProvider = searchFeatureProvider;
setHasStableIds(true);
@@ -93,7 +136,37 @@
return mSearchResults.size();
}
- /**
+ @MainThread
+ @Override
+ public void onRankingScoresAvailable(List<Pair<String, Float>> searchRankingScores) {
+ // Received the scores, stop the timeout timer.
+ getHandler().removeMessages(MSG_RANKING_TIMED_OUT);
+ if (mAsyncRankingState == PENDING_RESULTS) {
+ mAsyncRankingState = SUCCEEDED;
+ mSearchRankingScores.clear();
+ mSearchRankingScores.addAll(searchRankingScores);
+ if (canUpdateSearchResults()) {
+ updateSearchResults();
+ }
+ } else {
+ Log.w(TAG, "Ranking scores became available in invalid state: " + mAsyncRankingState);
+ }
+ }
+
+ @MainThread
+ @Override
+ public void onRankingFailed() {
+ if (mAsyncRankingState == PENDING_RESULTS) {
+ mAsyncRankingState = FAILED;
+ if (canUpdateSearchResults()) {
+ updateSearchResults();
+ }
+ } else {
+ Log.w(TAG, "Ranking scores failed in invalid states: " + mAsyncRankingState);
+ }
+ }
+
+ /**
* Store the results from each of the loaders to be merged when all loaders are finished.
*
* @param results the results from the loader.
@@ -120,71 +193,24 @@
}
/**
- * Merge the results from each of the loaders into one list for the adapter.
- * Prioritizes results from the local database over installed apps.
- *
- * @param query user query corresponding to these results
- * @return Number of matched results
+ * Notifies the adapter that all the unsorted results are loaded and now the ladapter can
+ * proceed with ranking the results.
*/
- public int displaySearchResults(String query) {
- List<? extends SearchResult> databaseResults = null;
- List<? extends SearchResult> installedAppResults = null;
- final String dbLoaderKey = DatabaseResultLoader.class.getName();
- final String appLoaderKey = InstalledAppResultLoader.class.getName();
- int dbSize = 0;
- int appSize = 0;
- if (mResultsMap.containsKey(dbLoaderKey)) {
- databaseResults = new ArrayList<>(mResultsMap.get(dbLoaderKey));
- dbSize = databaseResults.size();
- Collections.sort(databaseResults);
+ @MainThread
+ public void notifyResultsLoaded() {
+ mSearchResultsLoaded = true;
+ // static ranking is skipped only if asyc ranking is already succeeded.
+ if (mAsyncRankingState != SUCCEEDED) {
+ doStaticRanking();
}
- if (mResultsMap.containsKey(appLoaderKey)) {
- installedAppResults = new ArrayList<>(mResultsMap.get(appLoaderKey));
- appSize = installedAppResults.size();
- Collections.sort(installedAppResults);
+ if (canUpdateSearchResults()) {
+ updateSearchResults();
}
- final List<SearchResult> newResults = new ArrayList<>(dbSize + appSize);
-
- int dbIndex = 0;
- int appIndex = 0;
- int rank = SearchResult.TOP_RANK;
-
- while (rank <= SearchResult.BOTTOM_RANK) {
- while ((dbIndex < dbSize) && (databaseResults.get(dbIndex).rank == rank)) {
- newResults.add(databaseResults.get(dbIndex++));
- }
- while ((appIndex < appSize) && (installedAppResults.get(appIndex).rank == rank)) {
- newResults.add(installedAppResults.get(appIndex++));
- }
- rank++;
- }
-
- while (dbIndex < dbSize) {
- newResults.add(databaseResults.get(dbIndex++));
- }
- while (appIndex < appSize) {
- newResults.add(installedAppResults.get(appIndex++));
- }
-
- final boolean isSmartSearchRankingEnabled = mSearchFeatureProvider
- .isSmartSearchRankingEnabled(mFragment.getContext().getApplicationContext());
-
- if (isSmartSearchRankingEnabled) {
- // TODO: run this in parallel to loading the results if takes too long
- mSearchFeatureProvider.rankSearchResults(query, newResults);
- }
-
- final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
- new SearchResultDiffCallback(mSearchResults, newResults),
- isSmartSearchRankingEnabled);
- mSearchResults = newResults;
- diffResult.dispatchUpdatesTo(this);
-
- return mSearchResults.size();
}
public void clearResults() {
mSearchResults.clear();
+ mStaticallyRankedSearchResults.clear();
mResultsMap.clear();
notifyDataSetChanged();
}
@@ -193,4 +219,178 @@
public List<SearchResult> getSearchResults() {
return mSearchResults;
}
+
+ @MainThread
+ public void initializeSearch(String query) {
+ clearResults();
+ mSearchResultsLoaded = false;
+ mSearchResultsUpdated = false;
+ if (mSearchFeatureProvider.isSmartSearchRankingEnabled(mContext)) {
+ mAsyncRankingState = PENDING_RESULTS;
+ mSearchFeatureProvider.cancelPendingSearchQuery(mContext);
+ final Handler handler = getHandler();
+ handler.sendMessageDelayed(
+ handler.obtainMessage(MSG_RANKING_TIMED_OUT), RANKING_TIMEOUT_MS);
+ mSearchFeatureProvider.querySearchResults(mContext, query, this);
+ } else {
+ mAsyncRankingState = DISABLED;
+ }
+ }
+
+ /**
+ * Merge the results from each of the loaders into one list for the adapter.
+ * Prioritizes results from the local database over installed apps.
+ */
+ private void doStaticRanking() {
+ List<? extends SearchResult> databaseResults =
+ getSortedLoadedResults(DB_RESULTS_LOADER_KEY);
+ List<? extends SearchResult> installedAppResults =
+ getSortedLoadedResults(APP_RESULTS_LOADER_KEY);
+ int dbSize = databaseResults.size();
+ int appSize = installedAppResults.size();
+
+ int dbIndex = 0;
+ int appIndex = 0;
+ int rank = SearchResult.TOP_RANK;
+
+ mStaticallyRankedSearchResults.clear();
+ while (rank <= SearchResult.BOTTOM_RANK) {
+ while ((dbIndex < dbSize) && (databaseResults.get(dbIndex).rank == rank)) {
+ mStaticallyRankedSearchResults.add(databaseResults.get(dbIndex++));
+ }
+ while ((appIndex < appSize) && (installedAppResults.get(appIndex).rank == rank)) {
+ mStaticallyRankedSearchResults.add(installedAppResults.get(appIndex++));
+ }
+ rank++;
+ }
+
+ while (dbIndex < dbSize) {
+ mStaticallyRankedSearchResults.add(databaseResults.get(dbIndex++));
+ }
+ while (appIndex < appSize) {
+ mStaticallyRankedSearchResults.add(installedAppResults.get(appIndex++));
+ }
+ }
+
+ private void updateSearchResults() {
+ switch (mAsyncRankingState) {
+ case PENDING_RESULTS:
+ break;
+ case DISABLED:
+ case FAILED:
+ case TIMED_OUT:
+ // When DISABLED or FAILED or TIMED_OUT, we use static ranking results.
+ postSearchResults(mStaticallyRankedSearchResults, false);
+ break;
+ case SUCCEEDED:
+ postSearchResults(doAsyncRanking(), true);
+ break;
+ }
+ }
+
+ private boolean canUpdateSearchResults() {
+ // Results are not updated yet and db results are loaded and we are not waiting on async
+ // ranking scores.
+ return !mSearchResultsUpdated
+ && mSearchResultsLoaded
+ && mAsyncRankingState != PENDING_RESULTS;
+ }
+
+ @VisibleForTesting
+ List<SearchResult> doAsyncRanking() {
+ Set<? extends SearchResult> databaseResults =
+ getUnsortedLoadedResults(DB_RESULTS_LOADER_KEY);
+ List<? extends SearchResult> installedAppResults =
+ getSortedLoadedResults(APP_RESULTS_LOADER_KEY);
+ int dbSize = databaseResults.size();
+ int appSize = installedAppResults.size();
+
+ final List<SearchResult> asyncRankingResults = new ArrayList<>(dbSize + appSize);
+ List<SearchResult> databaseResultsSortedByScores = new ArrayList<>(databaseResults);
+ Collections.sort(databaseResultsSortedByScores, new Comparator<SearchResult>() {
+ @Override
+ public int compare(SearchResult o1, SearchResult o2) {
+ float score1 = getRankingScoreByStableId(o1.stableId);
+ float score2 = getRankingScoreByStableId(o2.stableId);
+ if (score1 > score2) {
+ return -1;
+ } else if (score1 == score2) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+ });
+ asyncRankingResults.addAll(databaseResultsSortedByScores);
+ // App results are not ranked by async ranking and appended at the end of the list.
+ asyncRankingResults.addAll(installedAppResults);
+ return asyncRankingResults;
+ }
+
+ @VisibleForTesting
+ Set<? extends SearchResult> getUnsortedLoadedResults(String loaderKey) {
+ return mResultsMap.containsKey(loaderKey) ?
+ mResultsMap.get(loaderKey) : new HashSet<SearchResult>();
+ }
+
+ @VisibleForTesting
+ List<? extends SearchResult> getSortedLoadedResults(String loaderKey) {
+ List<? extends SearchResult> sortedLoadedResults =
+ new ArrayList<>(getUnsortedLoadedResults(loaderKey));
+ Collections.sort(sortedLoadedResults);
+ return sortedLoadedResults;
+ }
+
+ /**
+ * Looks up ranking score for stableId
+ * @param stableId String of stableId
+ * @return the ranking score corresponding to the given stableId. If there is no score
+ * available for this stableId, -Float.MAX_VALUE is returned.
+ */
+ @VisibleForTesting
+ Float getRankingScoreByStableId(int stableId) {
+ for (Pair<String, Float> rankingScore : mSearchRankingScores) {
+ if (Integer.toString(stableId).compareTo(rankingScore.first) == 0) {
+ return rankingScore.second;
+ }
+ }
+ // If stableId not found in the list, we assign the minimum score so it will appear at
+ // the end of the list.
+ Log.w(TAG, "stableId " + stableId + " was not in the ranking scores.");
+ return -Float.MAX_VALUE;
+ }
+
+ @VisibleForTesting
+ Handler getHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_RANKING_TIMED_OUT) {
+ mSearchFeatureProvider.cancelPendingSearchQuery(mContext);
+ if (mAsyncRankingState == PENDING_RESULTS) {
+ mAsyncRankingState = TIMED_OUT;
+ if (canUpdateSearchResults()) {
+ updateSearchResults();
+ }
+ } else {
+ Log.w(TAG, "Ranking scores timed out in invalid state: " +
+ mAsyncRankingState);
+ }
+ }
+ }
+ };
+ }
+ return mHandler;
+ }
+
+ private void postSearchResults(List<SearchResult> newSearchResults, boolean detectMoves) {
+ final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(
+ new SearchResultDiffCallback(mSearchResults, newSearchResults), detectMoves);
+ mSearchResults.clear();
+ mSearchResults.addAll(newSearchResults);
+ diffResult.dispatchUpdatesTo(this);
+ mFragment.onSearchResultsDisplayed(mSearchResults.size());
+ mSearchResultsUpdated = true;
+ }
}
diff --git a/src/com/android/settings/search/ranking/SearchResultsRankerCallback.java b/src/com/android/settings/search/ranking/SearchResultsRankerCallback.java
new file mode 100644
index 0000000..c254a40
--- /dev/null
+++ b/src/com/android/settings/search/ranking/SearchResultsRankerCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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.search.ranking;
+
+import android.util.Pair;
+
+import java.util.List;
+
+public interface SearchResultsRankerCallback {
+
+ /**
+ * Called when ranker provides the ranking scores.
+ * @param searchRankingScores Ordered List of Pairs of String and Float corresponding to
+ * stableIds and ranking scores. The list must be descendingly
+ * ordered based on scores.
+ */
+ public void onRankingScoresAvailable(List<Pair<String, Float>> searchRankingScores);
+
+ /**
+ * Called when for any reason ranker fails, which notifies the client to proceed
+ * without ranking results.
+ */
+ public void onRankingFailed();
+}
diff --git a/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java b/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java
index 1a60256..bf67565 100644
--- a/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java
+++ b/src/com/android/settings/wifi/SavedAccessPointsWifiSettings.java
@@ -38,7 +38,7 @@
import com.android.settings.search.SearchIndexableRaw;
import com.android.settingslib.wifi.AccessPoint;
import com.android.settingslib.wifi.AccessPointPreference;
-import com.android.settingslib.wifi.WifiTracker;
+import com.android.settingslib.wifi.WifiSavedConfigUtils;
import java.util.ArrayList;
import java.util.Arrays;
@@ -124,7 +124,8 @@
PreferenceScreen preferenceScreen = getPreferenceScreen();
final Context context = getPrefContext();
- final List<AccessPoint> accessPoints = getSavedConfigs(context, mWifiManager);
+ final List<AccessPoint> accessPoints =
+ WifiSavedConfigUtils.getAllConfigs(context, mWifiManager);
Collections.sort(accessPoints, SAVED_NETWORK_COMPARATOR);
preferenceScreen.removeAll();
@@ -142,39 +143,6 @@
}
}
- /**
- * Retrieved the list of saved network configurations from {@link WifiManager}.
- * Each configuration is represented by {@link AccessPoint}.
- *
- * @param context The application context
- * @param wifiManager An instance of {@link WifiManager}
- * @return List of {@link AccessPoint}
- */
- private static List<AccessPoint> getSavedConfigs(Context context, WifiManager wifiManager) {
- List<AccessPoint> savedConfigs = new ArrayList<>();
- List<WifiConfiguration> savedNetworks = wifiManager.getConfiguredNetworks();
- for (WifiConfiguration network : savedNetworks) {
- // Configuration for Passpoint network is configured temporary by WifiService for
- // connection attempt only. The underlying configuration is saved as Passpoint
- // configuration, which will be retrieved with WifiManager#getPasspointConfiguration
- // call below.
- if (network.isPasspoint()) {
- continue;
- }
- savedConfigs.add(new AccessPoint(context, network));
- }
- try {
- List<PasspointConfiguration> savedPasspointConfigs =
- wifiManager.getPasspointConfigurations();
- for (PasspointConfiguration config : savedPasspointConfigs) {
- savedConfigs.add(new AccessPoint(context, config));
- }
- } catch (UnsupportedOperationException e) {
- // Passpoint not supported.
- }
- return savedConfigs;
- }
-
private void showDialog(LongPressAccessPointPreference accessPoint, boolean edit) {
if (mDialog != null) {
removeDialog(WifiSettings.WIFI_DIALOG_ID);
@@ -292,8 +260,8 @@
result.add(data);
// Add available Wi-Fi access points
- final List<AccessPoint> accessPoints =
- getSavedConfigs(context, context.getSystemService(WifiManager.class));
+ final List<AccessPoint> accessPoints = WifiSavedConfigUtils.getAllConfigs(
+ context, context.getSystemService(WifiManager.class));
final int accessPointsSize = accessPoints.size();
for (int i = 0; i < accessPointsSize; ++i){
diff --git a/src/com/android/settings/wifi/WifiDetailPreference.java b/src/com/android/settings/wifi/WifiDetailPreference.java
index 6d34ad1..b62df56 100644
--- a/src/com/android/settings/wifi/WifiDetailPreference.java
+++ b/src/com/android/settings/wifi/WifiDetailPreference.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceViewHolder;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.TextView;
@@ -37,6 +38,7 @@
}
public void setDetailText(String text) {
+ if (TextUtils.equals(mDetailText, text)) return;
mDetailText = text;
notifyChanged();
}
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index 01ccfee..6d575d8 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -697,6 +697,7 @@
case WifiManager.WIFI_STATE_DISABLED:
setOffMessage();
+ setAdditionalSettingsSummaries();
setProgressBarVisible(false);
break;
}
diff --git a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java
index d1f6c80..fc47f70 100644
--- a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java
+++ b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java
@@ -27,6 +27,7 @@
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.IpPrefix;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkBadging;
@@ -67,6 +68,7 @@
import java.net.UnknownHostException;
import java.util.List;
import java.util.StringJoiner;
+import java.util.stream.Collectors;
/**
* Controller for logic pertaining to displaying Wifi information for the
@@ -100,7 +102,9 @@
@VisibleForTesting
static final String KEY_DNS_PREF = "dns";
@VisibleForTesting
- static final String KEY_IPV6_ADDRESS_CATEGORY = "ipv6_details_category";
+ static final String KEY_IPV6_CATEGORY = "ipv6_category";
+ @VisibleForTesting
+ static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses";
private AccessPoint mAccessPoint;
private final ConnectivityManagerWrapper mConnectivityManagerWrapper;
@@ -133,8 +137,9 @@
private WifiDetailPreference mGatewayPref;
private WifiDetailPreference mSubnetPref;
private WifiDetailPreference mDnsPref;
+ private PreferenceCategory mIpv6Category;
+ private Preference mIpv6AddressPref;
- private PreferenceCategory mIpv6AddressCategory;
private final IntentFilter mFilter;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
@@ -241,8 +246,8 @@
mSubnetPref = (WifiDetailPreference) screen.findPreference(KEY_SUBNET_MASK_PREF);
mDnsPref = (WifiDetailPreference) screen.findPreference(KEY_DNS_PREF);
- mIpv6AddressCategory =
- (PreferenceCategory) screen.findPreference(KEY_IPV6_ADDRESS_CATEGORY);
+ mIpv6Category = (PreferenceCategory) screen.findPreference(KEY_IPV6_CATEGORY);
+ mIpv6AddressPref = (Preference) screen.findPreference(KEY_IPV6_ADDRESSES_PREF);
mSecurityPref.setDetailText(mAccessPoint.getSecurityString(false /* concise */));
mForgetButton = (Button) mButtonsPref.findViewById(R.id.forget_button);
@@ -315,8 +320,6 @@
mFrequencyPref.setDetailText(band);
updateIpLayerInfo();
- mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE
- || mSignInButton.getVisibility() == View.VISIBLE);
}
private void exitActivity() {
@@ -348,74 +351,69 @@
mSignalStrengthPref.setDetailText(mSignalStr[summarySignalLevel]);
}
+ private void updatePreference(WifiDetailPreference pref, String detailText) {
+ if (!TextUtils.isEmpty(detailText)) {
+ pref.setDetailText(detailText);
+ pref.setVisible(true);
+ } else {
+ pref.setVisible(false);
+ }
+ }
+
private void updateIpLayerInfo() {
mSignInButton.setVisibility(canSignIntoNetwork() ? View.VISIBLE : View.INVISIBLE);
-
- // Reset all fields
- mIpv6AddressCategory.removeAll();
- mIpv6AddressCategory.setVisible(false);
- mIpAddressPref.setVisible(false);
- mSubnetPref.setVisible(false);
- mGatewayPref.setVisible(false);
- mDnsPref.setVisible(false);
+ mButtonsPref.setVisible(mForgetButton.getVisibility() == View.VISIBLE
+ || mSignInButton.getVisibility() == View.VISIBLE);
if (mNetwork == null || mLinkProperties == null) {
+ mIpAddressPref.setVisible(false);
+ mSubnetPref.setVisible(false);
+ mGatewayPref.setVisible(false);
+ mDnsPref.setVisible(false);
+ mIpv6Category.setVisible(false);
return;
}
- List<InetAddress> addresses = mLinkProperties.getAddresses();
- // Set IPv4 and IPv6 addresses
- for (int i = 0; i < addresses.size(); i++) {
- InetAddress addr = addresses.get(i);
- if (addr instanceof Inet4Address) {
- mIpAddressPref.setDetailText(addr.getHostAddress());
- mIpAddressPref.setVisible(true);
- } else if (addr instanceof Inet6Address) {
- String ip = addr.getHostAddress();
- Preference pref = new Preference(mPrefContext);
- pref.setKey(ip);
- pref.setTitle(ip);
- pref.setSelectable(false);
- mIpv6AddressCategory.addPreference(pref);
- mIpv6AddressCategory.setVisible(true);
- }
- }
-
- // Set up IPv4 gateway and subnet mask
- String gateway = null;
+ // Find IPv4 and IPv6 addresses.
+ String ipv4Address = null;
String subnet = null;
+ StringJoiner ipv6Addresses = new StringJoiner("\n");
+
+ for (LinkAddress addr : mLinkProperties.getLinkAddresses()) {
+ if (addr.getAddress() instanceof Inet4Address) {
+ ipv4Address = addr.getAddress().getHostAddress();
+ subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength());
+ } else if (addr.getAddress() instanceof Inet6Address) {
+ ipv6Addresses.add(addr.getAddress().getHostAddress());
+ }
+ }
+
+ // Find IPv4 default gateway.
+ String gateway = null;
for (RouteInfo routeInfo : mLinkProperties.getRoutes()) {
- if (routeInfo.hasGateway() && routeInfo.getGateway() instanceof Inet4Address) {
+ if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) {
gateway = routeInfo.getGateway().getHostAddress();
- }
- IpPrefix ipPrefix = routeInfo.getDestination();
- if (ipPrefix != null && ipPrefix.getAddress() instanceof Inet4Address
- && ipPrefix.getPrefixLength() > 0) {
- subnet = ipv4PrefixLengthToSubnetMask(ipPrefix.getPrefixLength());
+ break;
}
}
- if (!TextUtils.isEmpty(subnet)) {
- mSubnetPref.setDetailText(subnet);
- mSubnetPref.setVisible(true);
- }
+ // Find IPv4 DNS addresses.
+ String dnsServers = mLinkProperties.getDnsServers().stream()
+ .filter(Inet4Address.class::isInstance)
+ .map(InetAddress::getHostAddress)
+ .collect(Collectors.joining(","));
- if (!TextUtils.isEmpty(gateway)) {
- mGatewayPref.setDetailText(gateway);
- mGatewayPref.setVisible(true);
- }
+ // Update UI.
+ updatePreference(mIpAddressPref, ipv4Address);
+ updatePreference(mSubnetPref, subnet);
+ updatePreference(mGatewayPref, gateway);
+ updatePreference(mDnsPref, dnsServers);
- // Set IPv4 DNS addresses
- StringJoiner stringJoiner = new StringJoiner(",");
- for (InetAddress dnsServer : mLinkProperties.getDnsServers()) {
- if (dnsServer instanceof Inet4Address) {
- stringJoiner.add(dnsServer.getHostAddress());
- }
- }
- String dnsText = stringJoiner.toString();
- if (!dnsText.isEmpty()) {
- mDnsPref.setDetailText(dnsText);
- mDnsPref.setVisible(true);
+ if (ipv6Addresses.length() > 0) {
+ mIpv6AddressPref.setSummary(ipv6Addresses.toString());
+ mIpv6Category.setVisible(true);
+ } else {
+ mIpv6Category.setVisible(false);
}
}
diff --git a/tests/robotests/src/com/android/settings/accounts/AutoSyncWorkDataPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accounts/AutoSyncWorkDataPreferenceControllerTest.java
index d93d3a7..cd66d5e 100644
--- a/tests/robotests/src/com/android/settings/accounts/AutoSyncWorkDataPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accounts/AutoSyncWorkDataPreferenceControllerTest.java
@@ -21,6 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
import android.app.Fragment;
@@ -46,6 +47,8 @@
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class AutoSyncWorkDataPreferenceControllerTest {
+ private static int MANAGED_PROFILE_ID = 10;
+
@Mock(answer = RETURNS_DEEP_STUBS)
private UserManager mUserManager;
@Mock(answer = RETURNS_DEEP_STUBS)
@@ -80,29 +83,30 @@
@Test
public void checkIsAvailable_singleUserProfile_shouldNotDisplay() {
- final List<UserInfo> infos = new ArrayList<>();
- infos.add(new UserInfo(1, "user 1", 0));
when(mUserManager.isManagedProfile()).thenReturn(false);
when(mUserManager.isLinkedUser()).thenReturn(false);
- when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
+
+ final List<UserInfo> infos = new ArrayList<>();
+ infos.add(new UserInfo(UserHandle.USER_SYSTEM, "user 1", 0 /* flags */));
+ when(mUserManager.getProfiles(eq(UserHandle.USER_SYSTEM))).thenReturn(infos);
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void multipleProfile_shouldInitWithWorkProfileUserHandle() {
- final int id1 = 1;
- final int id2 = 2;
- final UserInfo managedUser = new UserInfo(id2, "user 2", FLAG_MANAGED_PROFILE);
- final List<UserHandle> infos = new ArrayList<>();
- infos.add(new UserHandle(id1));
- infos.add(new UserHandle(id2));
- when(mUserManager.getUserProfiles()).thenReturn(infos);
- when(mUserManager.getUserHandle()).thenReturn(id1);
- when(mUserManager.getUserInfo(id2)).thenReturn(managedUser);
+ when(mUserManager.isManagedProfile()).thenReturn(false);
+ when(mUserManager.isLinkedUser()).thenReturn(false);
+
+ final List<UserInfo> infos = new ArrayList<>();
+ infos.add(new UserInfo(UserHandle.USER_SYSTEM, "user 1", 0 /* flags */));
+ infos.add(new UserInfo(
+ MANAGED_PROFILE_ID, "work profile", UserInfo.FLAG_MANAGED_PROFILE));
+ when(mUserManager.getProfiles(eq(UserHandle.USER_SYSTEM))).thenReturn(infos);
mController = new AutoSyncWorkDataPreferenceController(mContext, mFragment);
- assertThat(mController.mUserHandle.getIdentifier()).isEqualTo(id2);
+ assertThat(mController.mUserHandle.getIdentifier()).isEqualTo(MANAGED_PROFILE_ID);
+ assertThat(mController.isAvailable()).isTrue();
}
}
diff --git a/tests/robotests/src/com/android/settings/applications/InstalledAppDetailsTest.java b/tests/robotests/src/com/android/settings/applications/InstalledAppDetailsTest.java
index c2ffed1..e86288b 100644
--- a/tests/robotests/src/com/android/settings/applications/InstalledAppDetailsTest.java
+++ b/tests/robotests/src/com/android/settings/applications/InstalledAppDetailsTest.java
@@ -19,6 +19,7 @@
import android.app.AlertDialog;
import android.app.Fragment;
+import android.app.LoaderManager;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
@@ -27,6 +28,7 @@
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.BatteryStats;
+import android.os.Bundle;
import android.os.UserManager;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceManager;
@@ -109,8 +111,10 @@
private PackageManager mPackageManager;
@Mock
private BatteryUtils mBatteryUtils;
- private FakeFeatureFactory mFeatureFactory;
+ @Mock
+ private LoaderManager mLoaderManager;
+ private FakeFeatureFactory mFeatureFactory;
private InstalledAppDetails mAppDetail;
private Context mShadowContext;
private Preference mBatteryPreference;
@@ -530,6 +534,16 @@
verify(button).setText(R.string.disable_text);
}
+ @Test
+ public void testRestartBatteryStatsLoader() {
+ doReturn(mLoaderManager).when(mAppDetail).getLoaderManager();
+
+ mAppDetail.restartBatteryStatsLoader();
+
+ verify(mLoaderManager).restartLoader(InstalledAppDetails.LOADER_BATTERY, Bundle.EMPTY,
+ mAppDetail.mBatteryCallbacks);
+ }
+
@Implements(Utils.class)
public static class ShadowUtils {
@Implementation
diff --git a/tests/robotests/src/com/android/settings/core/DynamicAvailabilityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/core/DynamicAvailabilityPreferenceControllerTest.java
index d0cb0d3..c4635c6 100644
--- a/tests/robotests/src/com/android/settings/core/DynamicAvailabilityPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/core/DynamicAvailabilityPreferenceControllerTest.java
@@ -22,6 +22,7 @@
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
import com.android.settingslib.core.lifecycle.Lifecycle;
import org.junit.Before;
@@ -50,6 +51,7 @@
private @Mock Preference mPreference;
private @Mock PreferenceScreen mScreen;
private @Mock Lifecycle mLifecycle;
+ private @Mock PreferenceAvailabilityObserver mObserver;
private boolean mIsAvailable;
private Preference mUpdatedPreference = null;
@@ -115,6 +117,21 @@
assertThat(mUpdatedPreference).isEqualTo(mPreference);
}
+ @Test
+ public void testNotifyOnAvailabilityUpdate() {
+ final DynamicAvailabilityPreferenceController controller
+ = new DynamicAvailabilityPreferenceControllerTestable(mLifecycle);
+ controller.setAvailabilityObserver(mObserver);
+ assertThat(controller.getAvailabilityObserver()).isEqualTo(mObserver);
+
+ mIsAvailable = false;
+ controller.isAvailable();
+ verify(mObserver).onPreferenceAvailabilityUpdated(PREFERENCE_KEY, false);
+
+ mIsAvailable = true;
+ controller.isAvailable();
+ verify(mObserver).onPreferenceAvailabilityUpdated(PREFERENCE_KEY, true);
+ }
private class DynamicAvailabilityPreferenceControllerTestable
extends DynamicAvailabilityPreferenceController {
@@ -124,6 +141,7 @@
@Override
public boolean isAvailable() {
+ notifyOnAvailabilityUpdate(mIsAvailable);
return mIsAvailable;
}
diff --git a/tests/robotests/src/com/android/settings/display/AutoBrightnessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/AutoBrightnessPreferenceControllerTest.java
index a7da857..fae4873 100644
--- a/tests/robotests/src/com/android/settings/display/AutoBrightnessPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/display/AutoBrightnessPreferenceControllerTest.java
@@ -16,12 +16,15 @@
package com.android.settings.display;
+import android.content.ContentResolver;
import android.content.Context;
import android.provider.Settings;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.search.InlinePayload;
import com.android.settings.search.InlineSwitchPayload;
import com.android.settings.search.ResultPayload;
+import com.android.settings.search.ResultPayload.Availability;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -78,13 +81,25 @@
}
@Test
- public void testPreferenceController_CorrectPayload() {
- final Context context = ShadowApplication.getInstance().getApplicationContext();
- mController = new AutoBrightnessPreferenceController(context, PREFERENCE_KEY);
- InlineSwitchPayload payload = (InlineSwitchPayload) mController.getResultPayload();
- assertThat(payload.settingsUri).isEqualTo("screen_brightness_mode");
- assertThat(payload.settingSource).isEqualTo(ResultPayload.SettingsSource.SYSTEM);
- assertThat(payload.valueMap.get(1)).isEqualTo(true);
- assertThat(payload.valueMap.get(0)).isEqualTo(false);
+ public void testSetValue_updatesCorrectly() {
+ int newValue = 1;
+ ContentResolver resolver = mContext.getContentResolver();
+ Settings.System.putInt(resolver, SCREEN_BRIGHTNESS_MODE, 0);
+
+ ((InlinePayload) mController.getResultPayload()).setValue(mContext, newValue);
+ int updatedValue = Settings.System.getInt(resolver, SCREEN_BRIGHTNESS_MODE, -1);
+
+ assertThat(updatedValue).isEqualTo(newValue);
+ }
+
+ @Test
+ public void testGetValue_correctValueReturned() {
+ int currentValue = 1;
+ ContentResolver resolver = mContext.getContentResolver();
+ Settings.System.putInt(resolver, SCREEN_BRIGHTNESS_MODE, currentValue);
+
+ int newValue = ((InlinePayload) mController.getResultPayload()).getValue(mContext);
+
+ assertThat(newValue).isEqualTo(currentValue);
}
}
diff --git a/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java
index 96ce081..c1a3143 100644
--- a/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java
+++ b/tests/robotests/src/com/android/settings/enterprise/AdminGrantedPermissionsPreferenceControllerTestBase.java
@@ -21,6 +21,7 @@
import com.android.settings.R;
import com.android.settings.applications.ApplicationFeatureProvider;
+import com.android.settings.core.PreferenceAvailabilityObserver;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
@@ -32,9 +33,12 @@
import org.mockito.stubbing.Answer;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -48,6 +52,7 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
protected Context mContext;
private FakeFeatureFactory mFeatureFactory;
+ @Mock private PreferenceAvailabilityObserver mObserver;
protected AdminGrantedPermissionsPreferenceControllerBase mController;
@@ -64,6 +69,12 @@
FakeFeatureFactory.setupForTest(mContext);
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
mController = createController(true /* async */);
+ mController.setAvailabilityObserver(mObserver);
+ }
+
+ @Test
+ public void testGetAvailabilityObserver() {
+ assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
}
private void setNumberOfPackagesWithAdminGrantedPermissions(int number, boolean async) {
@@ -85,6 +96,7 @@
setNumberOfPackagesWithAdminGrantedPermissions(0, true /* async */);
mController.updateState(preference);
assertThat(preference.isVisible()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(mKey, false);
setNumberOfPackagesWithAdminGrantedPermissions(20, true /* async */);
when(mContext.getResources().getQuantityString(
@@ -93,27 +105,33 @@
mController.updateState(preference);
assertThat(preference.getSummary()).isEqualTo("minimum 20 apps");
assertThat(preference.isVisible()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(mKey, true);
}
@Test
public void testIsAvailableSync() {
final AdminGrantedPermissionsPreferenceControllerBase controller
= createController(false /* async */);
+ controller.setAvailabilityObserver(mObserver);
setNumberOfPackagesWithAdminGrantedPermissions(0, false /* async */);
assertThat(controller.isAvailable()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(mKey, false);
setNumberOfPackagesWithAdminGrantedPermissions(20, false /* async */);
assertThat(controller.isAvailable()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(mKey, true);
}
@Test
public void testIsAvailableAsync() {
setNumberOfPackagesWithAdminGrantedPermissions(0, true /* async */);
assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver, never()).onPreferenceAvailabilityUpdated(eq(mKey), anyBoolean());
setNumberOfPackagesWithAdminGrantedPermissions(20, true /* async */);
assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver, never()).onPreferenceAvailabilityUpdated(eq(mKey), anyBoolean());
}
@Test
diff --git a/tests/robotests/src/com/android/settings/enterprise/AlwaysOnVpnCurrentUserPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/AlwaysOnVpnCurrentUserPreferenceControllerTest.java
index 9d1bd58..51c8a7b 100644
--- a/tests/robotests/src/com/android/settings/enterprise/AlwaysOnVpnCurrentUserPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/AlwaysOnVpnCurrentUserPreferenceControllerTest.java
@@ -22,6 +22,7 @@
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
@@ -33,6 +34,7 @@
import org.robolectric.annotation.Config;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -42,12 +44,14 @@
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public final class AlwaysOnVpnCurrentUserPreferenceControllerTest {
- private final String VPN_SET_DEVICE = "VPN set";
- private final String VPN_SET_PERSONAL = "VPN set in personal profile";
+ private static final String VPN_SET_DEVICE = "VPN set";
+ private static final String VPN_SET_PERSONAL = "VPN set in personal profile";
+ private static final String KEY_ALWAYS_ON_VPN_PRIMARY_USER = "always_on_vpn_primary_user";
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
+ @Mock private PreferenceAvailabilityObserver mObserver;
private AlwaysOnVpnCurrentUserPreferenceController mController;
@@ -62,6 +66,12 @@
.thenReturn(VPN_SET_DEVICE);
when(mContext.getString(R.string.enterprise_privacy_always_on_vpn_personal))
.thenReturn(VPN_SET_PERSONAL);
+ mController.setAvailabilityObserver(mObserver);
+ }
+
+ @Test
+ public void testGetAvailabilityObserver() {
+ assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
}
@Test
@@ -85,10 +95,12 @@
when(mFeatureFactory.enterprisePrivacyFeatureProvider.isAlwaysOnVpnSetInCurrentUser())
.thenReturn(false);
assertThat(mController.isAvailable()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_ALWAYS_ON_VPN_PRIMARY_USER, false);
when(mFeatureFactory.enterprisePrivacyFeatureProvider.isAlwaysOnVpnSetInCurrentUser())
.thenReturn(true);
assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_ALWAYS_ON_VPN_PRIMARY_USER, true);
}
@Test
@@ -99,6 +111,6 @@
@Test
public void testGetPreferenceKey() {
- assertThat(mController.getPreferenceKey()).isEqualTo("always_on_vpn_primary_user");
+ assertThat(mController.getPreferenceKey()).isEqualTo(KEY_ALWAYS_ON_VPN_PRIMARY_USER);
}
}
diff --git a/tests/robotests/src/com/android/settings/enterprise/AlwaysOnVpnManagedProfilePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/AlwaysOnVpnManagedProfilePreferenceControllerTest.java
index 8ac10d1..4562711 100644
--- a/tests/robotests/src/com/android/settings/enterprise/AlwaysOnVpnManagedProfilePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/AlwaysOnVpnManagedProfilePreferenceControllerTest.java
@@ -21,6 +21,7 @@
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
@@ -32,6 +33,7 @@
import org.robolectric.annotation.Config;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -41,9 +43,12 @@
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public final class AlwaysOnVpnManagedProfilePreferenceControllerTest {
+ private static final String KEY_ALWAYS_ON_VPN_MANAGED_PROFILE = "always_on_vpn_managed_profile";
+
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
+ @Mock private PreferenceAvailabilityObserver mObserver;
private AlwaysOnVpnManagedProfilePreferenceController mController;
@@ -54,6 +59,12 @@
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
mController = new AlwaysOnVpnManagedProfilePreferenceController(mContext,
null /* lifecycle */);
+ mController.setAvailabilityObserver(mObserver);
+ }
+
+ @Test
+ public void testGetAvailabilityObserver() {
+ assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
}
@Test
@@ -61,10 +72,12 @@
when(mFeatureFactory.enterprisePrivacyFeatureProvider.isAlwaysOnVpnSetInManagedProfile())
.thenReturn(false);
assertThat(mController.isAvailable()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_ALWAYS_ON_VPN_MANAGED_PROFILE, false);
when(mFeatureFactory.enterprisePrivacyFeatureProvider.isAlwaysOnVpnSetInManagedProfile())
.thenReturn(true);
assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_ALWAYS_ON_VPN_MANAGED_PROFILE, true);
}
@Test
@@ -75,6 +88,6 @@
@Test
public void testGetPreferenceKey() {
- assertThat(mController.getPreferenceKey()).isEqualTo("always_on_vpn_managed_profile");
+ assertThat(mController.getPreferenceKey()).isEqualTo(KEY_ALWAYS_ON_VPN_MANAGED_PROFILE);
}
}
diff --git a/tests/robotests/src/com/android/settings/enterprise/CaCertsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/CaCertsPreferenceControllerTest.java
index fef2e0f..2c0e320 100644
--- a/tests/robotests/src/com/android/settings/enterprise/CaCertsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/CaCertsPreferenceControllerTest.java
@@ -23,6 +23,7 @@
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
@@ -34,6 +35,7 @@
import org.robolectric.annotation.Config;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -43,9 +45,12 @@
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public final class CaCertsPreferenceControllerTest {
+ private static final String KEY_CA_CERTS = "ca_certs";
+
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
+ @Mock private PreferenceAvailabilityObserver mObserver;
private CaCertsPreferenceController mController;
@@ -55,6 +60,12 @@
FakeFeatureFactory.setupForTest(mContext);
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
mController = new CaCertsPreferenceController(mContext, null /* lifecycle */);
+ mController.setAvailabilityObserver(mObserver);
+ }
+
+ @Test
+ public void testGetAvailabilityObserver() {
+ assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
}
@Test
@@ -74,10 +85,12 @@
when(mFeatureFactory.enterprisePrivacyFeatureProvider
.getNumberOfOwnerInstalledCaCertsForCurrentUserAndManagedProfile()).thenReturn(0);
assertThat(mController.isAvailable()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_CA_CERTS, false);
when(mFeatureFactory.enterprisePrivacyFeatureProvider
.getNumberOfOwnerInstalledCaCertsForCurrentUserAndManagedProfile()).thenReturn(10);
assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_CA_CERTS, true);
}
@Test
@@ -88,6 +101,6 @@
@Test
public void testGetPreferenceKey() {
- assertThat(mController.getPreferenceKey()).isEqualTo("ca_certs");
+ assertThat(mController.getPreferenceKey()).isEqualTo(KEY_CA_CERTS);
}
}
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java
index 4255d96..cf54bb0 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java
@@ -24,6 +24,7 @@
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.applications.ApplicationFeatureProvider;
+import com.android.settings.core.PreferenceAvailabilityObserver;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
@@ -37,9 +38,12 @@
import org.robolectric.annotation.Config;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyObject;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -49,9 +53,13 @@
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public final class EnterpriseInstalledPackagesPreferenceControllerTest {
+ private static final String KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES
+ = "number_enterprise_installed_packages";
+
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
+ @Mock private PreferenceAvailabilityObserver mObserver;
private EnterpriseInstalledPackagesPreferenceController mController;
@@ -62,6 +70,12 @@
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
mController = new EnterpriseInstalledPackagesPreferenceController(mContext,
null /* lifecycle */, true /* async */);
+ mController.setAvailabilityObserver(mObserver);
+ }
+
+ @Test
+ public void testGetAvailabilityObserver() {
+ assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
}
private void setNumberOfEnterpriseInstalledPackages(int number, boolean async) {
@@ -82,6 +96,8 @@
setNumberOfEnterpriseInstalledPackages(0, true /* async */);
mController.updateState(preference);
assertThat(preference.isVisible()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES,
+ false);
setNumberOfEnterpriseInstalledPackages(20, true /* async */);
when(mContext.getResources().getQuantityString(
@@ -90,6 +106,8 @@
mController.updateState(preference);
assertThat(preference.getSummary()).isEqualTo("minimum 20 apps");
assertThat(preference.isVisible()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES,
+ true);
}
@Test
@@ -97,21 +115,30 @@
final EnterpriseInstalledPackagesPreferenceController controller
= new EnterpriseInstalledPackagesPreferenceController(mContext,
null /* lifecycle */, false /* async */);
+ controller.setAvailabilityObserver(mObserver);
setNumberOfEnterpriseInstalledPackages(0, false /* async */);
assertThat(controller.isAvailable()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(
+ KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES, false);
setNumberOfEnterpriseInstalledPackages(20, false /* async */);
assertThat(controller.isAvailable()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(
+ KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES, true);
}
@Test
public void testIsAvailableAsync() {
setNumberOfEnterpriseInstalledPackages(0, true /* async */);
assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+ eq(KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES), anyBoolean());
setNumberOfEnterpriseInstalledPackages(20, true /* async */);
assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+ eq(KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES), anyBoolean());
}
@Test
@@ -123,6 +150,6 @@
@Test
public void testGetPreferenceKey() {
assertThat(mController.getPreferenceKey())
- .isEqualTo("number_enterprise_installed_packages");
+ .isEqualTo(KEY_NUMBER_ENTERPRISE_INSTALLED_PACKAGES);
}
}
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyPreferenceControllerTest.java
index fe48347..101a45a 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyPreferenceControllerTest.java
@@ -23,6 +23,7 @@
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
@@ -34,6 +35,7 @@
import org.robolectric.annotation.Config;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -43,13 +45,15 @@
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public final class EnterprisePrivacyPreferenceControllerTest {
- private final String MANAGED_GENERIC = "managed by organization";
- private final String MANAGED_WITH_NAME = "managed by Foo, Inc.";
- private final String MANAGING_ORGANIZATION = "Foo, Inc.";
+ private static final String MANAGED_GENERIC = "managed by organization";
+ private static final String MANAGED_WITH_NAME = "managed by Foo, Inc.";
+ private static final String MANAGING_ORGANIZATION = "Foo, Inc.";
+ private static final String KEY_ENTERPRISE_PRIVACY = "enterprise_privacy";
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
+ @Mock private PreferenceAvailabilityObserver mObserver;
private EnterprisePrivacyPreferenceController mController;
@@ -59,6 +63,12 @@
FakeFeatureFactory.setupForTest(mContext);
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
mController = new EnterprisePrivacyPreferenceController(mContext, null /* lifecycle */);
+ mController.setAvailabilityObserver(mObserver);
+ }
+
+ @Test
+ public void testGetAvailabilityObserver() {
+ assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
}
@Test
@@ -85,10 +95,11 @@
public void testIsAvailable() {
when(mFeatureFactory.enterprisePrivacyFeatureProvider.hasDeviceOwner()).thenReturn(false);
assertThat(mController.isAvailable()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_ENTERPRISE_PRIVACY, false);
when(mFeatureFactory.enterprisePrivacyFeatureProvider.hasDeviceOwner()).thenReturn(true);
assertThat(mController.isAvailable()).isTrue();
-
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_ENTERPRISE_PRIVACY, true);
}
@Test
@@ -99,6 +110,6 @@
@Test
public void testGetPreferenceKey() {
- assertThat(mController.getPreferenceKey()).isEqualTo("enterprise_privacy");
+ assertThat(mController.getPreferenceKey()).isEqualTo(KEY_ENTERPRISE_PRIVACY);
}
}
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java
index d41be75..16fa5ba 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java
@@ -16,12 +16,16 @@
package com.android.settings.enterprise;
+import android.app.Application;
import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.core.DynamicAvailabilityPreferenceController;
import com.android.settings.core.PreferenceController;
import com.android.settings.testutils.FakeFeatureFactory;
@@ -31,10 +35,14 @@
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
+import org.xmlpull.v1.XmlPullParser;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
@@ -46,6 +54,9 @@
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public final class EnterprisePrivacySettingsTest {
+ private final static String RESOURCES_NAMESPACE = "http://schemas.android.com/apk/res/android";
+ private final static String ATTR_KEY = "key";
+
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
@@ -101,23 +112,24 @@
}
@Test
- public void getPreferenceControllers() {
+ public void getPreferenceControllers() throws Exception {
final List<PreferenceController> controllers = mSettings.getPreferenceControllers(
ShadowApplication.getInstance().getApplicationContext());
verifyPreferenceControllers(controllers);
}
@Test
- public void getSearchIndexProviderPreferenceControllers() {
+ public void getSearchIndexProviderPreferenceControllers() throws Exception {
final List<PreferenceController> controllers
= EnterprisePrivacySettings.SEARCH_INDEX_DATA_PROVIDER.getPreferenceControllers(
ShadowApplication.getInstance().getApplicationContext());
verifyPreferenceControllers(controllers);
}
- private void verifyPreferenceControllers(List<PreferenceController> controllers) {
+ private void verifyPreferenceControllers(List<PreferenceController> controllers)
+ throws Exception {
assertThat(controllers).isNotNull();
- assertThat(controllers.size()).isEqualTo(15);
+ assertThat(controllers.size()).isEqualTo(16);
int position = 0;
assertThat(controllers.get(position++)).isInstanceOf(NetworkLogsPreferenceController.class);
assertThat(controllers.get(position++)).isInstanceOf(BugReportsPreferenceController.class);
@@ -137,14 +149,71 @@
AlwaysOnVpnCurrentUserPreferenceController.class);
assertThat(controllers.get(position++)).isInstanceOf(
AlwaysOnVpnManagedProfilePreferenceController.class);
+ assertThat(controllers.get(position++)).isInstanceOf(ImePreferenceController.class);
assertThat(controllers.get(position++)).isInstanceOf(
GlobalHttpProxyPreferenceController.class);
assertThat(controllers.get(position++)).isInstanceOf(
CaCertsPreferenceController.class);
+ final PreferenceController exposureChangesCategoryController = controllers.get(position);
+ final int exposureChangesCategoryControllerIndex = position;
+ assertThat(controllers.get(position++)).isInstanceOf(
+ ExposureChangesCategoryPreferenceController.class);
assertThat(controllers.get(position++)).isInstanceOf(
FailedPasswordWipeCurrentUserPreferenceController.class);
assertThat(controllers.get(position++)).isInstanceOf(
FailedPasswordWipeManagedProfilePreferenceController.class);
- assertThat(controllers.get(position++)).isInstanceOf(ImePreferenceController.class);
+
+ // The "Changes made by your organization's admin" category is hidden when all Preferences
+ // inside it become unavailable. To do this correctly, the category's controller must:
+ // a) Observe the availability of all Preferences in the category and
+ // b) Be listed after those Preferences' controllers, so that availability is updated in
+ // the correct order
+
+ // Find all Preferences in the category.
+ final XmlResourceParser parser = RuntimeEnvironment.application.getResources().getXml(
+ R.xml.enterprise_privacy_settings);
+ boolean done = false;
+ int type;
+ final Set<String> expectedObserved = new HashSet<>();
+ while (!done && (type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG || !"exposure_changes_category".equals(
+ parser.getAttributeValue(RESOURCES_NAMESPACE, ATTR_KEY))) {
+ continue;
+ }
+ int depth = 1;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG) {
+ final String key = parser.getAttributeValue(RESOURCES_NAMESPACE, ATTR_KEY);
+ if (key != null) {
+ expectedObserved.add(key);
+ }
+ depth++;
+ } else if (type == XmlPullParser.END_TAG) {
+ depth--;
+ if (depth == 0) {
+ done = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // Find all Preferences the category's controller is observing.
+ final Set<String> actualObserved = new HashSet<>();
+ int maxObservedIndex = -1;
+ for (int i = 0; i < controllers.size(); i++) {
+ final PreferenceController controller = controllers.get(i);
+ if (controller instanceof DynamicAvailabilityPreferenceController &&
+ ((DynamicAvailabilityPreferenceController) controller).getAvailabilityObserver()
+ == exposureChangesCategoryController) {
+ actualObserved.add(controller.getPreferenceKey());
+ maxObservedIndex = i;
+ }
+ }
+
+ // Verify that the category's controller is observing the Preferences inside it.
+ assertThat(actualObserved).isEqualTo(expectedObserved);
+ // Verify that the category's controller is listed after the Preferences' controllers.
+ assertThat(maxObservedIndex).isLessThan(exposureChangesCategoryControllerIndex);
}
}
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceControllerTest.java
index 862116f..793ae3c 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceControllerTest.java
@@ -37,6 +37,7 @@
import com.android.settings.TestConfig;
import com.android.settings.applications.EnterpriseDefaultApps;
import com.android.settings.applications.UserAppInfo;
+import com.android.settings.core.PreferenceAvailabilityObserver;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
@@ -51,6 +52,7 @@
import java.util.ArrayList;
import java.util.List;
+import static org.mockito.Mockito.verify;
/**
* Tests for {@link EnterpriseSetDefaultAppsPreferenceController}.
*/
@@ -58,11 +60,14 @@
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public final class EnterpriseSetDefaultAppsPreferenceControllerTest {
+ private static final String KEY_DEFAULT_APPS = "number_enterprise_set_default_apps";
+
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private UserManager mUm;
private FakeFeatureFactory mFeatureFactory;
+ @Mock private PreferenceAvailabilityObserver mObserver;
private EnterpriseSetDefaultAppsPreferenceController mController;
@@ -73,6 +78,12 @@
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
mController = new EnterpriseSetDefaultAppsPreferenceController(mContext,
null /* lifecycle */);
+ mController.setAvailabilityObserver(mObserver);
+ }
+
+ @Test
+ public void testGetAvailabilityObserver() {
+ assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
}
private void setEnterpriseSetDefaultApps(Intent[] intents, int number) {
@@ -119,10 +130,12 @@
when(mFeatureFactory.applicationFeatureProvider.findPersistentPreferredActivities(anyInt(),
any(Intent[].class))).thenReturn(new ArrayList<>());
assertThat(mController.isAvailable()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_DEFAULT_APPS, false);
setEnterpriseSetDefaultApps(EnterpriseDefaultApps.BROWSER.getIntents(), 1);
configureUsers(1);
assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_DEFAULT_APPS, true);
}
@Test
@@ -133,8 +146,7 @@
@Test
public void testGetPreferenceKey() {
- assertThat(mController.getPreferenceKey())
- .isEqualTo("number_enterprise_set_default_apps");
+ assertThat(mController.getPreferenceKey()).isEqualTo(KEY_DEFAULT_APPS);
}
private ArgumentMatcher<Intent[]> matchesIntents(Intent[] intents) {
diff --git a/tests/robotests/src/com/android/settings/enterprise/ExposureChangesCategoryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/ExposureChangesCategoryPreferenceControllerTest.java
new file mode 100644
index 0000000..1c92ea5
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/enterprise/ExposureChangesCategoryPreferenceControllerTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 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.enterprise;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.core.DynamicAvailabilityPreferenceController;
+import com.android.settings.core.PreferenceAvailabilityObserver;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link ExposureChangesCategoryPreferenceController}.
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public final class ExposureChangesCategoryPreferenceControllerTest {
+
+ private static final String KEY_1 = "key_1";
+ private static final String KEY_2 = "key_2";
+ private static final String KEY_EXPOSURE_CHANGES_CATEGORY = "exposure_changes_category";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+ private List<DynamicAvailabilityPreferenceController> mControllers;
+ private ExposureChangesCategoryPreferenceController mController;
+ @Mock private PreferenceAvailabilityObserver mObserver;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mControllers = Arrays.asList(mock(DynamicAvailabilityPreferenceController.class),
+ mock(DynamicAvailabilityPreferenceController.class));
+ mController = new ExposureChangesCategoryPreferenceController(mContext,
+ null /* lifecycle */, mControllers, true /* controllingUi */);
+ mController.setAvailabilityObserver(mObserver);
+ }
+
+ @Test
+ public void testInitialization() {
+ verify(mControllers.get(0)).setAvailabilityObserver(mController);
+ verify(mControllers.get(1)).setAvailabilityObserver(mController);
+ }
+
+ @Test
+ public void testGetAvailabilityObserver() {
+ assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
+ }
+
+ @Test
+ public void testOnPreferenceAvailabilityUpdated() {
+ final Preference preference = new Preference(mContext, null, 0, 0);
+ preference.setVisible(true);
+
+ mController.updateState(preference);
+ assertThat(preference.isVisible()).isFalse();
+
+ mController.onPreferenceAvailabilityUpdated(KEY_1, true);
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, true);
+ assertThat(preference.isVisible()).isTrue();
+ reset(mObserver);
+
+ mController.onPreferenceAvailabilityUpdated(KEY_2, true);
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, true);
+ assertThat(preference.isVisible()).isTrue();
+ reset(mObserver);
+
+ mController.onPreferenceAvailabilityUpdated(KEY_1, false);
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, true);
+ assertThat(preference.isVisible()).isTrue();
+ reset(mObserver);
+
+ mController.onPreferenceAvailabilityUpdated(KEY_2, false);
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, false);
+ assertThat(preference.isVisible()).isFalse();
+ }
+
+ @Test
+ public void testUpdateState() {
+ final Preference preference = new Preference(mContext, null, 0, 0);
+ preference.setVisible(false);
+
+ mController.onPreferenceAvailabilityUpdated(KEY_1, true);
+ mController.updateState(preference);
+ assertThat(preference.isVisible()).isTrue();
+ }
+
+ @Test
+ public void testIsAvailableForUi() {
+ assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+ eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+
+ mController.onPreferenceAvailabilityUpdated(KEY_1, true);
+ reset(mObserver);
+ assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+ eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+
+ mController.onPreferenceAvailabilityUpdated(KEY_1, false);
+ reset(mObserver);
+ assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+ eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+ }
+
+ @Test
+ public void testIsAvailableForSearch() {
+ final ExposureChangesCategoryPreferenceController controller
+ = new ExposureChangesCategoryPreferenceController(mContext, null /* lifecycle */,
+ mControllers, false /* controllingUi */);
+ controller.setAvailabilityObserver(mObserver);
+ verify(mControllers.get(0)).setAvailabilityObserver(controller);
+ verify(mControllers.get(1)).setAvailabilityObserver(controller);
+
+ assertThat(controller.isAvailable()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, false);
+ reset(mObserver);
+
+ controller.onPreferenceAvailabilityUpdated(KEY_1, true);
+ verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+ eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+ assertThat(controller.isAvailable()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, true);
+ reset(mObserver);
+
+ controller.onPreferenceAvailabilityUpdated(KEY_2, true);
+ verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+ eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+ assertThat(controller.isAvailable()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, true);
+ reset(mObserver);
+
+ controller.onPreferenceAvailabilityUpdated(KEY_1, false);
+ verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+ eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+ assertThat(controller.isAvailable()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, true);
+ reset(mObserver);
+
+ controller.onPreferenceAvailabilityUpdated(KEY_2, false);
+ verify(mObserver, never()).onPreferenceAvailabilityUpdated(
+ eq(KEY_EXPOSURE_CHANGES_CATEGORY), anyBoolean());
+ assertThat(controller.isAvailable()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_EXPOSURE_CHANGES_CATEGORY, false);
+ }
+
+ @Test
+ public void testHandlePreferenceTreeClick() {
+ assertThat(mController.handlePreferenceTreeClick(new Preference(mContext, null, 0, 0)))
+ .isFalse();
+ }
+
+ @Test
+ public void testGetPreferenceKey() {
+ assertThat(mController.getPreferenceKey()).isEqualTo(KEY_EXPOSURE_CHANGES_CATEGORY);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerTestBase.java b/tests/robotests/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerTestBase.java
index c14b71e..cbc220f 100644
--- a/tests/robotests/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerTestBase.java
+++ b/tests/robotests/src/com/android/settings/enterprise/FailedPasswordWipePreferenceControllerTestBase.java
@@ -21,6 +21,7 @@
import android.support.v7.preference.Preference;
import com.android.settings.R;
+import com.android.settings.core.PreferenceAvailabilityObserver;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
@@ -30,6 +31,7 @@
import org.mockito.MockitoAnnotations;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -42,6 +44,7 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
protected Context mContext;
protected FakeFeatureFactory mFeatureFactory;
+ @Mock private PreferenceAvailabilityObserver mObserver;
protected FailedPasswordWipePreferenceControllerBase mController;
@@ -56,6 +59,12 @@
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
}
+ @Test
+ public void testGetAvailabilityObserver() {
+ mController.setAvailabilityObserver(mObserver);
+ assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
+ }
+
public abstract void setMaximumFailedPasswordsBeforeWipe(int maximum);
@Test
@@ -72,11 +81,15 @@
@Test
public void testIsAvailable() {
+ mController.setAvailabilityObserver(mObserver);
+
setMaximumFailedPasswordsBeforeWipe(0);
assertThat(mController.isAvailable()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(mKey, false);
setMaximumFailedPasswordsBeforeWipe(10);
assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(mKey, true);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/enterprise/GlobalHttpProxyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/GlobalHttpProxyPreferenceControllerTest.java
index a0bc9ee..016d970 100644
--- a/tests/robotests/src/com/android/settings/enterprise/GlobalHttpProxyPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/GlobalHttpProxyPreferenceControllerTest.java
@@ -21,6 +21,7 @@
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
@@ -32,6 +33,7 @@
import org.robolectric.annotation.Config;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -40,9 +42,13 @@
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public final class GlobalHttpProxyPreferenceControllerTest {
+
+ private static final String KEY_GLOBAL_HTTP_PROXY = "global_http_proxy";
+
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
+ @Mock private PreferenceAvailabilityObserver mObserver;
private GlobalHttpProxyPreferenceController mController;
@@ -52,6 +58,12 @@
FakeFeatureFactory.setupForTest(mContext);
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
mController = new GlobalHttpProxyPreferenceController(mContext, null /* lifecycle */);
+ mController.setAvailabilityObserver(mObserver);
+ }
+
+ @Test
+ public void testGetAvailabilityObserver() {
+ assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
}
@Test
@@ -59,10 +71,12 @@
when(mFeatureFactory.enterprisePrivacyFeatureProvider.isGlobalHttpProxySet())
.thenReturn(false);
assertThat(mController.isAvailable()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_GLOBAL_HTTP_PROXY, false);
when(mFeatureFactory.enterprisePrivacyFeatureProvider.isGlobalHttpProxySet())
.thenReturn(true);
assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_GLOBAL_HTTP_PROXY, true);
}
@Test
@@ -73,6 +87,6 @@
@Test
public void testGetPreferenceKey() {
- assertThat(mController.getPreferenceKey()).isEqualTo("global_http_proxy");
+ assertThat(mController.getPreferenceKey()).isEqualTo(KEY_GLOBAL_HTTP_PROXY);
}
}
diff --git a/tests/robotests/src/com/android/settings/enterprise/ImePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/ImePreferenceControllerTest.java
index 05d0535..3304b44 100644
--- a/tests/robotests/src/com/android/settings/enterprise/ImePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/ImePreferenceControllerTest.java
@@ -23,6 +23,7 @@
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceAvailabilityObserver;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
@@ -34,6 +35,7 @@
import org.robolectric.annotation.Config;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -43,12 +45,14 @@
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public final class ImePreferenceControllerTest {
- private final String DEFAULT_IME_LABEL = "Test IME";
- private final String DEFAULT_IME_TEXT = "Set to Test IME";
+ private static final String DEFAULT_IME_LABEL = "Test IME";
+ private static final String DEFAULT_IME_TEXT = "Set to Test IME";
+ private static final String KEY_INPUT_METHOD = "input_method";
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
private FakeFeatureFactory mFeatureFactory;
+ @Mock private PreferenceAvailabilityObserver mObserver;
private ImePreferenceController mController;
@@ -60,6 +64,12 @@
mController = new ImePreferenceController(mContext, null /* lifecycle */);
when(mContext.getResources().getString(R.string.enterprise_privacy_input_method_name,
DEFAULT_IME_LABEL)).thenReturn(DEFAULT_IME_TEXT);
+ mController.setAvailabilityObserver(mObserver);
+ }
+
+ @Test
+ public void testGetAvailabilityObserver() {
+ assertThat(mController.getAvailabilityObserver()).isEqualTo(mObserver);
}
@Test
@@ -77,10 +87,12 @@
when(mFeatureFactory.enterprisePrivacyFeatureProvider.getImeLabelIfOwnerSet())
.thenReturn(null);
assertThat(mController.isAvailable()).isFalse();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_INPUT_METHOD, false);
when(mFeatureFactory.enterprisePrivacyFeatureProvider.getImeLabelIfOwnerSet())
.thenReturn(DEFAULT_IME_LABEL);
assertThat(mController.isAvailable()).isTrue();
+ verify(mObserver).onPreferenceAvailabilityUpdated(KEY_INPUT_METHOD, true);
}
@Test
@@ -91,6 +103,6 @@
@Test
public void testGetPreferenceKey() {
- assertThat(mController.getPreferenceKey()).isEqualTo("input_method");
+ assertThat(mController.getPreferenceKey()).isEqualTo(KEY_INPUT_METHOD);
}
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java
index cb91d41..3982f1b 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java
@@ -37,6 +37,7 @@
import android.os.BatteryStats;
import android.os.Bundle;
import android.os.UserHandle;
+import android.support.v7.preference.Preference;
import android.support.v7.widget.RecyclerView;
import com.android.internal.os.BatterySipper;
@@ -45,6 +46,7 @@
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.applications.LayoutPreference;
+import com.android.settings.R;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
import com.android.settings.widget.EntityHeaderController;
@@ -73,9 +75,10 @@
private static final String APP_LABEL = "app label";
private static final String SUMMARY = "summary";
private static final String[] PACKAGE_NAME = {"com.android.app"};
- private static final String USAGE_PERCENT = "16";
+ private static final String USAGE_PERCENT = "16%";
private static final int ICON_ID = 123;
private static final int UID = 1;
+ private static final int POWER_MAH = 150;
private static final long BACKGROUND_TIME_US = 100 * 1000;
private static final long FOREGROUND_TIME_US = 200 * 1000;
private static final long BACKGROUND_TIME_MS = 100;
@@ -84,8 +87,6 @@
private static final long PHONE_BACKGROUND_TIME_MS = 0;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
- private Context mContext;
- @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Activity mActivity;
@Mock
private EntityHeaderController mEntityHeaderController;
@@ -107,6 +108,10 @@
private BatteryStats.Uid mUid;
@Mock
private PackageManager mPackageManager;
+ private Context mContext;
+ private Preference mForegroundPreference;
+ private Preference mBackgroundPreference;
+ private Preference mPowerUsagePreference;
private AdvancedPowerUsageDetail mFragment;
private FakeFeatureFactory mFeatureFactory;
private SettingsActivity mTestActivity;
@@ -114,6 +119,8 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+
+ mContext = spy(RuntimeEnvironment.application);
FakeFeatureFactory.setupForTest(mContext);
mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
@@ -170,6 +177,13 @@
doAnswer(callable).when(mTestActivity).startPreferencePanelAsUser(
nullable(Fragment.class), nullable(String.class), captor.capture(), anyInt(),
nullable(CharSequence.class), nullable(UserHandle.class));
+
+ mForegroundPreference = new Preference(mContext);
+ mBackgroundPreference = new Preference(mContext);
+ mPowerUsagePreference = new Preference(mContext);
+ mFragment.mForegroundPreference = mForegroundPreference;
+ mFragment.mBackgroundPreference = mBackgroundPreference;
+ mFragment.mPowerUsagePreference = mPowerUsagePreference;
}
@After
@@ -305,4 +319,28 @@
"0%");
}
+ @Test
+ public void testInitPreference_hasCorrectSummary() {
+ Bundle bundle = new Bundle(4);
+ bundle.putLong(AdvancedPowerUsageDetail.EXTRA_BACKGROUND_TIME, BACKGROUND_TIME_MS);
+ bundle.putLong(AdvancedPowerUsageDetail.EXTRA_FOREGROUND_TIME, FOREGROUND_TIME_MS);
+ bundle.putString(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_PERCENT, USAGE_PERCENT);
+ bundle.putInt(AdvancedPowerUsageDetail.EXTRA_POWER_USAGE_AMOUNT, POWER_MAH);
+ doReturn(bundle).when(mFragment).getArguments();
+
+ doReturn(mContext.getText(R.string.battery_used_for)).when(mFragment).getText(
+ R.string.battery_used_for);
+ doReturn(mContext.getText(R.string.battery_active_for)).when(mFragment).getText(
+ R.string.battery_active_for);
+ doReturn(mContext.getString(R.string.battery_detail_power_percentage, USAGE_PERCENT,
+ POWER_MAH)).when(mFragment)
+ .getString(R.string.battery_detail_power_percentage, USAGE_PERCENT, POWER_MAH);
+
+ mFragment.initPreference();
+
+ assertThat(mForegroundPreference.getSummary().toString()).isEqualTo("Used for 0m");
+ assertThat(mBackgroundPreference.getSummary().toString()).isEqualTo("Active for 0m");
+ assertThat(mPowerUsagePreference.getSummary()).isEqualTo("16% of total app usage (150mAh)");
+ }
+
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
index 04bb2cf..c387278 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
@@ -27,7 +27,6 @@
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
-import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import android.widget.TextView;
@@ -37,7 +36,6 @@
import com.android.settings.applications.LayoutPreference;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
-import com.android.settingslib.BatteryInfo;
import org.junit.Before;
import org.junit.Test;
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoryPreferenceTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoryPreferenceTest.java
index bd6ae6a..c9be151 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoryPreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHistoryPreferenceTest.java
@@ -30,7 +30,6 @@
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
-import com.android.settingslib.BatteryInfo;
import com.android.settingslib.graph.UsageView;
import org.junit.Before;
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java
new file mode 100644
index 0000000..0d670b1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.SystemClock;
+
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.R;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.AdditionalMatchers.*;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BatteryInfoTest {
+ private static final String STATUS_FULL = "Full";
+ private static final String STATUS_CHARGING_NO_TIME = "Charging";
+ private static final String STATUS_CHARGING_TIME = "Charging - 2h left";
+ private static final int PLUGGED_IN = 1;
+ private static final long REMAINING_TIME_NULL = -1;
+ private static final long REMAINING_TIME = 2;
+ public static final String ENHANCED_STRING_SUFFIX = "left based on your usage";
+ private Intent mDisChargingBatteryBroadcast;
+ private Intent mChargingBatteryBroadcast;
+ private Context mRealContext;
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private BatteryStats mBatteryStats;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Resources mResources;
+
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ FakeFeatureFactory.setupForTest(mContext);
+
+ mDisChargingBatteryBroadcast = new Intent();
+ mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 0);
+ mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_SCALE, 100);
+ mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_STATUS,
+ BatteryManager.BATTERY_STATUS_FULL);
+
+ mChargingBatteryBroadcast = new Intent();
+ mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED,
+ BatteryManager.BATTERY_PLUGGED_AC);
+ mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 50);
+ mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_SCALE, 100);
+ mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_STATUS,
+ BatteryManager.BATTERY_STATUS_UNKNOWN);
+
+ when(mContext.getResources().getString(R.string.battery_info_status_full))
+ .thenReturn(STATUS_FULL);
+ when(mContext.getResources().getString(eq(R.string.power_charging), any(),
+ any())).thenReturn(STATUS_CHARGING_NO_TIME);
+ when(mContext.getResources().getString(eq(R.string.power_charging_duration), any(),
+ any())).thenReturn(STATUS_CHARGING_TIME);
+ mRealContext = RuntimeEnvironment.application;
+ }
+
+ @Test
+ public void testGetBatteryInfo_hasStatusLabel() {
+ doReturn(REMAINING_TIME_NULL).when(mBatteryStats).computeBatteryTimeRemaining(anyLong());
+ BatteryInfo info = BatteryInfo.getBatteryInfoOld(mContext,
+ mDisChargingBatteryBroadcast, mBatteryStats, SystemClock.elapsedRealtime() * 1000,
+ true /* shortString */);
+
+ assertThat(info.statusLabel).isEqualTo(STATUS_FULL);
+ }
+
+ @Test
+ public void testGetBatteryInfo_doNotShowChargingMethod_hasRemainingTime() {
+ doReturn(REMAINING_TIME).when(mBatteryStats).computeChargeTimeRemaining(anyLong());
+ BatteryInfo info = BatteryInfo.getBatteryInfoOld(mContext, mChargingBatteryBroadcast,
+ mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */);
+
+ assertThat(info.chargeLabelString).isEqualTo(STATUS_CHARGING_TIME);
+ }
+
+ @Test
+ public void testGetBatteryInfo_doNotShowChargingMethod_noRemainingTime() {
+ doReturn(REMAINING_TIME_NULL).when(mBatteryStats).computeChargeTimeRemaining(anyLong());
+ BatteryInfo info = BatteryInfo.getBatteryInfoOld(mContext, mChargingBatteryBroadcast,
+ mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */);
+
+ assertThat(info.chargeLabelString).isEqualTo(STATUS_CHARGING_NO_TIME);
+ }
+
+ @Test
+ public void testGetBatteryInfo_pluggedIn_dischargingFalse() {
+ BatteryInfo info = BatteryInfo.getBatteryInfoOld(mContext, mChargingBatteryBroadcast,
+ mBatteryStats, SystemClock.elapsedRealtime() * 1000, true /* shortString */);
+
+ assertThat(info.discharging).isEqualTo(false);
+ }
+
+ @Test
+ public void testGetBatteryInfo_basedOnUsageTrue_usesUsageString() {
+ doReturn(mResources).when(mContext).getResources();
+ when(mResources.getString(eq(R.string.battery_info_status_full))).thenReturn("");
+ when(mResources.getString(eq(R.string.power_remaining_duration_only_enhanced), any()))
+ .thenReturn(ENHANCED_STRING_SUFFIX);
+ when(mResources.getString(eq(R.string.power_remaining_duration_only_short_enhanced), any()))
+ .thenReturn(ENHANCED_STRING_SUFFIX);
+ BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
+ mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */,
+ 1000, true /* basedOnUsage */);
+ BatteryInfo info2 = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
+ mBatteryStats, SystemClock.elapsedRealtime() * 1000, true /* shortString */,
+ 1000, true /* basedOnUsage */);
+
+ assertThat(info.remainingLabel).contains(ENHANCED_STRING_SUFFIX);
+ assertThat(info2.remainingLabel).contains(ENHANCED_STRING_SUFFIX);
+ }
+
+ @Test
+ public void testGetBatteryInfo_basedOnUsageFalse_usesDefaultString() {
+ doReturn(mResources).when(mContext).getResources();
+ when(mResources.getString(eq(R.string.battery_info_status_full))).thenReturn("");
+ when(mResources.getString(not(eq(R.string.power_remaining_duration_only_enhanced)), any()))
+ .thenReturn(ENHANCED_STRING_SUFFIX);
+ when(mResources.getString(not(eq(R.string.power_remaining_duration_only_short_enhanced)),
+ any())).thenReturn("");
+ BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
+ mBatteryStats, SystemClock.elapsedRealtime() * 1000, false /* shortString */,
+ 1000, false /* basedOnUsage */);
+ BatteryInfo info2 = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
+ mBatteryStats, SystemClock.elapsedRealtime() * 1000, true /* shortString */,
+ 1000, false /* basedOnUsage */);
+
+ assertThat(info.remainingLabel).doesNotContain(ENHANCED_STRING_SUFFIX);
+ assertThat(info2.remainingLabel).doesNotContain(ENHANCED_STRING_SUFFIX);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
index e76e185..156a1ea 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
@@ -47,7 +47,6 @@
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
import com.android.settings.testutils.XmlTestUtils;
-import com.android.settingslib.BatteryInfo;
import org.junit.Before;
import org.junit.Test;
@@ -440,24 +439,6 @@
}
@Test
- public void testBatteryPredictionLoaderCallbacks_DoesNotCrashOnNull() {
- // Sanity test to check for crash
- mFragment.mBatteryPredictionLoaderCallbacks.onLoadFinished(null, null);
- }
-
- @Test
- public void testOnCreate_BatteryPredictionSkippedWhenDisabled() {
- PowerUsageFeatureProvider provider = mFeatureFactory.getPowerUsageFeatureProvider(mContext);
- when(provider.isEnhancedBatteryPredictionEnabled(any())).thenReturn(false);
- mFragment.mPowerFeatureProvider = provider;
- doReturn(mLoaderManager).when(mFragment).getLoaderManager();
- mFragment.initializeBatteryEstimateLoader();
-
- verify(mLoaderManager, never()).initLoader(eq(PowerUsageSummary.BATTERY_ESTIMATE_LOADER),
- eq(Bundle.EMPTY), any());
- }
-
- @Test
public void testInitAnomalyDetectionIfPossible_detectionEnabled_init() {
when(mFeatureFactory.powerUsageFeatureProvider.isAnomalyDetectionEnabled()).thenReturn(
true);
diff --git a/tests/robotests/src/com/android/settings/search/CursorToSearchResultConverterTest.java b/tests/robotests/src/com/android/settings/search/CursorToSearchResultConverterTest.java
index 514d598..eb25141 100644
--- a/tests/robotests/src/com/android/settings/search/CursorToSearchResultConverterTest.java
+++ b/tests/robotests/src/com/android/settings/search/CursorToSearchResultConverterTest.java
@@ -23,7 +23,6 @@
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.drawable.Drawable;
-import android.util.ArrayMap;
import com.android.settings.DisplaySettings;
import com.android.settings.R;
@@ -31,14 +30,9 @@
import com.android.settings.TestConfig;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.gestures.SwipeToNotificationSettings;
-import com.android.settings.search.CursorToSearchResultConverter;
-import com.android.settings.search.DatabaseResultLoader;
-import com.android.settings.search.InlineSwitchPayload;
-import com.android.settings.search.ResultPayload;
+import com.android.settings.search.ResultPayload.Availability;
import com.android.settings.search.ResultPayload.PayloadType;
-import com.android.settings.search.ResultPayloadUtils;
-import com.android.settings.search.SearchResult;
import com.android.settings.wifi.WifiSettings;
import org.junit.Before;
import org.junit.Test;
@@ -221,14 +215,12 @@
final String uri = "test.com";
final int type = ResultPayload.PayloadType.INLINE_SWITCH;
final int source = ResultPayload.SettingsSource.SECURE;
- final ArrayMap<Integer, Boolean> map = new ArrayMap<>();
- map.put(1, true);
- map.put(0, false);
final String intentKey = "key";
final String intentVal = "value";
final Intent intent = new Intent();
intent.putExtra(intentKey, intentVal);
- final InlineSwitchPayload payload = new InlineSwitchPayload(uri, source, map, intent);
+ final InlineSwitchPayload payload = new InlineSwitchPayload(uri, source, 1 /* onValue */,
+ intent, true /* isDeviceSupported */);
cursor.addRow(new Object[]{
KEY.hashCode(), // Doc ID
@@ -251,11 +243,11 @@
for (SearchResult result : results) {
final InlineSwitchPayload newPayload = (InlineSwitchPayload) result.payload;
final Intent rebuiltIntent = newPayload.getIntent();
- assertThat(newPayload.settingsUri).isEqualTo(uri);
- assertThat(newPayload.inlineType).isEqualTo(type);
- assertThat(newPayload.settingSource).isEqualTo(source);
- assertThat(newPayload.valueMap.get(1)).isTrue();
- assertThat(newPayload.valueMap.get(0)).isFalse();
+ assertThat(newPayload.mSettingKey).isEqualTo(uri);
+ assertThat(newPayload.mInlineType).isEqualTo(type);
+ assertThat(newPayload.mSettingSource).isEqualTo(source);
+ assertThat(newPayload.isStandard()).isTrue();
+ assertThat(newPayload.getAvailability()).isEqualTo(Availability.AVAILABLE);
assertThat(rebuiltIntent.getStringExtra(intentKey)).isEqualTo(intentVal);
}
}
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
index 6bcefca..f06f003 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
+++ b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
@@ -1089,7 +1089,6 @@
}
private void insertSpecialCase(String specialCase, boolean enabled, String key) {
-
ContentValues values = new ContentValues();
values.put(IndexDatabaseHelper.IndexColumns.DOCID, specialCase.hashCode());
values.put(IndexDatabaseHelper.IndexColumns.LOCALE, localeStr);
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseRowTest.java b/tests/robotests/src/com/android/settings/search/DatabaseRowTest.java
index db4227b..18f0316 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseRowTest.java
+++ b/tests/robotests/src/com/android/settings/search/DatabaseRowTest.java
@@ -26,17 +26,12 @@
import com.android.settings.search.DatabaseIndexingManager.DatabaseRow;
import com.android.settings.search.DatabaseIndexingManager.DatabaseRow.Builder;
-import com.android.settings.search.InlineSwitchPayload;
-import com.android.settings.search.ResultPayload;
-import com.android.settings.search.ResultPayloadUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
-import java.util.HashMap;
-
import static com.google.common.truth.Truth.assertThat;
@RunWith(SettingsRobolectricTestRunner.class)
@@ -118,15 +113,15 @@
@Test
public void testRowWithInlinePayload_genericPayloadNotAdded() {
final String URI = "test uri";
- final InlineSwitchPayload payload = new InlineSwitchPayload(URI, 0,
- new HashMap<Integer, Boolean>(), null);
+ final InlineSwitchPayload payload = new InlineSwitchPayload(URI, 0 /* mSettingSource */,
+ 1 /* onValue */, null /* intent */, true /* isDeviceSupported */);
mBuilder.setPayload(payload);
final DatabaseRow row = generateRow();
final InlineSwitchPayload unmarshalledPayload = ResultPayloadUtils
.unmarshall(row.payload, InlineSwitchPayload.CREATOR);
assertThat(row.payloadType).isEqualTo(ResultPayload.PayloadType.INLINE_SWITCH);
- assertThat(unmarshalledPayload.settingsUri).isEqualTo(URI);
+ assertThat(unmarshalledPayload.mSettingKey).isEqualTo(URI);
}
@Test
@@ -137,8 +132,8 @@
final Intent intent = new Intent();
intent.setComponent(component);
- final InlineSwitchPayload payload = new InlineSwitchPayload(URI, 0,
- new HashMap<Integer, Boolean>(), intent);
+ final InlineSwitchPayload payload = new InlineSwitchPayload(URI, 0 /* mSettingSource */,
+ 1 /* onValue */, intent, true /* isDeviceSupported */);
mBuilder.setPayload(payload);
final DatabaseRow row = generateRow();
final InlineSwitchPayload unmarshalledPayload = ResultPayloadUtils
diff --git a/tests/robotests/src/com/android/settings/search/InlineSwitchPayloadTest.java b/tests/robotests/src/com/android/settings/search/InlineSwitchPayloadTest.java
index 7167df0..3615a67 100644
--- a/tests/robotests/src/com/android/settings/search/InlineSwitchPayloadTest.java
+++ b/tests/robotests/src/com/android/settings/search/InlineSwitchPayloadTest.java
@@ -17,76 +17,57 @@
package com.android.settings.search;
+import android.content.ContentResolver;
import android.content.Intent;
import android.os.Parcel;
-import android.util.ArrayMap;
+import android.provider.Settings;
import android.content.Context;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.search.ResultPayload.Availability;
+import com.android.settings.search.ResultPayload.SettingsSource;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowApplication;
+import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class InlineSwitchPayloadTest {
- @Test
- public void testGetSwitch_EmptyMap_ExceptionThrown() {
- final String uri = "test.com";
- final int source = ResultPayload.SettingsSource.SECURE;
+ private static final String DUMMY_SETTING = "inline_test";
+ private static final int STANDARD_ON = 1;
+ private static final int FLIPPED_ON = 0;
- final Context context = ShadowApplication.getInstance().getApplicationContext();
- InlineSwitchPayload payload = new InlineSwitchPayload(uri, source, null, null);
- try {
- payload.getSwitchValue(context);
- fail("Should have thrown exception for null map");
- } catch (IllegalStateException e) {
- assertThat(e).isNotNull();
- }
- }
+ private Context mContext;
- @Test
- public void testGetSwitch_BadMap_ExceptionThrown() {
- final String uri = "test.com";
- final int source = ResultPayload.SettingsSource.SECURE;
- final ArrayMap<Integer, Boolean> map = new ArrayMap<>();
-
- final Context context = ShadowApplication.getInstance().getApplicationContext();
- InlineSwitchPayload payload = new InlineSwitchPayload(uri, source, map, null);
- try {
- payload.getSwitchValue(context);
- fail("Should have thrown exception for bad map");
- } catch (IllegalStateException e) {
- assertThat(e).isNotNull();
- }
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
}
@Test
public void testConstructor_DataRetained() {
final String uri = "test.com";
final int type = ResultPayload.PayloadType.INLINE_SWITCH;
- final int source = ResultPayload.SettingsSource.SECURE;
- final ArrayMap<Integer, Boolean> map = new ArrayMap<>();
- map.put(1, true);
- map.put(0, false);
+ final int source = SettingsSource.SECURE;
final String intentKey = "key";
final String intentVal = "value";
final Intent intent = new Intent();
intent.putExtra(intentKey, intentVal);
- InlineSwitchPayload payload = new InlineSwitchPayload(uri, source, map, intent);
+ InlineSwitchPayload payload = new InlineSwitchPayload(uri, source, 1, intent, true);
final Intent retainedIntent = payload.getIntent();
- assertThat(payload.settingsUri).isEqualTo(uri);
- assertThat(payload.inlineType).isEqualTo(type);
- assertThat(payload.settingSource).isEqualTo(source);
- assertThat(payload.valueMap.get(1)).isTrue();
- assertThat(payload.valueMap.get(0)).isFalse();
+ assertThat(payload.mSettingKey).isEqualTo(uri);
+ assertThat(payload.mInlineType).isEqualTo(type);
+ assertThat(payload.mSettingSource).isEqualTo(source);
+ assertThat(payload.isStandard()).isTrue();
+ assertThat(payload.getAvailability()).isEqualTo(ResultPayload.Availability.AVAILABLE);
assertThat(retainedIntent.getStringExtra(intentKey)).isEqualTo(intentVal);
}
@@ -94,32 +75,150 @@
public void testParcelConstructor_DataRetained() {
String uri = "test.com";
int type = ResultPayload.PayloadType.INLINE_SWITCH;
- int source = ResultPayload.SettingsSource.SECURE;
- final ArrayMap<Integer, Boolean> map = new ArrayMap<>();
- map.put(1, true);
- map.put(0, false);
+ int source = SettingsSource.SECURE;
final String intentKey = "key";
final String intentVal = "value";
final Intent intent = new Intent();
intent.putExtra(intentKey, intentVal);
Parcel parcel = Parcel.obtain();
+ parcel.writeParcelable(intent, 0);
parcel.writeString(uri);
parcel.writeInt(type);
parcel.writeInt(source);
- parcel.writeParcelable(intent, 0);
- parcel.writeMap(map);
+ parcel.writeInt(InlineSwitchPayload.TRUE);
+ parcel.writeInt(InlineSwitchPayload.TRUE);
parcel.setDataPosition(0);
InlineSwitchPayload payload = InlineSwitchPayload.CREATOR.createFromParcel(parcel);
final Intent builtIntent = payload.getIntent();
- assertThat(payload.settingsUri).isEqualTo(uri);
- assertThat(payload.inlineType).isEqualTo(type);
- assertThat(payload.settingSource).isEqualTo(source);
- assertThat(payload.valueMap.get(1)).isTrue();
- assertThat(payload.valueMap.get(0)).isFalse();
+ assertThat(payload.mSettingKey).isEqualTo(uri);
+ assertThat(payload.mInlineType).isEqualTo(type);
+ assertThat(payload.mSettingSource).isEqualTo(source);
+ assertThat(payload.isStandard()).isTrue();
+ assertThat(payload.getAvailability()).isEqualTo(Availability.AVAILABLE);
assertThat(builtIntent.getStringExtra(intentKey)).isEqualTo(intentVal);
}
+ @Test
+ public void testGetSecure_returnsSecureSetting() {
+ InlineSwitchPayload payload = new InlineSwitchPayload(DUMMY_SETTING, SettingsSource.SECURE,
+ STANDARD_ON, null /* intent */, true);
+ int currentValue = 1;
+ Settings.Secure.putInt(mContext.getContentResolver(), DUMMY_SETTING, currentValue);
+ int newValue = payload.getValue(mContext);
+
+ assertThat(newValue).isEqualTo(currentValue);
+ }
+
+ @Test
+ public void testGetGlobal_returnsGlobalSetting() {
+ InlineSwitchPayload payload = new InlineSwitchPayload(DUMMY_SETTING, SettingsSource.GLOBAL,
+ STANDARD_ON, null /* intent */, true);
+ int currentValue = 1;
+ Settings.Global.putInt(mContext.getContentResolver(), DUMMY_SETTING, currentValue);
+
+ int newValue = payload.getValue(mContext);
+
+ assertThat(newValue).isEqualTo(currentValue);
+ }
+
+ @Test
+ public void testGetSystem_returnsSystemSetting() {
+ InlineSwitchPayload payload = new InlineSwitchPayload(DUMMY_SETTING, SettingsSource.SYSTEM,
+ STANDARD_ON, null /* intent */, true);
+ int currentValue = 1;
+ Settings.System.putInt(mContext.getContentResolver(), DUMMY_SETTING, currentValue);
+
+ int newValue = payload.getValue(mContext);
+
+ assertThat(newValue).isEqualTo(currentValue);
+ }
+
+ @Test
+ public void testSetSecure_updatesSecureSetting() {
+ InlineSwitchPayload payload = new InlineSwitchPayload(DUMMY_SETTING, SettingsSource.SECURE,
+ STANDARD_ON, null /* intent */, true);
+ int newValue = 1;
+ ContentResolver resolver = mContext.getContentResolver();
+ Settings.Secure.putInt(resolver, SCREEN_BRIGHTNESS_MODE, 0);
+
+ payload.setValue(mContext, newValue);
+ int updatedValue = Settings.System.getInt(resolver, DUMMY_SETTING, -1);
+
+ assertThat(updatedValue).isEqualTo(newValue);
+ }
+
+ @Test
+ public void testSetGlobal_updatesGlobalSetting() {
+ InlineSwitchPayload payload = new InlineSwitchPayload(DUMMY_SETTING, SettingsSource.GLOBAL,
+ STANDARD_ON, null /* intent */, true);
+ int newValue = 1;
+ ContentResolver resolver = mContext.getContentResolver();
+ Settings.Global.putInt(resolver, SCREEN_BRIGHTNESS_MODE, 0);
+
+ payload.setValue(mContext, newValue);
+ int updatedValue = Settings.Global.getInt(resolver, DUMMY_SETTING, -1);
+
+ assertThat(updatedValue).isEqualTo(newValue);
+ }
+
+ @Test
+ public void testSetSystem_updatesSystemSetting() {
+ InlineSwitchPayload payload = new InlineSwitchPayload(DUMMY_SETTING, SettingsSource.SYSTEM,
+ STANDARD_ON, null /* intent */, true);
+ int newValue = 1;
+ ContentResolver resolver = mContext.getContentResolver();
+ Settings.System.putInt(resolver, SCREEN_BRIGHTNESS_MODE, 0);
+
+ payload.setValue(mContext, newValue);
+ int updatedValue = Settings.System.getInt(resolver, DUMMY_SETTING, -1);
+
+ assertThat(updatedValue).isEqualTo(newValue);
+ }
+
+ @Test
+ public void testGetSystem_flippedSetting_returnsFlippedValue() {
+ // Stores 1s as 0s, and vis versa
+ InlineSwitchPayload payload = new InlineSwitchPayload(DUMMY_SETTING, SettingsSource.SYSTEM,
+ FLIPPED_ON, null /* intent */, true);
+ int currentValue = 1;
+ Settings.System.putInt(mContext.getContentResolver(), DUMMY_SETTING, currentValue);
+
+ int newValue = payload.getValue(mContext);
+
+ assertThat(newValue).isEqualTo(1 - currentValue);
+ }
+
+ @Test
+ public void testSetSystem_flippedSetting_updatesToFlippedValue() {
+ // Stores 1s as 0s, and vis versa
+ InlineSwitchPayload payload = new InlineSwitchPayload(DUMMY_SETTING, SettingsSource.SYSTEM,
+ FLIPPED_ON, null /* intent */, true);
+ int newValue = 1;
+ ContentResolver resolver = mContext.getContentResolver();
+ Settings.System.putInt(resolver, SCREEN_BRIGHTNESS_MODE, newValue);
+
+ payload.setValue(mContext, newValue);
+ int updatedValue = Settings.System.getInt(resolver, DUMMY_SETTING, -1);
+
+ assertThat(updatedValue).isEqualTo(1 - newValue);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetSystem_negativeValue_ThrowsError() {
+ InlineSwitchPayload payload = new InlineSwitchPayload(DUMMY_SETTING, SettingsSource.SYSTEM,
+ STANDARD_ON, null /* intent */, true);
+
+ payload.setValue(mContext, -1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetSystem_highValue_ThrowsError() {
+ InlineSwitchPayload payload = new InlineSwitchPayload(DUMMY_SETTING, SettingsSource.SYSTEM,
+ STANDARD_ON, null /* intent */, true);
+
+ payload.setValue(mContext, 2);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/search/InlineSwitchViewHolderTest.java b/tests/robotests/src/com/android/settings/search/InlineSwitchViewHolderTest.java
index ce483a4..537aac4 100644
--- a/tests/robotests/src/com/android/settings/search/InlineSwitchViewHolderTest.java
+++ b/tests/robotests/src/com/android/settings/search/InlineSwitchViewHolderTest.java
@@ -86,7 +86,7 @@
@Test
public void testBindViewElements_AllUpdated() {
- when(mPayload.getSwitchValue(any(Context.class))).thenReturn(true);
+ when(mPayload.getValue(any(Context.class))).thenReturn(1);
SearchResult result = getSearchResult();
mHolder.onBind(mFragment, result);
// Precondition: switch is on.
@@ -102,10 +102,12 @@
private SearchResult getSearchResult() {
SearchResult.Builder builder = new SearchResult.Builder();
+
builder.setTitle(TITLE)
.setSummary(SUMMARY)
.setRank(1)
- .setPayload(new InlineSwitchPayload("", 0, null, null))
+ .setPayload(new InlineSwitchPayload("" /* uri */, 0 /* mSettingSource */,
+ 1 /* onValue */, null /* intent */, true /* isDeviceSupported */))
.addBreadcrumbs(new ArrayList<>())
.setIcon(mIcon)
.setPayload(mPayload)
diff --git a/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java b/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
index ba1408f7..efeeffc 100644
--- a/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
+++ b/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
@@ -97,7 +97,7 @@
assertThat(mHolder.summaryView.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mHolder.breadcrumbView.getVisibility()).isEqualTo(View.GONE);
- verify(mFragment).onSearchResultClicked(eq(mHolder), anyString());
+ verify(mFragment).onSearchResultClicked(eq(mHolder), any(SearchResult.class));
verify(mFragment).startActivity(any(Intent.class));
}
diff --git a/tests/robotests/src/com/android/settings/search/SearchFragmentTest.java b/tests/robotests/src/com/android/settings/search/SearchFragmentTest.java
index 44851b1..6adc895 100644
--- a/tests/robotests/src/com/android/settings/search/SearchFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchFragmentTest.java
@@ -18,6 +18,7 @@
package com.android.settings.search;
import android.app.LoaderManager;
+import android.content.Intent;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
@@ -26,6 +27,7 @@
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
+import com.android.settings.SettingsActivity;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.testutils.DatabaseTestUtils;
@@ -36,7 +38,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
@@ -75,6 +79,11 @@
private SavedQueryLoader mSavedQueryLoader;
@Mock
private SavedQueryController mSavedQueryController;
+ @Mock
+ private SearchResultsAdapter mSearchResultsAdapter;
+ @Captor
+ private ArgumentCaptor<String> mQueryCaptor = ArgumentCaptor.forClass(String.class);
+
private FakeFeatureFactory mFeatureFactory;
@Before
@@ -148,7 +157,7 @@
}
@Test
- public void queryTextChange_shouldTriggerLoader() {
+ public void queryTextChange_shouldTriggerLoaderAndInitializeSearch() {
when(mFeatureFactory.searchFeatureProvider
.getDatabaseSearchLoader(any(Context.class), anyString()))
.thenReturn(mDatabaseResultLoader);
@@ -167,6 +176,7 @@
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
.thenReturn(true);
+ ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
fragment.onQueryTextChange(testQuery);
activityController.get().onBackPressed();
@@ -181,10 +191,12 @@
.getDatabaseSearchLoader(any(Context.class), anyString());
verify(mFeatureFactory.searchFeatureProvider)
.getInstalledAppSearchLoader(any(Context.class), anyString());
+ verify(mSearchResultsAdapter).initializeSearch(mQueryCaptor.capture());
+ assertThat(mQueryCaptor.getValue()).isEqualTo(testQuery);
}
@Test
- public void queryTextChangeToEmpty_shouldLoadSavedQuery() {
+ public void queryTextChangeToEmpty_shouldLoadSavedQueryAndNotInitializeSearch() {
when(mFeatureFactory.searchFeatureProvider
.getDatabaseSearchLoader(any(Context.class), anyString()))
.thenReturn(mDatabaseResultLoader);
@@ -201,6 +213,7 @@
when(mFeatureFactory.searchFeatureProvider.isIndexingComplete(any(Context.class)))
.thenReturn(true);
ReflectionHelpers.setField(fragment, "mSavedQueryController", mSavedQueryController);
+ ReflectionHelpers.setField(fragment, "mSearchAdapter", mSearchResultsAdapter);
fragment.mQuery = "123";
fragment.onQueryTextChange("");
@@ -210,6 +223,7 @@
verify(mFeatureFactory.searchFeatureProvider, never())
.getInstalledAppSearchLoader(any(Context.class), anyString());
verify(mSavedQueryController).loadSavedQueries();
+ verify(mSearchResultsAdapter, never()).initializeSearch(anyString());
}
@Test
@@ -383,12 +397,21 @@
SearchFragment fragment = new SearchFragment();
ReflectionHelpers.setField(fragment, "mMetricsFeatureProvider",
mFeatureFactory.metricsFeatureProvider);
+ ReflectionHelpers.setField(fragment, "mSearchFeatureProvider",
+ mFeatureFactory.searchFeatureProvider);
ReflectionHelpers.setField(fragment, "mSearchAdapter", mock(SearchResultsAdapter.class));
fragment.mSavedQueryController = mock(SavedQueryController.class);
// Should log result name, result count, clicked rank, etc.
- final SearchViewHolder result = mock(SearchViewHolder.class);
- fragment.onSearchResultClicked(result, "test_setting");
+ final SearchViewHolder resultViewHolder = mock(SearchViewHolder.class);
+ ResultPayload payLoad = new ResultPayload(
+ (new Intent()).putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, "test_setting"));
+ SearchResult searchResult = new SearchResult.Builder()
+ .setStableId(payLoad.hashCode())
+ .setPayload(payLoad)
+ .setTitle("setting_title")
+ .build();
+ fragment.onSearchResultClicked(resultViewHolder, searchResult);
verify(mFeatureFactory.metricsFeatureProvider).action(
nullable(Context.class),
@@ -397,6 +420,9 @@
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SERACH_RESULT_COUNT)),
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SERACH_RESULT_RANK)),
argThat(pairMatches(MetricsProto.MetricsEvent.FIELD_SETTINGS_SERACH_QUERY_LENGTH)));
+
+ verify(mFeatureFactory.searchFeatureProvider).searchResultClicked(nullable(Context.class),
+ nullable(String.class), eq(searchResult));
}
private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag) {
diff --git a/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java b/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java
index 8290348..c6a5451 100644
--- a/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.util.Pair;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -28,24 +29,31 @@
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.search.SearchResult.Builder;
+import com.android.settings.search.ranking.SearchResultsRankerCallback;
+import static org.junit.Assert.assertNull;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -63,6 +71,9 @@
private SearchFeatureProvider mSearchFeatureProvider;
@Mock
private Context mMockContext;
+ @Captor
+ private ArgumentCaptor<Integer> mSearchResultsCountCaptor =
+ ArgumentCaptor.forClass(Integer.class);
private SearchResultsAdapter mAdapter;
private Context mContext;
private String mLoaderClassName;
@@ -73,10 +84,10 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = Robolectric.buildActivity(Activity.class).get();
- mAdapter = new SearchResultsAdapter(mFragment, mSearchFeatureProvider);
mLoaderClassName = DatabaseResultLoader.class.getName();
when(mFragment.getContext()).thenReturn(mMockContext);
when(mMockContext.getApplicationContext()).thenReturn(mContext);
+ mAdapter = new SearchResultsAdapter(mFragment, mSearchFeatureProvider);
}
@Test
@@ -88,8 +99,9 @@
@Test
public void testSingleSourceMerge_exactCopyReturned() {
Set<SearchResult> intentResults = getIntentSampleResults();
+ mAdapter.initializeSearch("");
mAdapter.addSearchResults(intentResults, mLoaderClassName);
- mAdapter.displaySearchResults("");
+ mAdapter.notifyResultsLoaded();
List<SearchResult> updatedResults = mAdapter.getSearchResults();
assertThat(updatedResults).containsAllIn(intentResults);
@@ -113,11 +125,12 @@
@Test
public void testEndToEndSearch_properResultsMerged_correctOrder() {
+ mAdapter.initializeSearch("");
mAdapter.addSearchResults(new HashSet<SearchResult>(getDummyAppResults()),
InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(new HashSet<SearchResult>(getDummyDbResults()),
DatabaseResultLoader.class.getName());
- int count = mAdapter.displaySearchResults("");
+ mAdapter.notifyResultsLoaded();
List<SearchResult> results = mAdapter.getSearchResults();
assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
@@ -126,25 +139,28 @@
assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
- assertThat(count).isEqualTo(6);
+ verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
+ assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
}
@Test
public void testEndToEndSearch_addResults_resultsAddedInOrder() {
- List<AppSearchResult> appResults = getDummyAppResults();
+ List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
+ mAdapter.initializeSearch("");
// Add two individual items
mAdapter.addSearchResults(new HashSet<SearchResult>(appResults.subList(0, 1)),
InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(new HashSet<SearchResult>(dbResults.subList(0, 1)),
DatabaseResultLoader.class.getName());
- mAdapter.displaySearchResults("");
+ mAdapter.notifyResultsLoaded();
// Add super-set of items
+ mAdapter.initializeSearch("");
mAdapter.addSearchResults(
new HashSet<SearchResult>(appResults), InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(
new HashSet<SearchResult>(dbResults), DatabaseResultLoader.class.getName());
- int count = mAdapter.displaySearchResults("");
+ mAdapter.notifyResultsLoaded();
List<SearchResult> results = mAdapter.getSearchResults();
assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
@@ -153,46 +169,333 @@
assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
- assertThat(count).isEqualTo(6);
- }
-
- @Test
- public void testDisplayResults_ShouldNotRunSmartRankingIfDisabled() {
- when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any()))
- .thenReturn(false);
- mAdapter.displaySearchResults("");
- verify(mSearchFeatureProvider, never()).rankSearchResults(anyString(), anyList());
- }
-
- @Test
- public void testDisplayResults_ShouldRunSmartRankingIfEnabled() {
- when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any()))
- .thenReturn(true);
- mAdapter.displaySearchResults("");
- verify(mSearchFeatureProvider, times(1)).rankSearchResults(anyString(), anyList());
+ verify(mFragment, times(2)).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
+ assertThat(mSearchResultsCountCaptor.getAllValues().toArray())
+ .isEqualTo(new Integer[] {2, 6});
}
@Test
public void testEndToEndSearch_removeResults_resultsAdded() {
- List<AppSearchResult> appResults = getDummyAppResults();
+ List<SearchResult> appResults = getDummyAppResults();
List<SearchResult> dbResults = getDummyDbResults();
// Add list of items
+ mAdapter.initializeSearch("");
mAdapter.addSearchResults(new HashSet<SearchResult>(appResults),
InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(new HashSet<SearchResult>(dbResults),
DatabaseResultLoader.class.getName());
- mAdapter.displaySearchResults("");
+ mAdapter.notifyResultsLoaded();
// Add subset of items
+ mAdapter.initializeSearch("");
mAdapter.addSearchResults(new HashSet<SearchResult>(appResults.subList(0, 1)),
InstalledAppResultLoader.class.getName());
mAdapter.addSearchResults(new HashSet<>(dbResults.subList(0, 1)),
DatabaseResultLoader.class.getName());
- int count = mAdapter.displaySearchResults("");
+ mAdapter.notifyResultsLoaded();
List<SearchResult> results = mAdapter.getSearchResults();
assertThat(results.get(0).title).isEqualTo(TITLES[0]);
assertThat(results.get(1).title).isEqualTo(TITLES[3]);
- assertThat(count).isEqualTo(2);
+ verify(mFragment, times(2)).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
+ assertThat(mSearchResultsCountCaptor.getAllValues().toArray())
+ .isEqualTo(new Integer[] {6, 2});
+ }
+ @Test
+ public void testEndToEndSearch_smartSearchRankingEnabledAndSucceededAfterResultsLoaded() {
+ when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
+
+ List<SearchResult> appResults = getDummyAppResults();
+ List<SearchResult> dbResults = getDummyDbResults();
+ mAdapter.initializeSearch("");
+ mAdapter.addSearchResults(
+ new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(
+ new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
+ mAdapter.notifyResultsLoaded();
+ mAdapter.onRankingScoresAvailable(getDummyRankingScores());
+
+ List<SearchResult> results = mAdapter.getSearchResults();
+ assertThat(results.get(0).title).isEqualTo(TITLES[2]); // charlie
+ assertThat(results.get(1).title).isEqualTo(TITLES[0]); // alpha
+ assertThat(results.get(2).title).isEqualTo(TITLES[1]); // bravo
+ assertThat(results.get(3).title).isEqualTo(TITLES[3]); // appAlpha
+ assertThat(results.get(4).title).isEqualTo(TITLES[4]); // appBravo
+ assertThat(results.get(5).title).isEqualTo(TITLES[5]); // appCharlie
+ verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
+ assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
+ }
+
+ @Test
+ public void testEndToEndSearch_smartSearchRankingEnabledAndSucceededBeforeResultsLoaded() {
+ when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
+
+ List<SearchResult> appResults = getDummyAppResults();
+ List<SearchResult> dbResults = getDummyDbResults();
+ mAdapter.initializeSearch("");
+ mAdapter.onRankingScoresAvailable(getDummyRankingScores());
+ mAdapter.addSearchResults(
+ new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(
+ new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
+ mAdapter.notifyResultsLoaded();
+
+ List<SearchResult> results = mAdapter.getSearchResults();
+ assertThat(results.get(0).title).isEqualTo(TITLES[2]); // charlie
+ assertThat(results.get(1).title).isEqualTo(TITLES[0]); // alpha
+ assertThat(results.get(2).title).isEqualTo(TITLES[1]); // bravo
+ assertThat(results.get(3).title).isEqualTo(TITLES[3]); // appAlpha
+ assertThat(results.get(4).title).isEqualTo(TITLES[4]); // appBravo
+ assertThat(results.get(5).title).isEqualTo(TITLES[5]); // appCharlie
+ verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
+ assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
+ }
+
+ @Test
+ public void testEndToEndSearch_smartSearchRankingEnabledAndFailedAfterResultsLoaded() {
+ when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
+
+ List<SearchResult> appResults = getDummyAppResults();
+ List<SearchResult> dbResults = getDummyDbResults();
+ mAdapter.initializeSearch("");
+ mAdapter.addSearchResults(
+ new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(
+ new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
+ mAdapter.notifyResultsLoaded();
+ mAdapter.onRankingFailed();
+
+ List<SearchResult> results = mAdapter.getSearchResults();
+ assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
+ assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
+ assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
+ assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
+ assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
+ assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
+ verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
+ assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
+ }
+
+ @Test
+ public void testEndToEndSearch_smartSearchRankingEnabledAndFailedBeforeResultsLoaded() {
+ when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
+
+ List<SearchResult> appResults = getDummyAppResults();
+ List<SearchResult> dbResults = getDummyDbResults();
+ mAdapter.initializeSearch("");
+ mAdapter.onRankingFailed();
+ mAdapter.addSearchResults(
+ new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(
+ new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
+ mAdapter.notifyResultsLoaded();
+
+ List<SearchResult> results = mAdapter.getSearchResults();
+ assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
+ assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
+ assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
+ assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
+ assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
+ assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
+ verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
+ assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
+ }
+
+ @Test
+ public void testEndToEndSearch_smartSearchRankingEnabledAndTimedoutAfterResultsLoaded() {
+ when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
+
+ List<SearchResult> appResults = getDummyAppResults();
+ List<SearchResult> dbResults = getDummyDbResults();
+ mAdapter.initializeSearch("");
+ mAdapter.addSearchResults(
+ new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(
+ new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
+ mAdapter.notifyResultsLoaded();
+
+ waitUntilRankingTimesOut();
+
+ List<SearchResult> results = mAdapter.getSearchResults();
+ assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
+ assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
+ assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
+ assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
+ assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
+ assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
+ verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
+ assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
+ }
+
+ @Test
+ public void testEndToEndSearch_smartSearchRankingEnabledAndTimedoutBeforeResultsLoaded() {
+ when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
+
+ List<SearchResult> appResults = getDummyAppResults();
+ List<SearchResult> dbResults = getDummyDbResults();
+ mAdapter.initializeSearch("");
+
+ waitUntilRankingTimesOut();
+
+ mAdapter.addSearchResults(
+ new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(
+ new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
+ mAdapter.notifyResultsLoaded();
+
+ List<SearchResult> results = mAdapter.getSearchResults();
+ assertThat(results.get(0).title).isEqualTo(TITLES[0]); // alpha
+ assertThat(results.get(1).title).isEqualTo(TITLES[3]); // appAlpha
+ assertThat(results.get(2).title).isEqualTo(TITLES[4]); // appBravo
+ assertThat(results.get(3).title).isEqualTo(TITLES[1]); // bravo
+ assertThat(results.get(4).title).isEqualTo(TITLES[5]); // appCharlie
+ assertThat(results.get(5).title).isEqualTo(TITLES[2]); // charlie
+ verify(mFragment).onSearchResultsDisplayed(mSearchResultsCountCaptor.capture());
+ assertThat(mSearchResultsCountCaptor.getValue()).isEqualTo(6);
+ }
+
+ @Test
+ public void testDoSmartRanking_shouldRankAppResultsAfterDbResults() {
+ when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
+
+ List<SearchResult> appResults = getDummyAppResults();
+ List<SearchResult> dbResults = getDummyDbResults();
+ mAdapter.initializeSearch("");
+ mAdapter.addSearchResults(
+ new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(
+ new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
+ mAdapter.notifyResultsLoaded();
+ mAdapter.onRankingScoresAvailable(getDummyRankingScores());
+ List<SearchResult> results = mAdapter.doAsyncRanking();
+ assertThat(results.get(0).title).isEqualTo(TITLES[2]); // charlie
+ assertThat(results.get(1).title).isEqualTo(TITLES[0]); // alpha
+ assertThat(results.get(2).title).isEqualTo(TITLES[1]); // bravo
+ assertThat(results.get(3).title).isEqualTo(TITLES[3]); // appAlpha
+ assertThat(results.get(4).title).isEqualTo(TITLES[4]); // appBravo
+ assertThat(results.get(5).title).isEqualTo(TITLES[5]); // appCharlie
+ }
+
+ @Test
+ public void testDoSmartRanking_shouldRankResultsWithMissingScoresAfterScoredResults() {
+ when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
+
+ List<SearchResult> appResults = getDummyAppResults();
+ List<SearchResult> dbResults = getDummyDbResults();
+ mAdapter.initializeSearch("");
+ mAdapter.addSearchResults(
+ new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(
+ new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
+ mAdapter.notifyResultsLoaded();
+ List<Pair<String, Float>> rankingScores = getDummyRankingScores();
+ rankingScores.remove(1); // no ranking score for alpha
+ mAdapter.onRankingScoresAvailable(rankingScores);
+ List<SearchResult> results = mAdapter.doAsyncRanking();
+ assertThat(results.get(0).title).isEqualTo(TITLES[2]); // charlie
+ assertThat(results.get(1).title).isEqualTo(TITLES[1]); // bravo
+ assertThat(results.get(2).title).isEqualTo(TITLES[0]); // alpha
+ assertThat(results.get(3).title).isEqualTo(TITLES[3]); // appAlpha
+ assertThat(results.get(4).title).isEqualTo(TITLES[4]); // appBravo
+ assertThat(results.get(5).title).isEqualTo(TITLES[5]); // appCharlie
+ }
+
+ @Test
+ public void testGetUnsortedLoadedResults () {
+ List<SearchResult> appResults = getDummyAppResults();
+ List<SearchResult> dbResults = getDummyDbResults();
+ mAdapter.initializeSearch("");
+ mAdapter.addSearchResults(
+ new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(
+ new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
+ Set<CharSequence> expectedDbTitles = new HashSet<>(
+ Arrays.asList("alpha", "bravo", "charlie"));
+ Set<CharSequence> expectedAppTitles = new HashSet<>(
+ Arrays.asList("appAlpha", "appBravo", "appCharlie"));
+ Set<CharSequence> actualDbTitles = new HashSet<>();
+ Set<CharSequence> actualAppTitles = new HashSet<>();
+ for (SearchResult result : mAdapter.getUnsortedLoadedResults(SearchResultsAdapter
+ .DB_RESULTS_LOADER_KEY)) {
+ actualDbTitles.add(result.title);
+ }
+ for (SearchResult result : mAdapter.getUnsortedLoadedResults(SearchResultsAdapter
+ .APP_RESULTS_LOADER_KEY)) {
+ actualAppTitles.add(result.title);
+ }
+ assertThat(actualDbTitles).isEqualTo(expectedDbTitles);
+ assertThat(actualAppTitles).isEqualTo(expectedAppTitles);
+ }
+
+ @Test
+ public void testGetSortedLoadedResults() {
+ List<SearchResult> appResults = getDummyAppResults();
+ List<SearchResult> dbResults = getDummyDbResults();
+ mAdapter.initializeSearch("");
+ mAdapter.addSearchResults(
+ new HashSet<>(appResults), InstalledAppResultLoader.class.getName());
+ mAdapter.addSearchResults(
+ new HashSet<>(dbResults), DatabaseResultLoader.class.getName());
+ List<? extends SearchResult> actualDbResults =
+ mAdapter.getSortedLoadedResults(SearchResultsAdapter.DB_RESULTS_LOADER_KEY);
+ List<? extends SearchResult> actualAppResults =
+ mAdapter.getSortedLoadedResults(SearchResultsAdapter.APP_RESULTS_LOADER_KEY);
+ assertThat(actualDbResults.get(0).title).isEqualTo(TITLES[0]); // charlie
+ assertThat(actualDbResults.get(1).title).isEqualTo(TITLES[1]); // bravo
+ assertThat(actualDbResults.get(2).title).isEqualTo(TITLES[2]); // alpha
+ assertThat(actualAppResults.get(0).title).isEqualTo(TITLES[3]); // appAlpha
+ assertThat(actualAppResults.get(1).title).isEqualTo(TITLES[4]); // appBravo
+ assertThat(actualAppResults.get(2).title).isEqualTo(TITLES[5]); // appCharlie
+ }
+
+ @Test
+ public void testInitializeSearch_shouldNotRunSmartRankingIfDisabled() {
+ when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(false);
+ mAdapter.initializeSearch("");
+ mAdapter.notifyResultsLoaded();
+ verify(mSearchFeatureProvider, never()).querySearchResults(
+ any(Context.class), anyString(), any(SearchResultsRankerCallback.class));
+ }
+
+ @Test
+ public void testInitialSearch_shouldRunSmartRankingIfEnabled() {
+ when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
+ mAdapter.initializeSearch("");
+ mAdapter.notifyResultsLoaded();
+ verify(mSearchFeatureProvider, times(1)).querySearchResults(
+ any(Context.class), anyString(), any(SearchResultsRankerCallback.class));
+ }
+
+ @Test
+ public void testGetRankingScoreByStableId() {
+ when(mSearchFeatureProvider.isSmartSearchRankingEnabled(any())).thenReturn(true);
+
+ List<SearchResult> appResults = getDummyAppResults();
+ List<SearchResult> dbResults = getDummyDbResults();
+ mAdapter.initializeSearch("");
+ mAdapter.onRankingScoresAvailable(getDummyRankingScores());
+ assertThat(mAdapter.getRankingScoreByStableId(dbResults.get(0).stableId))
+ .isWithin(1e-10f).of(0.8f);
+ assertThat(mAdapter.getRankingScoreByStableId(dbResults.get(1).stableId))
+ .isWithin(1e-10f).of(0.2f);
+ assertThat(mAdapter.getRankingScoreByStableId(dbResults.get(2).stableId))
+ .isWithin(1e-10f).of(0.9f);
+ assertThat(mAdapter.getRankingScoreByStableId(appResults.get(0).stableId))
+ .isEqualTo(-Float.MAX_VALUE);
+ assertThat(mAdapter.getRankingScoreByStableId(appResults.get(1).stableId))
+ .isEqualTo(-Float.MAX_VALUE);
+ assertThat(mAdapter.getRankingScoreByStableId(appResults.get(2).stableId))
+ .isEqualTo(-Float.MAX_VALUE);
+ }
+
+ private void waitUntilRankingTimesOut() {
+ while (mAdapter.getHandler().hasMessages(mAdapter.MSG_RANKING_TIMED_OUT)) {
+ try {
+ ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // Do nothing
+ }
+ }
}
private List<SearchResult> getDummyDbResults() {
@@ -218,8 +521,8 @@
return results;
}
- private List<AppSearchResult> getDummyAppResults() {
- List<AppSearchResult> results = new ArrayList<>();
+ private List<SearchResult> getDummyAppResults() {
+ List<SearchResult> results = new ArrayList<>();
ResultPayload payload = new ResultPayload(new Intent());
AppSearchResult.Builder builder = new AppSearchResult.Builder();
builder.setPayload(payload)
@@ -265,4 +568,16 @@
sampleResults.add(builder.build());
return sampleResults;
}
-}
\ No newline at end of file
+
+ private List<Pair<String, Float>> getDummyRankingScores() {
+ List<SearchResult> results = getDummyDbResults();
+ List<Pair<String, Float>> scores = new ArrayList<>();
+ scores.add(
+ new Pair<String, Float>(Long.toString(results.get(2).stableId), 0.9f)); // charlie
+ scores.add(
+ new Pair<String, Float>(Long.toString(results.get(0).stableId), 0.8f)); // alpha
+ scores.add(
+ new Pair<String, Float>(Long.toString(results.get(1).stableId), 0.2f)); // bravo
+ return scores;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/details/WifiDetailPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/details/WifiDetailPreferenceControllerTest.java
index 526c6d1..1811b63 100644
--- a/tests/robotests/src/com/android/settings/wifi/details/WifiDetailPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/details/WifiDetailPreferenceControllerTest.java
@@ -76,9 +76,13 @@
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -90,9 +94,6 @@
private static final String MAC_ADDRESS = WifiInfo.DEFAULT_MAC_ADDRESS;
private static final String SECURITY = "None";
- private InetAddress mIpv4Address;
- private Inet6Address mIpv6Address;
-
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private PreferenceScreen mockScreen;
@@ -121,35 +122,76 @@
@Mock private WifiDetailPreference mockSubnetPref;
@Mock private WifiDetailPreference mockDnsPref;
@Mock private Button mockForgetButton;
- @Mock private PreferenceCategory mockIpv6AddressCategory;
+ @Mock private PreferenceCategory mockIpv6Category;
+ @Mock private WifiDetailPreference mockIpv6AddressesPref;
@Captor private ArgumentCaptor<NetworkCallback> mCallbackCaptor;
@Captor private ArgumentCaptor<View.OnClickListener> mForgetClickListener;
+ @Captor private ArgumentCaptor<Preference> mIpv6AddressCaptor;
private Context mContext = RuntimeEnvironment.application;
private Lifecycle mLifecycle;
private LinkProperties mLinkProperties;
private WifiDetailPreferenceController mController;
+ // This class exists so that these values can be made static final. They can't be static final
+ // members of the test class, because any attempt to call IpPrefix or RouteInfo constructors
+ // during static initialization of the test class results in NoSuchMethorError being thrown
+ // when the test is run.
+ private static class Constants {
+ static final int IPV4_PREFIXLEN = 25;
+ static final LinkAddress IPV4_ADDR;
+ static final Inet4Address IPV4_GATEWAY;
+ static final RouteInfo IPV4_DEFAULT;
+ static final RouteInfo IPV4_SUBNET;
+ static final LinkAddress IPV6_LINKLOCAL;
+ static final LinkAddress IPV6_GLOBAL1;
+ static final LinkAddress IPV6_GLOBAL2;
+ static final InetAddress IPV4_DNS1;
+ static final InetAddress IPV4_DNS2;
+ static final InetAddress IPV6_DNS;
+
+ private static LinkAddress ipv6LinkAddress(String addr) throws UnknownHostException {
+ return new LinkAddress(InetAddress.getByName(addr), 64);
+ }
+
+ private static LinkAddress ipv4LinkAddress(String addr, int prefixlen)
+ throws UnknownHostException {
+ return new LinkAddress(InetAddress.getByName(addr), prefixlen);
+ }
+
+ static {
+ try {
+ // We create our test constants in these roundabout ways because the robolectric
+ // shadows don't contain NetworkUtils.parseNumericAddress and other utility methods,
+ // so the easy ways to do things fail with NoSuchMethodError.
+ IPV4_ADDR = ipv4LinkAddress("192.0.2.2", IPV4_PREFIXLEN);
+ IPV4_GATEWAY = (Inet4Address) InetAddress.getByName("192.0.2.127");
+
+ final Inet4Address any4 = (Inet4Address) InetAddress.getByName("0.0.0.0");
+ IpPrefix subnet = new IpPrefix(IPV4_ADDR.getAddress(), IPV4_PREFIXLEN);
+ IPV4_SUBNET = new RouteInfo(subnet, any4);
+ IPV4_DEFAULT = new RouteInfo(new IpPrefix(any4, 0), IPV4_GATEWAY);
+
+ IPV6_LINKLOCAL = ipv6LinkAddress("fe80::211:25ff:fef8:7cb2%1");
+ IPV6_GLOBAL1 = ipv6LinkAddress("2001:db8:1::211:25ff:fef8:7cb2");
+ IPV6_GLOBAL2 = ipv6LinkAddress("2001:db8:1::3dfe:8902:f98f:739d");
+
+ IPV4_DNS1 = InetAddress.getByName("8.8.8.8");
+ IPV4_DNS2 = InetAddress.getByName("8.8.4.4");
+ IPV6_DNS = InetAddress.getByName("2001:4860:4860::64");
+ } catch (UnknownHostException e) {
+ throw new RuntimeException("Invalid hardcoded IP addresss: " + e);
+ }
+ }
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mLifecycle = new Lifecycle();
- try {
- mIpv4Address = InetAddress.getByAddress(
- new byte[] { (byte) 255, (byte) 255, (byte) 255, (byte) 255 });
- mIpv6Address = Inet6Address.getByAddress(
- "123", /* host */
- new byte[] {
- (byte) 0xFE, (byte) 0x80, 0, 0, 0, 0, 0, 0, 0x02, 0x11, 0x25,
- (byte) 0xFF, (byte) 0xFE, (byte) 0xF8, (byte) 0x7C, (byte) 0xB2},
- 1 /*scope id */);
- } catch (UnknownHostException e) {
- throw new RuntimeException(e);
- }
-
when(mockAccessPoint.getConfig()).thenReturn(mockWifiConfig);
when(mockAccessPoint.getLevel()).thenReturn(LEVEL);
when(mockAccessPoint.getSecurityString(false)).thenReturn(SECURITY);
@@ -218,8 +260,10 @@
.thenReturn(mockSubnetPref);
when(mockScreen.findPreference(WifiDetailPreferenceController.KEY_DNS_PREF))
.thenReturn(mockDnsPref);
- when(mockScreen.findPreference(WifiDetailPreferenceController.KEY_IPV6_ADDRESS_CATEGORY))
- .thenReturn(mockIpv6AddressCategory);
+ when(mockScreen.findPreference(WifiDetailPreferenceController.KEY_IPV6_CATEGORY))
+ .thenReturn(mockIpv6Category);
+ when(mockScreen.findPreference(WifiDetailPreferenceController.KEY_IPV6_ADDRESSES_PREF))
+ .thenReturn(mockIpv6AddressesPref);
}
@Test
@@ -331,26 +375,23 @@
@Test
public void ipAddressPref_shouldHaveDetailTextSet() {
- LinkAddress ipv4Address = new LinkAddress(mIpv4Address, 32);
-
- mLinkProperties.addLinkAddress(ipv4Address);
+ mLinkProperties.addLinkAddress(Constants.IPV4_ADDR);
mController.displayPreference(mockScreen);
- verify(mockIpAddressPref).setDetailText(mIpv4Address.getHostAddress());
+ verify(mockIpAddressPref).setDetailText(Constants.IPV4_ADDR.getAddress().getHostAddress());
}
@Test
public void gatewayAndSubnet_shouldHaveDetailTextSet() {
- int prefixLength = 24;
- IpPrefix subnet = new IpPrefix(mIpv4Address, prefixLength);
- InetAddress gateway = mIpv4Address;
- mLinkProperties.addRoute(new RouteInfo(subnet, gateway));
+ mLinkProperties.addLinkAddress(Constants.IPV4_ADDR);
+ mLinkProperties.addRoute(Constants.IPV4_DEFAULT);
+ mLinkProperties.addRoute(Constants.IPV4_SUBNET);
mController.displayPreference(mockScreen);
- verify(mockSubnetPref).setDetailText("255.255.255.0");
- verify(mockGatewayPref).setDetailText(mIpv4Address.getHostAddress());
+ verify(mockSubnetPref).setDetailText("255.255.255.128");
+ verify(mockGatewayPref).setDetailText("192.0.2.127");
}
@Test
@@ -377,23 +418,96 @@
@Test
public void noLinkProperties_allIpDetailsHidden() {
when(mockConnectivityManager.getLinkProperties(mockNetwork)).thenReturn(null);
- reset(mockIpv6AddressCategory, mockIpAddressPref, mockSubnetPref, mockGatewayPref,
+ reset(mockIpv6Category, mockIpAddressPref, mockSubnetPref, mockGatewayPref,
mockDnsPref);
mController.displayPreference(mockScreen);
- verify(mockIpv6AddressCategory).setVisible(false);
+ verify(mockIpv6Category).setVisible(false);
verify(mockIpAddressPref).setVisible(false);
verify(mockSubnetPref).setVisible(false);
verify(mockGatewayPref).setVisible(false);
verify(mockDnsPref).setVisible(false);
- verify(mockIpv6AddressCategory, never()).setVisible(true);
+ verify(mockIpv6Category, never()).setVisible(true);
verify(mockIpAddressPref, never()).setVisible(true);
verify(mockSubnetPref, never()).setVisible(true);
verify(mockGatewayPref, never()).setVisible(true);
verify(mockDnsPref, never()).setVisible(true);
}
+ // Convenience method to convert a LinkAddress to a string without a prefix length.
+ private String asString(LinkAddress l) {
+ return l.getAddress().getHostAddress();
+ }
+
+ // Pretend that the NetworkCallback was triggered with a new copy of lp. We need to create a
+ // new copy because the code only updates if !mLinkProperties.equals(lp).
+ private void updateLinkProperties(LinkProperties lp) {
+ mCallbackCaptor.getValue().onLinkPropertiesChanged(mockNetwork, new LinkProperties(lp));
+ }
+
+ private void verifyDisplayedIpv6Addresses(InOrder inOrder, LinkAddress... addresses) {
+ String text = Arrays.stream(addresses)
+ .map(address -> asString(address))
+ .collect(Collectors.joining("\n"));
+ inOrder.verify(mockIpv6AddressesPref).setSummary(text);
+ }
+
+ @Test
+ public void onLinkPropertiesChanged_updatesFields() {
+ mController.displayPreference(mockScreen);
+ mController.onResume();
+
+ InOrder inOrder = inOrder(mockIpAddressPref, mockGatewayPref, mockSubnetPref,
+ mockDnsPref, mockIpv6Category, mockIpv6AddressesPref);
+
+ LinkProperties lp = new LinkProperties();
+
+ lp.addLinkAddress(Constants.IPV6_LINKLOCAL);
+ updateLinkProperties(lp);
+ verifyDisplayedIpv6Addresses(inOrder, Constants.IPV6_LINKLOCAL);
+ inOrder.verify(mockIpv6Category).setVisible(true);
+
+ lp.addRoute(Constants.IPV4_DEFAULT);
+ updateLinkProperties(lp);
+ inOrder.verify(mockGatewayPref).setDetailText(Constants.IPV4_GATEWAY.getHostAddress());
+ inOrder.verify(mockGatewayPref).setVisible(true);
+
+ lp.addLinkAddress(Constants.IPV4_ADDR);
+ lp.addRoute(Constants.IPV4_SUBNET);
+ updateLinkProperties(lp);
+ inOrder.verify(mockIpAddressPref).setDetailText(asString(Constants.IPV4_ADDR));
+ inOrder.verify(mockIpAddressPref).setVisible(true);
+ inOrder.verify(mockSubnetPref).setDetailText("255.255.255.128");
+ inOrder.verify(mockSubnetPref).setVisible(true);
+
+ lp.addLinkAddress(Constants.IPV6_GLOBAL1);
+ lp.addLinkAddress(Constants.IPV6_GLOBAL2);
+ updateLinkProperties(lp);
+ verifyDisplayedIpv6Addresses(inOrder,
+ Constants.IPV6_LINKLOCAL,
+ Constants.IPV6_GLOBAL1,
+ Constants.IPV6_GLOBAL2);
+
+ lp.removeLinkAddress(Constants.IPV6_GLOBAL1);
+ updateLinkProperties(lp);
+ verifyDisplayedIpv6Addresses(inOrder,
+ Constants.IPV6_LINKLOCAL,
+ Constants.IPV6_GLOBAL2);
+
+ lp.addDnsServer(Constants.IPV6_DNS);
+ updateLinkProperties(lp);
+ inOrder.verify(mockDnsPref, never()).setVisible(true);
+
+ lp.addDnsServer(Constants.IPV4_DNS1);
+ lp.addDnsServer(Constants.IPV4_DNS2);
+ updateLinkProperties(lp);
+ inOrder.verify(mockDnsPref).setDetailText(
+ Constants.IPV4_DNS1.getHostAddress() + "," +
+ Constants.IPV4_DNS2.getHostAddress());
+ inOrder.verify(mockDnsPref).setVisible(true);
+ }
+
@Test
public void canForgetNetwork_noNetwork() {
when(mockAccessPoint.getConfig()).thenReturn(null);
@@ -497,28 +611,29 @@
@Test
public void ipv6AddressPref_shouldHaveHostAddressTextSet() {
- LinkAddress ipv6Address = new LinkAddress(mIpv6Address, 128);
-
- mLinkProperties.addLinkAddress(ipv6Address);
+ mLinkProperties.addLinkAddress(Constants.IPV6_LINKLOCAL);
+ mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL1);
+ mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL2);
mController.displayPreference(mockScreen);
- ArgumentCaptor<Preference> preferenceCaptor = ArgumentCaptor.forClass(Preference.class);
- verify(mockIpv6AddressCategory).addPreference(preferenceCaptor.capture());
- assertThat(preferenceCaptor.getValue().getTitle()).isEqualTo(mIpv6Address.getHostAddress());
+ List <Preference> addrs = mIpv6AddressCaptor.getAllValues();
+
+ String expectedAddresses = String.join("\n",
+ asString(Constants.IPV6_LINKLOCAL),
+ asString(Constants.IPV6_GLOBAL1),
+ asString(Constants.IPV6_GLOBAL2));
+
+ verify(mockIpv6AddressesPref).setSummary(expectedAddresses);
}
@Test
public void ipv6AddressPref_shouldNotBeSelectable() {
- LinkAddress ipv6Address = new LinkAddress(mIpv6Address, 128);
-
- mLinkProperties.addLinkAddress(ipv6Address);
+ mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL2);
mController.displayPreference(mockScreen);
- ArgumentCaptor<Preference> preferenceCaptor = ArgumentCaptor.forClass(Preference.class);
- verify(mockIpv6AddressCategory).addPreference(preferenceCaptor.capture());
- assertThat(preferenceCaptor.getValue().isSelectable()).isFalse();
+ assertThat(mockIpv6AddressesPref.isSelectable()).isFalse();
}
@Test
diff --git a/tests/unit/src/com/android/settings/wifi/WifiSettingsUiTest.java b/tests/unit/src/com/android/settings/wifi/WifiSettingsUiTest.java
index c1c5199..c418d45 100644
--- a/tests/unit/src/com/android/settings/wifi/WifiSettingsUiTest.java
+++ b/tests/unit/src/com/android/settings/wifi/WifiSettingsUiTest.java
@@ -15,6 +15,36 @@
*/
package com.android.settings.wifi;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.settings.Settings.WifiSettingsActivity;
+import com.android.settingslib.wifi.AccessPoint;
+import com.android.settingslib.wifi.WifiTracker;
+import com.android.settingslib.wifi.WifiTracker.WifiListener;
+import com.android.settingslib.wifi.WifiTrackerFactory;
+
+import com.google.common.collect.Lists;
+
+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 java.util.List;
+
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
@@ -29,33 +59,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.Intent;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiSsid;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import com.android.settings.Settings.WifiSettingsActivity;
-import com.android.settingslib.wifi.AccessPoint;
-import com.android.settingslib.wifi.WifiTracker;
-import com.android.settingslib.wifi.WifiTracker.WifiListener;
-import com.android.settingslib.wifi.WifiTrackerFactory;
-import com.google.common.collect.Lists;
-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 java.util.List;
-
@RunWith(AndroidJUnit4.class)
public class WifiSettingsUiTest {
@@ -76,8 +79,10 @@
private static final int TEST_RSSI = 123;
private static final int TEST_NETWORK_ID = 1;
- @Mock private WifiTracker mWifiTracker;
- @Mock private WifiManager mWifiManager;
+ @Mock
+ private WifiTracker mWifiTracker;
+ @Mock
+ private WifiManager mWifiManager;
private Context mContext;
private WifiListener mWifiListener;
@@ -136,7 +141,7 @@
private void callOnWifiStateChanged(int state) {
mActivityRule.getActivity().getMainThreadHandler()
- .post( () -> mWifiListener.onWifiStateChanged(state) );
+ .post(() -> mWifiListener.onWifiStateChanged(state));
}
@Test
@@ -152,7 +157,7 @@
}
@Test
- public void noSavedNetworks_shouldNotShowSavedNetworksButton() {
+ public void noSavedNetworks_wifiEnabled_shouldNotShowSavedNetworksButton() {
setWifiState(WifiManager.WIFI_STATE_ENABLED);
when(mWifiTracker.getNumSavedNetworks()).thenReturn(0);
@@ -162,6 +167,16 @@
}
@Test
+ public void noSavedNetworks_wifiDisabled_shouldNotShowSavedNetworksButton() {
+ setWifiState(WifiManager.WIFI_STATE_DISABLED);
+ when(mWifiTracker.getNumSavedNetworks()).thenReturn(0);
+
+ launchActivity();
+
+ onView(withText(SAVED_NETWORKS)).check(doesNotExist());
+ }
+
+ @Test
public void savedNetworksExist_shouldShowSavedNetworksButton() {
setWifiState(WifiManager.WIFI_STATE_ENABLED);
when(mWifiTracker.getNumSavedNetworks()).thenReturn(1);