Merge "Making "Battery Saver" use MasterSwitchPreference."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 26a2326..2ffc5db 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2005,6 +2005,9 @@
<activity android:name=".AppPicker" android:label="@string/select_application"
android:theme="@android:style/Theme.DeviceDefault.Light.Dialog" />
+ <activity android:name=".webview.WebViewAppPicker" android:label="@string/select_webview_provider_dialog_title"
+ android:theme="@android:style/Theme.DeviceDefault.Light.Dialog" />
+
<!-- Keep compatibility with old shortcuts. -->
<activity-alias android:name="UsbSettings"
android:exported="true"
@@ -2561,6 +2564,20 @@
android:value="com.android.settings.applications.VrListenerSettings" />
</activity>
+ <activity android:name="Settings$PictureInPictureSettingsActivity"
+ android:label="@string/picture_in_picture_title"
+ android:taskAffinity="">
+ <intent-filter android:priority="1">
+ <action android:name="android.settings.PICTURE_IN_PICTURE_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.applications.PictureInPictureSettings" />
+ </activity>
<activity android:name="Settings$ZenAccessSettingsActivity"
android:label="@string/manage_zen_access_title"
@@ -2683,6 +2700,22 @@
android:value="com.android.settings.notification.AppNotificationSettings" />
</activity>
+ <!-- Show channel-level notification settings (channel passed in as extras) -->
+ <activity android:name="Settings$ChannelNotificationSettingsActivity"
+ android:label="@string/app_notifications_title"
+ android:exported="true">
+ <intent-filter android:priority="1">
+ <action android:name="android.settings.CHANNEL_NOTIFICATION_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.notification.ChannelNotificationSettings" />
+ </activity>
+
<!-- Show Manual (from settings item) -->
<activity android:name="ManualDisplayActivity"
android:label="@string/manual"
@@ -2940,7 +2973,9 @@
android:value="true" />
</activity>
- <activity android:name=".WebViewImplementation"
+ <!-- Keep compatibility with old WebView-picker implementation -->
+ <activity-alias android:name=".WebViewImplementation"
+ android:targetActivity=".webview.WebViewAppPicker"
android:exported="true"
android:excludeFromRecents="true"
android:theme="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert">
@@ -2950,7 +2985,7 @@
</intent-filter>
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
- </activity>
+ </activity-alias>
<!-- activity for gesture settings -->
<activity android:name="Settings$GestureSettingsActivity"
diff --git a/res/layout/preference_tts_engine.xml b/res/layout/preference_tts_engine.xml
index 8f4036b..19401f0 100644
--- a/res/layout/preference_tts_engine.xml
+++ b/res/layout/preference_tts_engine.xml
@@ -31,17 +31,4 @@
android:maxLines="2"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee"/>
-
- <ImageView
- android:id="@+id/tts_engine_settings"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:src="@drawable/ic_settings"
- android:contentDescription="@string/tts_engine_settings_button"
- android:layout_centerVertical="true"
- android:clickable="true"
- android:focusable="true"
- android:background="?android:attr/selectableItemBackground"/>
</LinearLayout>
diff --git a/res/layout/search_inline_switch_item.xml b/res/layout/search_inline_switch_item.xml
index 40639b0..75943c4 100644
--- a/res/layout/search_inline_switch_item.xml
+++ b/res/layout/search_inline_switch_item.xml
@@ -48,7 +48,6 @@
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
- android:maxLength="60"
android:maxLines="10"/>
<include layout="@layout/search_breadcrumb_view"/>
@@ -60,4 +59,4 @@
android:layout_height="match_parent"
android:gravity="top"
android:paddingStart="16dp"/>
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ab04b9a..4b93d8c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1634,6 +1634,8 @@
<string name="wifi_cellular_data_fallback_summary">Use cellular data when Wi\u2011Fi has no Internet access. Data usage may apply.</string>
<!-- Action bar text message to manually add a wifi network [CHAR LIMIT=20]-->
<string name="wifi_add_network">Add network</string>
+ <!-- Action bar title to open additional Wi-Fi settings-->
+ <string name="wifi_configure_settings_preference_title">Wi\u2011Fi preferences</string>
<!-- Header for the list of wifi networks-->
<string name="wifi_access_points">Wi\u2011Fi networks</string>
<!-- Menu option to do WPS Push Button [CHAR LIMIT=25]-->
@@ -4622,9 +4624,20 @@
behalf. It comes from the <xliff:g id="voice_input_service_app_name">%s</xliff:g>
application. Enable the use of this service?</string>
- <!-- On main TTS Settings screen, in default settings section, reset speech rate for synthesized voice to 1x speech rate.-->
+ <!-- [CHAR LIMIT=50] The text for the settings section that is used to set a preferred text to speech engine -->
+ <string name="tts_engine_preference_title">Preferred engine</string>
+ <!-- [CHAR LIMIT=50] The text for a settings screen of the currently set text to speech engine -->
+ <string name="tts_engine_settings_title">Engine settings</string>
+ <!-- [CHAR LIMIT=50] The text for a button that goes to the speech rate and pitch settings for text to speech. -->
+ <string name="tts_sliders_title">Speech rate & pitch</string>
+ <!-- [CHAR LIMIT=50] Name for the general text to speech settings section. -->
+ <string name="tts_engine_section_title">Engine</string>
+ <!-- [CHAR LIMIT=50] Name for the button that goes to the voice selection screen. -->
+ <string name="tts_install_voice_title">Voices</string>
+
+ <!-- Reset speech rate for synthesized voice to 1x speech rate in the text to speech settings.-->
<string name="tts_reset_speech_rate_title">Reset speech rate</string>
- <!-- On main TTS Settings screen, summary for reset speech rate for synthesized voice -->
+ <!-- Summary for reset speech rate for synthesized voice in the text to speech settings.-->
<string name="tts_reset_speech_rate_summary">Reset the speed at which the text is spoken to normal.</string>
<!-- Power Control Widget -->
@@ -6233,6 +6246,9 @@
<!-- [CHAR LIMIT=100] Notification Importance slider: unset importance level description -->
<string name="notification_importance_none">Not set</string>
+ <!-- [CHAR LIMIT=100] Notification Importance slider: unspecified importance level description -->
+ <string name="notification_importance_unspecified">Let the app decide</string>
+
<!-- [CHAR LIMIT=100] Notification Importance slider: blocked importance level description -->
<string name="notification_importance_blocked">Never show notifications</string>
@@ -6321,6 +6337,21 @@
flicker while in VR mode. -->
<string name="display_vr_pref_off">Reduce flicker</string>
+ <!-- Special access > Title for managing Picture-in-picture settings. [CHAR LIMIT=50] -->
+ <string name="picture_in_picture_title">Picture-in-picture</string>
+
+ <!-- Special access > Picture-in-picture > Text to display when the list is empty. [CHAR LIMIT=NONE] -->
+ <string name="picture_in_picture_empty_text">No installed apps support Picture-in-picture</string>
+
+ <!-- Special access > Picture-in-picture > Additional keywords to search for. [CHAR LIMIT=NONE] -->
+ <string name="picture_in_picture_keywords">pip picture in</string>
+
+ <!-- Apps > App Details > Advanced section string title. [CHAR LIMIT=NONE] -->
+ <string name="picture_in_picture_app_detail_title">Picture-in-picture</string>
+
+ <!-- Apps > App Details > Advanced section string description. [CHAR LIMIT=NONE] -->
+ <string name="picture_in_picture_app_detail_summary">Permit entering picture-in-picture when leaving app</string>
+
<!-- Sound & notification > Advanced section: Title for managing Do Not Disturb access option. [CHAR LIMIT=40] -->
<string name="manage_zen_access_title">Do Not Disturb access</string>
@@ -7641,6 +7672,8 @@
<string name="notification_log_details_importance">importance</string>
<!-- Notification log debug tool: header: notification importance explanation -->
<string name="notification_log_details_explanation">explanation</string>
+ <!-- Notification log debug tool: header: notification importance -->
+ <string name="notification_log_details_badge">can show badge</string>
<!-- Notification log debug tool: header: notification contentIntent field -->
<string name="notification_log_details_content_intent">intent</string>
<!-- Notification log debug tool: header: notification deleteIntent field -->
@@ -8050,6 +8083,11 @@
<item quantity="one"><xliff:g id="count">%d</xliff:g> app allowed access to your camera by your admin</item>
<item quantity="other"><xliff:g id="count">%d</xliff:g> apps allowed access to your camera by your admin</item>
</plurals>
+ <!-- Label indicating how many apps were set as default defaults for common actions (e.g. open browser, send e-mail) by the admin. [CHAR LIMIT=NONE] -->
+ <plurals name="enterprise_privacy_number_enterprise_set_default_apps">
+ <item quantity="one"><xliff:g id="count">%d</xliff:g> default app set by your admin</item>
+ <item quantity="other"><xliff:g id="count">%d</xliff:g> default apps set by your admin</item>
+ </plurals>
<!-- Label explaining that an always-on VPN was set by the admin for the entire device. [CHAR LIMIT=NONE] -->
<string name="enterprise_privacy_always_on_vpn_device">Always-on VPN turned on</string>
<!-- Label explaining that an always-on VPN was set by the admin in the personal profile. [CHAR LIMIT=NONE] -->
@@ -8058,6 +8096,10 @@
<string name="enterprise_privacy_always_on_vpn_work">Always-on VPN turned on in your work profile</string>
<!-- Label explaining that a global HTTP proxy was set by the admin. [CHAR LIMIT=NONE] -->
<string name="enterprise_privacy_global_http_proxy">Global HTTP proxy set</string>
+ <!-- Label explaining that the admin can lock the device and change the user's password. [CHAR LIMIT=NONE] -->
+ <string name="enterprise_privacy_lock_device">Admin can lock device and reset password</string>
+ <!-- Label explaining that the admin can wipe the device remotely. [CHAR LIMIT=NONE] -->
+ <string name="enterprise_privacy_wipe_device">Admin can delete all device data</string>
<!-- Message indicating that the device is enterprise-managed by a Device Owner [CHAR LIMIT=NONE] -->
<string name="do_disclosure_generic">This device is managed.</string>
<!-- Message indicating that the device is enterprise-managed by a Device Owner [CHAR LIMIT=NONE] -->
@@ -8101,4 +8143,29 @@
<!-- On status for the automatic storage manager. [CHAR_LIMIT=10] -->
<string name="storage_manager_indicator_on">On</string>
+ <!-- Title of games app storage screen [CHAR LIMIT=30] -->
+ <string name="game_storage_settings">Games</string>
+
+ <!-- UI webview setting: WebView uninstalled-for-user explanatory text [CHAR LIMIT=30] -->
+ <string name="webview_uninstalled_for_user">Uninstalled for user <xliff:g id="user" example="John Doe">%s</xliff:g>\n</string>
+ <!-- UI webview setting: WebView disabled-for-user explanatory text [CHAR LIMIT=30] -->
+ <string name="webview_disabled_for_user">Disabled for user <xliff:g id="user" example="John Doe">%s</xliff:g>\n</string>
+
+ <!-- AutoFill strings -->
+ <!-- Preference label for the auto-fill app. [CHAR LIMIT=60] -->
+ <string name="autofill_app">Autofill app</string>
+ <!-- Keywords for the auto-fill feature. [CHAR LIMIT=NONE] -->
+ <string name="autofill_keywords">auto, fill, autofill</string>
+ <!-- Title of the warning dialog for setting the auto-fill app. [CHAR_LIMIT=NONE] -->
+ <string name="autofill_confirmation_message">
+ Make <xliff:g id="app_name">%1$s</xliff:g> your autofill app? <xliff:g id="app_name">%1$s</xliff:g> will be able to read your screen and fill fields in other apps.
+ </string>
+
+ <!-- Name of setting for switching device theme [CHAR LIMIT=60] -->
+ <string name="device_theme">Device theme</string>
+ <!-- Name of default device theme [CHAR LIMIT=60] -->
+ <string name="default_theme">Default</string>
+ <!-- Temporary reboot string, will be removed -->
+ <string name="change_theme_reboot" translatable="false">Changing the theme requires a restart.</string>
+
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 29cb390..092d997 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -293,10 +293,6 @@
<!-- Scrollbar style OUTSIDE_OVERLAY -->
<integer name="preference_scrollbar_style">33554432</integer>
- <style name="AppListSwitchPreference" parent="@*android:style/Preference.Material.DialogPreference">
- <item name="android:widgetLayout">@*android:layout/preference_widget_switch</item>
- </style>
-
<style name="TextAppearance.Medium" parent="@android:style/TextAppearance.Material.Medium" />
<style name="TextAppearance.Small" parent="@android:style/TextAppearance.Material.Small" />
<style name="TextAppearance.Switch" parent="@android:style/TextAppearance.Material.Title" />
diff --git a/res/xml/advanced_apps.xml b/res/xml/advanced_apps.xml
deleted file mode 100644
index 0f9f82b..0000000
--- a/res/xml/advanced_apps.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<PreferenceScreen
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
- android:key="applications_settings">
-
- <Preference
- android:key="manage_perms"
- android:title="@string/app_permissions"
- settings:keywords="@string/keywords_app_permissions">
- <intent android:action="android.intent.action.MANAGE_PERMISSIONS"/>
- </Preference>
-
- <PreferenceCategory
- android:title="@string/default_apps_title">
-
- <Preference
- android:key="domain_urls"
- android:title="@string/domain_urls_title"
- android:fragment="com.android.settings.applications.ManageDomainUrls" />
-
- <Preference
- android:key="assist_and_voice_input"
- android:fragment="com.android.settings.applications.ManageAssist"
- android:title="@string/assist_and_voice_input_title" />
-
- <com.android.settings.applications.DefaultHomePreference
- android:key="default_home"
- android:title="@string/home_app"
- android:summary="@string/no_default_home"
- settings:keywords="@string/keywords_home" />
-
- <com.android.settings.applications.DefaultBrowserPreference
- android:key="default_browser"
- android:title="@string/default_browser_title"
- android:summary="@string/default_browser_title_none" />
-
- <com.android.settings.applications.DefaultPhonePreference
- android:key="default_phone_app"
- android:title="@string/default_phone_title"
- settings:keywords="@string/keywords_default_phone_app" />
-
- <com.android.settings.applications.DefaultEmergencyPreference
- android:key="default_emergency_app"
- android:title="@string/default_emergency_app"
- settings:keywords="@string/keywords_emergency_app" />
-
- <com.android.settings.applications.DefaultSmsPreference
- android:key="default_sms_app"
- android:title="@string/sms_application_title"
- settings:keywords="@string/keywords_more_default_sms_app" />
-
- <com.android.settings.applications.DefaultNotificationAssistantPreference
- android:key="default_notification_asst_app"
- android:title="@string/default_notification_assistant" />
-
- </PreferenceCategory>
-
- <com.android.settings.WorkOnlyCategory
- android:key="work_defaults"
- android:title="@string/default_for_work">
-
- <com.android.settings.applications.DefaultBrowserPreference
- android:key="work_default_browser"
- android:title="@string/default_browser_title"
- android:summary="@string/default_browser_title_none"
- settings:forWork="true" />
-
- <com.android.settings.applications.DefaultPhonePreference
- android:key="work_default_phone_app"
- android:title="@string/default_phone_title"
- settings:keywords="@string/keywords_default_phone_app"
- settings:forWork="true" />
-
- </com.android.settings.WorkOnlyCategory>
-
- <PreferenceCategory
- android:title="@string/advanced_apps">
-
- <Preference
- android:key="special_access"
- android:fragment="com.android.settings.applications.SpecialAccessSettings"
- android:title="@string/special_access" />
-
- </PreferenceCategory>
-</PreferenceScreen>
diff --git a/res/xml/apn_editor.xml b/res/xml/apn_editor.xml
index a05d547..eac78e9 100644
--- a/res/xml/apn_editor.xml
+++ b/res/xml/apn_editor.xml
@@ -15,8 +15,7 @@
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
- android:title="@string/apn_edit">
+ android:title="@string/apn_edit">
<EditTextPreference
android:title="@string/apn_name"
android:dialogTitle="@string/apn_name"
diff --git a/res/xml/app_default_settings.xml b/res/xml/app_default_settings.xml
index 58a3959..27200e7 100644
--- a/res/xml/app_default_settings.xml
+++ b/res/xml/app_default_settings.xml
@@ -26,41 +26,53 @@
android:fragment="com.android.settings.applications.ManageAssist"
android:order="-20"/>
- <com.android.settings.applications.DefaultBrowserPreference
+ <com.android.settings.applications.DefaultAutoFillPreference
+ android:key="default_autofill"
+ android:title="@string/autofill_app"
+ android:summary="@string/app_list_preference_none"
+ settings:keywords="@string/autofill_keywords"
+ android:order="-19"/>
+
+ <Preference
android:key="default_browser"
android:title="@string/default_browser_title"
android:summary="@string/default_browser_title_none"
- android:order="-19"/>
+ android:fragment="com.android.settings.applications.defaultapps.DefaultBrowserPicker"
+ android:order="-18">
+ <extra android:name="for_work" android:value="false"/>
+ </Preference>
- <com.android.settings.applications.DefaultHomePreference
+ <Preference
android:key="default_home"
android:title="@string/home_app"
android:summary="@string/no_default_home"
settings:keywords="@string/keywords_home"
- android:order="-18"/>
-
- <com.android.settings.applications.DefaultPhonePreference
- android:key="default_phone_app"
- android:title="@string/default_phone_title"
- settings:keywords="@string/keywords_default_phone_app"
android:order="-17"/>
- <com.android.settings.applications.DefaultSmsPreference
- android:key="default_sms_app"
- android:title="@string/sms_application_title"
- settings:keywords="@string/keywords_more_default_sms_app"
+ <Preference
+ android:key="default_phone_app"
+ android:title="@string/default_phone_title"
+ android:fragment="com.android.settings.applications.defaultapps.DefaultPhonePicker"
+ settings:keywords="@string/keywords_default_phone_app"
android:order="-16"/>
- <com.android.settings.applications.DefaultEmergencyPreference
+ <Preference
+ android:key="default_sms_app"
+ android:title="@string/sms_application_title"
+ android:fragment="com.android.settings.applications.defaultapps.DefaultSmsPicker"
+ settings:keywords="@string/keywords_more_default_sms_app"
+ android:order="-15"/>
+
+ <Preference
android:key="default_emergency_app"
android:title="@string/default_emergency_app"
settings:keywords="@string/keywords_emergency_app"
- android:order="-15"/>
+ android:order="-14"/>
- <com.android.settings.applications.DefaultNotificationAssistantPreference
+ <Preference
android:key="default_notification_asst_app"
android:title="@string/default_notification_assistant"
- android:order="-14"/>
+ android:order="-13"/>
<Preference
android:key="domain_urls"
@@ -71,17 +83,21 @@
android:key="work_defaults"
android:title="@string/default_for_work">
- <com.android.settings.applications.DefaultBrowserPreference
+ <Preference
android:key="work_default_browser"
android:title="@string/default_browser_title"
android:summary="@string/default_browser_title_none"
- settings:forWork="true"/>
+ android:fragment="com.android.settings.applications.defaultapps.DefaultBrowserPicker">
+ <extra android:name="for_work" android:value="true"/>
+ </Preference>
- <com.android.settings.applications.DefaultPhonePreference
- android:key="work_default_phone_app"
- android:title="@string/default_phone_title"
- settings:keywords="@string/keywords_default_phone_app"
- settings:forWork="true"/>
+ <Preference
+ android:key="work_default_phone_app_new"
+ android:title="new phone pref work"
+ android:fragment="com.android.settings.applications.defaultapps.DefaultPhonePicker"
+ settings:keywords="@string/keywords_default_phone_app">
+ <extra android:name="for_work" android:value="true"/>
+ </Preference>
</com.android.settings.WorkOnlyCategory>
@@ -90,4 +106,4 @@
android:fragment="com.android.settings.applications.SpecialAccessSettings"
android:title="@string/special_access"/>
-</PreferenceScreen>
\ No newline at end of file
+</PreferenceScreen>
diff --git a/res/xml/app_notification_settings.xml b/res/xml/app_notification_settings.xml
index 82660dc..7d51fa8 100644
--- a/res/xml/app_notification_settings.xml
+++ b/res/xml/app_notification_settings.xml
@@ -23,6 +23,15 @@
android:key="block"
android:title="@string/app_notification_block_title"
android:summary="@string/app_notification_block_summary"
+ android:order="1"
+ settings:useAdditionalSummary="true"
+ settings:restrictedSwitchSummary="@string/enabled_by_admin" />
+
+ <!-- Show badge -->
+ <com.android.settingslib.RestrictedSwitchPreference
+ android:key="badge"
+ android:title="@string/notification_badge_title"
+ android:summary="@string/notification_badge_summary"
android:order="2"
settings:useAdditionalSummary="true"
settings:restrictedSwitchSummary="@string/enabled_by_admin" />
diff --git a/res/xml/app_picker_prefs.xml b/res/xml/app_picker_prefs.xml
new file mode 100644
index 0000000..9a921ee
--- /dev/null
+++ b/res/xml/app_picker_prefs.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<PreferenceScreen>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/channel_notification_settings.xml b/res/xml/channel_notification_settings.xml
index e1d6d55..4af9fe8 100644
--- a/res/xml/channel_notification_settings.xml
+++ b/res/xml/channel_notification_settings.xml
@@ -27,54 +27,55 @@
settings:useAdditionalSummary="true"
settings:restrictedSwitchSummary="@string/enabled_by_admin" />
+ <!-- Importance -->
+ <com.android.settings.notification.RestrictedDropDownPreference
+ android:key="importance"
+ android:title="@string/notification_importance_title"
+ android:order="2"/>
+
+ <!-- Default ringtone -->
+ <com.android.settings.notification.DefaultNotificationTonePreference
+ android:key="ringtone"
+ android:title="@string/notification_ringtone_title"
+ android:dialogTitle="@string/notification_ringtone_title"
+ android:order="3"
+ android:ringtoneType="notification" />
+
+ <!-- Vibration -->
+ <com.android.settingslib.RestrictedSwitchPreference
+ android:key="vibrate"
+ android:title="@string/notification_vibrate_title"
+ android:order="4"
+ settings:useAdditionalSummary="true" />
+
<!-- Show badge -->
<com.android.settingslib.RestrictedSwitchPreference
android:key="badge"
android:title="@string/notification_badge_title"
android:summary="@string/notification_badge_summary"
- android:order="3"
+ android:order="5"
settings:useAdditionalSummary="true"
settings:restrictedSwitchSummary="@string/enabled_by_admin" />
- <!-- Importance -->
- <com.android.settings.notification.RestrictedDropDownPreference
- android:key="importance"
- android:title="@string/notification_importance_title"
- android:order="4"/>
+ <!-- Lights -->
+ <com.android.settingslib.RestrictedSwitchPreference
+ android:key="lights"
+ android:title="@string/notification_show_lights_title"
+ android:order="6"
+ settings:useAdditionalSummary="true" />
<!-- Visibility Override -->
<com.android.settings.notification.RestrictedDropDownPreference
android:key="visibility_override"
android:title="@string/app_notification_visibility_override_title"
- android:order="5" />
+ android:order="7" />
<!-- Bypass DND -->
<com.android.settingslib.RestrictedSwitchPreference
android:key="bypass_dnd"
android:title="@string/app_notification_override_dnd_title"
android:summary="@string/app_notification_override_dnd_summary"
- android:order="6"
- settings:useAdditionalSummary="true" />
-
- <!-- Lights -->
- <com.android.settingslib.RestrictedSwitchPreference
- android:key="lights"
- android:title="@string/notification_show_lights_title"
- android:order="7"
- settings:useAdditionalSummary="true" />
-
- <!-- Vibration -->
- <com.android.settingslib.RestrictedSwitchPreference
- android:key="vibrate"
- android:title="@string/notification_vibrate_title"
android:order="8"
settings:useAdditionalSummary="true" />
- <!-- Default ringtone -->
- <com.android.settings.notification.DefaultNotificationTonePreference
- android:key="ringtone"
- android:title="@string/notification_ringtone_title"
- android:dialogTitle="@string/notification_ringtone_title"
- android:order="9"
- android:ringtoneType="notification" />
</PreferenceScreen>
diff --git a/res/xml/development_prefs.xml b/res/xml/development_prefs.xml
index 3c2bab9..22327c3 100644
--- a/res/xml/development_prefs.xml
+++ b/res/xml/development_prefs.xml
@@ -74,11 +74,9 @@
android:title="@string/picture_color_mode"
android:summary="@string/picture_color_mode_desc" />
- <ListPreference
- android:key="select_webview_provider"
+ <Preference android:key="select_webview_provider"
android:title="@string/select_webview_provider_title"
- android:dialogTitle="@string/select_webview_provider_dialog_title"
- android:summary="%s" />
+ android:dialogTitle="@string/select_webview_provider_dialog_title" />
<SwitchPreference
android:key="enable_webview_multiprocess"
diff --git a/res/xml/enterprise_privacy_settings.xml b/res/xml/enterprise_privacy_settings.xml
index 4bb2a8c..cc63d5f 100644
--- a/res/xml/enterprise_privacy_settings.xml
+++ b/res/xml/enterprise_privacy_settings.xml
@@ -76,6 +76,10 @@
settings:allowDividerBelow="true"
settings:multiLine="true"/>
<com.android.settings.DividerPreference
+ android:key="number_enterprise_set_default_apps"
+ settings:allowDividerBelow="true"
+ settings:multiLine="true"/>
+ <com.android.settings.DividerPreference
android:key="always_on_vpn_primary_user"
settings:allowDividerBelow="true"
settings:multiLine="true"/>
@@ -92,5 +96,15 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/enterprise_privacy_device_access_category">
+ <com.android.settings.DividerPreference
+ android:key="lock_device"
+ android:title="@string/enterprise_privacy_lock_device"
+ settings:allowDividerBelow="true"
+ settings:multiLine="true"/>
+ <com.android.settings.DividerPreference
+ android:key="wipe_device"
+ android:title="@string/enterprise_privacy_wipe_device"
+ settings:allowDividerBelow="true"
+ settings:multiLine="true"/>
</PreferenceCategory>
</PreferenceScreen>
diff --git a/res/xml/ia_display_settings.xml b/res/xml/ia_display_settings.xml
index 946466b..ed026f1 100644
--- a/res/xml/ia_display_settings.xml
+++ b/res/xml/ia_display_settings.xml
@@ -105,6 +105,11 @@
android:title="@string/tap_to_wake"
android:summary="@string/tap_to_wake_summary" />
+ <ListPreference
+ android:key="theme"
+ android:title="@string/device_theme"
+ android:summary="%s" />
+
<Preference
android:key="wifi_display"
android:title="@string/wifi_display_settings_title"
diff --git a/res/xml/special_access.xml b/res/xml/special_access.xml
index 7d85195..8bf5c56 100644
--- a/res/xml/special_access.xml
+++ b/res/xml/special_access.xml
@@ -75,6 +75,11 @@
android:fragment="com.android.settings.notification.NotificationAccessSettings" />
<Preference
+ android:key="picture_in_picture"
+ android:title="@string/picture_in_picture_title"
+ android:fragment="com.android.settings.applications.PictureInPictureSettings"
+ settings:keywords="@string/picture_in_picture_keywords" />
+ <Preference
android:key="premium_sms"
android:title="@string/premium_sms_access"
android:fragment="com.android.settings.applications.PremiumSmsAccess" />
diff --git a/res/xml/tts_engine_picker.xml b/res/xml/tts_engine_picker.xml
new file mode 100644
index 0000000..61e3e7e
--- /dev/null
+++ b/res/xml/tts_engine_picker.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:title="@string/tts_settings_title">
+
+ <PreferenceCategory android:key="tts_engine_preference_category"
+ android:title="@string/tts_engine_preference_title"/>
+
+</PreferenceScreen>
diff --git a/res/xml/tts_engine_settings.xml b/res/xml/tts_engine_settings.xml
deleted file mode 100644
index d00c102..0000000
--- a/res/xml/tts_engine_settings.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:title="">
- <ListPreference
- android:key="tts_default_lang"
- android:title="@string/tts_default_lang_title"
- android:summary="@string/tts_default_lang_summary"
- android:order="100" />
-
- <Preference
- android:key="tts_engine_settings"
- android:persistent="false"
- android:title="@string/tts_engine_settings_title"
- android:order="200" />
-
- <Preference
- android:key="tts_install_data"
- android:persistent="false"
- android:title="@string/tts_install_data_title"
- android:summary="@string/tts_install_data_summary"
- android:order="300" />
-</PreferenceScreen>
diff --git a/res/xml/tts_settings.xml b/res/xml/tts_settings.xml
index 31d2b17..b02a749 100644
--- a/res/xml/tts_settings.xml
+++ b/res/xml/tts_settings.xml
@@ -17,38 +17,40 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/tts_settings_title">
- <!-- The contents of this category are filled in by the Java code
- based on the list of available engines. -->
- <PreferenceCategory android:key="tts_engine_preference_section"
- android:title="@string/tts_engine_preference_section_title" />
+ <PreferenceCategory android:key="tts_engine_section"
+ android:title="@string/tts_engine_section_title">
+
+ <com.android.settingslib.RestrictedPreference android:key="tts_engine_preference"
+ android:title="@string/tts_engine_preference_title"
+ android:fragment="com.android.settings.tts.TtsEnginePreferenceFragment"/>
+
+ <Preference
+ android:key="tts_engine_settings"
+ android:persistent="false"
+ android:title="@string/tts_engine_settings_title"
+ android:order="200" />
+
+ <Preference
+ android:key="tts_install_data"
+ android:persistent="false"
+ android:title="@string/tts_install_voice_title"
+ android:order="300" />
+
+ </PreferenceCategory>
<PreferenceCategory android:key="tts_general_section"
android:title="@string/tts_general_section_title">
- <!-- The max value for seek bars here should be kept in sync
- with the max value specified in TextToSpeechSettings class. -->
- <com.android.settings.SeekBarPreference
- android:key="tts_default_rate"
- android:title="@string/tts_default_rate_title"
- android:summary="@string/tts_default_rate_summary"
- android:defaultValue="50"
- android:max="600"/>
- <com.android.settings.SeekBarPreference
- android:key="tts_default_pitch"
- android:title="@string/tts_default_pitch_title"
- android:summary="@string/tts_default_pitch_summary"
- android:defaultValue="100"
- android:max="400"/>
+ <com.android.settingslib.RestrictedPreference
+ android:key="tts_sliders"
+ android:title="@string/tts_sliders_title"
+ android:fragment="com.android.settings.tts.TtsSlidersFragment"/>
- <Preference android:key="reset_speech_rate"
- android:persistent="false"
- android:title="@string/tts_reset_speech_rate_title"
- android:summary="@string/tts_reset_speech_rate_summary" />
-
- <Preference android:key="reset_speech_pitch"
- android:persistent="false"
- android:title="@string/tts_reset_speech_pitch_title"
- android:summary="@string/tts_reset_speech_pitch_summary" />
+ <ListPreference
+ android:key="tts_default_lang"
+ android:title="@string/tts_default_lang_title"
+ android:summary="@string/tts_default_lang_summary"
+ android:persistent="false" />
<Preference android:key="tts_play_example"
android:persistent="false"
diff --git a/res/xml/tts_sliders.xml b/res/xml/tts_sliders.xml
new file mode 100644
index 0000000..3c767b0
--- /dev/null
+++ b/res/xml/tts_sliders.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:title="@string/tts_settings_title">
+
+ <!-- The max value for seek bars here should be kept in sync
+ with the max value specified in TextToSpeechSettings class. -->
+ <com.android.settings.SeekBarPreference
+ android:key="tts_default_rate"
+ android:title="@string/tts_default_rate_title"
+ android:summary="@string/tts_default_rate_summary"
+ android:defaultValue="50"
+ android:max="600"/>
+
+ <com.android.settings.SeekBarPreference
+ android:key="tts_default_pitch"
+ android:title="@string/tts_default_pitch_title"
+ android:summary="@string/tts_default_pitch_summary"
+ android:defaultValue="100"
+ android:max="400"/>
+
+ <Preference android:key="reset_speech_rate"
+ android:persistent="false"
+ android:title="@string/tts_reset_speech_rate_title"
+ android:summary="@string/tts_reset_speech_rate_summary" />
+
+ <Preference android:key="reset_speech_pitch"
+ android:persistent="false"
+ android:title="@string/tts_reset_speech_pitch_title"
+ android:summary="@string/tts_reset_speech_pitch_summary" />
+
+</PreferenceScreen>
diff --git a/res/xml/wifi_configure_settings.xml b/res/xml/wifi_configure_settings.xml
index a43ea87..1e04749 100644
--- a/res/xml/wifi_configure_settings.xml
+++ b/res/xml/wifi_configure_settings.xml
@@ -17,11 +17,6 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/wifi_configure_titlebar">
- <Preference
- android:key="saved_networks"
- android:title="@string/wifi_saved_access_points_label"
- android:fragment="com.android.settings.wifi.SavedAccessPointsWifiSettings" />
-
<!-- android:dependency="enable_wifi" -->
<ListPreference
android:key="sleep_policy"
diff --git a/res/xml/wifi_settings.xml b/res/xml/wifi_settings.xml
index b4ab126..15743ea 100644
--- a/res/xml/wifi_settings.xml
+++ b/res/xml/wifi_settings.xml
@@ -14,14 +14,23 @@
limitations under the License.
-->
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
android:title="@string/wifi_settings"
settings:keywords="@string/keywords_wifi">
- <!-- Needed so PreferenceGroupAdapter allows AccessPointPreference to be
- recycled. Removed in onResume -->
- <com.android.settings.wifi.LongPressAccessPointPreference
- android:key="dummy" />
+ <PreferenceCategory android:key="access_points"/>
+ <PreferenceCategory android:key="additional_settings">
+ <Preference
+ android:key="configure_settings"
+ android:title="@string/wifi_configure_settings_preference_title"
+ android:fragment="com.android.settings.wifi.ConfigureWifiSettings" />
+
+ <Preference
+ android:key="saved_networks"
+ android:title="@string/wifi_saved_access_points_label"
+ android:fragment="com.android.settings.wifi.SavedAccessPointsWifiSettings" />
+ </PreferenceCategory>
</PreferenceScreen>
diff --git a/src/com/android/settings/AppListPreference.java b/src/com/android/settings/AppListPreference.java
index 1fe0c13..8585454 100644
--- a/src/com/android/settings/AppListPreference.java
+++ b/src/com/android/settings/AppListPreference.java
@@ -49,7 +49,11 @@
* Extends ListPreference to allow us to show the icons for a given list of applications. We do this
* because the names of applications are very similar and the user may not be able to determine what
* app they are selecting without an icon.
+ *
+ * @deprecated Selecting app from a list should be done in full UI. Use DefaultAppPickerFragment
+ * instead.
*/
+@Deprecated
public class AppListPreference extends CustomListPreference {
public static final String ITEM_NONE_VALUE = "";
@@ -57,6 +61,8 @@
protected final boolean mForWork;
protected final int mUserId;
+
+ private boolean mSavesState = true;
private Drawable[] mEntryDrawables;
private boolean mShowItemNone = false;
private CharSequence[] mSummaries;
@@ -126,6 +132,10 @@
: UserHandle.myUserId();
}
+ public void setSavesState(boolean savesState) {
+ mSavesState = savesState;
+ }
+
public void setShowItemNone(boolean showItemNone) {
mShowItemNone = showItemNone;
}
@@ -257,12 +267,16 @@
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
- return new SavedState(getEntryValues(), getValue(), mSummaries, mShowItemNone, superState);
+ if (mSavesState) {
+ return new SavedState(getEntryValues(), getValue(), mSummaries, mShowItemNone, superState);
+ } else {
+ return superState;
+ }
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
- if (state instanceof SavedState) {
+ if (mSavesState || state instanceof SavedState) {
SavedState savedState = (SavedState) state;
mShowItemNone = savedState.showItemNone;
setPackageNames(savedState.entryValues, savedState.value);
diff --git a/src/com/android/settings/AppListSwitchPreference.java b/src/com/android/settings/AppListSwitchPreference.java
deleted file mode 100644
index 007f243..0000000
--- a/src/com/android/settings/AppListSwitchPreference.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.android.settings;
-
-import android.content.Context;
-import android.support.v7.preference.PreferenceViewHolder;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.widget.Checkable;
-
-/**
- * A hybrid of AppListPreference and SwitchPreference, representing a preference which can be on or
- * off but must have a selected value when turned on.
- *
- * It is invalid to show this preference when zero valid apps are present.
- */
-public class AppListSwitchPreference extends AppListPreference {
- private static final String TAG = "AppListSwitchPref";
-
- private Checkable mSwitch;
-
- public AppListSwitchPreference(Context context, AttributeSet attrs) {
- super(context, attrs, 0, R.style.AppListSwitchPreference);
- }
-
- @Override
- public void onBindViewHolder(PreferenceViewHolder view) {
- super.onBindViewHolder(view);
- mSwitch = (Checkable) view.findViewById(com.android.internal.R.id.switch_widget);
- mSwitch.setChecked(getValue() != null);
- }
-
- @Override
- protected void onClick() {
- if (getValue() != null) {
- // Turning off the current value.
- if (callChangeListener(null)) {
- setValue(null);
- }
- } else if (getEntryValues() == null || getEntryValues().length == 0) {
- Log.e(TAG, "Attempting to show dialog with zero entries: " + getKey());
- } else if (getEntryValues().length == 1) {
- // Suppress the dialog and just toggle the preference with the only choice.
- String value = getEntryValues()[0].toString();
- if (callChangeListener(value)) {
- setValue(value);
- }
- } else {
- super.onClick();
- }
- }
-
- @Override
- public void setValue(String value) {
- super.setValue(value);
- if (mSwitch != null) {
- mSwitch.setChecked(value != null);
- }
- }
-}
diff --git a/src/com/android/settings/BackupSettingsActivity.java b/src/com/android/settings/BackupSettingsActivity.java
index a4cc4b7..c0456b2 100644
--- a/src/com/android/settings/BackupSettingsActivity.java
+++ b/src/com/android/settings/BackupSettingsActivity.java
@@ -17,19 +17,13 @@
package com.android.settings;
import android.app.Activity;
-import android.app.backup.BackupManager;
-import android.app.backup.IBackupManager;
-import android.content.Context;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.Bundle;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.text.TextUtils;
import android.util.Log;
-import com.android.settings.R;
-
-import java.net.URISyntaxException;
+import com.android.settings.Settings.PrivacySettingsActivity;
/**
* A trampoline activity used to launch the configured Backup activity.
@@ -42,30 +36,24 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- String backup = getResources().getString(R.string.config_backup_settings_intent);
- if (!TextUtils.isEmpty(backup)) {
- try {
- Intent intent = Intent.parseUri(backup, 0);
- if (intent.resolveActivity(getPackageManager()) != null) {
- // use startActivityForResult to let the activity check the caller signature
- IBackupManager bmgr = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
- boolean backupOkay;
- try {
- backupOkay = bmgr.isBackupServiceActive(UserHandle.myUserId());
- } catch (Exception e) {
- // things go wrong talking to the backup system => ignore and
- // pass the default 'false' as the "backup is a thing?" state.
- backupOkay = false;
- }
- intent.putExtra(BackupManager.EXTRA_BACKUP_SERVICES_AVAILABLE, backupOkay);
- startActivityForResult(intent, -1);
- } else {
- Log.e(TAG, "Backup component not found!");
- }
- } catch (URISyntaxException e) {
- Log.e(TAG, "Invalid backup component URI!", e);
+
+ BackupSettingsHelper backupHelper = new BackupSettingsHelper();
+ if (backupHelper.isIntentProvidedByTransport(getPackageManager())) {
+ Intent intent = backupHelper.getIntentForBackupSettings();
+ if (intent != null) {
+ // use startActivityForResult to let the activity check the caller signature
+ startActivityForResult(intent, -1);
}
+ } else {
+ // This should never happen, because isIntentProvidedByTransport() is called before
+ // starting this activity.
+ Log.e(TAG, "Backup transport has not provided an intent"
+ + " or the component for the intent is not found!");
+ getPackageManager().setComponentEnabledSetting(
+ new ComponentName(getPackageName(), PrivacySettingsActivity.class.getName()),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP);
+ startActivityForResult(new Intent(this, PrivacySettingsActivity.class), -1);
}
finish();
}
diff --git a/src/com/android/settings/BackupSettingsHelper.java b/src/com/android/settings/BackupSettingsHelper.java
new file mode 100644
index 0000000..37c0971
--- /dev/null
+++ b/src/com/android/settings/BackupSettingsHelper.java
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+
+import android.app.backup.BackupManager;
+import android.app.backup.BackupTransport;
+import android.app.backup.IBackupManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Helper class for {@link BackupSettingsActivity} that interacts with {@link IBackupManager}.
+ */
+public class BackupSettingsHelper {
+ private static final String TAG = "BackupSettingsHelper";
+
+ private IBackupManager mBackupManager = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+
+
+ /**
+ * Gets the intent from Backup transport and adds the extra depending on whether the user has
+ * rights to see backup settings.
+ *
+ * @return Intent to launch Backup settings provided by the Backup transport.
+ */
+ public Intent getIntentForBackupSettings() {
+ Intent intent = getIntentFromBackupTransport();
+ if (intent != null) {
+ intent.putExtra(BackupManager.EXTRA_BACKUP_SERVICES_AVAILABLE, isBackupServiceActive());
+ }
+ return intent;
+ }
+
+
+ /**
+ * Checks if the transport provided the intent to launch the backup settings and if that
+ * intent resolves to an activity.
+ */
+ public boolean isIntentProvidedByTransport(PackageManager packageManager) {
+ Intent intent = getIntentFromBackupTransport();
+ return intent != null && intent.resolveActivity(packageManager) != null;
+ }
+
+ /**
+ * Gets an intent to launch the backup settings from the current transport using
+ * {@link BackupTransport#dataManagementIntent()} API.
+ *
+ * @return intent provided by transport or null if no intent was provided.
+ */
+ private Intent getIntentFromBackupTransport() {
+ try {
+ Intent intent =
+ mBackupManager.getDataManagementIntent(mBackupManager.getCurrentTransport());
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (intent != null) {
+ Log.d(TAG, "Parsed intent from backup transport: " + intent.toString());
+ } else {
+ Log.d(TAG, "Received a null intent from backup transport");
+ }
+ }
+ return intent;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error getting data management intent", e);
+ }
+ return null;
+ }
+
+ /** Checks if backup service is enabled for this user. */
+ private boolean isBackupServiceActive() {
+ boolean backupOkay;
+ try {
+ backupOkay = mBackupManager.isBackupServiceActive(UserHandle.myUserId());
+ } catch (Exception e) {
+ // things go wrong talking to the backup system => ignore and
+ // pass the default 'false' as the "backup is a thing?" state.
+ backupOkay = false;
+ }
+ return backupOkay;
+ }
+}
diff --git a/src/com/android/settings/DateTimeSettings.java b/src/com/android/settings/DateTimeSettings.java
index fc47fef..326f200 100644
--- a/src/com/android/settings/DateTimeSettings.java
+++ b/src/com/android/settings/DateTimeSettings.java
@@ -56,11 +56,6 @@
}
@Override
- protected String getCategoryKey() {
- return null;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
index 8184905..749eb11 100644
--- a/src/com/android/settings/DevelopmentSettings.java
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -78,7 +78,7 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.webkit.IWebViewUpdateService;
-import android.webkit.WebViewProviderInfo;
+import android.webkit.WebViewFactory;
import android.widget.Switch;
import android.widget.Toast;
@@ -92,6 +92,7 @@
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
+import com.android.settings.webview.WebViewAppPreferenceController;
import com.android.settings.widget.SwitchBar;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -126,7 +127,6 @@
private static final String ENABLE_TERMINAL = "enable_terminal";
private static final String KEEP_SCREEN_ON = "keep_screen_on";
private static final String BT_HCI_SNOOP_LOG = "bt_hci_snoop_log";
- private static final String WEBVIEW_PROVIDER_KEY = "select_webview_provider";
private static final String WEBVIEW_MULTIPROCESS_KEY = "enable_webview_multiprocess";
private static final String ENABLE_OEM_UNLOCK = "oem_unlock_enable";
private static final String HDCP_CHECKING_KEY = "hdcp_checking";
@@ -230,6 +230,7 @@
private static final int RESULT_DEBUG_APP = 1000;
private static final int RESULT_MOCK_LOCATION_APP = 1001;
+ private static final int RESULT_WEBVIEW_APP = 1002;
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
private static final String FLASH_LOCKED_PROP = "ro.boot.flash.locked";
@@ -310,8 +311,8 @@
private ListPreference mAnimatorDurationScale;
private ListPreference mOverlayDisplayDevices;
+ private WebViewAppPreferenceController mWebViewAppPrefController;
private SwitchPreference mWebViewMultiprocess;
- private ListPreference mWebViewProvider;
private ListPreference mSimulateColorSpace;
@@ -371,8 +372,7 @@
mWindowManager = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
mBackupManager = IBackupManager.Stub.asInterface(
ServiceManager.getService(Context.BACKUP_SERVICE));
- mWebViewUpdateService =
- IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
+ mWebViewUpdateService = WebViewFactory.getUpdateService();
mOemUnlockManager = (PersistentDataBlockManager) getActivity()
.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
mTelephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
@@ -384,6 +384,7 @@
mBugReportController = new BugReportPreferenceController(getActivity());
mBugReportInPowerController = new BugReportInPowerPreferenceController(getActivity());
+ mWebViewAppPrefController = new WebViewAppPreferenceController(getActivity());
setIfOnlyAvailableForAdmins(true);
if (isUiRestricted() || !Utils.isDeviceProvisioned(getActivity())) {
@@ -414,6 +415,7 @@
mBugReportController.displayPreference(getPreferenceScreen());
mBugReportInPowerController.displayPreference(getPreferenceScreen());
+ mWebViewAppPrefController.displayPreference(getPreferenceScreen());
mKeepScreenOn = (RestrictedSwitchPreference) findAndInitSwitchPref(KEEP_SCREEN_ON);
mBtHciSnoopLog = findAndInitSwitchPref(BT_HCI_SNOOP_LOG);
@@ -483,7 +485,6 @@
mLogpersist = null;
}
mUsbConfiguration = addListPreference(USB_CONFIGURATION_KEY);
- mWebViewProvider = addListPreference(WEBVIEW_PROVIDER_KEY);
mWebViewMultiprocess = findAndInitSwitchPref(WEBVIEW_MULTIPROCESS_KEY);
mBluetoothDisableAbsVolume = findAndInitSwitchPref(BLUETOOTH_DISABLE_ABSOLUTE_VOLUME_KEY);
@@ -544,7 +545,6 @@
removePreference(KEY_COLOR_MODE);
mColorModePreference = null;
}
- updateWebViewProviderOptions();
mColorTemperaturePreference = (SwitchPreference) findPreference(COLOR_TEMPERATURE_KEY);
if (getResources().getBoolean(R.bool.config_enableColorTemperature)) {
@@ -629,6 +629,7 @@
pref.setEnabled(enabled && !mDisabledPrefs.contains(pref));
}
mBugReportInPowerController.enablePreference(enabled);
+ mWebViewAppPrefController.enablePreference(enabled);
updateAllOptions();
}
@@ -793,8 +794,8 @@
updateSimulateColorSpace();
updateUSBAudioOptions();
updateForceResizableOptions();
+ mWebViewAppPrefController.updateState(null);
updateWebViewMultiprocessOptions();
- updateWebViewProviderOptions();
updateOemUnlockOptions();
if (mColorTemperaturePreference != null) {
updateColorTemperature();
@@ -831,39 +832,6 @@
pokeSystemProperties();
}
- private void updateWebViewProviderOptions() {
- try {
- WebViewProviderInfo[] providers = mWebViewUpdateService.getValidWebViewPackages();
- if (providers == null) {
- Log.e(TAG, "No WebView providers available");
- return;
- }
- ArrayList<String> options = new ArrayList<String>();
- ArrayList<String> values = new ArrayList<String>();
- for (int n = 0; n < providers.length; n++) {
- if (Utils.isPackageEnabled(getActivity(), providers[n].packageName)) {
- options.add(providers[n].description);
- values.add(providers[n].packageName);
- }
- }
- mWebViewProvider.setEntries(options.toArray(new String[options.size()]));
- mWebViewProvider.setEntryValues(values.toArray(new String[values.size()]));
-
- String value = mWebViewUpdateService.getCurrentWebViewPackageName();
- if (value == null) {
- value = "";
- }
-
- for (int i = 0; i < values.size(); i++) {
- if (value.contentEquals(values.get(i))) {
- mWebViewProvider.setValueIndex(i);
- return;
- }
- }
- } catch (RemoteException e) {
- }
- }
-
private void updateWebViewMultiprocessOptions() {
try {
updateSwitchPreference(mWebViewMultiprocess,
@@ -918,17 +886,6 @@
mBtHciSnoopLog.isChecked() ? 1 : 0);
}
- private boolean writeWebViewProviderOptions(Object newValue) {
- try {
- String updatedProvider = mWebViewUpdateService.changeProviderAndSetting(
- newValue == null ? "" : newValue.toString());
- updateWebViewProviderOptions();
- return newValue != null && newValue.equals(updatedProvider);
- } catch (RemoteException e) {
- }
- return false;
- }
-
private void writeDebuggerOptions() {
try {
ActivityManager.getService().setDebugApp(
@@ -2327,6 +2284,8 @@
writeMockLocation();
updateMockLocation();
}
+ } else if (requestCode == RESULT_WEBVIEW_APP) {
+ mWebViewAppPrefController.onActivityResult(resultCode, data);
} else if (requestCode == REQUEST_CODE_ENABLE_OEM_UNLOCK) {
if (resultCode == Activity.RESULT_OK) {
if (mEnableOemUnlock.isChecked()) {
@@ -2349,6 +2308,10 @@
if (mBugReportInPowerController.handlePreferenceTreeClick(preference)) {
return true;
}
+ if (mWebViewAppPrefController.handlePreferenceTreeClick(preference)) {
+ startActivityForResult(
+ mWebViewAppPrefController.getActivityIntent(), RESULT_WEBVIEW_APP);
+ }
if (preference == mEnableAdb) {
if (mEnableAdb.isChecked()) {
@@ -2502,21 +2465,6 @@
updateHdcpValues();
pokeSystemProperties();
return true;
- } else if (preference == mWebViewProvider) {
- if (newValue == null) {
- Log.e(TAG, "Tried to set a null WebView provider");
- return false;
- }
- if (writeWebViewProviderOptions(newValue)) {
- return true;
- } else {
- // The user chose a package that became invalid since the list was last updated,
- // show a Toast to explain the situation.
- Toast toast = Toast.makeText(getActivity(),
- R.string.select_webview_provider_toast_text, Toast.LENGTH_SHORT);
- toast.show();
- }
- return false;
} else if ((preference == mBluetoothSelectA2dpCodec) ||
(preference == mBluetoothSelectA2dpSampleRate) ||
(preference == mBluetoothSelectA2dpBitsPerSample) ||
diff --git a/src/com/android/settings/DisplaySettings.java b/src/com/android/settings/DisplaySettings.java
index 91b193d..320cb83 100644
--- a/src/com/android/settings/DisplaySettings.java
+++ b/src/com/android/settings/DisplaySettings.java
@@ -35,6 +35,7 @@
import com.android.settings.display.NightModePreferenceController;
import com.android.settings.display.ScreenSaverPreferenceController;
import com.android.settings.display.TapToWakePreferenceController;
+import com.android.settings.display.ThemePreferenceController;
import com.android.settings.display.TimeoutPreferenceController;
import com.android.settings.display.VrDisplayPreferenceController;
import com.android.settings.display.WallpaperPreferenceController;
@@ -68,11 +69,6 @@
}
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_DISPLAY;
- }
-
- @Override
protected int getPreferenceScreenResId() {
if (mDashboardFeatureProvider.isEnabled()) {
return R.xml.ia_display_settings;
@@ -97,6 +93,7 @@
controllers.add(new TimeoutPreferenceController(context));
controllers.add(new VrDisplayPreferenceController(context));
controllers.add(new WallpaperPreferenceController(context));
+ controllers.add(new ThemePreferenceController(context));
return controllers;
}
@@ -182,6 +179,7 @@
new TimeoutPreferenceController(context).updateNonIndexableKeys(result);
new VrDisplayPreferenceController(context).updateNonIndexableKeys(result);
new WallpaperPreferenceController(context).updateNonIndexableKeys(result);
+ new ThemePreferenceController(context).updateNonIndexableKeys(result);
return result;
}
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index a393436..360d258 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -105,6 +105,7 @@
public static class UserSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationAccessSettingsActivity extends SettingsActivity { /* empty */ }
public static class VrListenersSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class PictureInPictureSettingsActivity extends SettingsActivity { /* empty */ }
public static class ZenAccessSettingsActivity extends SettingsActivity { /* empty */ }
public static class ConditionProviderSettingsActivity extends SettingsActivity { /* empty */ }
public static class UsbSettingsActivity extends SettingsActivity { /* empty */ }
@@ -123,9 +124,11 @@
public static class ConfigureNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class NotificationAppListActivity extends SettingsActivity { /* empty */ }
public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class ChannelNotificationSettingsActivity extends SettingsActivity { /* empty */ }
public static class OtherSoundSettingsActivity extends SettingsActivity { /* empty */ }
public static class ManageDomainUrlsActivity extends SettingsActivity { /* empty */ }
public static class AutomaticStorageManagerSettingsActivity extends SettingsActivity { /* empty */ }
+ public static class GamesStorageActivity extends SettingsActivity { /* empty */ }
public static class TopLevelSettings extends SettingsActivity { /* empty */ }
public static class ApnSettingsActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index c01e4c4..8bb6834 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -33,6 +33,8 @@
import android.content.res.Configuration;
import android.nfc.NfcAdapter;
import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Build.VERSION;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
@@ -965,29 +967,27 @@
}
}
- String backupIntent = getResources().getString(R.string.config_backup_settings_intent);
- boolean useDefaultBackup = TextUtils.isEmpty(backupIntent);
+ // Check if the backup transport has provided an intent to launch the backup settings.
+ BackupSettingsHelper backupHelper = new BackupSettingsHelper();
+ boolean useDefaultBackup = !backupHelper.isIntentProvidedByTransport(getPackageManager());
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Enabling default backup settings page: " + useDefaultBackup);
+ }
+
setTileEnabled(new ComponentName(packageName,
Settings.PrivacySettingsActivity.class.getName()), useDefaultBackup, isAdmin);
setTileEnabled(new ComponentName(packageName,
"com.android.settings.PrivacyDashboardAlias"),
useDefaultBackup, isAdmin);
- boolean hasBackupActivity = false;
- if (!useDefaultBackup) {
- try {
- Intent intent = Intent.parseUri(backupIntent, 0);
- hasBackupActivity = !getPackageManager().queryIntentActivities(intent, 0).isEmpty();
- } catch (URISyntaxException e) {
- Log.e(LOG_TAG, "Invalid backup intent URI!", e);
- }
- }
-
// Enable/disable BackupSettingsActivity and its alias.
+ if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+ Log.v(LOG_TAG, "Enabling transport provided backup settings: " + !useDefaultBackup);
+ }
setTileEnabled(new ComponentName(packageName,
- BackupSettingsActivity.class.getName()), hasBackupActivity, isAdmin);
+ BackupSettingsActivity.class.getName()), !useDefaultBackup, isAdmin);
setTileEnabled(new ComponentName(packageName,
- "com.android.settings.BackupResetDashboardAlias"), hasBackupActivity, isAdmin);
+ "com.android.settings.BackupResetDashboardAlias"), !useDefaultBackup, isAdmin);
setTileEnabled(new ComponentName(packageName,
Settings.EnterprisePrivacySettingsActivity.class.getName()),
diff --git a/src/com/android/settings/SettingsInitialize.java b/src/com/android/settings/SettingsInitialize.java
index 07fec07..66fc4d6 100644
--- a/src/com/android/settings/SettingsInitialize.java
+++ b/src/com/android/settings/SettingsInitialize.java
@@ -44,6 +44,8 @@
private static final String TAG = "Settings";
private static final String PRIMARY_PROFILE_SETTING =
"com.android.settings.PRIMARY_PROFILE_CONTROLLED";
+ private static final String SETTINGS_PACKAGE = "com.android.settings";
+ private static final String WEBVIEW_IMPLEMENTATION_ACTIVITY = ".WebViewImplementation";
@Override
public void onReceive(Context context, Intent broadcast) {
@@ -100,7 +102,7 @@
return;
}
ComponentName settingsComponentName =
- new ComponentName(context, WebViewImplementation.class);
+ new ComponentName(SETTINGS_PACKAGE, SETTINGS_PACKAGE + WEBVIEW_IMPLEMENTATION_ACTIVITY);
pm.setComponentEnabledSetting(settingsComponentName,
userInfo.isAdmin() ?
PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java
index ee500a9..3bad5f3 100644
--- a/src/com/android/settings/SettingsPreferenceFragment.java
+++ b/src/com/android/settings/SettingsPreferenceFragment.java
@@ -727,10 +727,6 @@
getActivity().setResult(result);
}
- protected final Context getPrefContext() {
- return getPreferenceManager().getContext();
- }
-
public boolean startFragment(Fragment caller, String fragmentClass, int titleRes,
int requestCode, Bundle extras) {
final Activity activity = getActivity();
diff --git a/src/com/android/settings/WebViewImplementation.java b/src/com/android/settings/WebViewImplementation.java
deleted file mode 100644
index 349f5e9..0000000
--- a/src/com/android/settings/WebViewImplementation.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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;
-
-import android.annotation.Nullable;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnClickListener;
-import android.content.DialogInterface.OnDismissListener;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserManager;
-import android.util.Log;
-import android.webkit.IWebViewUpdateService;
-import android.webkit.WebViewProviderInfo;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settings.core.InstrumentedActivity;
-
-import java.util.ArrayList;
-
-public class WebViewImplementation extends InstrumentedActivity implements
- OnCancelListener, OnDismissListener {
-
- private static final String TAG = "WebViewImplementation";
-
- private IWebViewUpdateService mWebViewUpdateService;
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (!UserManager.get(this).isAdminUser()) {
- finish();
- return;
- }
- mWebViewUpdateService =
- IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate"));
- try {
- WebViewProviderInfo[] providers = mWebViewUpdateService.getValidWebViewPackages();
- if (providers == null) {
- Log.e(TAG, "No WebView providers available");
- finish();
- return;
- }
-
- String currentValue = mWebViewUpdateService.getCurrentWebViewPackageName();
- if (currentValue == null) {
- currentValue = "";
- }
-
- int currentIndex = -1;
- ArrayList<String> options = new ArrayList<>();
- final ArrayList<String> values = new ArrayList<>();
- for (WebViewProviderInfo provider : providers) {
- if (Utils.isPackageEnabled(this, provider.packageName)) {
- options.add(provider.description);
- values.add(provider.packageName);
- if (currentValue.contentEquals(provider.packageName)) {
- currentIndex = values.size() - 1;
- }
- }
- }
-
- new AlertDialog.Builder(this)
- .setTitle(R.string.select_webview_provider_dialog_title)
- .setSingleChoiceItems(options.toArray(new String[0]), currentIndex,
- new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- try {
- mWebViewUpdateService.changeProviderAndSetting(values.get(which));
- } catch (RemoteException e) {
- Log.w(TAG, "Problem reaching webviewupdate service", e);
- }
- finish();
- }
- }).setNegativeButton(android.R.string.cancel, null)
- .setOnCancelListener(this)
- .setOnDismissListener(this)
- .show();
- } catch (RemoteException e) {
- Log.w(TAG, "Problem reaching webviewupdate service", e);
- finish();
- }
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.WEBVIEW_IMPLEMENTATION;
- }
-
- @Override
- public void onCancel(DialogInterface dialog) {
- finish();
- }
-
- @Override
- public void onDismiss(DialogInterface dialog) {
- finish();
- }
-}
diff --git a/src/com/android/settings/accounts/AccountDetailDashboardFragment.java b/src/com/android/settings/accounts/AccountDetailDashboardFragment.java
index 12f78ff..8143e96 100644
--- a/src/com/android/settings/accounts/AccountDetailDashboardFragment.java
+++ b/src/com/android/settings/accounts/AccountDetailDashboardFragment.java
@@ -22,15 +22,14 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.support.annotation.VisibleForTesting;
-
import android.support.v7.preference.Preference;
+
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.core.PreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.accounts.AuthenticatorHelper;
-import com.android.settingslib.drawer.CategoryKey;
import com.android.settingslib.drawer.Tile;
import java.util.ArrayList;
@@ -62,8 +61,8 @@
Bundle args = getArguments();
final Activity activity = getActivity();
UserHandle userHandle = Utils.getSecureTargetUser(activity.getActivityToken(),
- (UserManager) getSystemService(Context.USER_SERVICE), args,
- activity.getIntent().getExtras());
+ (UserManager) getSystemService(Context.USER_SERVICE), args,
+ activity.getIntent().getExtras());
if (args != null) {
if (args.containsKey(KEY_ACCOUNT)) {
mAccount = args.getParcelable(KEY_ACCOUNT);
@@ -94,11 +93,6 @@
}
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_ACCOUNT;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
@@ -142,6 +136,6 @@
}
final AuthenticatorHelper helper = new AuthenticatorHelper(context, userHandle, null);
headerPreference.setIcon(helper.getDrawableForType(context, mAccountType));
- }
+ }
}
\ No newline at end of file
diff --git a/src/com/android/settings/accounts/UserAndAccountDashboardFragment.java b/src/com/android/settings/accounts/UserAndAccountDashboardFragment.java
index 4113152..dbc0292 100644
--- a/src/com/android/settings/accounts/UserAndAccountDashboardFragment.java
+++ b/src/com/android/settings/accounts/UserAndAccountDashboardFragment.java
@@ -25,7 +25,6 @@
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settingslib.drawer.CategoryKey;
import com.android.settingslib.drawer.Tile;
import java.util.ArrayList;
@@ -45,11 +44,6 @@
}
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_ACCOUNT;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/applications/ActivityInfoWrapper.java b/src/com/android/settings/applications/ActivityInfoWrapper.java
new file mode 100644
index 0000000..4cb6e68
--- /dev/null
+++ b/src/com/android/settings/applications/ActivityInfoWrapper.java
@@ -0,0 +1,30 @@
+/*
+ * 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.applications;
+
+/**
+ * This interface replicates a subset of the android.content.pm.ActivityInfo. The interface
+ * exists so that we can use a thin wrapper around the ActivityInfo in production code and a mock in
+ * tests.
+ */
+public interface ActivityInfoWrapper {
+
+ /**
+ * Returns whether this activity supports picture-in-picture.
+ */
+ boolean supportsPictureInPicture();
+}
diff --git a/src/com/android/settings/applications/ActivityInfoWrapperImpl.java b/src/com/android/settings/applications/ActivityInfoWrapperImpl.java
new file mode 100644
index 0000000..b70a1e8
--- /dev/null
+++ b/src/com/android/settings/applications/ActivityInfoWrapperImpl.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.applications;
+
+import android.content.pm.ActivityInfo;
+
+public class ActivityInfoWrapperImpl implements ActivityInfoWrapper {
+
+ private final ActivityInfo mInfo;
+
+ public ActivityInfoWrapperImpl(ActivityInfo info) {
+ mInfo = info;
+ }
+
+ @Override
+ public boolean supportsPictureInPicture() {
+ return mInfo.supportsPictureInPicture();
+ }
+}
diff --git a/src/com/android/settings/applications/AdvancedAppSettings.java b/src/com/android/settings/applications/AdvancedAppSettings.java
index 5bce24d..e59f94b 100644
--- a/src/com/android/settings/applications/AdvancedAppSettings.java
+++ b/src/com/android/settings/applications/AdvancedAppSettings.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -20,14 +20,19 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
-import com.android.settings.Utils;
+import com.android.settings.applications.defaultapps.DefaultBrowserPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultEmergencyPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultHomePreferenceController;
+import com.android.settings.applications.defaultapps.DefaultPhonePreferenceController;
+import com.android.settings.applications.defaultapps.DefaultSmsPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultWorkBrowserPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultWorkPhonePreferenceController;
import com.android.settings.core.PreferenceController;
import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
-import com.android.settingslib.drawer.CategoryKey;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -36,25 +41,26 @@
static final String TAG = "AdvancedAppSettings";
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_APPS_DEFAULT;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
- return mDashboardFeatureProvider.isEnabled()
- ? R.xml.app_default_settings
- : R.xml.advanced_apps;
+ return R.xml.app_default_settings;
}
@Override
protected List<PreferenceController> getPreferenceControllers(Context context) {
- return null;
+ final List<PreferenceController> controllers = new ArrayList<>();
+ controllers.add(new DefaultBrowserPreferenceController(context));
+ controllers.add(new DefaultWorkBrowserPreferenceController(context));
+ controllers.add(new DefaultPhonePreferenceController(context));
+ controllers.add(new DefaultWorkPhonePreferenceController(context));
+ controllers.add(new DefaultSmsPreferenceController(context));
+ controllers.add(new DefaultEmergencyPreferenceController(context));
+ controllers.add(new DefaultHomePreferenceController(context));
+ return controllers;
}
@Override
@@ -68,16 +74,8 @@
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
- sir.xmlResId = FeatureFactory.getFactory(context)
- .getDashboardFeatureProvider(context).isEnabled()
- ? R.xml.app_default_settings
- : R.xml.advanced_apps;
+ sir.xmlResId = R.xml.app_default_settings;
return Arrays.asList(sir);
}
-
- @Override
- public List<String> getNonIndexableKeys(Context context) {
- return Utils.getNonIndexable(R.xml.advanced_apps, context);
- }
};
}
diff --git a/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java b/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
index be70f86..5dda9c1 100644
--- a/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
+++ b/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
@@ -25,7 +25,6 @@
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settingslib.drawer.CategoryKey;
import java.util.Arrays;
import java.util.List;
@@ -40,17 +39,6 @@
}
@Override
- public void onAttach(Context context) {
- super.onAttach(context);
- mProgressiveDisclosureMixin.setTileLimit(3);
- }
-
- @Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_APPS;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounter.java b/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounter.java
index 331d384..52f1da5 100644
--- a/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounter.java
+++ b/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounter.java
@@ -17,7 +17,6 @@
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.RemoteException;
@@ -33,11 +32,11 @@
private final String[] mPermissions;
private final PackageManagerWrapper mPackageManager;
- private final IPackageManager mPackageManagerService;
+ private final IPackageManagerWrapper mPackageManagerService;
private final DevicePolicyManagerWrapper mDevicePolicyManager;
public AppWithAdminGrantedPermissionsCounter(Context context, String[] permissions,
- PackageManagerWrapper packageManager, IPackageManager packageManagerService,
+ PackageManagerWrapper packageManager, IPackageManagerWrapper packageManagerService,
DevicePolicyManagerWrapper devicePolicyManager) {
super(context, packageManager);
mPermissions = permissions;
diff --git a/src/com/android/settings/applications/ApplicationFeatureProvider.java b/src/com/android/settings/applications/ApplicationFeatureProvider.java
index 101ae91..fccbbd3 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProvider.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProvider.java
@@ -17,8 +17,11 @@
package com.android.settings.applications;
import android.app.Fragment;
+import android.content.Intent;
import android.view.View;
+import java.util.Set;
+
public interface ApplicationFeatureProvider {
/**
@@ -55,9 +58,47 @@
NumberOfAppsCallback callback);
/**
+ * Return the persistent preferred activities configured by the admin for the current user and
+ * all its managed profiles. A persistent preferred activity is an activity that the admin
+ * configured to always handle a given intent (e.g. open browser), even if the user has other
+ * apps installed that would also be able to handle the intent.
+ *
+ * @param intent The intents for which to find persistent preferred activities
+ *
+ * @return the persistent preferred activites for the given intent
+ */
+ Set<PersistentPreferredActivityInfo> findPersistentPreferredActivities(Intent[] intents);
+
+ /**
* Callback that receives the number of packages installed on the device.
*/
interface NumberOfAppsCallback {
void onNumberOfAppsResult(int num);
}
+
+ public static class PersistentPreferredActivityInfo {
+ public final String packageName;
+ public final int userId;
+
+ public PersistentPreferredActivityInfo(String packageName, int userId) {
+ this.packageName = packageName;
+ this.userId = userId;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof PersistentPreferredActivityInfo)) {
+ return false;
+ }
+ final PersistentPreferredActivityInfo otherActivityInfo
+ = (PersistentPreferredActivityInfo) other;
+ return otherActivityInfo.packageName.equals(packageName)
+ && otherActivityInfo.userId == userId;
+ }
+
+ @Override
+ public int hashCode() {
+ return packageName.hashCode() ^ userId;
+ }
+ }
}
diff --git a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
index ff7f0f0..2d2bce0 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProviderImpl.java
@@ -18,25 +18,34 @@
import android.app.Fragment;
import android.content.Context;
-import android.content.pm.IPackageManager;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ComponentInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
+import android.util.ArraySet;
import android.view.View;
import com.android.settings.enterprise.DevicePolicyManagerWrapper;
import java.util.List;
+import java.util.Set;
public class ApplicationFeatureProviderImpl implements ApplicationFeatureProvider {
private final Context mContext;
private final PackageManagerWrapper mPm;
- private final IPackageManager mPms;
+ private final IPackageManagerWrapper mPms;
private final DevicePolicyManagerWrapper mDpm;
private final UserManager mUm;
public ApplicationFeatureProviderImpl(Context context, PackageManagerWrapper pm,
- IPackageManager pms, DevicePolicyManagerWrapper dpm) {
+ IPackageManagerWrapper pms, DevicePolicyManagerWrapper dpm) {
mContext = context.getApplicationContext();
mPm = pm;
mPms = pms;
@@ -61,6 +70,39 @@
callback).execute();
}
+ @Override
+ public Set<PersistentPreferredActivityInfo> findPersistentPreferredActivities(
+ Intent[] intents) {
+ final Set<PersistentPreferredActivityInfo> activities = new ArraySet<>();
+ final List<UserHandle> users = mUm.getUserProfiles();
+ for (final Intent intent : intents) {
+ for (final UserHandle user : users) {
+ final int userId = user.getIdentifier();
+ try {
+ final ResolveInfo resolveInfo = mPms.findPersistentPreferredActivity(intent,
+ userId);
+ if (resolveInfo != null) {
+ ComponentInfo componentInfo = null;
+ if (resolveInfo.activityInfo != null) {
+ componentInfo = resolveInfo.activityInfo;
+ } else if (resolveInfo.serviceInfo != null) {
+ componentInfo = resolveInfo.serviceInfo;
+ } else if (resolveInfo.providerInfo != null) {
+ componentInfo = resolveInfo.providerInfo;
+ }
+ if (componentInfo != null) {
+ activities.add(new PersistentPreferredActivityInfo(
+ componentInfo.packageName, userId));
+ }
+ }
+ } catch (RemoteException exception) {
+ }
+ }
+
+ }
+ return activities;
+ }
+
private static class AllUserInstalledAppCounter extends InstalledAppCounter {
private NumberOfAppsCallback mCallback;
@@ -86,7 +128,7 @@
private NumberOfAppsCallback mCallback;
AllUserAppWithAdminGrantedPermissionsCounter(Context context, String[] permissions,
- PackageManagerWrapper packageManager, IPackageManager packageManagerService,
+ PackageManagerWrapper packageManager, IPackageManagerWrapper packageManagerService,
DevicePolicyManagerWrapper devicePolicyManager, NumberOfAppsCallback callback) {
super(context, permissions, packageManager, packageManagerService, devicePolicyManager);
mCallback = callback;
diff --git a/src/com/android/settings/applications/DefaultAutoFillPreference.java b/src/com/android/settings/applications/DefaultAutoFillPreference.java
new file mode 100644
index 0000000..9ed2e19
--- /dev/null
+++ b/src/com/android/settings/applications/DefaultAutoFillPreference.java
@@ -0,0 +1,141 @@
+/**
+ * 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.applications;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Parcelable;
+import android.provider.Settings;
+import android.service.autofill.AutoFillService;
+import android.service.autofill.AutoFillServiceInfo;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.settings.R;
+import com.android.settings.AppListPreferenceWithSettings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultAutoFillPreference extends AppListPreferenceWithSettings {
+ private static final String TAG = "DefaultAutoFill";
+
+ private static final String SETTING = Settings.Secure.AUTO_FILL_SERVICE;
+
+ public DefaultAutoFillPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ setSavesState(false);
+ setShowItemNone(true);
+
+ refreshData();
+ }
+
+ @Override
+ protected CharSequence getConfirmationMessage(String value) {
+ if (value == null || value.isEmpty()) {
+ return null;
+ }
+
+ int index = findIndexOfValue(value);
+ CharSequence[] entries = getEntries();
+ if (index < 0 || index >= entries.length) {
+ return null;
+ }
+
+ CharSequence entry = entries[index];
+ return getContext().getString(R.string.autofill_confirmation_message, entry);
+ }
+
+ @Override
+ protected boolean persistString(String value) {
+ Settings.Secure.putString(getContext().getContentResolver(), SETTING, value);
+ refreshData();
+ return true;
+ }
+
+ private void refreshData() {
+ ComponentName selectedComponent = getSelectedComponentName();
+ List<AutoFillServiceInfo> infos = getInfos();
+
+ AutoFillServiceInfo selectedInfo = null;
+ int numberOfComponents = infos.size();
+ ComponentName[] components = new ComponentName[numberOfComponents];
+ for (int i = 0; i < numberOfComponents; ++i) {
+ AutoFillServiceInfo info = infos.get(i);
+ ServiceInfo serviceInfo = info.getServiceInfo();
+ ComponentName component =
+ new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ components[i] = component;
+
+ if (component.equals(selectedComponent)) {
+ selectedInfo = info;
+ }
+ }
+
+ ComponentName selectedComponentSettings = null;
+ if (selectedInfo != null) {
+ String settingsActivity = selectedInfo.getSettingsActivity();
+ selectedComponentSettings = settingsActivity != null
+ ? new ComponentName(selectedComponent.getPackageName(), settingsActivity)
+ : null;
+ } else { // selected component not found
+ Log.w(TAG, "Selected AutoFillService not found " + selectedComponent);
+ selectedComponent = null;
+ selectedComponentSettings = null;
+ }
+
+ setComponentNames(components, selectedComponent);
+ setSettingsComponent(selectedComponentSettings);
+ setSummary(getEntry());
+ }
+
+ @Nullable
+ private ComponentName getSelectedComponentName() {
+ String componentString =
+ Settings.Secure.getString(getContext().getContentResolver(), SETTING);
+ if (componentString == null) {
+ return null;
+ }
+
+ return ComponentName.unflattenFromString(componentString);
+ }
+
+ private List<AutoFillServiceInfo> getInfos() {
+ PackageManager pm = getContext().getPackageManager();
+ List<ResolveInfo> resolveInfos = pm.queryIntentServices(
+ new Intent(AutoFillService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA);
+ List<AutoFillServiceInfo> infos = new ArrayList<>(resolveInfos.size());
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ AutoFillServiceInfo info = new AutoFillServiceInfo(pm, serviceInfo);
+ if (info.getParseError() == null) {
+ infos.add(info);
+ } else {
+ Log.i(TAG, "Invalid AutoFillService " + serviceInfo + ": " + info.getParseError());
+ }
+ }
+ return infos;
+ }
+}
diff --git a/src/com/android/settings/applications/DefaultBrowserPreference.java b/src/com/android/settings/applications/DefaultBrowserPreference.java
deleted file mode 100644
index 9d84e1e..0000000
--- a/src/com/android/settings/applications/DefaultBrowserPreference.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2015 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.applications;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-
-import com.android.internal.content.PackageMonitor;
-import com.android.settings.AppListPreference;
-import com.android.settings.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class DefaultBrowserPreference extends AppListPreference {
-
- private static final String TAG = "DefaultBrowserPref";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final long DELAY_UPDATE_BROWSER_MILLIS = 500;
- private static final Intent BROWSE_PROBE = new Intent()
- .setAction(Intent.ACTION_VIEW)
- .addCategory(Intent.CATEGORY_BROWSABLE)
- .setData(Uri.parse("http:"));
-
- private final Handler mHandler = new Handler();
- private final PackageManager mPm;
-
- public DefaultBrowserPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- mPm = context.getPackageManager();
- refreshBrowserApps();
- }
-
- @Override
- public void onAttached() {
- super.onAttached();
- updateDefaultBrowserPreference();
- mPackageMonitor.register(getContext(), getContext().getMainLooper(), false);
- }
-
- @Override
- public void onDetached() {
- mPackageMonitor.unregister();
- super.onDetached();
- }
-
- @Override
- protected boolean persistString(String newValue) {
- final CharSequence packageName = newValue;
- if (TextUtils.isEmpty(packageName)) {
- return false;
- }
- boolean result = mPm.setDefaultBrowserPackageNameAsUser(
- packageName.toString(), mUserId);
- if (result) {
- setSummary("%s");
- }
- return result && super.persistString(newValue);
- }
-
- public void refreshBrowserApps() {
- List<String> browsers = resolveBrowserApps();
-
- setPackageNames(browsers.toArray(new String[browsers.size()]), null);
- }
-
- private void updateDefaultBrowserPreference() {
- refreshBrowserApps();
-
- final PackageManager pm = getContext().getPackageManager();
-
- String packageName = pm.getDefaultBrowserPackageNameAsUser(mUserId);
- if (!TextUtils.isEmpty(packageName)) {
- // Check if the default Browser package is still there
- final Intent intent = new Intent(BROWSE_PROBE)
- .setPackage(packageName);
-
- final ResolveInfo info = mPm.resolveActivityAsUser(intent, 0, mUserId);
- if (info != null) {
- setValue(packageName);
- setSummary("%s");
- } else {
- setSummary(R.string.default_browser_title_none);
- }
- } else {
- if (DEBUG) Log.d(TAG, "No default browser app.");
- setSoleAppLabelAsSummary();
- }
- }
-
- private List<String> resolveBrowserApps() {
- List<String> result = new ArrayList<>();
-
- // Resolve that intent and check that the handleAllWebDataURI boolean is set
- List<ResolveInfo> list = mPm.queryIntentActivitiesAsUser(BROWSE_PROBE,
- PackageManager.MATCH_ALL, mUserId);
-
- final int count = list.size();
- for (int i = 0; i < count; i++) {
- ResolveInfo info = list.get(i);
- if (info.activityInfo == null || result.contains(info.activityInfo.packageName)
- || !info.handleAllWebDataURI) {
- continue;
- }
-
- result.add(info.activityInfo.packageName);
- }
-
- return result;
- }
-
- @Override
- protected CharSequence getSoleAppLabel() {
- // Resolve that intent and check that the handleAllWebDataURI boolean is set
- List<ResolveInfo> list = mPm.queryIntentActivitiesAsUser(BROWSE_PROBE,
- PackageManager.MATCH_ALL, mUserId);
- if (list.size() == 1) {
- return list.get(0).loadLabel(mPm);
- }
- return null;
- }
-
- private final Runnable mUpdateRunnable = new Runnable() {
- @Override
- public void run() {
- updateDefaultBrowserPreference();
- }
- };
-
- private final PackageMonitor mPackageMonitor = new PackageMonitor() {
- @Override
- public void onPackageAdded(String packageName, int uid) {
- sendUpdate();
- }
-
- @Override
- public void onPackageAppeared(String packageName, int reason) {
- sendUpdate();
- }
-
- @Override
- public void onPackageDisappeared(String packageName, int reason) {
- sendUpdate();
- }
-
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- sendUpdate();
- }
-
- private void sendUpdate() {
- mHandler.postDelayed(mUpdateRunnable, DELAY_UPDATE_BROWSER_MILLIS);
- }
- };
-
- public static boolean hasBrowserPreference(String pkg, Context context) {
- Intent intent = new Intent();
- intent.setAction(Intent.ACTION_VIEW);
- intent.addCategory(Intent.CATEGORY_BROWSABLE);
- intent.setData(Uri.parse("http:"));
- intent.setPackage(pkg);
- final List<ResolveInfo> resolveInfos =
- context.getPackageManager().queryIntentActivities(intent, 0);
- return resolveInfos != null && resolveInfos.size() != 0;
- }
-
- public static boolean isBrowserDefault(String pkg, Context context) {
- String defaultPackage = context.getPackageManager()
- .getDefaultBrowserPackageNameAsUser(UserHandle.myUserId());
- return defaultPackage != null && defaultPackage.equals(pkg);
- }
-}
diff --git a/src/com/android/settings/applications/DefaultEmergencyPreference.java b/src/com/android/settings/applications/DefaultEmergencyPreference.java
deleted file mode 100644
index dd4dc2e..0000000
--- a/src/com/android/settings/applications/DefaultEmergencyPreference.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2015 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.applications;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.AsyncTask;
-import android.provider.Settings;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.AttributeSet;
-
-import com.android.settings.AppListPreference;
-import com.android.settings.R;
-import com.android.settings.SelfAvailablePreference;
-import com.android.settings.Utils;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * A preference for choosing the default emergency app
- */
-public class DefaultEmergencyPreference extends AppListPreference
- implements SelfAvailablePreference {
-
- private static final boolean DEFAULT_EMERGENCY_APP_IS_CONFIGURABLE = false;
- private final ContentResolver mContentResolver;
-
- public static final Intent QUERY_INTENT = new Intent(
- TelephonyManager.ACTION_EMERGENCY_ASSISTANCE);
-
- public DefaultEmergencyPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContentResolver = context.getContentResolver();
- load();
- }
-
- @Override
- protected CharSequence getConfirmationMessage(String value) {
- return Utils.isPackageDirectBootAware(getContext(), value) ? null
- : getContext().getText(R.string.direct_boot_unaware_dialog_message);
- }
-
- @Override
- protected boolean persistString(String value) {
- String previousValue = Settings.Secure.getString(mContentResolver,
- Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
-
- if (!TextUtils.isEmpty(value) && !Objects.equals(value, previousValue)) {
- Settings.Secure.putString(mContentResolver,
- Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
- value);
- }
- setSummary(getEntry());
- return true;
- }
-
- private void load() {
- new AsyncTask<Void, Void, Set<String>>() {
- @Override
- protected Set<String> doInBackground(Void[] params) {
- return resolveAssistPackageAndQueryApps();
- }
-
- @Override
- protected void onPostExecute(Set<String> entries) {
- String currentPkg = Settings.Secure.getString(mContentResolver,
- Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
- setPackageNames(entries.toArray(new String[entries.size()]), currentPkg);
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
-
- private Set<String> resolveAssistPackageAndQueryApps() {
- Set<String> packages = new ArraySet<>();
-
- PackageManager packageManager = getContext().getPackageManager();
- List<ResolveInfo> infos = packageManager.queryIntentActivities(QUERY_INTENT, 0);
-
- PackageInfo bestMatch = null;
- final int size = infos.size();
- for (int i = 0; i < size; i++) {
- ResolveInfo info = infos.get(i);
- if (info == null || info.activityInfo == null
- || packages.contains(info.activityInfo.packageName)) {
- continue;
- }
-
- String packageName = info.activityInfo.packageName;
-
- packages.add(packageName);
-
- PackageInfo packageInfo;
- try {
- packageInfo = packageManager.getPackageInfo(packageName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- continue;
- }
-
- // Get earliest installed system app.
- if (isSystemApp(packageInfo) && (bestMatch == null ||
- bestMatch.firstInstallTime > packageInfo.firstInstallTime)) {
- bestMatch = packageInfo;
- }
- }
-
- String defaultPackage = Settings.Secure.getString(mContentResolver,
- Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
- boolean defaultMissing = TextUtils.isEmpty(defaultPackage)
- || !packages.contains(defaultPackage);
- if (bestMatch != null && defaultMissing) {
- Settings.Secure.putString(mContentResolver,
- Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
- bestMatch.packageName);
- }
-
- return packages;
- }
-
- private static boolean isCapable(Context context) {
- return TelephonyManager.EMERGENCY_ASSISTANCE_ENABLED
- && context.getResources().getBoolean(
- com.android.internal.R.bool.config_voice_capable);
- }
-
- private static boolean isSystemApp(PackageInfo info) {
- return info.applicationInfo != null
- && (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- }
-
- public boolean isAvailable(Context context) {
- return DEFAULT_EMERGENCY_APP_IS_CONFIGURABLE
- && isCapable(context)
- && context.getPackageManager().resolveActivity(QUERY_INTENT, 0) != null;
- }
-
- public static boolean hasEmergencyPreference(String pkg, Context context) {
- Intent i = new Intent(QUERY_INTENT);
- i.setPackage(pkg);
- final List<ResolveInfo> resolveInfos =
- context.getPackageManager().queryIntentActivities(i, 0);
- return resolveInfos != null && resolveInfos.size() != 0;
- }
-
- public static boolean isEmergencyDefault(String pkg, Context context) {
- String defaultPackage = Settings.Secure.getString(context.getContentResolver(),
- Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
- return defaultPackage != null && defaultPackage.equals(pkg);
- }
-}
diff --git a/src/com/android/settings/applications/DefaultHomePreference.java b/src/com/android/settings/applications/DefaultHomePreference.java
deleted file mode 100644
index 46fba66..0000000
--- a/src/com/android/settings/applications/DefaultHomePreference.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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.applications;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
-import android.os.Build;
-import android.os.UserManager;
-import android.util.AttributeSet;
-
-import com.android.settings.AppListPreference;
-import com.android.settings.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class DefaultHomePreference extends AppListPreference {
-
- private final ArrayList<ComponentName> mAllHomeComponents = new ArrayList<>();
- private final IntentFilter mHomeFilter;
- private final String mPackageName;
-
- public DefaultHomePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- mPackageName = getContext().getPackageName();
- mHomeFilter = new IntentFilter(Intent.ACTION_MAIN);
- mHomeFilter.addCategory(Intent.CATEGORY_HOME);
- mHomeFilter.addCategory(Intent.CATEGORY_DEFAULT);
- refreshHomeOptions();
- }
-
- @Override
- public void performClick() {
- refreshHomeOptions();
- super.performClick();
- }
-
- @Override
- protected boolean persistString(String value) {
- if (value != null) {
- ComponentName component = ComponentName.unflattenFromString(value);
- getContext().getPackageManager().replacePreferredActivity(mHomeFilter,
- IntentFilter.MATCH_CATEGORY_EMPTY,
- mAllHomeComponents.toArray(new ComponentName[0]), component);
- setSummary(getEntry());
- } else {
- // If there is only 1 launcher, use its label as summary text.
- setSoleAppLabelAsSummary();
- }
- return super.persistString(value);
- }
-
- @Override
- protected CharSequence getSoleAppLabel() {
- final PackageManager pm = getContext().getPackageManager();
- final List<ResolveInfo> homeActivities = new ArrayList<>();
- final List<CharSequence> appLabels = new ArrayList<>();
-
- pm.getHomeActivities(homeActivities);
- for (ResolveInfo candidate : homeActivities) {
- final ActivityInfo info = candidate.activityInfo;
- if (info.packageName.equals(mPackageName)) {
- continue;
- }
- appLabels.add(info.loadLabel(pm));
- }
- return appLabels.size() == 1 ? appLabels.get(0) : null;
- }
-
- public void refreshHomeOptions() {
- ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
- PackageManager pm = getContext().getPackageManager();
- ComponentName currentDefaultHome = pm.getHomeActivities(homeActivities);
- ArrayList<ComponentName> components = new ArrayList<>();
- mAllHomeComponents.clear();
- List<CharSequence> summaries = new ArrayList<>();
-
- boolean mustSupportManagedProfile = hasManagedProfile();
- for (ResolveInfo candidate : homeActivities) {
- final ActivityInfo info = candidate.activityInfo;
- ComponentName activityName = new ComponentName(info.packageName, info.name);
- mAllHomeComponents.add(activityName);
- if (info.packageName.equals(mPackageName)) {
- continue;
- }
- components.add(activityName);
- if (mustSupportManagedProfile && !launcherHasManagedProfilesFeature(candidate, pm)) {
- summaries.add(getContext().getString(R.string.home_work_profile_not_supported));
- } else {
- summaries.add(null);
- }
- }
- setComponentNames(components.toArray(new ComponentName[0]), currentDefaultHome,
- summaries.toArray(new CharSequence[0]));
- }
-
- private boolean launcherHasManagedProfilesFeature(ResolveInfo resolveInfo, PackageManager pm) {
- try {
- ApplicationInfo appInfo = pm.getApplicationInfo(
- resolveInfo.activityInfo.packageName, 0 /* default flags */);
- return versionNumberAtLeastL(appInfo.targetSdkVersion);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
- private boolean versionNumberAtLeastL(int versionNumber) {
- return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
- }
-
- private boolean hasManagedProfile() {
- UserManager userManager = getContext().getSystemService(UserManager.class);
- List<UserInfo> profiles = userManager.getProfiles(getContext().getUserId());
- for (UserInfo userInfo : profiles) {
- if (userInfo.isManagedProfile()) return true;
- }
- return false;
- }
-
- public static boolean hasHomePreference(String pkg, Context context) {
- ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
- PackageManager pm = context.getPackageManager();
- pm.getHomeActivities(homeActivities);
- for (int i = 0; i < homeActivities.size(); i++) {
- if (homeActivities.get(i).activityInfo.packageName.equals(pkg)) {
- return true;
- }
- }
- return false;
- }
-
- public static boolean isHomeDefault(String pkg, Context context) {
- ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>();
- PackageManager pm = context.getPackageManager();
- ComponentName def = pm.getHomeActivities(homeActivities);
-
- return def != null && def.getPackageName().equals(pkg);
- }
-}
diff --git a/src/com/android/settings/applications/DefaultPhonePreference.java b/src/com/android/settings/applications/DefaultPhonePreference.java
deleted file mode 100644
index e151274..0000000
--- a/src/com/android/settings/applications/DefaultPhonePreference.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2015 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.applications;
-
-import android.content.Context;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.telecom.DefaultDialerManager;
-import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-
-import com.android.settings.AppListPreference;
-import com.android.settings.R;
-import com.android.settings.SelfAvailablePreference;
-import com.android.settings.Utils;
-
-import java.util.List;
-import java.util.Objects;
-
-public class DefaultPhonePreference extends AppListPreference implements SelfAvailablePreference {
- public DefaultPhonePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- loadDialerApps();
- }
-
- @Override
- protected CharSequence getConfirmationMessage(String value) {
- return Utils.isPackageDirectBootAware(getContext(), value) ? null
- : getContext().getText(R.string.direct_boot_unaware_dialog_message);
- }
-
- @Override
- protected boolean persistString(String value) {
- if (!TextUtils.isEmpty(value) && !Objects.equals(value, getDefaultPackage())) {
- DefaultDialerManager.setDefaultDialerApplication(getContext(), value, mUserId);
- }
- setSummary(getEntry());
- return true;
- }
-
- private void loadDialerApps() {
- List<String> dialerPackages =
- DefaultDialerManager.getInstalledDialerApplications(getContext(), mUserId);
-
- final String[] dialers = new String[dialerPackages.size()];
- for (int i = 0; i < dialerPackages.size(); i++) {
- dialers[i] = dialerPackages.get(i);
- }
- setPackageNames(dialers, getDefaultPackage(), getSystemPackage());
- }
-
- private String getDefaultPackage() {
- return DefaultDialerManager.getDefaultDialerApplication(getContext(), mUserId);
- }
-
- private String getSystemPackage() {
- TelecomManager tm = TelecomManager.from(getContext());
- return tm.getSystemDialerPackage();
- }
-
- @Override
- public boolean isAvailable(Context context) {
- final TelephonyManager tm =
- (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- if (!tm.isVoiceCapable()) {
- return false;
- }
-
- final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
- final boolean hasUserRestriction =
- um.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS);
- final CharSequence[] entries = getEntries();
- return !hasUserRestriction
- && entries != null
- && entries.length > 0;
- }
-
- public static boolean hasPhonePreference(String pkg, Context context) {
- List<String> dialerPackages =
- DefaultDialerManager.getInstalledDialerApplications(context, UserHandle.myUserId());
- return dialerPackages.contains(pkg);
- }
-
- public static boolean isPhoneDefault(String pkg, Context context) {
- String def = DefaultDialerManager.getDefaultDialerApplication(context,
- UserHandle.myUserId());
- return def != null && def.equals(pkg);
- }
-}
diff --git a/src/com/android/settings/applications/DefaultSmsPreference.java b/src/com/android/settings/applications/DefaultSmsPreference.java
deleted file mode 100644
index 96ac9a2..0000000
--- a/src/com/android/settings/applications/DefaultSmsPreference.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2015 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.applications;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-
-import com.android.internal.telephony.SmsApplication;
-import com.android.internal.telephony.SmsApplication.SmsApplicationData;
-import com.android.settings.AppListPreference;
-import com.android.settings.R;
-import com.android.settings.SelfAvailablePreference;
-import com.android.settings.Utils;
-
-import java.util.Collection;
-import java.util.Objects;
-
-public class DefaultSmsPreference extends AppListPreference implements SelfAvailablePreference {
- public DefaultSmsPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- loadSmsApps();
- }
-
- private void loadSmsApps() {
- Collection<SmsApplicationData> smsApplications =
- SmsApplication.getApplicationCollection(getContext());
-
- int count = smsApplications.size();
- String[] packageNames = new String[count];
- int i = 0;
- for (SmsApplicationData smsApplicationData : smsApplications) {
- packageNames[i++] = smsApplicationData.mPackageName;
- }
- setPackageNames(packageNames, getDefaultPackage());
- }
-
- private String getDefaultPackage() {
- ComponentName appName = SmsApplication.getDefaultSmsApplication(getContext(), true);
- if (appName != null) {
- return appName.getPackageName();
- }
- return null;
- }
-
- @Override
- protected CharSequence getConfirmationMessage(String value) {
- return Utils.isPackageDirectBootAware(getContext(), value) ? null
- : getContext().getText(R.string.direct_boot_unaware_dialog_message);
- }
-
- @Override
- protected boolean persistString(String value) {
- if (!TextUtils.isEmpty(value) && !Objects.equals(value, getDefaultPackage())) {
- SmsApplication.setDefaultApplication(value, getContext());
- }
- setSummary(getEntry());
- return true;
- }
-
- @Override
- public boolean isAvailable(Context context) {
- boolean isRestrictedUser =
- UserManager.get(context)
- .getUserInfo(UserHandle.myUserId()).isRestricted();
- TelephonyManager tm =
- (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- return !isRestrictedUser && tm.isSmsCapable();
- }
-
- public static boolean hasSmsPreference(String pkg, Context context) {
- Collection<SmsApplicationData> smsApplications =
- SmsApplication.getApplicationCollection(context);
- for (SmsApplicationData data : smsApplications) {
- if (data.mPackageName.equals(pkg)) {
- return true;
- }
- }
- return false;
- }
-
- public static boolean isSmsDefault(String pkg, Context context) {
- ComponentName appName = SmsApplication.getDefaultSmsApplication(context, true);
- return appName != null && appName.getPackageName().equals(pkg);
- }
-}
diff --git a/src/com/android/settings/applications/IPackageManagerWrapper.java b/src/com/android/settings/applications/IPackageManagerWrapper.java
new file mode 100644
index 0000000..f885985
--- /dev/null
+++ b/src/com/android/settings/applications/IPackageManagerWrapper.java
@@ -0,0 +1,44 @@
+/*
+ * 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.applications;
+
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.RemoteException;
+
+/**
+ * This interface replicates a subset of the android.content.pm.IPackageManager (PMS). The interface
+ * exists so that we can use a thin wrapper around the PMS in production code and a mock in tests.
+ * We cannot directly mock or shadow the PMS, because some of the methods we rely on are newer than
+ * the API version supported by Robolectric.
+ */
+public interface IPackageManagerWrapper {
+
+ /**
+ * Calls {@code IPackageManager.checkUidPermission()}.
+ *
+ * @see android.content.pm.IPackageManager#checkUidPermission
+ */
+ int checkUidPermission(String permName, int uid) throws RemoteException;
+
+ /**
+ * Calls {@code IPackageManager.findPersistentPreferredActivity()}.
+ *
+ * @see android.content.pm.IPackageManager#findPersistentPreferredActivity
+ */
+ ResolveInfo findPersistentPreferredActivity(Intent intent, int userId) throws RemoteException;
+}
diff --git a/src/com/android/settings/applications/IPackageManagerWrapperImpl.java b/src/com/android/settings/applications/IPackageManagerWrapperImpl.java
new file mode 100644
index 0000000..5ea15b9
--- /dev/null
+++ b/src/com/android/settings/applications/IPackageManagerWrapperImpl.java
@@ -0,0 +1,42 @@
+/*
+ * 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.applications;
+
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.RemoteException;
+
+public class IPackageManagerWrapperImpl implements IPackageManagerWrapper {
+
+ private final IPackageManager mPms;
+
+ public IPackageManagerWrapperImpl(IPackageManager pms) {
+ mPms = pms;
+ }
+
+ @Override
+ public int checkUidPermission(String permName, int uid) throws RemoteException {
+ return mPms.checkUidPermission(permName, uid);
+ }
+
+ @Override
+ public ResolveInfo findPersistentPreferredActivity(Intent intent, int userId)
+ throws RemoteException {
+ return mPms.findPersistentPreferredActivity(intent, userId);
+ }
+}
diff --git a/src/com/android/settings/applications/InstalledAppDetails.java b/src/com/android/settings/applications/InstalledAppDetails.java
index 930957e..873c5fd 100755
--- a/src/com/android/settings/applications/InstalledAppDetails.java
+++ b/src/com/android/settings/applications/InstalledAppDetails.java
@@ -21,8 +21,6 @@
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.LoaderManager.LoaderCallbacks;
-import android.app.Notification;
-import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@@ -52,9 +50,8 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
-import android.service.notification.NotificationListenerService.Ranking;
import android.support.annotation.VisibleForTesting;
+import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.Preference.OnPreferenceClickListener;
import android.support.v7.preference.PreferenceCategory;
@@ -78,7 +75,6 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
-import com.android.internal.widget.LockPatternUtils;
import com.android.settings.AppHeader;
import com.android.settings.DeviceAdminAdd;
import com.android.settings.R;
@@ -86,6 +82,11 @@
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
import com.android.settings.applications.PermissionsSummaryHelper.PermissionsResultCallback;
+import com.android.settings.applications.defaultapps.DefaultBrowserPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultEmergencyPreferenceController;
+import com.android.settings.applications.defaultapps.DefaultHomePreferenceController;
+import com.android.settings.applications.defaultapps.DefaultPhonePreferenceController;
+import com.android.settings.applications.defaultapps.DefaultSmsPreferenceController;
import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.datausage.AppDataUsage;
import com.android.settings.datausage.DataUsageList;
@@ -908,34 +909,49 @@
return;
}
final PreferenceScreen screen = getPreferenceScreen();
- if (DefaultHomePreference.hasHomePreference(mPackageName, getContext())) {
+ final Context context = getContext();
+ if (DefaultHomePreferenceController.hasHomePreference(mPackageName, context)) {
screen.addPreference(new ShortcutPreference(getPrefContext(),
AdvancedAppSettings.class, "default_home", R.string.home_app,
R.string.configure_apps));
}
- if (DefaultBrowserPreference.hasBrowserPreference(mPackageName, getContext())) {
+ if (DefaultBrowserPreferenceController.hasBrowserPreference(mPackageName, context)) {
screen.addPreference(new ShortcutPreference(getPrefContext(),
AdvancedAppSettings.class, "default_browser", R.string.default_browser_title,
R.string.configure_apps));
}
- if (DefaultPhonePreference.hasPhonePreference(mPackageName, getContext())) {
+ if (DefaultPhonePreferenceController.hasPhonePreference(mPackageName, context)) {
screen.addPreference(new ShortcutPreference(getPrefContext(),
AdvancedAppSettings.class, "default_phone_app", R.string.default_phone_title,
R.string.configure_apps));
}
- if (DefaultEmergencyPreference.hasEmergencyPreference(mPackageName, getContext())) {
+ if (DefaultEmergencyPreferenceController.hasEmergencyPreference(mPackageName, context)) {
screen.addPreference(new ShortcutPreference(getPrefContext(),
AdvancedAppSettings.class, "default_emergency_app",
R.string.default_emergency_app, R.string.configure_apps));
}
- if (DefaultSmsPreference.hasSmsPreference(mPackageName, getContext())) {
+ if (DefaultSmsPreferenceController.hasSmsPreference(mPackageName, context)) {
screen.addPreference(new ShortcutPreference(getPrefContext(),
AdvancedAppSettings.class, "default_sms_app", R.string.sms_application_title,
R.string.configure_apps));
}
+
+ // Get the package info with the activities
+ PackageInfo packageInfoWithActivities = null;
+ try {
+ packageInfoWithActivities = mPm.getPackageInfoAsUser(mPackageName,
+ PackageManager.GET_ACTIVITIES, UserHandle.myUserId());
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Exception while retrieving the package info of " + mPackageName, e);
+ }
+
boolean hasDrawOverOtherApps = hasPermission(permission.SYSTEM_ALERT_WINDOW);
boolean hasWriteSettings = hasPermission(permission.WRITE_SETTINGS);
- if (hasDrawOverOtherApps || hasWriteSettings) {
+ boolean hasPictureInPictureActivities = (packageInfoWithActivities != null) &&
+ PictureInPictureSettings.checkPackageHasPictureInPictureActivities(
+ packageInfoWithActivities.packageName,
+ packageInfoWithActivities.activities);
+ if (hasDrawOverOtherApps || hasWriteSettings || hasPictureInPictureActivities) {
PreferenceCategory category = new PreferenceCategory(getPrefContext());
category.setTitle(R.string.advanced_apps);
screen.addPreference(category);
@@ -968,6 +984,23 @@
});
category.addPreference(pref);
}
+ if (hasPictureInPictureActivities) {
+ final SwitchPreference pref = new SwitchPreference(getPrefContext());
+ pref.setPersistent(false);
+ pref.setTitle(R.string.picture_in_picture_app_detail_title);
+ pref.setSummary(R.string.picture_in_picture_app_detail_summary);
+ pref.setChecked(PictureInPictureSettings.getEnterPipOnHideStateForPackage(
+ getContext(), mPackageInfo.applicationInfo.uid, mPackageName));
+ pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ PictureInPictureSettings.setEnterPipOnHideStateForPackage(getContext(),
+ mPackageInfo.applicationInfo.uid, mPackageName, (Boolean) newValue);
+ return true;
+ }
+ });
+ category.addPreference(pref);
+ }
}
addAppInstallerInfoPref(screen);
@@ -1021,29 +1054,33 @@
}
private void updateDynamicPrefs() {
+ final Context context = getContext();
Preference pref = findPreference("default_home");
+
if (pref != null) {
- pref.setSummary(DefaultHomePreference.isHomeDefault(mPackageName, getContext())
+ pref.setSummary(DefaultHomePreferenceController.isHomeDefault(mPackageName, context)
? R.string.yes : R.string.no);
}
pref = findPreference("default_browser");
if (pref != null) {
- pref.setSummary(DefaultBrowserPreference.isBrowserDefault(mPackageName, getContext())
+ pref.setSummary(
+ DefaultBrowserPreferenceController.isBrowserDefault(mPackageName, context)
? R.string.yes : R.string.no);
}
pref = findPreference("default_phone_app");
if (pref != null) {
- pref.setSummary(DefaultPhonePreference.isPhoneDefault(mPackageName, getContext())
+ pref.setSummary(
+ DefaultPhonePreferenceController.isPhoneDefault(mPackageName, context)
? R.string.yes : R.string.no);
}
pref = findPreference("default_emergency_app");
if (pref != null) {
- pref.setSummary(DefaultEmergencyPreference.isEmergencyDefault(mPackageName,
+ pref.setSummary(DefaultEmergencyPreferenceController.isEmergencyDefault(mPackageName,
getContext()) ? R.string.yes : R.string.no);
}
pref = findPreference("default_sms_app");
if (pref != null) {
- pref.setSummary(DefaultSmsPreference.isSmsDefault(mPackageName, getContext())
+ pref.setSummary(DefaultSmsPreferenceController.isSmsDefault(mPackageName, context)
? R.string.yes : R.string.no);
}
pref = findPreference("system_alert_window");
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index a87ba53..f6b303c 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -31,7 +31,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.preference.PreferenceFrameLayout;
-import android.support.v7.preference.Preference;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
@@ -57,6 +56,7 @@
import com.android.settings.AppHeader;
import com.android.settings.R;
import com.android.settings.Settings.AllApplicationsActivity;
+import com.android.settings.Settings.GamesStorageActivity;
import com.android.settings.Settings.HighPowerApplicationsActivity;
import com.android.settings.Settings.ManageExternalSourcesActivity;
import com.android.settings.Settings.NotificationAppListActivity;
@@ -216,6 +216,7 @@
public static final int LIST_TYPE_OVERLAY = 6;
public static final int LIST_TYPE_WRITE_SETTINGS = 7;
public static final int LIST_TYPE_MANAGE_SOURCES = 8;
+ public static final int LIST_TYPE_GAMES = 9;
private View mRootView;
@@ -267,6 +268,9 @@
mListType = LIST_TYPE_WRITE_SETTINGS;
} else if (className.equals(ManageExternalSourcesActivity.class.getName())) {
mListType = LIST_TYPE_MANAGE_SOURCES;
+ } else if (className.equals(GamesStorageActivity.class.getName())) {
+ mListType = LIST_TYPE_GAMES;
+ mSortOrder = R.id.sort_order_size;
} else {
mListType = LIST_TYPE_MAIN;
}
@@ -359,6 +363,9 @@
if (mListType == LIST_TYPE_STORAGE) {
mApplications.setOverrideFilter(new VolumeFilter(mVolumeUuid));
}
+ if (mListType == LIST_TYPE_GAMES) {
+ mApplications.setOverrideFilter(ApplicationsState.FILTER_GAMES);
+ }
}
@Override
@@ -399,6 +406,7 @@
case LIST_TYPE_MAIN:
case LIST_TYPE_NOTIFICATION:
case LIST_TYPE_STORAGE:
+ case LIST_TYPE_GAMES:
return mSortOrder == R.id.sort_order_alpha;
default:
return false;
@@ -413,6 +421,7 @@
case LIST_TYPE_NOTIFICATION:
return MetricsEvent.MANAGE_APPLICATIONS_NOTIFICATIONS;
case LIST_TYPE_STORAGE:
+ case LIST_TYPE_GAMES:
return MetricsEvent.APPLICATIONS_STORAGE_APPS;
case LIST_TYPE_USAGE_ACCESS:
return MetricsEvent.USAGE_ACCESS;
@@ -517,6 +526,8 @@
break;
case LIST_TYPE_MANAGE_SOURCES:
startAppInfoFragment(ExternalSourcesDetails.class, R.string.install_other_apps);
+ case LIST_TYPE_GAMES:
+ startAppInfoFragment(AppStorageSettings.class, R.string.game_storage_settings);
break;
// TODO: Figure out if there is a way where we can spin up the profile's settings
// process ahead of time, to avoid a long load of data when user clicks on a managed app.
diff --git a/src/com/android/settings/applications/PackageManagerWrapper.java b/src/com/android/settings/applications/PackageManagerWrapper.java
index 4166ccf..2be92ed 100644
--- a/src/com/android/settings/applications/PackageManagerWrapper.java
+++ b/src/com/android/settings/applications/PackageManagerWrapper.java
@@ -16,7 +16,9 @@
package com.android.settings.applications;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -58,11 +60,42 @@
*/
List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId);
-
/**
* Calls {@code PackageManager.getInstallReason()}.
*
* @see android.content.pm.PackageManager#getInstallReason
*/
int getInstallReason(String packageName, UserHandle user);
+
+ /**
+ * Calls {@code PackageManager.getApplicationInfoAsUser}
+ */
+ ApplicationInfo getApplicationInfoAsUser(String packageName, int i, int userId)
+ throws PackageManager.NameNotFoundException;
+
+ /**
+ * Calls {@code PackageManager.setDefaultBrowserPackageNameAsUser}
+ */
+ boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId);
+
+ /**
+ * Calls {@code PackageManager.getDefaultBrowserPackageNameAsUser}
+ */
+ String getDefaultBrowserPackageNameAsUser(int userId);
+
+ /**
+ * Calls {@code PackageManager.getHomeActivities}
+ */
+ ComponentName getHomeActivities(List<ResolveInfo> homeActivities);
+
+ /**
+ * Calls {@code PackageManager.queryIntentServicesAsUser}
+ */
+ List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int i, int user);
+
+ /**
+ * Calls {@code PackageManager.replacePreferredActivity}
+ */
+ void replacePreferredActivity(IntentFilter homeFilter, int matchCategoryEmpty,
+ ComponentName[] componentNames, ComponentName component);
}
diff --git a/src/com/android/settings/applications/PackageManagerWrapperImpl.java b/src/com/android/settings/applications/PackageManagerWrapperImpl.java
index 2427cce..698c14c 100644
--- a/src/com/android/settings/applications/PackageManagerWrapperImpl.java
+++ b/src/com/android/settings/applications/PackageManagerWrapperImpl.java
@@ -16,7 +16,9 @@
package com.android.settings.applications;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -56,4 +58,36 @@
public int getInstallReason(String packageName, UserHandle user) {
return mPm.getInstallReason(packageName, user);
}
+
+ @Override
+ public ApplicationInfo getApplicationInfoAsUser(String packageName, int i, int userId)
+ throws PackageManager.NameNotFoundException {
+ return mPm.getApplicationInfoAsUser(packageName, i, userId);
+ }
+
+ @Override
+ public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) {
+ return mPm.setDefaultBrowserPackageNameAsUser(packageName, userId);
+ }
+
+ @Override
+ public String getDefaultBrowserPackageNameAsUser(int userId) {
+ return mPm.getDefaultBrowserPackageNameAsUser(userId);
+ }
+
+ @Override
+ public ComponentName getHomeActivities(List<ResolveInfo> homeActivities) {
+ return mPm.getHomeActivities(homeActivities);
+ }
+
+ @Override
+ public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int i, int user) {
+ return mPm.queryIntentServicesAsUser(intent, i, user);
+ }
+
+ @Override
+ public void replacePreferredActivity(IntentFilter homeFilter, int matchCategoryEmpty,
+ ComponentName[] componentNames, ComponentName component) {
+ mPm.replacePreferredActivity(homeFilter, matchCategoryEmpty, componentNames, component);
+ }
}
diff --git a/src/com/android/settings/applications/PictureInPictureSettings.java b/src/com/android/settings/applications/PictureInPictureSettings.java
new file mode 100644
index 0000000..df73572
--- /dev/null
+++ b/src/com/android/settings/applications/PictureInPictureSettings.java
@@ -0,0 +1,200 @@
+/*
+ * 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.applications;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE;
+import static android.content.pm.PackageManager.GET_ACTIVITIES;
+
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.util.ArrayMap;
+import android.view.View;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.R;
+import com.android.settings.notification.EmptyTextSettings;
+import com.android.settings.overlay.FeatureFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class PictureInPictureSettings extends EmptyTextSettings {
+
+ private static final String TAG = PictureInPictureSettings.class.getSimpleName();
+ @VisibleForTesting
+ static final List<String> IGNORE_PACKAGE_LIST = new ArrayList<>();
+ static {
+ IGNORE_PACKAGE_LIST.add("com.android.systemui");
+ }
+
+ private Context mContext;
+ private PackageManager mPackageManager;
+
+ /**
+ * @return true if the package has any activities that declare that they support
+ * picture-in-picture.
+ */
+ static boolean checkPackageHasPictureInPictureActivities(String packageName,
+ ActivityInfo[] activities) {
+ ActivityInfoWrapper[] wrappedActivities = null;
+ if (activities != null) {
+ wrappedActivities = new ActivityInfoWrapper[activities.length];
+ for (int i = 0; i < activities.length; i++) {
+ wrappedActivities[i] = new ActivityInfoWrapperImpl(activities[i]);
+ }
+ }
+ return checkPackageHasPictureInPictureActivities(packageName, wrappedActivities);
+ }
+
+ /**
+ * @return true if the package has any activities that declare that they support
+ * picture-in-picture.
+ */
+ @VisibleForTesting
+ static boolean checkPackageHasPictureInPictureActivities(String packageName,
+ ActivityInfoWrapper[] activities) {
+ // Skip if it's in the ignored list
+ if (IGNORE_PACKAGE_LIST.contains(packageName)) {
+ return false;
+ }
+
+ // Iterate through all the activities and check if it is resizeable and supports
+ // picture-in-picture
+ if (activities != null) {
+ for (int i = activities.length - 1; i >= 0; i--) {
+ if (activities[i].supportsPictureInPicture()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets whether the app associated with the given {@param packageName} is allowed to enter
+ * picture-in-picture when it is hidden.
+ */
+ static void setEnterPipOnHideStateForPackage(Context context, int uid, String packageName,
+ boolean value) {
+ final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
+ final int newMode = value ? MODE_ALLOWED : MODE_ERRORED;
+ appOps.setMode(OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
+ uid, packageName, newMode);
+ }
+
+ /**
+ * @return whether the app associated with the given {@param packageName} is allowed to enter
+ * picture-in-picture when it is hidden.
+ */
+ static boolean getEnterPipOnHideStateForPackage(Context context, int uid, String packageName) {
+ final AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
+ return appOps.checkOpNoThrow(OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
+ uid, packageName) == MODE_ALLOWED;
+ }
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mContext = getActivity();
+ mPackageManager = mContext.getPackageManager();
+ setPreferenceScreen(getPreferenceManager().createPreferenceScreen(mContext));
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ // Clear the prefs
+ final PreferenceScreen screen = getPreferenceScreen();
+ screen.removeAll();
+
+ // Fetch the set of applications which have at least one activity that declare that they
+ // support picture-in-picture
+ final ArrayMap<String, Boolean> packageToState = new ArrayMap<>();
+ final ArrayList<ApplicationInfo> pipApps = new ArrayList<>();
+ final List<PackageInfo> installedPackages = mPackageManager.getInstalledPackagesAsUser(
+ GET_ACTIVITIES, UserHandle.myUserId());
+ for (PackageInfo packageInfo : installedPackages) {
+ if (checkPackageHasPictureInPictureActivities(packageInfo.packageName,
+ packageInfo.activities)) {
+ final String packageName = packageInfo.applicationInfo.packageName;
+ final boolean state = getEnterPipOnHideStateForPackage(mContext,
+ packageInfo.applicationInfo.uid, packageName);
+ pipApps.add(packageInfo.applicationInfo);
+ packageToState.put(packageName, state);
+ }
+ }
+ Collections.sort(pipApps, new PackageItemInfo.DisplayNameComparator(mPackageManager));
+
+ // Rebuild the list of prefs
+ final Context prefContext = getPrefContext();
+ for (final ApplicationInfo appInfo : pipApps) {
+ final String packageName = appInfo.packageName;
+ final CharSequence label = appInfo.loadLabel(mPackageManager);
+ final SwitchPreference pref = new SwitchPreference(prefContext);
+ pref.setPersistent(false);
+ pref.setIcon(appInfo.loadIcon(mPackageManager));
+ pref.setTitle(label);
+ pref.setChecked(packageToState.get(packageName));
+ pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ logSpecialPermissionChange((Boolean) newValue, packageName);
+ setEnterPipOnHideStateForPackage(mContext, appInfo.uid, packageName,
+ (Boolean) newValue);
+ return true;
+ }
+ });
+ screen.addPreference(pref);
+ }
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ setEmptyText(R.string.picture_in_picture_empty_text);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.SETTINGS_MANAGE_PICTURE_IN_PICTURE;
+ }
+
+ @VisibleForTesting
+ void logSpecialPermissionChange(boolean newState, String packageName) {
+ int logCategory = newState
+ ? MetricsEvent.APP_PICTURE_IN_PICTURE_ON_HIDE_ALLOW
+ : MetricsEvent.APP_PICTURE_IN_PICTURE_ON_HIDE_DENY;
+ FeatureFactory.getFactory(getContext())
+ .getMetricsFeatureProvider().action(getContext(), logCategory, packageName);
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppInfo.java b/src/com/android/settings/applications/defaultapps/DefaultAppInfo.java
new file mode 100644
index 0000000..7801591
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultAppInfo.java
@@ -0,0 +1,108 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+/**
+ * Data model representing an app in DefaultAppPicker UI.
+ */
+class DefaultAppInfo {
+
+ public final int userId;
+ public final ComponentName componentName;
+ public final PackageItemInfo packageItemInfo;
+ public final String summary;
+
+ public DefaultAppInfo(int uid, ComponentName cn, String summary) {
+ packageItemInfo = null;
+ userId = uid;
+ componentName = cn;
+ this.summary = summary;
+ }
+
+ public DefaultAppInfo(PackageItemInfo info) {
+ userId = UserHandle.myUserId();
+ packageItemInfo = info;
+ componentName = null;
+ summary = null;
+ }
+
+ public CharSequence loadLabel(PackageManager pm) {
+ if (componentName != null) {
+ try {
+ final ActivityInfo actInfo = AppGlobals.getPackageManager().getActivityInfo(
+ componentName, 0, userId);
+ if (actInfo != null) {
+ return actInfo.loadLabel(pm);
+ } else {
+ final ApplicationInfo appInfo = pm.getApplicationInfoAsUser(
+ componentName.getPackageName(), 0, userId);
+ return appInfo.loadLabel(pm);
+ }
+ } catch (RemoteException | PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ } else if (packageItemInfo != null) {
+ return packageItemInfo.loadLabel(pm);
+ } else {
+ return null;
+ }
+
+ }
+
+ public Drawable loadIcon(PackageManager pm) {
+ if (componentName != null) {
+ try {
+ final ActivityInfo actInfo = AppGlobals.getPackageManager().getActivityInfo(
+ componentName, 0, userId);
+ if (actInfo != null) {
+ return actInfo.loadIcon(pm);
+ } else {
+ final ApplicationInfo appInfo = pm.getApplicationInfoAsUser(
+ componentName.getPackageName(), 0, userId);
+ return appInfo.loadIcon(pm);
+ }
+ } catch (RemoteException | PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+ if (packageItemInfo != null) {
+ return packageItemInfo.loadIcon(pm);
+ } else {
+ return null;
+ }
+ }
+
+ public String getKey() {
+ if (componentName != null) {
+ return componentName.flattenToString();
+ } else if (packageItemInfo != null) {
+ return packageItemInfo.packageName;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java b/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java
new file mode 100644
index 0000000..b322dab
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragment.java
@@ -0,0 +1,228 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.applications.PackageManagerWrapper;
+import com.android.settings.applications.PackageManagerWrapperImpl;
+import com.android.settings.core.InstrumentedPreferenceFragment;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.widget.RadioButtonPreference;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A generic app picker fragment that shows a list of app as radio button group.
+ */
+public abstract class DefaultAppPickerFragment extends InstrumentedPreferenceFragment implements
+ RadioButtonPreference.OnClickListener {
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ static final String EXTRA_FOR_WORK = "for_work";
+
+ private final Map<String, DefaultAppInfo> mCandidates = new ArrayMap<>();
+
+ protected PackageManagerWrapper mPm;
+ protected UserManager mUserManager;
+ protected int mUserId;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mPm = new PackageManagerWrapperImpl(context.getPackageManager());
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ final Bundle arguments = getArguments();
+
+ boolean mForWork = false;
+ if (arguments != null) {
+ mForWork = arguments.getBoolean(EXTRA_FOR_WORK);
+ }
+ final UserHandle managedProfile = Utils.getManagedProfile(mUserManager);
+ mUserId = mForWork && managedProfile != null
+ ? managedProfile.getIdentifier()
+ : UserHandle.myUserId();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ final View view = super.onCreateView(inflater, container, savedInstanceState);
+ setHasOptionsMenu(true);
+ return view;
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ super.onCreatePreferences(savedInstanceState, rootKey);
+ addPreferencesFromResource(R.xml.app_picker_prefs);
+ mCandidates.clear();
+ final List<DefaultAppInfo> candidateList = getCandidates();
+ if (candidateList != null) {
+ for (DefaultAppInfo info : candidateList) {
+ mCandidates.put(info.getKey(), info);
+ }
+ }
+ final String defaultAppKey = getDefaultAppKey();
+ final String systemDefaultAppKey = getSystemDefaultAppKey();
+ final PreferenceScreen screen = getPreferenceScreen();
+ screen.removeAll();
+ if (shouldShowItemNone()) {
+ final RadioButtonPreference nonePref = new RadioButtonPreference(getPrefContext());
+ nonePref.setIcon(R.drawable.ic_remove_circle);
+ nonePref.setTitle(R.string.app_list_preference_none);
+ nonePref.setChecked(TextUtils.isEmpty(defaultAppKey));
+ nonePref.setOnClickListener(this);
+ screen.addPreference(nonePref);
+ }
+ for (Map.Entry<String, DefaultAppInfo> app : mCandidates.entrySet()) {
+ final RadioButtonPreference pref = new RadioButtonPreference(getPrefContext());
+ final String appKey = app.getKey();
+
+ pref.setTitle(app.getValue().loadLabel(mPm.getPackageManager()));
+ pref.setIcon(app.getValue().loadIcon(mPm.getPackageManager()));
+ pref.setKey(appKey);
+ if (TextUtils.equals(defaultAppKey, appKey)) {
+ pref.setChecked(true);
+ }
+ if (TextUtils.equals(systemDefaultAppKey, appKey)) {
+ pref.setSummary(R.string.system_app);
+ }
+ pref.setOnClickListener(this);
+ screen.addPreference(pref);
+ }
+ }
+
+ @Override
+ public void onRadioButtonClicked(RadioButtonPreference selected) {
+ final String selectedKey = selected.getKey();
+ final String confirmationMessage = getConfirmationMessage(mCandidates.get(selectedKey));
+ final Activity activity = getActivity();
+ if (TextUtils.isEmpty(confirmationMessage)) {
+ onRadioButtonConfirmed(selectedKey);
+ } else if (activity != null) {
+ final DialogFragment fragment = ConfirmationDialogFragment.newInstance(
+ this, selectedKey, confirmationMessage);
+ fragment.show(activity.getFragmentManager(), ConfirmationDialogFragment.TAG);
+ }
+ }
+
+ private void onRadioButtonConfirmed(String selectedKey) {
+ final boolean success = setDefaultAppKey(selectedKey);
+ if (success) {
+ final PreferenceScreen screen = getPreferenceScreen();
+ if (screen != null) {
+ final int count = screen.getPreferenceCount();
+ for (int i = 0; i < count; i++) {
+ final Preference pref = screen.getPreference(i);
+ if (pref instanceof RadioButtonPreference) {
+ final RadioButtonPreference radioPref = (RadioButtonPreference) pref;
+ final boolean newCheckedState =
+ TextUtils.equals(pref.getKey(), selectedKey);
+ if (radioPref.isChecked() != newCheckedState) {
+ radioPref.setChecked(TextUtils.equals(pref.getKey(), selectedKey));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected boolean shouldShowItemNone() {
+ return false;
+ }
+
+ protected String getSystemDefaultAppKey() {
+ return null;
+ }
+
+ protected abstract List<DefaultAppInfo> getCandidates();
+
+ protected abstract String getDefaultAppKey();
+
+ protected abstract boolean setDefaultAppKey(String key);
+
+ protected String getConfirmationMessage(DefaultAppInfo appInfo) {
+ return null;
+ }
+
+ public static class ConfirmationDialogFragment extends InstrumentedDialogFragment
+ implements DialogInterface.OnClickListener {
+
+ public static final String TAG = "DefaultAppConfirm";
+ public static final String EXTRA_KEY = "extra_key";
+ public static final String EXTRA_MESSAGE = "extra_message";
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DEFAULT_APP_PICKER_CONFIRMATION_DIALOG;
+ }
+
+ public static ConfirmationDialogFragment newInstance(DefaultAppPickerFragment parent,
+ String key, String message) {
+ final ConfirmationDialogFragment fragment = new ConfirmationDialogFragment();
+ final Bundle argument = new Bundle();
+ argument.putString(EXTRA_KEY, key);
+ argument.putString(EXTRA_MESSAGE, message);
+ fragment.setArguments(argument);
+ fragment.setTargetFragment(parent, 0);
+ return fragment;
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Bundle bundle = getArguments();
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setMessage(bundle.getString(EXTRA_MESSAGE))
+ .setPositiveButton(android.R.string.ok, this)
+ .setNegativeButton(android.R.string.cancel, null);
+ return builder.create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final Fragment fragment = getTargetFragment();
+ if (fragment instanceof DefaultAppPickerFragment) {
+ final Bundle bundle = getArguments();
+ ((DefaultAppPickerFragment) fragment).onRadioButtonConfirmed(
+ bundle.getString(EXTRA_KEY));
+ }
+ }
+ }
+
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
new file mode 100644
index 0000000..a9433ac
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceController.java
@@ -0,0 +1,61 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.settings.applications.PackageManagerWrapper;
+import com.android.settings.applications.PackageManagerWrapperImpl;
+import com.android.settings.core.PreferenceController;
+
+public abstract class DefaultAppPreferenceController extends PreferenceController {
+
+ private static final String TAG = "DefaultAppPrefControl";
+
+ protected final PackageManagerWrapper mPackageManager;
+ protected final UserManager mUserManager;
+
+ protected int mUserId;
+
+ public DefaultAppPreferenceController(Context context) {
+ super(context);
+ mPackageManager = new PackageManagerWrapperImpl(context.getPackageManager());
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mUserId = UserHandle.myUserId();
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final DefaultAppInfo app = getDefaultAppInfo();
+ CharSequence defaultAppLabel = null;
+ if (app != null) {
+ defaultAppLabel = app.loadLabel(mPackageManager.getPackageManager());
+ }
+ if (!TextUtils.isEmpty(defaultAppLabel)) {
+ preference.setSummary(defaultAppLabel);
+ } else {
+ Log.d(TAG, "No default app");
+ }
+ }
+
+ protected abstract DefaultAppInfo getDefaultAppInfo();
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultBrowserPicker.java b/src/com/android/settings/applications/defaultapps/DefaultBrowserPicker.java
new file mode 100644
index 0000000..70d9415
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultBrowserPicker.java
@@ -0,0 +1,71 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+import com.android.internal.logging.nano.MetricsProto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Fragment for choosing default browser.
+ */
+public class DefaultBrowserPicker extends DefaultAppPickerFragment {
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DEFAULT_BROWSER_PICKER;
+ }
+
+ @Override
+ protected String getDefaultAppKey() {
+ return mPm.getDefaultBrowserPackageNameAsUser(mUserId);
+ }
+
+ @Override
+ protected boolean setDefaultAppKey(String packageName) {
+ return mPm.setDefaultBrowserPackageNameAsUser(packageName, mUserId);
+ }
+
+ @Override
+ protected List<DefaultAppInfo> getCandidates() {
+ final List<DefaultAppInfo> candidates = new ArrayList<>();
+
+ // Resolve that intent and check that the handleAllWebDataURI boolean is set
+ final List<ResolveInfo> list = mPm.queryIntentActivitiesAsUser(
+ DefaultBrowserPreferenceController.BROWSE_PROBE, PackageManager.MATCH_ALL, mUserId);
+
+ final int count = list.size();
+ for (int i = 0; i < count; i++) {
+ ResolveInfo info = list.get(i);
+ if (info.activityInfo == null || !info.handleAllWebDataURI) {
+ continue;
+ }
+ try {
+ candidates.add(new DefaultAppInfo(
+ mPm.getApplicationInfoAsUser(info.activityInfo.packageName, 0, mUserId)));
+ } catch (PackageManager.NameNotFoundException e) {
+ // Skip unknown packages.
+ }
+ }
+
+ return candidates;
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceController.java
new file mode 100644
index 0000000..dca300b
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceController.java
@@ -0,0 +1,104 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+
+import java.util.List;
+
+public class DefaultBrowserPreferenceController extends DefaultAppPreferenceController {
+
+ static final Intent BROWSE_PROBE = new Intent()
+ .setAction(Intent.ACTION_VIEW)
+ .addCategory(Intent.CATEGORY_BROWSABLE)
+ .setData(Uri.parse("http:"));
+
+ public DefaultBrowserPreferenceController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return "default_browser";
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ final DefaultAppInfo defaultApp = getDefaultAppInfo();
+ final CharSequence defaultAppLabel = defaultApp != null
+ ? defaultApp.loadLabel(mPackageManager.getPackageManager()) : null;
+ if (TextUtils.isEmpty(defaultAppLabel)) {
+ final String onlyAppLabel = getOnlyAppLabel();
+ if (!TextUtils.isEmpty(onlyAppLabel)) {
+ preference.setSummary(onlyAppLabel);
+ }
+ }
+ }
+
+ private String getOnlyAppLabel() {
+ // Resolve that intent and check that the handleAllWebDataURI boolean is set
+ final List<ResolveInfo> list = mPackageManager.queryIntentActivitiesAsUser(BROWSE_PROBE,
+ PackageManager.MATCH_ALL, mUserId);
+ if (list != null && list.size() == 1) {
+ return list.get(0).loadLabel(mPackageManager.getPackageManager()).toString();
+ }
+ return null;
+ }
+
+ @Override
+ protected DefaultAppInfo getDefaultAppInfo() {
+ try {
+ return new DefaultAppInfo(mPackageManager.getPackageManager().getApplicationInfo(
+ mPackageManager.getDefaultBrowserPackageNameAsUser(mUserId), 0));
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Whether or not the pkg contains browser capability
+ */
+ public static boolean hasBrowserPreference(String pkg, Context context) {
+ final Intent intent = new Intent(BROWSE_PROBE);
+ intent.setPackage(pkg);
+ final List<ResolveInfo> resolveInfos =
+ context.getPackageManager().queryIntentActivities(intent, 0);
+ return resolveInfos != null && resolveInfos.size() != 0;
+ }
+
+ /**
+ * Whether or not the pkg is the default browser
+ */
+ public static boolean isBrowserDefault(String pkg, Context context) {
+ String defaultPackage = context.getPackageManager()
+ .getDefaultBrowserPackageNameAsUser(UserHandle.myUserId());
+ return defaultPackage != null && defaultPackage.equals(pkg);
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultEmergencyPicker.java b/src/com/android/settings/applications/defaultapps/DefaultEmergencyPicker.java
new file mode 100644
index 0000000..b3cf882
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultEmergencyPicker.java
@@ -0,0 +1,101 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.content.ContentResolver;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.Utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultEmergencyPicker extends DefaultAppPickerFragment {
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DEFAULT_EMERGENCY_APP_PICKER;
+ }
+
+ @Override
+ protected List<DefaultAppInfo> getCandidates() {
+ final List<DefaultAppInfo> candidates = new ArrayList<>();
+ final List<ResolveInfo> infos = mPm.getPackageManager().queryIntentActivities(
+ DefaultEmergencyPreferenceController.QUERY_INTENT, 0);
+ PackageInfo bestMatch = null;
+ for (ResolveInfo info : infos) {
+ try {
+ final PackageInfo packageInfo =
+ mPm.getPackageManager().getPackageInfo(info.activityInfo.packageName, 0);
+ final ApplicationInfo appInfo = packageInfo.applicationInfo;
+ candidates.add(new DefaultAppInfo(appInfo));
+ // Get earliest installed system app.
+ if (isSystemApp(appInfo) && (bestMatch == null ||
+ bestMatch.firstInstallTime > packageInfo.firstInstallTime)) {
+ bestMatch = packageInfo;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Skip unknown packages.
+ }
+ if (bestMatch != null) {
+ final String defaultKey = getDefaultAppKey();
+ if (TextUtils.isEmpty(defaultKey)) {
+ setDefaultAppKey(bestMatch.packageName);
+ }
+ }
+ }
+ return candidates;
+ }
+
+ @Override
+ protected String getConfirmationMessage(DefaultAppInfo info) {
+ return Utils.isPackageDirectBootAware(getContext(), info.getKey()) ? null
+ : getContext().getString(R.string.direct_boot_unaware_dialog_message);
+ }
+
+ @Override
+ protected String getDefaultAppKey() {
+ return Settings.Secure.getString(getContext().getContentResolver(),
+ Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
+ }
+
+ @Override
+ protected boolean setDefaultAppKey(String key) {
+ final ContentResolver contentResolver = getContext().getContentResolver();
+ final String previousValue = Settings.Secure.getString(contentResolver,
+ Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
+
+ if (!TextUtils.isEmpty(key) && !TextUtils.equals(key, previousValue)) {
+ Settings.Secure.putString(contentResolver,
+ Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
+ key);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isSystemApp(ApplicationInfo info) {
+ return info != null && (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultEmergencyPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultEmergencyPreferenceController.java
new file mode 100644
index 0000000..7713dbd
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultEmergencyPreferenceController.java
@@ -0,0 +1,74 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+
+import java.util.List;
+
+public class DefaultEmergencyPreferenceController extends DefaultAppPreferenceController {
+
+ private static final boolean DEFAULT_EMERGENCY_APP_IS_CONFIGURABLE = false;
+
+ public static final Intent QUERY_INTENT = new Intent(
+ TelephonyManager.ACTION_EMERGENCY_ASSISTANCE);
+
+ public DefaultEmergencyPreferenceController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return DEFAULT_EMERGENCY_APP_IS_CONFIGURABLE
+ && isCapable()
+ && mPackageManager.getPackageManager().resolveActivity(QUERY_INTENT, 0) != null;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return "default_emergency_app";
+ }
+
+ @Override
+ protected DefaultAppInfo getDefaultAppInfo() {
+ return null;
+ }
+
+ private boolean isCapable() {
+ return TelephonyManager.EMERGENCY_ASSISTANCE_ENABLED
+ && mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_voice_capable);
+ }
+
+ public static boolean hasEmergencyPreference(String pkg, Context context) {
+ Intent i = new Intent(QUERY_INTENT);
+ i.setPackage(pkg);
+ final List<ResolveInfo> resolveInfos =
+ context.getPackageManager().queryIntentActivities(i, 0);
+ return resolveInfos != null && resolveInfos.size() != 0;
+ }
+
+ public static boolean isEmergencyDefault(String pkg, Context context) {
+ String defaultPackage = Settings.Secure.getString(context.getContentResolver(),
+ Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION);
+ return defaultPackage != null && defaultPackage.equals(pkg);
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultHomePicker.java b/src/com/android/settings/applications/defaultapps/DefaultHomePicker.java
new file mode 100644
index 0000000..5e48bf1
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultHomePicker.java
@@ -0,0 +1,133 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.os.Build;
+import android.text.TextUtils;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultHomePicker extends DefaultAppPickerFragment {
+
+ private String mPackageName;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mPackageName = context.getPackageName();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DEFAULT_HOME_PICKER;
+ }
+
+ @Override
+ protected List<DefaultAppInfo> getCandidates() {
+ final boolean mustSupportManagedProfile = hasManagedProfile();
+ final List<DefaultAppInfo> candidates = new ArrayList<>();
+ final List<ResolveInfo> homeActivities = new ArrayList<>();
+ mPm.getHomeActivities(homeActivities);
+
+ for (ResolveInfo resolveInfo : homeActivities) {
+ final ActivityInfo info = resolveInfo.activityInfo;
+ final ComponentName activityName = new ComponentName(info.packageName, info.name);
+ if (info.packageName.equals(mPackageName)) {
+ continue;
+ }
+
+ final String summary;
+ if (mustSupportManagedProfile && !launcherHasManagedProfilesFeature(resolveInfo)) {
+ summary = getContext().getString(R.string.home_work_profile_not_supported);
+ } else {
+ summary = null;
+ }
+ final DefaultAppInfo candidate = new DefaultAppInfo(mUserId, activityName, summary);
+ candidates.add(candidate);
+ }
+ return candidates;
+ }
+
+ @Override
+ protected String getDefaultAppKey() {
+ final ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
+ final ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities);
+ if (currentDefaultHome != null) {
+ return currentDefaultHome.flattenToString();
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean setDefaultAppKey(String key) {
+ if (!TextUtils.isEmpty(key)) {
+ final ComponentName component = ComponentName.unflattenFromString(key);
+ final List<ResolveInfo> homeActivities = new ArrayList<>();
+ mPm.getHomeActivities(homeActivities);
+ final List<ComponentName> allComponents = new ArrayList<>();
+ for (ResolveInfo info : homeActivities) {
+ final ActivityInfo appInfo = info.activityInfo;
+ ComponentName activityName = new ComponentName(appInfo.packageName, appInfo.name);
+ allComponents.add(activityName);
+ }
+ mPm.replacePreferredActivity(
+ DefaultHomePreferenceController.HOME_FILTER,
+ IntentFilter.MATCH_CATEGORY_EMPTY,
+ allComponents.toArray(new ComponentName[0]),
+ component);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean hasManagedProfile() {
+ final Context context = getContext();
+ List<UserInfo> profiles = mUserManager.getProfiles(context.getUserId());
+ for (UserInfo userInfo : profiles) {
+ if (userInfo.isManagedProfile()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean launcherHasManagedProfilesFeature(ResolveInfo resolveInfo) {
+ try {
+ ApplicationInfo appInfo = mPm.getPackageManager().getApplicationInfo(
+ resolveInfo.activityInfo.packageName, 0 /* default flags */);
+ return versionNumberAtLeastL(appInfo.targetSdkVersion);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ private boolean versionNumberAtLeastL(int versionNumber) {
+ return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceController.java
new file mode 100644
index 0000000..055c23b
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceController.java
@@ -0,0 +1,117 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultHomePreferenceController extends DefaultAppPreferenceController {
+
+ static final IntentFilter HOME_FILTER;
+
+ private final String mPackageName;
+
+ static {
+ HOME_FILTER = new IntentFilter(Intent.ACTION_MAIN);
+ HOME_FILTER.addCategory(Intent.CATEGORY_HOME);
+ HOME_FILTER.addCategory(Intent.CATEGORY_DEFAULT);
+ }
+
+ public DefaultHomePreferenceController(Context context) {
+ super(context);
+ mPackageName = mContext.getPackageName();
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return "default_home";
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ final DefaultAppInfo defaultApp = getDefaultAppInfo();
+ final CharSequence defaultAppLabel = defaultApp != null
+ ? defaultApp.loadLabel(mPackageManager.getPackageManager()) : null;
+ if (TextUtils.isEmpty(defaultAppLabel)) {
+ final String onlyAppLabel = getOnlyAppLabel();
+ if (!TextUtils.isEmpty(onlyAppLabel)) {
+ preference.setSummary(onlyAppLabel);
+ }
+ }
+ }
+
+ @Override
+ protected DefaultAppInfo getDefaultAppInfo() {
+ final ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
+ final ComponentName currentDefaultHome = mPackageManager.getHomeActivities(homeActivities);
+
+ return new DefaultAppInfo(mUserId, currentDefaultHome, null /* summary */);
+ }
+
+ private String getOnlyAppLabel() {
+ final List<ResolveInfo> homeActivities = new ArrayList<>();
+ final List<ActivityInfo> appLabels = new ArrayList<>();
+
+ mPackageManager.getHomeActivities(homeActivities);
+ for (ResolveInfo candidate : homeActivities) {
+ final ActivityInfo info = candidate.activityInfo;
+ if (info.packageName.equals(mPackageName)) {
+ continue;
+ }
+ appLabels.add(info);
+ }
+ return appLabels.size() == 1
+ ? appLabels.get(0).loadLabel(mPackageManager.getPackageManager()).toString()
+ : null;
+ }
+
+ public static boolean hasHomePreference(String pkg, Context context) {
+ ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
+ PackageManager pm = context.getPackageManager();
+ pm.getHomeActivities(homeActivities);
+ for (int i = 0; i < homeActivities.size(); i++) {
+ if (homeActivities.get(i).activityInfo.packageName.equals(pkg)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isHomeDefault(String pkg, Context context) {
+ ArrayList<ResolveInfo> homeActivities = new ArrayList<>();
+ PackageManager pm = context.getPackageManager();
+ ComponentName def = pm.getHomeActivities(homeActivities);
+
+ return def != null && def.getPackageName().equals(pkg);
+ }
+}
diff --git a/src/com/android/settings/applications/DefaultNotificationAssistantPreference.java b/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPicker.java
similarity index 62%
rename from src/com/android/settings/applications/DefaultNotificationAssistantPreference.java
rename to src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPicker.java
index 91fc0c8..af67917 100644
--- a/src/com/android/settings/applications/DefaultNotificationAssistantPreference.java
+++ b/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPicker.java
@@ -1,5 +1,5 @@
-/**
- * Copyright (C) 2016 The Android Open Source Project
+/*
+ * 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.
@@ -14,65 +14,48 @@
* limitations under the License.
*/
-package com.android.settings.applications;
-
-import com.android.settings.AppListPreference;
+package com.android.settings.applications.defaultapps;
import android.app.ActivityManager;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.provider.Settings;
import android.service.notification.NotificationAssistantService;
-import android.util.AttributeSet;
import android.util.Slog;
-import java.util.ArrayList;
-import java.util.List;
-
import com.android.settings.R;
import com.android.settings.utils.ManagedServiceSettings;
-public class DefaultNotificationAssistantPreference extends AppListPreference {
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultNotificationAssistantPicker extends DefaultAppPickerFragment {
private static final String TAG = "DefaultNotiAssist";
- private PackageManager mPm;
- private final ManagedServiceSettings.Config mConfig;
- private final Context mContext;
+ private final ManagedServiceSettings.Config mConfig = getConfig();
- public DefaultNotificationAssistantPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- mContext = context;
- mPm = context.getPackageManager();
- mConfig = getConfig();
- setShowItemNone(true);
- updateList(getServices());
+ @Override
+ public int getMetricsCategory() {
+ return 0;
}
@Override
- protected boolean persistString(String value) {
- Settings.Secure.putString(mContext.getContentResolver(), mConfig.setting, value);
- setSummary(getEntry());
+ protected String getDefaultAppKey() {
+ return Settings.Secure.getString(getContext().getContentResolver(), mConfig.setting);
+ }
+
+ @Override
+ protected boolean setDefaultAppKey(String value) {
+ Settings.Secure.putString(getContext().getContentResolver(), mConfig.setting, value);
return true;
}
- private void updateList(List<ServiceInfo> services) {
- final ComponentName[] assistants = new ComponentName[services.size()];
- for (int i = 0; i < services.size(); i++) {
- assistants[i] = new ComponentName(services.get(i).packageName, services.get(i).name);
- }
- final String assistant =
- Settings.Secure.getString(mContext.getContentResolver(), mConfig.setting);
- setComponentNames(assistants, assistant == null ? null
- : ComponentName.unflattenFromString(assistant));
- }
-
- private List<ServiceInfo> getServices() {
- List<ServiceInfo> services = new ArrayList<>();
+ @Override
+ protected List<DefaultAppInfo> getCandidates() {
+ List<DefaultAppInfo> candidates = new ArrayList<>();
final int user = ActivityManager.getCurrentUser();
List<ResolveInfo> installedServices = mPm.queryIntentServicesAsUser(
@@ -91,9 +74,16 @@
+ mConfig.permission);
continue;
}
- services.add(info);
+
+ candidates.add(new DefaultAppInfo(
+ mUserId, new ComponentName(info.packageName, info.name), null /* summary */));
}
- return services;
+ return candidates;
+ }
+
+ @Override
+ protected boolean shouldShowItemNone() {
+ return true;
}
private ManagedServiceSettings.Config getConfig() {
@@ -108,4 +98,4 @@
c.emptyText = R.string.no_notification_listeners;
return c;
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultPhonePicker.java b/src/com/android/settings/applications/defaultapps/DefaultPhonePicker.java
new file mode 100644
index 0000000..0fa9390
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultPhonePicker.java
@@ -0,0 +1,102 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.telecom.DefaultDialerManager;
+import android.telecom.TelecomManager;
+import android.text.TextUtils;
+
+import com.android.internal.logging.nano.MetricsProto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultPhonePicker extends DefaultAppPickerFragment {
+
+ private DefaultKeyUpdater mDefaultKeyUpdater;
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DEFAULT_PHONE_PICKER;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mDefaultKeyUpdater = new DefaultKeyUpdater(
+ (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE));
+ }
+
+ @Override
+ protected List<DefaultAppInfo> getCandidates() {
+ final List<DefaultAppInfo> candidates = new ArrayList<>();
+ final List<String> dialerPackages =
+ DefaultDialerManager.getInstalledDialerApplications(getContext(), mUserId);
+ for (String packageName : dialerPackages) {
+ try {
+ candidates.add(new DefaultAppInfo(
+ mPm.getApplicationInfoAsUser(packageName, 0, mUserId)));
+ } catch (PackageManager.NameNotFoundException e) {
+ // Skip unknown packages.
+ }
+ }
+ return candidates;
+ }
+
+ @Override
+ protected String getDefaultAppKey() {
+ return mDefaultKeyUpdater.getDefaultDialerApplication(getContext(), mUserId);
+ }
+
+ @Override
+ protected String getSystemDefaultAppKey() {
+ return mDefaultKeyUpdater.getSystemDialerPackage();
+ }
+
+ @Override
+ protected boolean setDefaultAppKey(String key) {
+ if (!TextUtils.isEmpty(key) && !TextUtils.equals(key, getDefaultAppKey())) {
+ return mDefaultKeyUpdater.setDefaultDialerApplication(getContext(), key, mUserId);
+ }
+ return false;
+ }
+
+ /**
+ * Wrapper class to handle default phone app update.
+ */
+ static class DefaultKeyUpdater {
+ private final TelecomManager mTelecomManager;
+
+ public DefaultKeyUpdater(TelecomManager telecomManager) {
+ mTelecomManager = telecomManager;
+ }
+
+ public String getSystemDialerPackage() {
+ return mTelecomManager.getSystemDialerPackage();
+ }
+
+ public String getDefaultDialerApplication(Context context, int uid) {
+ return DefaultDialerManager.getDefaultDialerApplication(context, uid);
+ }
+
+ public boolean setDefaultDialerApplication(Context context, String key, int uid) {
+ return DefaultDialerManager.setDefaultDialerApplication(context, key, uid);
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultPhonePreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultPhonePreferenceController.java
new file mode 100644
index 0000000..fa0d28f
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultPhonePreferenceController.java
@@ -0,0 +1,74 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telecom.DefaultDialerManager;
+import android.telephony.TelephonyManager;
+
+import java.util.List;
+
+public class DefaultPhonePreferenceController extends DefaultAppPreferenceController {
+
+ public DefaultPhonePreferenceController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ final TelephonyManager tm =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ if (!tm.isVoiceCapable()) {
+ return false;
+ }
+ final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ final boolean hasUserRestriction =
+ um.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS);
+
+ return !hasUserRestriction;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return "default_phone_app";
+ }
+
+ @Override
+ protected DefaultAppInfo getDefaultAppInfo() {
+ try {
+ return new DefaultAppInfo(mPackageManager.getPackageManager().getApplicationInfo(
+ DefaultDialerManager.getDefaultDialerApplication(mContext, mUserId), 0));
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ public static boolean hasPhonePreference(String pkg, Context context) {
+ List<String> dialerPackages =
+ DefaultDialerManager.getInstalledDialerApplications(context, UserHandle.myUserId());
+ return dialerPackages.contains(pkg);
+ }
+
+ public static boolean isPhoneDefault(String pkg, Context context) {
+ String def = DefaultDialerManager.getDefaultDialerApplication(context,
+ UserHandle.myUserId());
+ return def != null && def.equals(pkg);
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultSmsPicker.java b/src/com/android/settings/applications/defaultapps/DefaultSmsPicker.java
new file mode 100644
index 0000000..31bb6f6
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultSmsPicker.java
@@ -0,0 +1,97 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.text.TextUtils;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.telephony.SmsApplication;
+import com.android.settings.R;
+import com.android.settings.Utils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class DefaultSmsPicker extends DefaultAppPickerFragment {
+
+ private DefaultKeyUpdater mDefaultKeyUpdater = new DefaultKeyUpdater();
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DEFAULT_SMS_PICKER;
+ }
+
+ @Override
+ protected List<DefaultAppInfo> getCandidates() {
+ final Collection<SmsApplication.SmsApplicationData> smsApplications =
+ SmsApplication.getApplicationCollection(getContext());
+ final List<DefaultAppInfo> candidates = new ArrayList<>(smsApplications.size());
+
+ for (SmsApplication.SmsApplicationData smsApplicationData : smsApplications) {
+ try {
+ candidates.add(new DefaultAppInfo(
+ mPm.getApplicationInfoAsUser(smsApplicationData.mPackageName, 0, mUserId)));
+ } catch (PackageManager.NameNotFoundException e) {
+ // Skip unknown packages.
+ }
+ }
+
+ return candidates;
+ }
+
+ @Override
+ protected String getDefaultAppKey() {
+ return mDefaultKeyUpdater.getDefaultApplication(getContext());
+ }
+
+ @Override
+ protected boolean setDefaultAppKey(String key) {
+ if (!TextUtils.isEmpty(key) && !TextUtils.equals(key, getDefaultAppKey())) {
+ mDefaultKeyUpdater.setDefaultApplication(getContext(), key);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected String getConfirmationMessage(DefaultAppInfo info) {
+ return Utils.isPackageDirectBootAware(getContext(), info.getKey()) ? null
+ : getContext().getString(R.string.direct_boot_unaware_dialog_message);
+ }
+
+ /**
+ * Wrapper class to handle default phone app update.
+ */
+ static class DefaultKeyUpdater {
+
+ public String getDefaultApplication(Context context) {
+ final ComponentName appName = SmsApplication.getDefaultSmsApplication(context, true);
+ if (appName != null) {
+ return appName.getPackageName();
+ }
+ return null;
+ }
+
+ public void setDefaultApplication(Context context, String key) {
+ SmsApplication.setDefaultApplication(key, context);
+ }
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultSmsPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultSmsPreferenceController.java
new file mode 100644
index 0000000..90b9d83
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultSmsPreferenceController.java
@@ -0,0 +1,70 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.SmsApplication;
+
+import java.util.Collection;
+
+public class DefaultSmsPreferenceController extends DefaultAppPreferenceController {
+
+ public DefaultSmsPreferenceController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ boolean isRestrictedUser = mUserManager.getUserInfo(mUserId).isRestricted();
+ TelephonyManager tm =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ return !isRestrictedUser && tm.isSmsCapable();
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return "default_sms_app";
+ }
+
+ @Override
+ protected DefaultAppInfo getDefaultAppInfo() {
+ final ComponentName app = SmsApplication.getDefaultSmsApplication(mContext, true);
+ if (app != null) {
+ return new DefaultAppInfo(mUserId, app, null /* summary */);
+ }
+ return null;
+ }
+
+ public static boolean hasSmsPreference(String pkg, Context context) {
+ Collection<SmsApplication.SmsApplicationData> smsApplications =
+ SmsApplication.getApplicationCollection(context);
+ for (SmsApplication.SmsApplicationData data : smsApplications) {
+ if (data.mPackageName.equals(pkg)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isSmsDefault(String pkg, Context context) {
+ ComponentName appName = SmsApplication.getDefaultSmsApplication(context, true);
+ return appName != null && appName.getPackageName().equals(pkg);
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultWorkBrowserPreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultWorkBrowserPreferenceController.java
new file mode 100644
index 0000000..0cee3c5
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultWorkBrowserPreferenceController.java
@@ -0,0 +1,43 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+import com.android.settings.Utils;
+
+public class DefaultWorkBrowserPreferenceController extends DefaultBrowserPreferenceController {
+
+ public DefaultWorkBrowserPreferenceController(Context context) {
+ super(context);
+ final UserHandle managedProfile = Utils.getManagedProfile(mUserManager);
+ if (managedProfile != null) {
+ mUserId = managedProfile.getIdentifier();
+ }
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return Utils.getManagedProfile(mUserManager) != null;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return "work_default_browser";
+ }
+}
diff --git a/src/com/android/settings/applications/defaultapps/DefaultWorkPhonePreferenceController.java b/src/com/android/settings/applications/defaultapps/DefaultWorkPhonePreferenceController.java
new file mode 100644
index 0000000..f793fa3
--- /dev/null
+++ b/src/com/android/settings/applications/defaultapps/DefaultWorkPhonePreferenceController.java
@@ -0,0 +1,31 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.content.Context;
+
+public class DefaultWorkPhonePreferenceController extends DefaultPhonePreferenceController {
+
+ public DefaultWorkPhonePreferenceController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return "work_default_phone_app";
+ }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
index b956aa5..3fc0abf 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -284,12 +284,12 @@
}
}
if (btClass != null) {
- if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
- return new Pair<Integer, String>(R.drawable.ic_bt_headphones_a2dp, HEADPHONE);
- }
- if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
+ if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
return new Pair<Integer, String>(R.drawable.ic_bt_headset_hfp, HEADSET);
}
+ if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
+ return new Pair<Integer, String>(R.drawable.ic_bt_headphones_a2dp, HEADPHONE);
+ }
}
return new Pair<Integer, String>(R.drawable.ic_settings_bluetooth, BLUETOOTH);
}
diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
index fe0e1d2..481a0eb 100644
--- a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java
@@ -30,7 +30,6 @@
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
-import com.android.settingslib.drawer.CategoryKey;
import java.util.ArrayList;
import java.util.Arrays;
@@ -48,11 +47,6 @@
}
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_DEVICE;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
@@ -73,8 +67,7 @@
mUsbPrefController = new UsbModePreferenceController(context, new UsbBackend(context));
lifecycle.addObserver(mUsbPrefController);
controllers.add(mUsbPrefController);
- mBluetoothPreferenceController =
- new BluetoothMasterSwitchPreferenceController(
+ mBluetoothPreferenceController = new BluetoothMasterSwitchPreferenceController(
context, Utils.getLocalBtManager(context));
lifecycle.addObserver(mBluetoothPreferenceController);
controllers.add(mBluetoothPreferenceController);
diff --git a/src/com/android/settings/core/InstrumentedPreferenceFragment.java b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
index 29ba287..ce0daa9 100644
--- a/src/com/android/settings/core/InstrumentedPreferenceFragment.java
+++ b/src/com/android/settings/core/InstrumentedPreferenceFragment.java
@@ -76,4 +76,8 @@
mDividerDecoration.setDivider(divider);
super.setDivider(new ColorDrawable(Color.TRANSPARENT));
}
+
+ protected final Context getPrefContext() {
+ return getPreferenceManager().getContext();
+ }
}
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index 682fa36..51a990e 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -57,6 +57,7 @@
import com.android.settings.applications.ManageAssist;
import com.android.settings.applications.ManageDomainUrls;
import com.android.settings.applications.NotificationApps;
+import com.android.settings.applications.PictureInPictureSettings;
import com.android.settings.applications.ProcessStatsSummary;
import com.android.settings.applications.ProcessStatsUi;
import com.android.settings.applications.UsageAccessDetails;
@@ -100,6 +101,7 @@
import com.android.settings.nfc.AndroidBeam;
import com.android.settings.nfc.PaymentSettings;
import com.android.settings.notification.AppNotificationSettings;
+import com.android.settings.notification.ChannelNotificationSettings;
import com.android.settings.notification.ConfigureNotificationSettings;
import com.android.settings.notification.NotificationAccessSettings;
import com.android.settings.notification.NotificationStation;
@@ -208,6 +210,7 @@
InstalledAppDetails.class.getName(),
BatterySaverSettings.class.getName(),
AppNotificationSettings.class.getName(),
+ ChannelNotificationSettings.class.getName(),
OtherSoundSettings.class.getName(),
ApnSettings.class.getName(),
ApnEditor.class.getName(),
@@ -225,6 +228,7 @@
AdvancedAppSettings.class.getName(),
WallpaperTypeSettings.class.getName(),
VrListenerSettings.class.getName(),
+ PictureInPictureSettings.class.getName(),
ManagedProfileSettings.class.getName(),
ChooseAccountActivity.class.getName(),
IccLockSettings.class.getName(),
@@ -302,5 +306,6 @@
"com.android.settings.DateTimeDashboardAlias",
"com.android.settings.AccessibilityDashboardAlias",
"com.android.settings.AboutDeviceDashboardAlias",
+ "com.android.settings.EnterprisePrivacyDashboardAlias",
};
}
diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java
index de86bd5..f8a27e0 100644
--- a/src/com/android/settings/dashboard/DashboardFragment.java
+++ b/src/com/android/settings/dashboard/DashboardFragment.java
@@ -194,7 +194,10 @@
/**
* Returns the CategoryKey for loading {@link DashboardCategory} for this fragment.
*/
- protected abstract String getCategoryKey();
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ public String getCategoryKey() {
+ return DashboardFragmentRegistry.PARENT_TO_CATEGORY_KEY_MAP.get(getClass().getName());
+ }
/**
* Get the tag string for logging.
@@ -302,9 +305,9 @@
final Context context = getContext();
mSummaryLoader = new SummaryLoader(getActivity(), getCategoryKey());
mSummaryLoader.setSummaryConsumer(this);
- final TypedArray a = context.obtainStyledAttributes(new int[] {
- mDashboardFeatureProvider.isEnabled() ? android.R.attr.colorControlNormal
- : android.R.attr.colorAccent});
+ final TypedArray a = context.obtainStyledAttributes(new int[]{
+ mDashboardFeatureProvider.isEnabled() ? android.R.attr.colorControlNormal
+ : android.R.attr.colorAccent});
final int tintColor = a.getColor(0, context.getColor(android.R.color.white));
a.recycle();
final String pkgName = context.getPackageName();
@@ -319,7 +322,7 @@
continue;
}
if (pkgName != null && tile.intent != null
- && !pkgName.equals(tile.intent.getComponent().getPackageName())) {
+ && !pkgName.equals(tile.intent.getComponent().getPackageName())) {
// If this drawable is coming from outside Settings, tint it to match the color.
tile.icon.setTint(tintColor);
}
diff --git a/src/com/android/settings/dashboard/DashboardFragmentRegistry.java b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java
new file mode 100644
index 0000000..a315836
--- /dev/null
+++ b/src/com/android/settings/dashboard/DashboardFragmentRegistry.java
@@ -0,0 +1,99 @@
+/*
+ * 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.dashboard;
+
+import android.util.ArrayMap;
+
+import com.android.settings.DevelopmentSettings;
+import com.android.settings.DisplaySettings;
+import com.android.settings.SecuritySettings;
+import com.android.settings.accounts.AccountDetailDashboardFragment;
+import com.android.settings.accounts.UserAndAccountDashboardFragment;
+import com.android.settings.applications.AdvancedAppSettings;
+import com.android.settings.applications.AppAndNotificationDashboardFragment;
+import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
+import com.android.settings.deviceinfo.StorageDashboardFragment;
+import com.android.settings.fuelgauge.PowerUsageSummary;
+import com.android.settings.inputmethod.InputAndGestureSettings;
+import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
+import com.android.settings.language.LanguageAndRegionSettings;
+import com.android.settings.network.NetworkDashboardFragment;
+import com.android.settings.notification.SoundSettings;
+import com.android.settings.system.SystemDashboardFragment;
+import com.android.settingslib.drawer.CategoryKey;
+
+import java.util.Map;
+
+/**
+ * A registry to keep track of which page hosts which category.
+ */
+public class DashboardFragmentRegistry {
+
+ /**
+ * Map from parent fragment to category key. The parent fragment hosts child with
+ * category_key.
+ */
+ public static final Map<String, String> PARENT_TO_CATEGORY_KEY_MAP;
+
+ /**
+ * Map from category_key to parent. This is a helper to look up which fragment hosts the
+ * category_key.
+ */
+ public static final Map<String, String> CATEGORY_KEY_TO_PARENT_MAP;
+
+ static {
+ PARENT_TO_CATEGORY_KEY_MAP = new ArrayMap<>();
+ PARENT_TO_CATEGORY_KEY_MAP.put(
+ NetworkDashboardFragment.class.getName(), CategoryKey.CATEGORY_NETWORK);
+ PARENT_TO_CATEGORY_KEY_MAP.put(ConnectedDeviceDashboardFragment.class.getName(),
+ CategoryKey.CATEGORY_DEVICE);
+ PARENT_TO_CATEGORY_KEY_MAP.put(AppAndNotificationDashboardFragment.class.getName(),
+ CategoryKey.CATEGORY_APPS);
+ PARENT_TO_CATEGORY_KEY_MAP.put(PowerUsageSummary.class.getName(),
+ CategoryKey.CATEGORY_BATTERY);
+ PARENT_TO_CATEGORY_KEY_MAP.put(AdvancedAppSettings.class.getName(),
+ CategoryKey.CATEGORY_APPS_DEFAULT);
+ PARENT_TO_CATEGORY_KEY_MAP.put(DisplaySettings.class.getName(),
+ CategoryKey.CATEGORY_DISPLAY);
+ PARENT_TO_CATEGORY_KEY_MAP.put(SoundSettings.class.getName(),
+ CategoryKey.CATEGORY_SOUND);
+ PARENT_TO_CATEGORY_KEY_MAP.put(StorageDashboardFragment.class.getName(),
+ CategoryKey.CATEGORY_STORAGE);
+ PARENT_TO_CATEGORY_KEY_MAP.put(SecuritySettings.class.getName(),
+ CategoryKey.CATEGORY_SECURITY);
+ PARENT_TO_CATEGORY_KEY_MAP.put(AccountDetailDashboardFragment.class.getName(),
+ CategoryKey.CATEGORY_ACCOUNT);
+ PARENT_TO_CATEGORY_KEY_MAP.put(UserAndAccountDashboardFragment.class.getName(),
+ CategoryKey.CATEGORY_ACCOUNT);
+ PARENT_TO_CATEGORY_KEY_MAP.put(
+ SystemDashboardFragment.class.getName(), CategoryKey.CATEGORY_SYSTEM);
+ PARENT_TO_CATEGORY_KEY_MAP.put(
+ InputAndGestureSettings.class.getName(), CategoryKey.CATEGORY_SYSTEM_INPUT);
+ PARENT_TO_CATEGORY_KEY_MAP.put(InputMethodAndLanguageSettings.class.getName(),
+ CategoryKey.CATEGORY_SYSTEM_LANGUAGE);
+ PARENT_TO_CATEGORY_KEY_MAP.put(LanguageAndRegionSettings.class.getName(),
+ CategoryKey.CATEGORY_SYSTEM_LANGUAGE);
+ PARENT_TO_CATEGORY_KEY_MAP.put(DevelopmentSettings.class.getName(),
+ CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
+
+ CATEGORY_KEY_TO_PARENT_MAP = new ArrayMap<>(PARENT_TO_CATEGORY_KEY_MAP.size());
+
+ for (Map.Entry<String, String> parentToKey : PARENT_TO_CATEGORY_KEY_MAP.entrySet()) {
+ CATEGORY_KEY_TO_PARENT_MAP.put(parentToKey.getValue(), parentToKey.getKey());
+ }
+ }
+}
diff --git a/src/com/android/settings/dashboard/SiteMapManager.java b/src/com/android/settings/dashboard/SiteMapManager.java
new file mode 100644
index 0000000..3144398
--- /dev/null
+++ b/src/com/android/settings/dashboard/SiteMapManager.java
@@ -0,0 +1,212 @@
+/*
+ * 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.dashboard;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.settings.SettingsActivity;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.search.IndexDatabaseHelper.IndexColumns;
+import com.android.settings.search.IndexDatabaseHelper.SiteMapColumns;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static com.android.settings.dashboard.DashboardFragmentRegistry.CATEGORY_KEY_TO_PARENT_MAP;
+
+/**
+ * A manager class that maintains a "site map" and look up breadcrumb for a certain page on demand.
+ * <p/>
+ * The methods on this class can only be called on a background thread.
+ */
+public class SiteMapManager {
+
+ private static final String TAG = "SiteMapManager";
+ private static final boolean DEBUG_TIMING = false;
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ public static final String[] SITE_MAP_COLUMNS = {
+ SiteMapColumns.PARENT_CLASS,
+ SiteMapColumns.PARENT_TITLE,
+ SiteMapColumns.CHILD_CLASS,
+ SiteMapColumns.CHILD_TITLE
+ };
+
+ private static final String[] CLASS_TO_SCREEN_TITLE_COLUMNS = {
+ IndexColumns.CLASS_NAME,
+ IndexColumns.SCREEN_TITLE,
+ };
+
+ private final List<SiteMapPair> mPairs = new ArrayList<>();
+
+ private boolean mInitialized;
+
+ /**
+ * Given a fragment class name and its screen title, build a breadcrumb from Settings root to
+ * this screen.
+ * <p/>
+ * Not all screens have a full breadcrumb path leading up to root, it's because either some
+ * page in the breadcrumb path is not indexed, or it's only reachable via search.
+ */
+ @WorkerThread
+ public synchronized List<String> buildBreadCrumb(Context context, String clazz,
+ String screenTitle) {
+ init(context);
+ final long startTime = System.currentTimeMillis();
+ final List<String> breadcrumbs = new ArrayList<>();
+ if (!mInitialized) {
+ Log.w(TAG, "SiteMap is not initialized yet, skipping");
+ return breadcrumbs;
+ }
+ breadcrumbs.add(screenTitle);
+ String currentClass = clazz;
+ String currentTitle = screenTitle;
+ // Look up current page's parent, if found add it to breadcrumb string list, and repeat.
+ while (true) {
+ final SiteMapPair pair = lookUpParent(currentClass, currentTitle);
+ if (pair == null) {
+ if (DEBUG_TIMING) {
+ Log.d(TAG, "BreadCrumb timing: " + (System.currentTimeMillis() - startTime));
+ }
+ return breadcrumbs;
+ }
+ breadcrumbs.add(0, pair.parentTitle);
+ currentClass = pair.parentClass;
+ currentTitle = pair.parentTitle;
+ }
+ }
+
+ /**
+ * Initialize a list of {@link SiteMapPair}s. Each pair knows about a single parent-child
+ * page relationship.
+ *
+ * We get the knowledge of such mPairs from 2 sources:
+ * 1. Static indexing time: we know which page(s) a parent can open by parsing its pref xml.
+ * 2. IA: We know from {@link DashboardFeatureProvider} which page can be dynamically
+ * injected to where.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ @WorkerThread
+ synchronized void init(Context context) {
+ if (mInitialized) {
+ // Make sure only init once.
+ return;
+ }
+ final long startTime = System.currentTimeMillis();
+ // First load site map from static index table.
+ final Context appContext = context.getApplicationContext();
+ final SQLiteDatabase db = IndexDatabaseHelper.getInstance(appContext).getReadableDatabase();
+ Cursor sitemap = db.query(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, SITE_MAP_COLUMNS, null,
+ null, null, null, null);
+ while (sitemap.moveToNext()) {
+ final SiteMapPair pair = new SiteMapPair(
+ sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_CLASS)),
+ sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_TITLE)),
+ sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_CLASS)),
+ sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_TITLE)));
+ mPairs.add(pair);
+ }
+ sitemap.close();
+
+ // Then prepare a local map that contains class name -> screen title mapping. This is needed
+ // to figure out the display name for any fragment if it's injected dynamically through IA.
+ final Map<String, String> classToTitleMap = new HashMap<>();
+ final Cursor titleQuery = db.query(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX,
+ CLASS_TO_SCREEN_TITLE_COLUMNS, null, null, null, null, null);
+ while (titleQuery.moveToNext()) {
+ classToTitleMap.put(
+ titleQuery.getString(titleQuery.getColumnIndex(IndexColumns.CLASS_NAME)),
+ titleQuery.getString(titleQuery.getColumnIndex(IndexColumns.SCREEN_TITLE)));
+ }
+ titleQuery.close();
+
+ // Loop through all IA categories and pages and build additional SiteMapPairs
+ List<DashboardCategory> categories = FeatureFactory.getFactory(context)
+ .getDashboardFeatureProvider(context).getAllCategories();
+
+ for (DashboardCategory category : categories) {
+ // Find the category key first.
+ final String parentClass = CATEGORY_KEY_TO_PARENT_MAP.get(category.key);
+ if (parentClass == null) {
+ continue;
+ }
+ // Use the key to look up parent (which page hosts this key)
+ final String parentName = classToTitleMap.get(parentClass);
+ if (parentName == null) {
+ continue;
+ }
+ // Build parent-child mPairs for all children listed under this key.
+ for (Tile tile : category.tiles) {
+ final String childTitle = tile.title.toString();
+ String childClass = null;
+ if (tile.metaData != null) {
+ childClass = tile.metaData.getString(
+ SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS);
+ }
+ if (childClass == null) {
+ continue;
+ }
+ mPairs.add(new SiteMapPair(parentClass, parentName, childClass, childTitle));
+ }
+ }
+ // Done.
+ mInitialized = true;
+ if (DEBUG_TIMING) {
+ Log.d(TAG, "Init timing: " + (System.currentTimeMillis() - startTime));
+ }
+ }
+
+ @WorkerThread
+ private SiteMapPair lookUpParent(String clazz, String title) {
+ for (SiteMapPair pair : mPairs) {
+ if (TextUtils.equals(pair.childClass, clazz)
+ && TextUtils.equals(title, pair.childTitle)) {
+ return pair;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Data model for a parent-child page pair.
+ */
+ private static class SiteMapPair {
+ public final String parentClass;
+ public final String parentTitle;
+ public final String childClass;
+ public final String childTitle;
+
+ public SiteMapPair(String parentClass, String parentTitle, String childClass,
+ String childTitle) {
+ this.parentClass = parentClass;
+ this.parentTitle = parentTitle;
+ this.childClass = childClass;
+ this.childTitle = childTitle;
+ }
+ }
+}
diff --git a/src/com/android/settings/dashboard/SuggestionFeatureProvider.java b/src/com/android/settings/dashboard/SuggestionFeatureProvider.java
index 769c6e8..b20edee 100644
--- a/src/com/android/settings/dashboard/SuggestionFeatureProvider.java
+++ b/src/com/android/settings/dashboard/SuggestionFeatureProvider.java
@@ -32,4 +32,4 @@
/** Return true if the suggestion has already been completed and does not need to be shown */
boolean isSuggestionCompleted(Context context);
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/datausage/BillingCycleSettings.java b/src/com/android/settings/datausage/BillingCycleSettings.java
index 9b39840..dda984b 100644
--- a/src/com/android/settings/datausage/BillingCycleSettings.java
+++ b/src/com/android/settings/datausage/BillingCycleSettings.java
@@ -44,14 +44,16 @@
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.WARNING_DISABLED;
-import static android.net.TrafficStats.GB_IN_BYTES;
-import static android.net.TrafficStats.MB_IN_BYTES;
public class BillingCycleSettings extends DataUsageBase implements
Preference.OnPreferenceChangeListener, DataUsageEditController {
private static final String TAG = "BillingCycleSettings";
private static final boolean LOGD = false;
+ public static final long KB_IN_BYTES = 1000;
+ public static final long MB_IN_BYTES = KB_IN_BYTES * 1000;
+ public static final long GB_IN_BYTES = MB_IN_BYTES * 1000;
+
private static final long MAX_DATA_LIMIT_BYTES = 50000 * GB_IN_BYTES;
private static final String TAG_CONFIRM_LIMIT = "confirmLimit";
diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
index d05d088..59f9a84 100644
--- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
+++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
@@ -21,9 +21,6 @@
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.provider.SearchIndexableResource;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
@@ -62,7 +59,8 @@
// Initialize the storage sizes that we can quickly calc.
StorageManager sm = context.getSystemService(StorageManager.class);
- String volumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID);
+ String volumeId = getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID,
+ VolumeInfo.ID_PRIVATE_INTERNAL);
mVolume = sm.findVolumeById(volumeId);
if (!isVolumeValid()) {
getActivity().finish();
@@ -103,11 +101,6 @@
}
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_STORAGE;
- }
-
- @Override
protected int getPreferenceScreenResId() {
return R.xml.storage_dashboard_fragment;
}
diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
index 7e98918..22f9c4c 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java
@@ -122,7 +122,8 @@
intent = getAudioIntent();
break;
case GAME_KEY:
- // TODO: Once app categorization is added, make this section.
+ intent = getGamesIntent();
+ break;
case OTHER_APPS_KEY:
// Because we are likely constructed with a null volume, this is theoretically
// possible.
@@ -259,6 +260,15 @@
false);
}
+ private Intent getGamesIntent() {
+ Bundle args = new Bundle(1);
+ args.putString(ManageApplications.EXTRA_CLASSNAME,
+ Settings.GamesStorageActivity.class.getName());
+ return Utils.onBuildStartFragmentIntent(mContext,
+ ManageApplications.class.getName(), args, null, R.string.game_storage_settings,
+ null, false);
+ }
+
private Intent getFilesIntent() {
return mSvp.findEmulatedForPrivate(mVolume).buildBrowseIntent();
}
diff --git a/src/com/android/settings/display/ThemePreferenceController.java b/src/com/android/settings/display/ThemePreferenceController.java
new file mode 100644
index 0000000..da5b84a
--- /dev/null
+++ b/src/com/android/settings/display/ThemePreferenceController.java
@@ -0,0 +1,112 @@
+/*
+ * 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.display;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_THEME;
+
+import android.app.AlertDialog;
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.DialogInterface.OnClickListener;
+import android.support.v7.preference.ListPreference;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.core.instrumentation.MetricsFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
+
+import libcore.util.Objects;
+
+public class ThemePreferenceController extends PreferenceController implements
+ Preference.OnPreferenceChangeListener {
+
+ private static final String KEY_THEME = "theme";
+
+ private final UiModeManager mUiModeManager;
+ private final MetricsFeatureProvider mMetricsFeatureProvider;
+
+ public ThemePreferenceController(Context context) {
+ super(context);
+ mUiModeManager = context.getSystemService(UiModeManager.class);
+ mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_THEME;
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (KEY_THEME.equals(preference.getKey())) {
+ mMetricsFeatureProvider.action(mContext, ACTION_THEME);
+ }
+ return false;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ ListPreference pref = (ListPreference) preference;
+ String[] options = mUiModeManager.getAvailableThemes();
+ for (int i = 0; i < options.length; i++) {
+ options[i] = nullToDefault(options[i]);
+ }
+ pref.setEntries(options);
+ pref.setEntryValues(options);
+ String theme = mUiModeManager.getTheme();
+ if (theme == null) {
+ theme = mContext.getString(R.string.default_theme);
+ }
+ pref.setValue(nullToDefault(theme));
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (Objects.equal(newValue, mUiModeManager.getTheme())) {
+ return true;
+ }
+ // TODO: STOPSHIP Don't require reboot and remove this prompt.
+ OnClickListener onConfirm = (d, i) -> {
+ mUiModeManager.setTheme(defaultToNull((String) newValue));
+ ((ListPreference) preference).setValue((String) newValue);
+ };
+ new AlertDialog.Builder(mContext)
+ .setTitle(R.string.change_theme_reboot)
+ .setPositiveButton(com.android.internal.R.string.global_action_restart, onConfirm)
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ return false;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ String[] themes = mUiModeManager.getAvailableThemes();
+ return themes != null && themes.length > 1;
+ }
+
+ private String nullToDefault(String input) {
+ if (input == null) {
+ return mContext.getString(R.string.default_theme);
+ }
+ return input;
+ }
+
+ private String defaultToNull(String input) {
+ if (mContext.getString(R.string.default_theme).equals(input)) {
+ return null;
+ }
+ return input;
+ }
+}
diff --git a/src/com/android/settings/enterprise/EnterprisePrivacySettings.java b/src/com/android/settings/enterprise/EnterprisePrivacySettings.java
index 208bf0c..75d3b10 100644
--- a/src/com/android/settings/enterprise/EnterprisePrivacySettings.java
+++ b/src/com/android/settings/enterprise/EnterprisePrivacySettings.java
@@ -24,7 +24,6 @@
import com.android.settings.core.PreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settings.search.Indexable.SearchIndexProvider;
import java.util.ArrayList;
import java.util.Arrays;
@@ -40,11 +39,6 @@
}
@Override
- protected String getCategoryKey() {
- return null;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
@@ -65,6 +59,7 @@
controllers.add(new AdminGrantedLocationPermissionsPreferenceController(context));
controllers.add(new AdminGrantedMicrophonePermissionPreferenceController(context));
controllers.add(new AdminGrantedCameraPermissionPreferenceController(context));
+ controllers.add(new EnterpriseSetDefaultAppsPreferenceController(context));
controllers.add(new AlwaysOnVpnPrimaryUserPreferenceController(context));
controllers.add(new AlwaysOnVpnManagedProfilePreferenceController(context));
controllers.add(new GlobalHttpProxyPreferenceController(context));
diff --git a/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceController.java b/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceController.java
new file mode 100644
index 0000000..23627cd
--- /dev/null
+++ b/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceController.java
@@ -0,0 +1,101 @@
+/*
+ * 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.content.Intent;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.applications.ApplicationFeatureProvider;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+
+public class EnterpriseSetDefaultAppsPreferenceController extends PreferenceController {
+
+ private static final String KEY_DEFAULT_APPS = "number_enterprise_set_default_apps";
+ private final ApplicationFeatureProvider mFeatureProvider;
+
+ public EnterpriseSetDefaultAppsPreferenceController(Context context) {
+ super(context);
+ mFeatureProvider = FeatureFactory.getFactory(context)
+ .getApplicationFeatureProvider(context);
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ // Browser
+ int num = mFeatureProvider.findPersistentPreferredActivities(new Intent[] {
+ buildIntent(Intent.ACTION_VIEW, Intent.CATEGORY_BROWSABLE, "http:", null)}).size();
+ // Camera
+ num += mFeatureProvider.findPersistentPreferredActivities(new Intent[] {
+ new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
+ new Intent(MediaStore.ACTION_VIDEO_CAPTURE)}).size();
+ // Map
+ num += mFeatureProvider.findPersistentPreferredActivities(new Intent[] {
+ buildIntent(Intent.ACTION_VIEW, null, "geo:", null)}).size();
+ // E-mail
+ num += mFeatureProvider.findPersistentPreferredActivities(new Intent[] {
+ new Intent(Intent.ACTION_SENDTO), new Intent(Intent.ACTION_SEND),
+ new Intent(Intent.ACTION_SEND_MULTIPLE)}).size();
+ // Calendar
+ num += mFeatureProvider.findPersistentPreferredActivities(new Intent[] {
+ buildIntent(Intent.ACTION_INSERT, null, null, "vnd.android.cursor.dir/event")})
+ .size();
+ // Contacts
+ num += mFeatureProvider.findPersistentPreferredActivities(new Intent[] {
+ buildIntent(Intent.ACTION_PICK, null, null,
+ ContactsContract.Contacts.CONTENT_TYPE)}).size();
+ // Dialer
+ num += mFeatureProvider.findPersistentPreferredActivities(new Intent[] {
+ new Intent(Intent.ACTION_DIAL), new Intent(Intent.ACTION_CALL)}).size();
+
+ if (num == 0) {
+ preference.setVisible(false);
+ } else {
+ preference.setVisible(true);
+ preference.setTitle(mContext.getResources().getQuantityString(
+ R.plurals.enterprise_privacy_number_enterprise_set_default_apps,
+ num, num));
+ }
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_DEFAULT_APPS;
+ }
+
+ private static Intent buildIntent(String action, String category, String protocol,
+ String type) {
+ final Intent intent = new Intent(action);
+ if (category != null) {
+ intent.addCategory(category);
+ }
+ if (protocol != null) {
+ intent.setData(Uri.parse(protocol));
+ }
+ if (type != null) {
+ intent.setType(type);
+ }
+ return intent;
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/PowerUsageDetail.java b/src/com/android/settings/fuelgauge/PowerUsageDetail.java
index dbc5360..7acb7ce 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageDetail.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageDetail.java
@@ -388,11 +388,6 @@
}
@Override
- protected String getCategoryKey() {
- return null;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
index 24218b0..a989ee6 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
@@ -37,6 +37,7 @@
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatterySipper.DrainType;
@@ -52,7 +53,6 @@
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.BatteryInfo;
-import com.android.settingslib.drawer.CategoryKey;
import java.util.ArrayList;
import java.util.Arrays;
@@ -139,11 +139,6 @@
}
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_BATTERY;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
@@ -345,6 +340,8 @@
final List<BatterySipper> usageList = getCoalescedUsageList(
USE_FAKE_DATA ? getFakeStats() : mStatsHelper.getUsageList());
+ final double screenPowerMah = removeScreenBatterySipper(usageList);
+
final int dischargeAmount = USE_FAKE_DATA ? 5000
: stats != null ? stats.getDischargeAmount(mStatsType) : 0;
final int numSippers = usageList.size();
@@ -353,9 +350,16 @@
if (shouldHideSipper(sipper)) {
continue;
}
- double totalPower = USE_FAKE_DATA ? 4000 : mStatsHelper.getTotalPower();
+
+ // Deduct the screen power from total power, used to calculate percentOfTotal
+ double totalPower = USE_FAKE_DATA ?
+ 4000 : mStatsHelper.getTotalPower() - screenPowerMah;
+
+ // With deduction in totalPower, percentOfTotal is higher because it adds the part
+ // used in screen
final double percentOfTotal =
((sipper.totalPowerMah / totalPower) * dischargeAmount);
+
if (((int) (percentOfTotal + .5)) < 1) {
continue;
}
@@ -406,8 +410,9 @@
pref.setTitle(entry.getLabel());
pref.setOrder(i + 1);
pref.setPercent(percentOfMax, percentOfTotal);
- if ((sipper.drainType != DrainType.APP || sipper.uidObj.getUid() == Process.ROOT_UID)
- && sipper.drainType != DrainType.USER) {
+ if ((sipper.drainType != DrainType.APP
+ || sipper.uidObj.getUid() == Process.ROOT_UID)
+ && sipper.drainType != DrainType.USER) {
pref.setTint(colorControl);
}
addedSome = true;
@@ -450,6 +455,19 @@
}
}
+ @VisibleForTesting
+ double removeScreenBatterySipper(List<BatterySipper> sippers) {
+ for (int i = 0, size = sippers.size(); i < size; i++) {
+ final BatterySipper sipper = sippers.get(i);
+ if (sipper.drainType == DrainType.SCREEN) {
+ sippers.remove(i);
+ return sipper.totalPowerMah;
+ }
+ }
+
+ return 0;
+ }
+
private static List<BatterySipper> getFakeStats() {
ArrayList<BatterySipper> stats = new ArrayList<>();
float use = 5;
@@ -558,7 +576,7 @@
= new SummaryLoader.SummaryProviderFactory() {
@Override
public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
- SummaryLoader summaryLoader) {
+ SummaryLoader summaryLoader) {
return new SummaryProvider(activity, summaryLoader);
}
};
diff --git a/src/com/android/settings/gestures/DoubleTapPowerSettings.java b/src/com/android/settings/gestures/DoubleTapPowerSettings.java
index c31e999..9251f0b 100644
--- a/src/com/android/settings/gestures/DoubleTapPowerSettings.java
+++ b/src/com/android/settings/gestures/DoubleTapPowerSettings.java
@@ -40,11 +40,6 @@
}
@Override
- protected String getCategoryKey() {
- return null;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/gestures/DoubleTapScreenSettings.java b/src/com/android/settings/gestures/DoubleTapScreenSettings.java
index d203fbc..f374f9e 100644
--- a/src/com/android/settings/gestures/DoubleTapScreenSettings.java
+++ b/src/com/android/settings/gestures/DoubleTapScreenSettings.java
@@ -42,11 +42,6 @@
}
@Override
- protected String getCategoryKey() {
- return null;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/gestures/DoubleTwistGestureSettings.java b/src/com/android/settings/gestures/DoubleTwistGestureSettings.java
index 1303ca1..5f5b873 100644
--- a/src/com/android/settings/gestures/DoubleTwistGestureSettings.java
+++ b/src/com/android/settings/gestures/DoubleTwistGestureSettings.java
@@ -40,11 +40,6 @@
}
@Override
- protected String getCategoryKey() {
- return null;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/gestures/GestureSettings.java b/src/com/android/settings/gestures/GestureSettings.java
index 9b71b96..db6925e 100644
--- a/src/com/android/settings/gestures/GestureSettings.java
+++ b/src/com/android/settings/gestures/GestureSettings.java
@@ -122,11 +122,6 @@
}
@Override
- protected String getCategoryKey() {
- return null;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
@@ -170,7 +165,7 @@
List<PreferenceController> preferenceControllers =
getPreferenceControllers(context);
- for(PreferenceController controller : preferenceControllers) {
+ for (PreferenceController controller : preferenceControllers) {
controller.updateNonIndexableKeys(result);
}
return result;
diff --git a/src/com/android/settings/gestures/PickupGestureSettings.java b/src/com/android/settings/gestures/PickupGestureSettings.java
index 8c0f74c..08e61fa 100644
--- a/src/com/android/settings/gestures/PickupGestureSettings.java
+++ b/src/com/android/settings/gestures/PickupGestureSettings.java
@@ -42,11 +42,6 @@
}
@Override
- protected String getCategoryKey() {
- return null;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/gestures/SwipeToNotificationSettings.java b/src/com/android/settings/gestures/SwipeToNotificationSettings.java
index fba074e..155412c 100644
--- a/src/com/android/settings/gestures/SwipeToNotificationSettings.java
+++ b/src/com/android/settings/gestures/SwipeToNotificationSettings.java
@@ -40,11 +40,6 @@
}
@Override
- protected String getCategoryKey() {
- return null;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/inputmethod/InputAndGestureSettings.java b/src/com/android/settings/inputmethod/InputAndGestureSettings.java
index 4ad5af2..5b2c51a 100644
--- a/src/com/android/settings/inputmethod/InputAndGestureSettings.java
+++ b/src/com/android/settings/inputmethod/InputAndGestureSettings.java
@@ -34,7 +34,6 @@
import com.android.settings.gestures.SwipeToNotificationPreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settingslib.drawer.CategoryKey;
import java.util.ArrayList;
import java.util.Arrays;
@@ -52,11 +51,6 @@
}
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_SYSTEM_INPUT;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
index 587d039..121af98 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
@@ -38,7 +38,6 @@
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.search.SearchIndexableRaw;
-import com.android.settingslib.drawer.CategoryKey;
import java.util.ArrayList;
import java.util.List;
@@ -61,11 +60,6 @@
}
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_SYSTEM_LANGUAGE;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/language/LanguageAndRegionSettings.java b/src/com/android/settings/language/LanguageAndRegionSettings.java
index 7e92e05..649b2ff 100644
--- a/src/com/android/settings/language/LanguageAndRegionSettings.java
+++ b/src/com/android/settings/language/LanguageAndRegionSettings.java
@@ -24,7 +24,6 @@
import com.android.settings.core.PreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.inputmethod.SpellCheckerPreferenceController;
-import com.android.settingslib.drawer.CategoryKey;
import java.util.ArrayList;
import java.util.List;
@@ -39,11 +38,6 @@
}
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_SYSTEM_LANGUAGE;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java
index 89dfa02..7e6de96 100644
--- a/src/com/android/settings/network/NetworkDashboardFragment.java
+++ b/src/com/android/settings/network/NetworkDashboardFragment.java
@@ -27,7 +27,6 @@
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settingslib.drawer.CategoryKey;
import java.util.ArrayList;
import java.util.Arrays;
@@ -47,11 +46,6 @@
}
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_NETWORK;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java
index b740b92..45762c7 100644
--- a/src/com/android/settings/notification/AppNotificationSettings.java
+++ b/src/com/android/settings/notification/AppNotificationSettings.java
@@ -19,13 +19,16 @@
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
+import android.provider.Settings;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -35,11 +38,13 @@
import com.android.settings.Utils;
import com.android.settings.applications.AppHeaderController;
import com.android.settings.applications.AppInfoBase;
+import com.android.settings.core.PreferenceController;
import com.android.settings.dashboard.DashboardFeatureProvider;
import com.android.settings.notification.NotificationBackend.AppRow;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.RestrictedSwitchPreference;
+import com.android.settingslib.drawer.CategoryKey;
import java.text.Collator;
import java.util.Collections;
@@ -81,6 +86,11 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) {
+ Log.w(TAG, "Missing package or uid or packageinfo");
+ toastAndFinish();
+ return;
+ }
final Activity activity = getActivity();
mDashboardFeatureProvider =
FeatureFactory.getFactory(activity).getDashboardFeatureProvider(activity);
@@ -88,10 +98,12 @@
addPreferencesFromResource(R.xml.app_notification_settings);
mBlock = (RestrictedSwitchPreference) getPreferenceScreen().findPreference(KEY_BLOCK);
+ mBadge = (RestrictedSwitchPreference) getPreferenceScreen().findPreference(KEY_BADGE);
mChannels = (PreferenceCategory) findPreference(KEY_CHANNELS);
if (mPkgInfo != null) {
- setupBlock(mAppRow.systemApp, mAppRow.banned);
+ setupBlock();
+ setupBadge();
// load settings intent
ArrayMap<String, AppRow> rows = new ArrayMap<String, AppRow>();
rows.put(mAppRow.pkg, mAppRow);
@@ -113,12 +125,13 @@
if (channel.isDeleted()) {
channelPref.setTitle(
getString(R.string.deleted_channel_name, channel.getName()));
+ channelPref.setEnabled(false);
} else {
Bundle channelArgs = new Bundle();
channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid);
channelArgs.putBoolean(AppHeader.EXTRA_HIDE_INFO_BUTTON, true);
channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mPkg);
- channelArgs.putString(ARG_CHANNEL, channel.getId());
+ channelArgs.putString(Settings.EXTRA_CHANNEL_ID, channel.getId());
Intent channelIntent = Utils.onBuildStartFragmentIntent(getActivity(),
ChannelNotificationSettings.class.getName(),
channelArgs, null, 0, null, false);
@@ -148,21 +161,32 @@
@Override
public void onResume() {
super.onResume();
- if ((mUid != -1 && getPackageManager().getPackagesForUid(mUid) == null)) {
- // App isn't around anymore, must have been removed.
+ if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) {
+ Log.w(TAG, "Missing package or uid or packageinfo");
finish();
return;
}
- if (mBlock != null) {
- mBlock.setDisabledByAdmin(mSuspendedAppsAdmin);
- }
}
- private void setupBlock(boolean notBlockable, boolean banned) {
- if (notBlockable) {
+ private void setupBadge() {
+ mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
+ mBadge.setChecked(mAppRow.showBadge);
+ mBadge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ final boolean value = (Boolean) newValue;
+ mBackend.setShowBadge(mPkg, mUid, value);
+ return true;
+ }
+ });
+ }
+
+ private void setupBlock() {
+ if (mAppRow.systemApp) {
setVisible(mBlock, false);
} else {
- mBlock.setChecked(banned);
+ mBlock.setDisabledByAdmin(mSuspendedAppsAdmin);
+ mBlock.setChecked(mAppRow.banned);
mBlock.setOnPreferenceChangeListener(
new Preference.OnPreferenceChangeListener() {
@Override
@@ -180,6 +204,7 @@
private void updateDependents(boolean banned) {
setVisible(mChannels, !(mChannelList.isEmpty() || banned));
+ setVisible(mBadge, !banned);
}
private List<ResolveInfo> queryNotificationConfigActivities() {
diff --git a/src/com/android/settings/notification/ChannelNotificationSettings.java b/src/com/android/settings/notification/ChannelNotificationSettings.java
index e73feb5..ecce79e 100644
--- a/src/com/android/settings/notification/ChannelNotificationSettings.java
+++ b/src/com/android/settings/notification/ChannelNotificationSettings.java
@@ -34,6 +34,8 @@
import android.provider.Settings;
import android.service.notification.NotificationListenerService.Ranking;
import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+import android.util.Log;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.widget.LockPatternUtils;
@@ -47,15 +49,17 @@
import com.android.settingslib.RestrictedSwitchPreference;
import java.util.ArrayList;
+import java.util.List;
public class ChannelNotificationSettings extends NotificationSettingsBase {
+ private static final String TAG = "ChannelSettings";
+
protected static final String KEY_BYPASS_DND = "bypass_dnd";
protected static final String KEY_VISIBILITY_OVERRIDE = "visibility_override";
protected static final String KEY_IMPORTANCE = "importance";
protected static final String KEY_LIGHTS = "lights";
protected static final String KEY_VIBRATE = "vibrate";
protected static final String KEY_RINGTONE = "ringtone";
- protected static final String KEY_BADGE = "badge";
protected RestrictedSwitchPreference mLights;
protected RestrictedSwitchPreference mVibrate;
@@ -84,6 +88,11 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null || mChannel == null) {
+ Log.w(TAG, "Missing package or uid or packageinfo or channel");
+ toastAndFinish();
+ return;
+ }
final Activity activity = getActivity();
mDashboardFeatureProvider =
FeatureFactory.getFactory(activity).getDashboardFeatureProvider(activity);
@@ -100,7 +109,7 @@
mVibrate = (RestrictedSwitchPreference) findPreference(KEY_VIBRATE);
mRingtone = (DefaultNotificationTonePreference) findPreference(KEY_RINGTONE);
- if (mPkgInfo != null) {
+ if (mPkgInfo != null && mChannel != null) {
setupPriorityPref(mChannel.canBypassDnd());
setupVisOverridePref(mChannel.getLockscreenVisibility());
setupLights();
@@ -114,8 +123,8 @@
.getApplicationFeatureProvider(activity)
.newAppHeaderController(this /* fragment */, null /* appHeader */)
.setIcon(mAppRow.icon)
- .setLabel(mAppRow.label)
- .setSummary(mChannel.getName())
+ .setLabel(mChannel.getName())
+ .setSummary(mAppRow.label)
.setPackageName(mAppRow.pkg)
.setUid(mAppRow.uid)
.setButtonActions(AppHeaderController.ActionType.ACTION_APP_INFO,
@@ -128,8 +137,8 @@
@Override
public void onResume() {
super.onResume();
- if ((mUid != -1 && getPackageManager().getPackagesForUid(mUid) == null)) {
- // App isn't around anymore, must have been removed.
+ if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null || mChannel == null) {
+ Log.w(TAG, "Missing package or uid or packageinfo or channel");
finish();
return;
}
@@ -138,8 +147,6 @@
mImportance.setDisabledByAdmin(mSuspendedAppsAdmin);
mPriority.setDisabledByAdmin(mSuspendedAppsAdmin);
mVisibilityOverride.setDisabledByAdmin(mSuspendedAppsAdmin);
- mBlock.setDisabledByAdmin(mSuspendedAppsAdmin);
- mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
}
private void setupLights() {
@@ -204,6 +211,7 @@
}
});
mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
+ mBadge.setEnabled(mAppRow.showBadge);
mBadge.setChecked(mChannel.canShowBadge());
mBadge.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
@@ -218,15 +226,20 @@
mImportance.setDisabledByAdmin(mSuspendedAppsAdmin);
final int numImportances = IMPORTANCE_HIGH - IMPORTANCE_MIN + 1;
- String[] summaries = new String[numImportances];
- String[] values = new String[numImportances];
+ List<String> summaries = new ArrayList<>();
+ List<String> values = new ArrayList<>();;
for (int i = 0; i < numImportances; i++) {
int importance = i + 1;
- summaries[i] = getSummary(importance);
- values[i] = String.valueOf(importance);
+ summaries.add(getSummary(importance));
+ values.add(String.valueOf(importance));
}
- mImportance.setEntryValues(values);
- mImportance.setEntries(summaries);
+ if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())) {
+ // Add option to reset to letting the app decide
+ summaries.add(getSummary(NotificationManager.IMPORTANCE_UNSPECIFIED));
+ values.add(String.valueOf(NotificationManager.IMPORTANCE_UNSPECIFIED));
+ }
+ mImportance.setEntryValues(values.toArray(new String[0]));
+ mImportance.setEntries(summaries.toArray(new String[0]));
mImportance.setValue(String.valueOf(mChannel.getImportance()));
mImportance.setSummary("%s");
@@ -245,6 +258,8 @@
private String getSummary(int importance) {
switch (importance) {
+ case NotificationManager.IMPORTANCE_UNSPECIFIED:
+ return getContext().getString(R.string.notification_importance_unspecified);
case NotificationManager.IMPORTANCE_NONE:
return getContext().getString(R.string.notification_importance_blocked);
case NotificationManager.IMPORTANCE_MIN:
diff --git a/src/com/android/settings/notification/ConfigureNotificationSettings.java b/src/com/android/settings/notification/ConfigureNotificationSettings.java
index fd59c97..2a737d4 100644
--- a/src/com/android/settings/notification/ConfigureNotificationSettings.java
+++ b/src/com/android/settings/notification/ConfigureNotificationSettings.java
@@ -40,11 +40,6 @@
}
@Override
- protected String getCategoryKey() {
- return "";
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java
index 692e1f6..124579f 100644
--- a/src/com/android/settings/notification/NotificationBackend.java
+++ b/src/com/android/settings/notification/NotificationBackend.java
@@ -26,6 +26,7 @@
import android.content.pm.ParceledListSlice;
import android.graphics.drawable.Drawable;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.util.Log;
import com.android.settingslib.Utils;
@@ -48,6 +49,8 @@
}
row.icon = app.loadIcon(pm);
row.banned = getNotificationsBanned(row.pkg, row.uid);
+ row.showBadge = canShowBadge(row.pkg, row.uid);
+ row.userId = UserHandle.getUserId(row.uid);
return row;
}
@@ -87,6 +90,25 @@
}
}
+ public boolean canShowBadge(String pkg, int uid) {
+ try {
+ return sINM.canShowBadge(pkg, uid);
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return false;
+ }
+ }
+
+ public boolean setShowBadge(String pkg, int uid, boolean showBadge) {
+ try {
+ sINM.setShowBadge(pkg, uid, showBadge);
+ return true;
+ } catch (Exception e) {
+ Log.w(TAG, "Error calling NoMan", e);
+ return false;
+ }
+ }
+
public NotificationChannel getChannel(String pkg, int uid, String channelId) {
if (channelId == null) {
return null;
@@ -129,6 +151,8 @@
public boolean banned;
public boolean first; // first app in section
public boolean systemApp;
+ public boolean showBadge;
+ public int userId;
}
public static class ChannelRow extends AppRow {
diff --git a/src/com/android/settings/notification/NotificationSettingsBase.java b/src/com/android/settings/notification/NotificationSettingsBase.java
index f3e4390..f6c6591 100644
--- a/src/com/android/settings/notification/NotificationSettingsBase.java
+++ b/src/com/android/settings/notification/NotificationSettingsBase.java
@@ -16,45 +16,37 @@
package com.android.settings.notification;
-import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedSwitchPreference;
-import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
-import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.service.notification.NotificationListenerService.Ranking;
import android.support.v7.preference.Preference;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
-import java.util.ArrayList;
-
-import static com.android.settings.notification.RestrictedDropDownPreference.RestrictedItem;
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
abstract public class NotificationSettingsBase extends SettingsPreferenceFragment {
private static final String TAG = "NotifiSettingsBase";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- protected static final String ARG_CHANNEL = "channel";
-
protected static final String KEY_BLOCK = "block";
+ protected static final String KEY_BADGE = "badge";
protected PackageManager mPm;
protected UserManager mUm;
@@ -106,26 +98,26 @@
mUid = args != null && args.containsKey(AppInfoBase.ARG_PACKAGE_UID)
? args.getInt(AppInfoBase.ARG_PACKAGE_UID)
: intent.getIntExtra(Settings.EXTRA_APP_UID, -1);
- if (mUid == -1 || TextUtils.isEmpty(mPkg)) {
- Log.w(TAG, "Missing extras: " + Settings.EXTRA_APP_PACKAGE + " was " + mPkg + ", "
- + Settings.EXTRA_APP_UID + " was " + mUid);
- toastAndFinish();
- return;
- }
- mUserId = UserHandle.getUserId(mUid);
- if (DEBUG) Log.d(TAG, "Load details for pkg=" + mPkg + " uid=" + mUid);
+ if (mUid < 0) {
+ try {
+ mUid = mPm.getPackageUid(mPkg, 0);
+ } catch (NameNotFoundException e) {
+ }
+ }
+
mPkgInfo = findPackageInfo(mPkg, mUid);
- if (mPkgInfo == null) {
- Log.w(TAG, "Failed to find package info: " + Settings.EXTRA_APP_PACKAGE + " was " + mPkg
- + ", " + Settings.EXTRA_APP_UID + " was " + mUid);
+
+ if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) {
+ Log.w(TAG, "Missing package or uid or packageinfo");
toastAndFinish();
return;
}
+ mUserId = UserHandle.getUserId(mUid);
mAppRow = mBackend.loadAppRow(mContext, mPm, mPkgInfo);
- mChannel = (args != null && args.containsKey(ARG_CHANNEL)) ?
- mBackend.getChannel(mPkg, mUid, args.getString(ARG_CHANNEL)) : null;
+ mChannel = (args != null && args.containsKey(Settings.EXTRA_CHANNEL_ID)) ?
+ mBackend.getChannel(mPkg, mUid, args.getString(Settings.EXTRA_CHANNEL_ID)) : null;
mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended(
mContext, mPkg, mUserId);
@@ -137,14 +129,15 @@
@Override
public void onResume() {
super.onResume();
- if ((mUid != -1 && getPackageManager().getPackagesForUid(mUid) == null)) {
- // App isn't around anymore, must have been removed.
+ if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) {
+ Log.w(TAG, "Missing package or uid or packageinfo");
finish();
return;
}
mSuspendedAppsAdmin = RestrictedLockUtils.checkIfApplicationIsSuspended(
mContext, mPkg, mUserId);
mBlock.setDisabledByAdmin(mSuspendedAppsAdmin);
+ mBadge.setDisabledByAdmin(mSuspendedAppsAdmin);
}
protected void setVisible(Preference p, boolean visible) {
@@ -163,6 +156,9 @@
}
private PackageInfo findPackageInfo(String pkg, int uid) {
+ if (pkg == null || uid < 0) {
+ return null;
+ }
final String[] packages = mPm.getPackagesForUid(uid);
if (packages != null && pkg != null) {
final int N = packages.length;
@@ -179,4 +175,16 @@
}
return null;
}
+
+ private PackageInfo findPackageInfo(String pkg) {
+ if (pkg == null) {
+ return null;
+ }
+ try {
+ return mPm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Failed to load package " + pkg, e);
+ }
+ return null;
+ }
}
diff --git a/src/com/android/settings/notification/NotificationStation.java b/src/com/android/settings/notification/NotificationStation.java
index 04897e4..50bcd95 100644
--- a/src/com/android/settings/notification/NotificationStation.java
+++ b/src/com/android/settings/notification/NotificationStation.java
@@ -56,7 +56,7 @@
public class NotificationStation extends SettingsPreferenceFragment {
private static final String TAG = NotificationStation.class.getSimpleName();
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private static final boolean DUMP_EXTRAS = true;
private static final boolean DUMP_PARCEL = true;
private Handler mHandler;
@@ -362,6 +362,11 @@
.append(delim)
.append(rank.getImportanceExplanation());
}
+ sb.append("\n")
+ .append(bold(getString(
+ R.string.notification_log_details_badge)))
+ .append(delim)
+ .append(Boolean.toString(rank.canShowBadge()));
} else {
if (mRanking == null) {
sb.append("\n")
diff --git a/src/com/android/settings/notification/OtherSoundSettings.java b/src/com/android/settings/notification/OtherSoundSettings.java
index 15e7f8c..5c409d3 100644
--- a/src/com/android/settings/notification/OtherSoundSettings.java
+++ b/src/com/android/settings/notification/OtherSoundSettings.java
@@ -47,11 +47,6 @@
}
@Override
- protected String getCategoryKey() {
- return null;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/notification/SoundSettings.java b/src/com/android/settings/notification/SoundSettings.java
index a55278d..1cf0ae6 100644
--- a/src/com/android/settings/notification/SoundSettings.java
+++ b/src/com/android/settings/notification/SoundSettings.java
@@ -20,7 +20,6 @@
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -43,7 +42,7 @@
import com.android.settings.dashboard.SummaryLoader;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
-import com.android.settingslib.drawer.CategoryKey;
+
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -103,11 +102,6 @@
}
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_SOUND;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java
index 2c81241..c45bf0e 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.java
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java
@@ -25,6 +25,7 @@
import com.android.settings.applications.ApplicationFeatureProvider;
import com.android.settings.applications.ApplicationFeatureProviderImpl;
+import com.android.settings.applications.IPackageManagerWrapperImpl;
import com.android.settings.applications.PackageManagerWrapperImpl;
import com.android.settings.core.instrumentation.MetricsFeatureProvider;
import com.android.settings.core.instrumentation.MetricsFeatureProviderImpl;
@@ -90,7 +91,7 @@
if (mApplicationFeatureProvider == null) {
mApplicationFeatureProvider = new ApplicationFeatureProviderImpl(context,
new PackageManagerWrapperImpl(context.getPackageManager()),
- AppGlobals.getPackageManager(),
+ new IPackageManagerWrapperImpl(AppGlobals.getPackageManager()),
new DevicePolicyManagerWrapperImpl((DevicePolicyManager) context
.getSystemService(Context.DEVICE_POLICY_SERVICE)));
}
diff --git a/src/com/android/settings/search/IndexDatabaseHelper.java b/src/com/android/settings/search/IndexDatabaseHelper.java
index 8de6c54..60378c2 100644
--- a/src/com/android/settings/search/IndexDatabaseHelper.java
+++ b/src/com/android/settings/search/IndexDatabaseHelper.java
@@ -28,12 +28,13 @@
private static final String TAG = "IndexDatabaseHelper";
private static final String DATABASE_NAME = "search_index.db";
- private static final int DATABASE_VERSION = 116;
+ private static final int DATABASE_VERSION = 117;
private static final String INDEX = "index";
public interface Tables {
String TABLE_PREFS_INDEX = "prefs_index";
+ String TABLE_SITE_MAP = "site_map";
String TABLE_META_INDEX = "meta_index";
String TABLE_SAVED_QUERIES = "saved_queries";
}
@@ -72,6 +73,14 @@
String TIME_STAMP = "timestamp";
}
+ public interface SiteMapColumns {
+ String DOCID = "docid";
+ String PARENT_CLASS = "parent_class";
+ String CHILD_CLASS = "child_class";
+ String PARENT_TITLE = "parent_title";
+ String CHILD_TITLE = "child_title";
+ }
+
private static final String CREATE_INDEX_TABLE =
"CREATE VIRTUAL TABLE " + Tables.TABLE_PREFS_INDEX + " USING fts4" +
"(" +
@@ -132,6 +141,17 @@
SavedQueriesColumns.TIME_STAMP + " INTEGER" +
")";
+ private static final String CREATE_SITE_MAP_TABLE =
+ "CREATE VIRTUAL TABLE " + Tables.TABLE_SITE_MAP + " USING fts4" +
+ "(" +
+ SiteMapColumns.PARENT_CLASS +
+ ", " +
+ SiteMapColumns.CHILD_CLASS +
+ ", " +
+ SiteMapColumns.PARENT_TITLE +
+ ", " +
+ SiteMapColumns.CHILD_TITLE +
+ ")";
private static final String INSERT_BUILD_VERSION =
"INSERT INTO " + Tables.TABLE_META_INDEX +
" VALUES ('" + Build.VERSION.INCREMENTAL + "');";
@@ -164,6 +184,7 @@
db.execSQL(CREATE_INDEX_TABLE);
db.execSQL(CREATE_META_TABLE);
db.execSQL(CREATE_SAVED_QUERIES_TABLE);
+ db.execSQL(CREATE_SITE_MAP_TABLE);
db.execSQL(INSERT_BUILD_VERSION);
Log.i(TAG, "Bootstrapped database");
}
@@ -241,5 +262,6 @@
db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_META_INDEX);
db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_PREFS_INDEX);
db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SAVED_QUERIES);
+ db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_SITE_MAP);
}
}
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
index 4f9f92c..5040e45 100644
--- a/src/com/android/settings/search/SearchIndexableResources.java
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -72,6 +72,8 @@
import com.android.settings.print.PrintSettingsFragment;
import com.android.settings.sim.SimSettings;
import com.android.settings.system.SystemDashboardFragment;
+import com.android.settings.tts.TtsEnginePreferenceFragment;
+import com.android.settings.tts.TtsSlidersFragment;
import com.android.settings.users.UserSettings;
import com.android.settings.wifi.AdvancedWifiSettings;
import com.android.settings.wifi.ConfigureWifiSettings;
@@ -167,6 +169,9 @@
addIndex(ConnectedDeviceDashboardFragment.class, NO_DATA_RES_ID, R.drawable.ic_bt_laptop);
addIndex(EnterprisePrivacySettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_about);
addIndex(PaymentSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_nfc_payment);
+ addIndex(
+ TtsEnginePreferenceFragment.class, NO_DATA_RES_ID, R.drawable.ic_settings_language);
+ addIndex(TtsSlidersFragment.class, NO_DATA_RES_ID, R.drawable.ic_settings_language);
}
private SearchIndexableResources() {
diff --git a/src/com/android/settings/search2/CursorToSearchResultConverter.java b/src/com/android/settings/search2/CursorToSearchResultConverter.java
index 948397f..6549c18 100644
--- a/src/com/android/settings/search2/CursorToSearchResultConverter.java
+++ b/src/com/android/settings/search2/CursorToSearchResultConverter.java
@@ -28,8 +28,10 @@
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
+
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
+import com.android.settings.dashboard.SiteMapManager;
import java.util.ArrayList;
import java.util.Collections;
@@ -39,18 +41,20 @@
import java.util.Map;
import java.util.Set;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ID;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SCREEN_TITLE;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_TITLE;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SUMMARY_ON;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_CLASS_NAME;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ICON;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_ID;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE;
+import static com.android.settings.search2.DatabaseResultLoader
+ .COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS;
+import static com.android.settings.search2.DatabaseResultLoader
+ .COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_KEY;
-import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD_TYPE;
import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_PAYLOAD_TYPE;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SCREEN_TITLE;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_SUMMARY_ON;
+import static com.android.settings.search2.DatabaseResultLoader.COLUMN_INDEX_TITLE;
/**
* Controller to Build search results from {@link Cursor} Objects.
@@ -78,7 +82,8 @@
mQueryText = queryText;
}
- public List<SearchResult> convertCursor(Cursor cursorResults, int baseRank) {
+ public List<SearchResult> convertCursor(SiteMapManager sitemapManager,
+ Cursor cursorResults, int baseRank) {
if (cursorResults == null) {
return null;
}
@@ -86,8 +91,8 @@
final List<SearchResult> results = new ArrayList<>();
while (cursorResults.moveToNext()) {
- SearchResult result = buildSingleSearchResultFromCursor(contextMap, cursorResults,
- baseRank);
+ SearchResult result = buildSingleSearchResultFromCursor(sitemapManager,
+ contextMap, cursorResults, baseRank);
if (result != null) {
results.add(result);
}
@@ -96,8 +101,8 @@
return results;
}
- private SearchResult buildSingleSearchResultFromCursor(Map<String, Context> contextMap,
- Cursor cursor, int baseRank) {
+ private SearchResult buildSingleSearchResultFromCursor(SiteMapManager sitemapManager,
+ Map<String, Context> contextMap, Cursor cursor, int baseRank) {
final String docId = cursor.getString(COLUMN_INDEX_ID);
/* Make sure that this result has not yet been added as a result. Checking the docID
covers the case of multiple queries matching the same row, but we need to also to check
@@ -128,7 +133,7 @@
return null;
}
- final List<String> breadcrumbs = getBreadcrumbs(cursor);
+ final List<String> breadcrumbs = getBreadcrumbs(sitemapManager, cursor);
final int rank = getRank(breadcrumbs, baseRank);
final SearchResult.Builder builder = new SearchResult.Builder();
@@ -210,12 +215,11 @@
return null;
}
- private List<String> getBreadcrumbs(Cursor cursor) {
- final List<String> breadcrumbs = new ArrayList<>();
+ private List<String> getBreadcrumbs(SiteMapManager siteMapManager, Cursor cursor) {
final String screenTitle = cursor.getString(COLUMN_INDEX_SCREEN_TITLE);
- if (!TextUtils.isEmpty(screenTitle)) {
- breadcrumbs.add(screenTitle);
- }
+ final String screenClass = cursor.getString(COLUMN_INDEX_CLASS_NAME);
+ final List<String> breadcrumbs = siteMapManager.buildBreadCrumb(mContext, screenClass,
+ screenTitle);
return breadcrumbs;
}
diff --git a/src/com/android/settings/search2/DatabaseIndexingManager.java b/src/com/android/settings/search2/DatabaseIndexingManager.java
index 073e202..d7c8746 100644
--- a/src/com/android/settings/search2/DatabaseIndexingManager.java
+++ b/src/com/android/settings/search2/DatabaseIndexingManager.java
@@ -34,7 +34,6 @@
import android.provider.SearchIndexableResource;
import android.provider.SearchIndexablesContract;
import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Xml;
@@ -517,10 +516,7 @@
nonIndexableKeys.addAll(resNonIndxableKeys);
}
- indexFromResource(sir.context, database, localeStr,
- sir.xmlResId, sir.className, sir.iconResId, sir.rank,
- sir.intentAction, sir.intentTargetPackage, sir.intentTargetClass,
- nonIndexableKeys);
+ indexFromResource(database, localeStr, sir, nonIndexableKeys);
} else {
if (TextUtils.isEmpty(sir.className)) {
Log.w(LOG_TAG, "Cannot index an empty Search Provider name!");
@@ -544,20 +540,17 @@
nonIndexableKeys.addAll(providerNonIndexableKeys);
}
- indexFromProvider(mContext, database, localeStr, provider, sir.className,
- sir.iconResId, sir.rank, sir.enabled, nonIndexableKeys);
+ indexFromProvider(database, localeStr, provider, sir, nonIndexableKeys);
}
}
}
- private void indexFromResource(Context context, SQLiteDatabase database, String localeStr,
- int xmlResId, String fragmentName, int iconResId, int rank,
- String intentAction, String intentTargetPackage, String intentTargetClass,
- List<String> nonIndexableKeys) {
-
+ private void indexFromResource(SQLiteDatabase database, String localeStr,
+ SearchIndexableResource sir, List<String> nonIndexableKeys) {
+ final Context context = sir.context;
XmlResourceParser parser = null;
try {
- parser = context.getResources().getXml(xmlResId);
+ parser = context.getResources().getXml(sir.xmlResId);
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -582,39 +575,47 @@
String title;
String summary;
String keywords;
+ String childFragment;
ResultPayload payload;
+ boolean enabled;
+ final String fragmentName = sir.className;
+ final int iconResId = sir.iconResId;
+ final int rank = sir.rank;
+ final String intentAction = sir.intentAction;
+ final String intentTargetPackage = sir.intentTargetPackage;
+ final String intentTargetClass = sir.intentTargetClass;
- ArrayMap<String, PreferenceController> controllerUriMap = null;
+ Map<String, PreferenceController> controllerUriMap = null;
if (fragmentName != null) {
- controllerUriMap = (ArrayMap) DatabaseIndexingUtils
+ controllerUriMap = DatabaseIndexingUtils
.getPreferenceControllerUriMap(fragmentName, context);
}
// Insert rows for the main PreferenceScreen node. Rewrite the data for removing
// hyphens.
- if (!nonIndexableKeys.contains(key)) {
- title = XmlParserUtils.getDataTitle(context, attrs);
- summary = XmlParserUtils.getDataSummary(context, attrs);
- keywords = XmlParserUtils.getDataKeywords(context, attrs);
- DatabaseRow.Builder builder = new DatabaseRow.Builder();
- builder.setLocale(localeStr)
- .setEntries(null)
- .setClassName(fragmentName)
- .setScreenTitle(screenTitle)
- .setIconResId(iconResId)
- .setRank(rank)
- .setIntentAction(intentAction)
- .setIntentTargetPackage(intentTargetPackage)
- .setIntentTargetClass(intentTargetClass)
- .setEnabled(true)
- .setKey(key)
- .setUserId(-1 /* default user id */);
+ title = XmlParserUtils.getDataTitle(context, attrs);
+ summary = XmlParserUtils.getDataSummary(context, attrs);
+ keywords = XmlParserUtils.getDataKeywords(context, attrs);
+ enabled = !nonIndexableKeys.contains(key);
- updateOneRowWithFilteredData(database, builder, title, summary,
- null /* summary off */, keywords);
- }
+ DatabaseRow.Builder builder = new DatabaseRow.Builder();
+ builder.setLocale(localeStr)
+ .setEntries(null)
+ .setClassName(fragmentName)
+ .setScreenTitle(screenTitle)
+ .setIconResId(iconResId)
+ .setRank(rank)
+ .setIntentAction(intentAction)
+ .setIntentTargetPackage(intentTargetPackage)
+ .setIntentTargetClass(intentTargetClass)
+ .setEnabled(enabled)
+ .setKey(key)
+ .setUserId(-1 /* default user id */);
+
+ updateOneRowWithFilteredData(database, builder, title, summary,
+ null /* summary off */, keywords);
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
@@ -625,14 +626,11 @@
nodeName = parser.getName();
key = XmlParserUtils.getDataKey(context, attrs);
- if (nonIndexableKeys.contains(key)) {
- continue;
- }
-
+ enabled = ! nonIndexableKeys.contains(key);
title = XmlParserUtils.getDataTitle(context, attrs);
keywords = XmlParserUtils.getDataKeywords(context, attrs);
- DatabaseRow.Builder builder = new DatabaseRow.Builder();
+ builder = new DatabaseRow.Builder();
builder.setLocale(localeStr)
.setClassName(fragmentName)
.setScreenTitle(screenTitle)
@@ -641,7 +639,7 @@
.setIntentAction(intentAction)
.setIntentTargetPackage(intentTargetPackage)
.setIntentTargetClass(intentTargetClass)
- .setEnabled(true)
+ .setEnabled(enabled)
.setKey(key)
.setUserId(-1 /* default user id */);
@@ -655,8 +653,10 @@
}
payload = DatabaseIndexingUtils.getPayloadFromUriMap(controllerUriMap, key);
+ childFragment = XmlParserUtils.getDataChildFragment(context, attrs);
builder.setEntries(entries)
+ .setChildClassName(childFragment)
.setPayload(payload);
// Insert rows for the child nodes of PreferenceScreen
@@ -683,16 +683,21 @@
}
}
- private void indexFromProvider(Context context, SQLiteDatabase database, String localeStr,
- Indexable.SearchIndexProvider provider, String className, int iconResId, int rank,
- boolean enabled, List<String> nonIndexableKeys) {
+ private void indexFromProvider(SQLiteDatabase database, String localeStr,
+ Indexable.SearchIndexProvider provider, SearchIndexableResource sir,
+ List<String> nonIndexableKeys) {
+
+ final String className = sir.className;
+ final int iconResId = sir.iconResId;
+ final int rank = sir.rank;
if (provider == null) {
Log.w(LOG_TAG, "Cannot find provider: " + className);
return;
}
- final List<SearchIndexableRaw> rawList = provider.getRawDataToIndex(context, enabled);
+ final List<SearchIndexableRaw> rawList = provider.getRawDataToIndex(mContext,
+ true /* enabled */);
if (rawList != null) {
@@ -704,10 +709,7 @@
if (!raw.locale.toString().equalsIgnoreCase(localeStr)) {
continue;
}
-
- if (nonIndexableKeys.contains(raw.key)) {
- continue;
- }
+ boolean enabled = !nonIndexableKeys.contains(raw.key);
DatabaseRow.Builder builder = new DatabaseRow.Builder();
builder.setLocale(localeStr)
@@ -719,7 +721,7 @@
.setIntentAction(raw.intentAction)
.setIntentTargetPackage(raw.intentTargetPackage)
.setIntentTargetClass(raw.intentTargetClass)
- .setEnabled(raw.enabled)
+ .setEnabled(enabled)
.setKey(raw.key)
.setUserId(raw.userId);
@@ -729,7 +731,7 @@
}
final List<SearchIndexableResource> resList =
- provider.getXmlResourcesToIndex(context, enabled);
+ provider.getXmlResourcesToIndex(mContext, true);
if (resList != null) {
final int resSize = resList.size();
for (int i = 0; i < resSize; i++) {
@@ -740,15 +742,10 @@
continue;
}
- final int itemIconResId = (item.iconResId == 0) ? iconResId : item.iconResId;
- final int itemRank = (item.rank == 0) ? rank : item.rank;
- String itemClassName = (TextUtils.isEmpty(item.className))
- ? className : item.className;
+ item.iconResId = (item.iconResId == 0) ? iconResId : item.iconResId;
+ item.className = (TextUtils.isEmpty(item.className)) ? className : item.className;
- indexFromResource(context, database, localeStr,
- item.xmlResId, itemClassName, itemIconResId, itemRank,
- item.intentAction, item.intentTargetPackage,
- item.intentTargetClass, nonIndexableKeys);
+ indexFromResource(database, localeStr, item, nonIndexableKeys);
}
}
}
@@ -811,6 +808,18 @@
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD, row.payload);
database.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
+
+ if (!TextUtils.isEmpty(row.className) && !TextUtils.isEmpty(row.childClassName)) {
+ ContentValues siteMapPair = new ContentValues();
+ final int pairDocId = Objects.hash(row.className, row.childClassName);
+ siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.DOCID, pairDocId);
+ siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.PARENT_CLASS, row.className);
+ siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.PARENT_TITLE, row.screenTitle);
+ siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.CHILD_CLASS, row.childClassName);
+ siteMapPair.put(IndexDatabaseHelper.SiteMapColumns.CHILD_TITLE, row.updatedTitle);
+
+ database.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, null, siteMapPair);
+ }
}
/**
@@ -950,6 +959,7 @@
public final String normalizedSummaryOff;
public final String entries;
public final String className;
+ public final String childClassName;
public final String screenTitle;
public final int iconResId;
public final int rank;
@@ -973,6 +983,7 @@
normalizedSummaryOff = builder.mNormalizedSummaryOff;
entries = builder.mEntries;
className = builder.mClassName;
+ childClassName = builder.mChildClassName;
screenTitle = builder.mScreenTitle;
iconResId = builder.mIconResId;
rank = builder.mRank;
@@ -1008,6 +1019,7 @@
private String mNormalizedSummaryOff;
private String mEntries;
private String mClassName;
+ private String mChildClassName;
private String mScreenTitle;
private int mIconResId;
private int mRank;
@@ -1067,6 +1079,11 @@
return this;
}
+ public Builder setChildClassName(String childClassName) {
+ mChildClassName = childClassName;
+ return this;
+ }
+
public Builder setScreenTitle(String screenTitle) {
mScreenTitle = screenTitle;
return this;
@@ -1142,4 +1159,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/com/android/settings/search2/DatabaseIndexingUtils.java b/src/com/android/settings/search2/DatabaseIndexingUtils.java
index 9fdf732..bd06ef3 100644
--- a/src/com/android/settings/search2/DatabaseIndexingUtils.java
+++ b/src/com/android/settings/search2/DatabaseIndexingUtils.java
@@ -105,7 +105,7 @@
* @return The Payload from the {@link PreferenceController} specified by the key, if it exists.
* Otherwise null.
*/
- public static ResultPayload getPayloadFromUriMap(ArrayMap<String, PreferenceController> uriMap,
+ public static ResultPayload getPayloadFromUriMap(Map<String, PreferenceController> uriMap,
String key) {
if (uriMap == null) {
return null;
diff --git a/src/com/android/settings/search2/DatabaseResultLoader.java b/src/com/android/settings/search2/DatabaseResultLoader.java
index f7acd25..8bb1a41 100644
--- a/src/com/android/settings/search2/DatabaseResultLoader.java
+++ b/src/com/android/settings/search2/DatabaseResultLoader.java
@@ -20,6 +20,9 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.text.TextUtils;
+import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.IndexDatabaseHelper;
import com.android.settings.utils.AsyncLoader;
@@ -35,11 +38,6 @@
*/
public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
private static final String LOG = "DatabaseResultLoader";
- private final String mQueryText;
-
- protected final SQLiteDatabase mDatabase;
-
- private final CursorToSearchResultConverter mConverter;
/* These indices are used to match the columns of the this loader's SELECT statement.
These are not necessarily the same order nor similar coverage as the schema defined in
@@ -99,8 +97,15 @@
*/
private static final int[] BASE_RANKS = {1, 4, 7};
+ private final String mQueryText;
+ private final SQLiteDatabase mDatabase;
+ private final CursorToSearchResultConverter mConverter;
+ private final SiteMapManager mSiteMapManager;
+
public DatabaseResultLoader(Context context, String queryText) {
super(context);
+ mSiteMapManager = FeatureFactory.getFactory(context)
+ .getSearchFeatureProvider().getSiteMapManager();
mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase();
mQueryText = cleanQuery(queryText);
mConverter = new CursorToSearchResultConverter(context, mQueryText);
@@ -125,7 +130,6 @@
secondaryResults = query(MATCH_COLUMNS_SECONDARY, BASE_RANKS[1]);
tertiaryResults = query(MATCH_COLUMNS_TERTIARY, BASE_RANKS[2]);
-
final List<SearchResult> results = new ArrayList<>(primaryResults.size()
+ secondaryResults.size()
+ tertiaryResults.size());
@@ -144,7 +148,7 @@
final Cursor resultCursor = mDatabase.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, whereClause,
selection, null, null, null);
- return mConverter.convertCursor(resultCursor, baseRank);
+ return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank);
}
@Override
@@ -155,14 +159,18 @@
/**
* A generic method to make the query suitable for searching the database.
+ *
* @return the cleaned query string
*/
private static String cleanQuery(String query) {
+ if (TextUtils.isEmpty(query)) {
+ return null;
+ }
return query.trim();
}
private static String buildWhereClause(String[] matchColumns) {
- StringBuilder sb = new StringBuilder(" ");
+ StringBuilder sb = new StringBuilder(" (");
final int count = matchColumns.length;
for (int n = 0; n < count; n++) {
sb.append(matchColumns[n]);
@@ -171,6 +179,7 @@
sb.append(" OR ");
}
}
+ sb.append(") AND enabled = 1");
return sb.toString();
}
}
diff --git a/src/com/android/settings/search2/InstalledAppResultLoader.java b/src/com/android/settings/search2/InstalledAppResultLoader.java
index 17bb157..0b828a3 100644
--- a/src/com/android/settings/search2/InstalledAppResultLoader.java
+++ b/src/com/android/settings/search2/InstalledAppResultLoader.java
@@ -29,7 +29,10 @@
import android.text.TextUtils;
import com.android.settings.R;
+import com.android.settings.applications.ManageApplications;
import com.android.settings.applications.PackageManagerWrapper;
+import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.utils.AsyncLoader;
import java.util.ArrayList;
@@ -46,7 +49,8 @@
private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER);
- private final List<String> mBreadcrumb;
+ private List<String> mBreadcrumb;
+ private SiteMapManager mSiteMapManager;
private final String mQuery;
private final UserManager mUserManager;
private final PackageManagerWrapper mPackageManager;
@@ -55,9 +59,8 @@
public InstalledAppResultLoader(Context context, PackageManagerWrapper pmWrapper,
String query) {
super(context);
- mBreadcrumb = new ArrayList<>();
- mBreadcrumb.add(context.getString(R.string.app_and_notification_dashboard_title));
- mBreadcrumb.add(context.getString(R.string.applications_settings));
+ mSiteMapManager = FeatureFactory.getFactory(context)
+ .getSearchFeatureProvider().getSiteMapManager();
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
mPackageManager = pmWrapper;
mQuery = query;
@@ -92,7 +95,7 @@
builder.addIcon(info.loadIcon(pm))
.addTitle(info.loadLabel(pm))
.addRank(wordDiff)
- .addBreadcrumbs(mBreadcrumb)
+ .addBreadcrumbs(getBreadCrumb())
.addPayload(new IntentPayload(intent));
results.add(builder.build());
}
@@ -162,4 +165,14 @@
// to infinity.
return valueText.length - queryTokens.length;
}
+
+ private List<String> getBreadCrumb() {
+ if (mBreadcrumb == null || mBreadcrumb.isEmpty()) {
+ final Context context = getContext();
+ mBreadcrumb = mSiteMapManager.buildBreadCrumb(
+ context, ManageApplications.class.getName(),
+ context.getString(R.string.applications_settings));
+ }
+ return mBreadcrumb;
+ }
}
diff --git a/src/com/android/settings/search2/SearchFeatureProvider.java b/src/com/android/settings/search2/SearchFeatureProvider.java
index 91a1444..a9be5a1 100644
--- a/src/com/android/settings/search2/SearchFeatureProvider.java
+++ b/src/com/android/settings/search2/SearchFeatureProvider.java
@@ -19,6 +19,8 @@
import android.content.Context;
import android.view.Menu;
+import com.android.settings.dashboard.SiteMapManager;
+
/**
* FeatureProvider for Settings Search
*/
@@ -58,6 +60,11 @@
DatabaseIndexingManager getIndexingManager(Context context);
/**
+ * Returns the manager for looking up breadcrumbs.
+ */
+ SiteMapManager getSiteMapManager();
+
+ /**
* Updates the Settings indexes
*/
void updateIndex(Context context);
diff --git a/src/com/android/settings/search2/SearchFeatureProviderImpl.java b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
index 5d62412..b575b15 100644
--- a/src/com/android/settings/search2/SearchFeatureProviderImpl.java
+++ b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
@@ -25,6 +25,7 @@
import com.android.settings.R;
import com.android.settings.applications.PackageManagerWrapperImpl;
+import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.search.Index;
/**
@@ -35,6 +36,7 @@
private static final String TAG = "SearchFeatureProvider";
private DatabaseIndexingManager mDatabaseIndexingManager;
+ private SiteMapManager mSiteMapManager;
@Override
public boolean isEnabled(Context context) {
@@ -86,6 +88,13 @@
return mDatabaseIndexingManager;
}
+ public SiteMapManager getSiteMapManager() {
+ if (mSiteMapManager == null) {
+ mSiteMapManager = new SiteMapManager();
+ }
+ return mSiteMapManager;
+ }
+
@Override
public void updateIndex(Context context) {
long indexStartTime = System.currentTimeMillis();
diff --git a/src/com/android/settings/search2/XmlParserUtils.java b/src/com/android/settings/search2/XmlParserUtils.java
index 748d4b0..90b1c1f 100644
--- a/src/com/android/settings/search2/XmlParserUtils.java
+++ b/src/com/android/settings/search2/XmlParserUtils.java
@@ -24,9 +24,6 @@
import com.android.settings.R;
-import java.text.Normalizer;
-import java.util.regex.Pattern;
-
/**
* Utility class to parse elements of XML preferences
*/
@@ -74,6 +71,14 @@
return getData(context, attrs, R.styleable.Preference, R.styleable.Preference_keywords);
}
+ /**
+ * Returns the fragment name if this preference launches a child fragment.
+ */
+ public static String getDataChildFragment(Context context, AttributeSet attrs) {
+ return getData(context, attrs, R.styleable.Preference,
+ R.styleable.Preference_android_fragment);
+ }
+
private static String getData(Context context, AttributeSet set, int[] attrs, int resId) {
final TypedArray sa = context.obtainStyledAttributes(set, attrs);
final TypedValue tv = sa.peekValue(resId);
diff --git a/src/com/android/settings/system/SystemDashboardFragment.java b/src/com/android/settings/system/SystemDashboardFragment.java
index b06d403..c46bc6f 100644
--- a/src/com/android/settings/system/SystemDashboardFragment.java
+++ b/src/com/android/settings/system/SystemDashboardFragment.java
@@ -30,7 +30,6 @@
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
-import com.android.settingslib.drawer.CategoryKey;
import java.util.ArrayList;
import java.util.Arrays;
@@ -57,11 +56,6 @@
}
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_SYSTEM;
- }
-
- @Override
protected List<PreferenceController> getPreferenceControllers(Context context) {
final List<PreferenceController> controllers = new ArrayList<>();
controllers.add(new SystemUpdatePreferenceController(context, UserManager.get(context)));
diff --git a/src/com/android/settings/tts/TextToSpeechSettings.java b/src/com/android/settings/tts/TextToSpeechSettings.java
index 96a7f4e..fe78d20 100644
--- a/src/com/android/settings/tts/TextToSpeechSettings.java
+++ b/src/com/android/settings/tts/TextToSpeechSettings.java
@@ -16,29 +16,27 @@
package com.android.settings.tts;
+import android.support.v7.preference.ListPreference;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Intent;
import android.os.Bundle;
-import android.provider.Settings.SettingNotFoundException;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.EngineInfo;
import android.speech.tts.TtsEngines;
import android.speech.tts.UtteranceProgressListener;
-import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceCategory;
import android.text.TextUtils;
import android.util.Log;
-import android.widget.Checkable;
+import android.util.Pair;
+import java.util.Comparator;
+import java.util.Collections;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R;
-import com.android.settings.SeekBarPreference;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.tts.TtsEnginePreference.RadioButtonGroupState;
import java.util.ArrayList;
import java.util.HashMap;
@@ -48,13 +46,15 @@
import java.util.Objects;
import java.util.Set;
-import static android.provider.Settings.Secure.TTS_DEFAULT_PITCH;
-import static android.provider.Settings.Secure.TTS_DEFAULT_RATE;
import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH;
+import static android.provider.Settings.Secure.TTS_DEFAULT_PITCH;
-public class TextToSpeechSettings extends SettingsPreferenceFragment implements
- Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener,
- RadioButtonGroupState {
+public class TextToSpeechSettings extends SettingsPreferenceFragment
+ implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener {
+
+ private static final String STATE_KEY_LOCALE_ENTRIES = "locale_entries";
+ private static final String STATE_KEY_LOCALE_ENTRY_VALUES = "locale_entry_values";
+ private static final String STATE_KEY_LOCALE_VALUE = "locale_value";
private static final String TAG = "TextToSpeechSettings";
private static final boolean DBG = false;
@@ -62,84 +62,37 @@
/** Preference key for the "play TTS example" preference. */
private static final String KEY_PLAY_EXAMPLE = "tts_play_example";;
- /** Preference key for the TTS pitch selection slider. */
- private static final String KEY_DEFAULT_PITCH = "tts_default_pitch";
-
- /** Preference key for the TTS rate selection slider. */
- private static final String KEY_DEFAULT_RATE = "tts_default_rate";
-
- /** Preference key for the TTS reset speech rate preference. */
- private static final String KEY_RESET_SPEECH_RATE = "reset_speech_rate";
-
- /** Preference key for the TTS reset speech pitch preference. */
- private static final String KEY_RESET_SPEECH_PITCH = "reset_speech_pitch";
-
/** Preference key for the TTS status field. */
private static final String KEY_STATUS = "tts_status";
/**
- * Preference key for the engine selection preference.
- */
- private static final String KEY_ENGINE_PREFERENCE_SECTION =
- "tts_engine_preference_section";
-
- /**
* These look like birth years, but they aren't mine. I'm much younger than this.
*/
private static final int GET_SAMPLE_TEXT = 1983;
private static final int VOICE_DATA_INTEGRITY_CHECK = 1977;
- /**
- * Speech rate value.
- * This value should be kept in sync with the max value set in tts_settings xml.
- */
- private static final int MAX_SPEECH_RATE = 600;
- private static final int MIN_SPEECH_RATE = 10;
-
- /**
- * Speech pitch value.
- * TTS pitch value varies from 25 to 400, where 100 is the value
- * for normal pitch. The max pitch value is set to 400, based on feedback from users
- * and the GoogleTTS pitch variation range. The range for pitch is not set in stone
- * and should be readjusted based on user need.
- * This value should be kept in sync with the max value set in tts_settings xml.
- */
- private static final int MAX_SPEECH_PITCH = 400;
- private static final int MIN_SPEECH_PITCH = 25;
-
- private PreferenceCategory mEnginePreferenceCategory;
- private SeekBarPreference mDefaultPitchPref;
- private SeekBarPreference mDefaultRatePref;
- private Preference mResetSpeechRate;
- private Preference mResetSpeechPitch;
private Preference mPlayExample;
private Preference mEngineStatus;
- private int mDefaultPitch = TextToSpeech.Engine.DEFAULT_PITCH;
- private int mDefaultRate = TextToSpeech.Engine.DEFAULT_RATE;
- /**
- * The currently selected engine.
- */
+ private static final String KEY_ENGINE_LOCALE = "tts_default_lang";
+ private static final String KEY_ENGINE_SETTINGS = "tts_engine_settings";
+ private static final String KEY_INSTALL_DATA = "tts_install_data";
+
+ private int mSelectedLocaleIndex = -1;
+
+ /** The currently selected engine. */
private String mCurrentEngine;
- /**
- * The engine checkbox that is currently checked. Saves us a bit of effort
- * in deducing the right one from the currently selected engine.
- */
- private Checkable mCurrentChecked;
-
- /**
- * The previously selected TTS engine. Useful for rollbacks if the users
- * choice is not loaded or fails a voice integrity check.
- */
- private String mPreviousEngine;
-
private TextToSpeech mTts = null;
private TtsEngines mEnginesHelper = null;
private String mSampleText = null;
+ private ListPreference mLocalePreference;
+ private Preference mEngineSettingsPreference;
+ private Preference mInstallVoicesPreference;
+
/**
* Default locale used by selected TTS engine, null if not connected to any engine.
*/
@@ -164,18 +117,6 @@
}
};
- /**
- * The initialization listener used when the user changes his choice of
- * engine (as opposed to when then screen is being initialized for the first
- * time).
- */
- private final TextToSpeech.OnInitListener mUpdateListener = new TextToSpeech.OnInitListener() {
- @Override
- public void onInit(int status) {
- onUpdateEngine(status);
- }
- };
-
@Override
public int getMetricsCategory() {
return MetricsEvent.TTS_TEXT_TO_SPEECH;
@@ -192,21 +133,40 @@
mPlayExample.setOnPreferenceClickListener(this);
mPlayExample.setEnabled(false);
- mResetSpeechRate = findPreference(KEY_RESET_SPEECH_RATE);
- mResetSpeechRate.setOnPreferenceClickListener(this);
- mResetSpeechPitch = findPreference(KEY_RESET_SPEECH_PITCH);
- mResetSpeechPitch.setOnPreferenceClickListener(this);
+ mEnginesHelper = new TtsEngines(getActivity().getApplicationContext());
- mEnginePreferenceCategory = (PreferenceCategory) findPreference(
- KEY_ENGINE_PREFERENCE_SECTION);
- mDefaultPitchPref = (SeekBarPreference) findPreference(KEY_DEFAULT_PITCH);
- mDefaultRatePref = (SeekBarPreference) findPreference(KEY_DEFAULT_RATE);
+ mLocalePreference = (ListPreference) findPreference(KEY_ENGINE_LOCALE);
+ mLocalePreference.setOnPreferenceChangeListener(this);
+
+ if (savedInstanceState == null) {
+ mLocalePreference.setEnabled(false);
+ mLocalePreference.setEntries(new CharSequence[0]);
+ mLocalePreference.setEntryValues(new CharSequence[0]);
+ } else {
+ // Repopulate mLocalePreference with saved state. Will be updated later with
+ // up-to-date values when checkTtsData() calls back with results.
+ final CharSequence[] entries =
+ savedInstanceState.getCharSequenceArray(STATE_KEY_LOCALE_ENTRIES);
+ final CharSequence[] entryValues =
+ savedInstanceState.getCharSequenceArray(STATE_KEY_LOCALE_ENTRY_VALUES);
+ final CharSequence value = savedInstanceState.getCharSequence(STATE_KEY_LOCALE_VALUE);
+
+ mLocalePreference.setEntries(entries);
+ mLocalePreference.setEntryValues(entryValues);
+ mLocalePreference.setValue(value != null ? value.toString() : null);
+ mLocalePreference.setEnabled(entries.length > 0);
+ }
+
+ mEngineSettingsPreference = findPreference(KEY_ENGINE_SETTINGS);
+ mEngineSettingsPreference.setOnPreferenceClickListener(this);
+ mInstallVoicesPreference = findPreference(KEY_INSTALL_DATA);
+ mInstallVoicesPreference.setOnPreferenceClickListener(this);
+ mInstallVoicesPreference.setEnabled(false);
mEngineStatus = findPreference(KEY_STATUS);
updateEngineStatus(R.string.tts_status_checking);
mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener);
- mEnginesHelper = new TtsEngines(getActivity().getApplicationContext());
setTtsUtteranceProgressListener();
initSettings();
@@ -222,6 +182,23 @@
if (mTts == null || mCurrentDefaultLocale == null) {
return;
}
+ if (!mTts.getDefaultEngine().equals(mTts.getCurrentEngine())) {
+ try {
+ mTts.shutdown();
+ mTts = null;
+ } catch (Exception e) {
+ Log.e(TAG, "Error shutting down TTS engine" + e);
+ }
+ mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener);
+ setTtsUtteranceProgressListener();
+ initSettings();
+ } else {
+ // Do set pitch correctly after it may have changed, and unlike speed, it doesn't change
+ // immediately.
+ final ContentResolver resolver = getContentResolver();
+ mTts.setPitch(android.provider.Settings.Secure.getInt(resolver, TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH)/100.0f);
+ }
+
Locale ttsDefaultLocale = mTts.getDefaultLanguage();
if (mCurrentDefaultLocale != null && !mCurrentDefaultLocale.equals(ttsDefaultLocale)) {
updateWidgetState(false);
@@ -259,26 +236,9 @@
private void initSettings() {
final ContentResolver resolver = getContentResolver();
- // Set up the default rate and pitch.
- mDefaultRate = android.provider.Settings.Secure.getInt(
- resolver, TTS_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE);
- mDefaultPitch = android.provider.Settings.Secure.getInt(
- resolver, TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH);
-
- mDefaultRatePref.setProgress(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, mDefaultRate));
- mDefaultRatePref.setOnPreferenceChangeListener(this);
- mDefaultRatePref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, MAX_SPEECH_RATE));
-
- mDefaultPitchPref.setProgress(getSeekBarProgressFromValue(KEY_DEFAULT_PITCH,
- mDefaultPitch));
- mDefaultPitchPref.setOnPreferenceChangeListener(this);
- mDefaultPitchPref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_PITCH,
- MAX_SPEECH_PITCH));
-
if (mTts != null) {
mCurrentEngine = mTts.getCurrentEngine();
- mTts.setSpeechRate(mDefaultRate/100.0f);
- mTts.setPitch(mDefaultPitch/100.0f);
+ mTts.setPitch(android.provider.Settings.Secure.getInt(resolver, TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH)/100.0f);
}
SettingsActivity activity = null;
@@ -289,53 +249,37 @@
"Settings");
}
- mEnginePreferenceCategory.removeAll();
+ if (mCurrentEngine != null) {
+ EngineInfo info = mEnginesHelper.getEngineInfo(mCurrentEngine);
+ mEngineSettingsPreference.setSummary(info.label);
+ final Intent settingsIntent = mEnginesHelper.getSettingsIntent(info.name);
+ mEngineSettingsPreference.setIntent(settingsIntent);
+ if (settingsIntent == null) {
+ mEngineSettingsPreference.setEnabled(false);
+ }
- List<EngineInfo> engines = mEnginesHelper.getEngines();
- for (EngineInfo engine : engines) {
- TtsEnginePreference enginePref = new TtsEnginePreference(getPrefContext(), engine,
- this, activity);
- mEnginePreferenceCategory.addPreference(enginePref);
+ Preference mEnginePreference = findPreference("tts_engine_preference");
+ mEnginePreference.setSummary(info.label);
}
checkVoiceData(mCurrentEngine);
}
/**
- * The minimum speech pitch/rate value should be > 0 but the minimum value of a seekbar in
- * android is fixed at 0. Therefore, we increment the seekbar progress with MIN_SPEECH_VALUE
- * so that the minimum seekbar progress value is MIN_SPEECH_PITCH/RATE.
- * SPEECH_VALUE = MIN_SPEECH_VALUE + SEEKBAR_PROGRESS
- */
- private int getValueFromSeekBarProgress(String preferenceKey, int progress) {
- if (preferenceKey.equals(KEY_DEFAULT_RATE)) {
- return MIN_SPEECH_RATE + progress;
- } else if (preferenceKey.equals(KEY_DEFAULT_PITCH)) {
- return MIN_SPEECH_PITCH + progress;
- }
- return progress;
- }
-
- /**
- * Since we are appending the MIN_SPEECH value to the speech seekbar progress, the
- * speech seekbar progress should be set to (speechValue - MIN_SPEECH value).
- */
- private int getSeekBarProgressFromValue(String preferenceKey, int value) {
- if (preferenceKey.equals(KEY_DEFAULT_RATE)) {
- return value - MIN_SPEECH_RATE;
- } else if (preferenceKey.equals(KEY_DEFAULT_PITCH)) {
- return value - MIN_SPEECH_PITCH;
- }
- return value;
- }
-
- /**
* Called when the TTS engine is initialized.
*/
public void onInitEngine(int status) {
if (status == TextToSpeech.SUCCESS) {
if (DBG) Log.d(TAG, "TTS engine for settings screen initialized.");
checkDefaultLocale();
+ getActivity()
+ .runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ mLocalePreference.setEnabled(true);
+ }
+ });
} else {
if (DBG) Log.d(TAG, "TTS engine for settings screen failed to initialize successfully.");
updateWidgetState(false);
@@ -452,9 +396,88 @@
onSampleTextReceived(resultCode, data);
} else if (requestCode == VOICE_DATA_INTEGRITY_CHECK) {
onVoiceDataIntegrityCheckDone(data);
+ if (resultCode != TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL) {
+ updateDefaultLocalePref(data);
+ }
}
}
+ private void updateDefaultLocalePref(Intent data) {
+ final ArrayList<String> availableLangs =
+ data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
+
+ final ArrayList<String> unavailableLangs =
+ data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES);
+
+ if (availableLangs != null && unavailableLangs.size() > 0) {
+ mInstallVoicesPreference.setEnabled(true);
+ } else {
+ mInstallVoicesPreference.setEnabled(false);
+ }
+
+ if (availableLangs == null || availableLangs.size() == 0) {
+ mLocalePreference.setEnabled(false);
+ return;
+ }
+ Locale currentLocale = null;
+ if (!mEnginesHelper.isLocaleSetToDefaultForEngine(mTts.getCurrentEngine())) {
+ currentLocale = mEnginesHelper.getLocalePrefForEngine(mTts.getCurrentEngine());
+ }
+
+ ArrayList<Pair<String, Locale>> entryPairs =
+ new ArrayList<Pair<String, Locale>>(availableLangs.size());
+ for (int i = 0; i < availableLangs.size(); i++) {
+ Locale locale = mEnginesHelper.parseLocaleString(availableLangs.get(i));
+ if (locale != null) {
+ entryPairs.add(new Pair<String, Locale>(locale.getDisplayName(), locale));
+ }
+ }
+
+ // Sort it
+ Collections.sort(
+ entryPairs,
+ new Comparator<Pair<String, Locale>>() {
+ @Override
+ public int compare(Pair<String, Locale> lhs, Pair<String, Locale> rhs) {
+ return lhs.first.compareToIgnoreCase(rhs.first);
+ }
+ });
+
+ // Get two arrays out of one of pairs
+ mSelectedLocaleIndex = 0; // Will point to the R.string.tts_lang_use_system value
+ CharSequence[] entries = new CharSequence[availableLangs.size() + 1];
+ CharSequence[] entryValues = new CharSequence[availableLangs.size() + 1];
+
+ entries[0] = getActivity().getString(R.string.tts_lang_use_system);
+ entryValues[0] = "";
+
+ int i = 1;
+ for (Pair<String, Locale> entry : entryPairs) {
+ if (entry.second.equals(currentLocale)) {
+ mSelectedLocaleIndex = i;
+ }
+ entries[i] = entry.first;
+ entryValues[i++] = entry.second.toString();
+ }
+
+ mLocalePreference.setEntries(entries);
+ mLocalePreference.setEntryValues(entryValues);
+ mLocalePreference.setEnabled(true);
+ setLocalePreference(mSelectedLocaleIndex);
+ }
+
+ /** Set entry from entry table in mLocalePreference */
+ private void setLocalePreference(int index) {
+ if (index < 0) {
+ mLocalePreference.setValue("");
+ mLocalePreference.setSummary(R.string.tts_lang_not_selected);
+ } else {
+ mLocalePreference.setValueIndex(index);
+ mLocalePreference.setSummary(mLocalePreference.getEntries()[index]);
+ }
+ }
+
+
private String getDefaultSampleString() {
if (mTts != null && mTts.getLanguage() != null) {
try {
@@ -522,17 +545,59 @@
@Override
public boolean onPreferenceChange(Preference preference, Object objValue) {
- if (KEY_DEFAULT_RATE.equals(preference.getKey())) {
- updateSpeechRate((Integer) objValue);
- } else if (KEY_DEFAULT_PITCH.equals(preference.getKey())) {
- updateSpeechPitchValue((Integer) objValue);
+ if (preference == mLocalePreference) {
+ String localeString = (String) objValue;
+ updateLanguageTo(
+ (!TextUtils.isEmpty(localeString)
+ ? mEnginesHelper.parseLocaleString(localeString)
+ : null));
+ checkDefaultLocale();
+ return true;
}
return true;
}
+ private void updateLanguageTo(Locale locale) {
+ int selectedLocaleIndex = -1;
+ String localeString = (locale != null) ? locale.toString() : "";
+ for (int i = 0; i < mLocalePreference.getEntryValues().length; i++) {
+ if (localeString.equalsIgnoreCase(mLocalePreference.getEntryValues()[i].toString())) {
+ selectedLocaleIndex = i;
+ break;
+ }
+ }
+
+ if (selectedLocaleIndex == -1) {
+ Log.w(TAG, "updateLanguageTo called with unknown locale argument");
+ return;
+ }
+ mLocalePreference.setSummary(mLocalePreference.getEntries()[selectedLocaleIndex]);
+ mSelectedLocaleIndex = selectedLocaleIndex;
+
+ mEnginesHelper.updateLocalePrefForEngine(mTts.getCurrentEngine(), locale);
+
+ // Null locale means "use system default"
+ mTts.setLanguage((locale != null) ? locale : Locale.getDefault());
+ }
+
/**
- * Called when mPlayExample, mResetSpeechRate or mResetSpeechPitch is
- * clicked.
+ * Ask the current default engine to launch the matching INSTALL_TTS_DATA activity so the
+ * required TTS files are properly installed.
+ */
+ private void installVoiceData() {
+ if (TextUtils.isEmpty(mCurrentEngine)) return;
+ Intent intent = new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
+ intent.setPackage(mCurrentEngine);
+ try {
+ Log.v(TAG, "Installing voice data: " + intent.toUri(0));
+ startActivity(intent);
+ } catch (ActivityNotFoundException ex) {
+ Log.e(TAG, "Failed to install TTS data, no acitivty found for " + intent + ")");
+ }
+ }
+
+ /**
+ * Called when mPlayExample, mInstallVoicesPreference is clicked.
*/
@Override
public boolean onPreferenceClick(Preference preference) {
@@ -541,57 +606,15 @@
// the actual speaking
speakSampleText();
return true;
- } else if (preference == mResetSpeechRate) {
- int speechRateSeekbarProgress = getSeekBarProgressFromValue(
- KEY_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE);
- mDefaultRatePref.setProgress(speechRateSeekbarProgress);
- updateSpeechRate(speechRateSeekbarProgress);
- return true;
- } else if (preference == mResetSpeechPitch) {
- int pitchSeekbarProgress = getSeekBarProgressFromValue(
- KEY_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH);
- mDefaultPitchPref.setProgress(pitchSeekbarProgress);
- updateSpeechPitchValue(pitchSeekbarProgress);
- return true;
+ } else if (preference == mInstallVoicesPreference) {
+ installVoiceData();
+ return true;
}
return false;
}
- private void updateSpeechRate(int speechRateSeekBarProgress) {
- mDefaultRate = getValueFromSeekBarProgress(KEY_DEFAULT_RATE,
- speechRateSeekBarProgress);
- try {
- android.provider.Settings.Secure.putInt(getContentResolver(),
- TTS_DEFAULT_RATE, mDefaultRate);
- if (mTts != null) {
- mTts.setSpeechRate(mDefaultRate / 100.0f);
- }
- if (DBG) Log.d(TAG, "TTS default rate changed, now " + mDefaultRate);
- } catch (NumberFormatException e) {
- Log.e(TAG, "could not persist default TTS rate setting", e);
- }
- return;
- }
-
- private void updateSpeechPitchValue(int speechPitchSeekBarProgress) {
- mDefaultPitch = getValueFromSeekBarProgress(KEY_DEFAULT_PITCH,
- speechPitchSeekBarProgress);
- try {
- android.provider.Settings.Secure.putInt(getContentResolver(),
- TTS_DEFAULT_PITCH, mDefaultPitch);
- if (mTts != null) {
- mTts.setPitch(mDefaultPitch / 100.0f);
- }
- if (DBG) Log.d(TAG, "TTS default pitch changed, now" + mDefaultPitch);
- } catch (NumberFormatException e) {
- Log.e(TAG, "could not persist default TTS pitch setting", e);
- }
- return;
- }
-
private void updateWidgetState(boolean enable) {
mPlayExample.setEnabled(enable);
- mDefaultRatePref.setEnabled(enable);
mEngineStatus.setEnabled(enable);
}
@@ -614,67 +637,7 @@
dialog.show();
}
- private void updateDefaultEngine(String engine) {
- if (DBG) Log.d(TAG, "Updating default synth to : " + engine);
-
- // Disable the "play sample text" preference and the speech
- // rate preference while the engine is being swapped.
- updateWidgetState(false);
- updateEngineStatus(R.string.tts_status_checking);
-
- // Keep track of the previous engine that was being used. So that
- // we can reuse the previous engine.
- //
- // Note that if TextToSpeech#getCurrentEngine is not null, it means at
- // the very least that we successfully bound to the engine service.
- mPreviousEngine = mTts.getCurrentEngine();
-
- // Step 1: Shut down the existing TTS engine.
- if (mTts != null) {
- try {
- mTts.shutdown();
- mTts = null;
- } catch (Exception e) {
- Log.e(TAG, "Error shutting down TTS engine" + e);
- }
- }
-
- // Step 2: Connect to the new TTS engine.
- // Step 3 is continued on #onUpdateEngine (below) which is called when
- // the app binds successfully to the engine.
- if (DBG) Log.d(TAG, "Updating engine : Attempting to connect to engine: " + engine);
- mTts = new TextToSpeech(getActivity().getApplicationContext(), mUpdateListener, engine);
- setTtsUtteranceProgressListener();
- }
-
- /*
- * Step 3: We have now bound to the TTS engine the user requested. We will
- * attempt to check voice data for the engine if we successfully bound to it,
- * or revert to the previous engine if we didn't.
- */
- public void onUpdateEngine(int status) {
- if (status == TextToSpeech.SUCCESS) {
- if (DBG) {
- Log.d(TAG, "Updating engine: Successfully bound to the engine: " +
- mTts.getCurrentEngine());
- }
- checkVoiceData(mTts.getCurrentEngine());
- } else {
- if (DBG) Log.d(TAG, "Updating engine: Failed to bind to engine, reverting.");
- if (mPreviousEngine != null) {
- // This is guaranteed to at least bind, since mPreviousEngine would be
- // null if the previous bind to this engine failed.
- mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener,
- mPreviousEngine);
- setTtsUtteranceProgressListener();
- }
- mPreviousEngine = null;
- }
- }
-
- /*
- * Step 4: Check whether the voice data for the engine is ok.
- */
+ /** Check whether the voice data for the engine is ok. */
private void checkVoiceData(String engine) {
Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
intent.setPackage(engine);
@@ -686,9 +649,7 @@
}
}
- /*
- * Step 5: The voice data check is complete.
- */
+ /** The voice data check is complete. */
private void onVoiceDataIntegrityCheckDone(Intent data) {
final String engine = mTts.getCurrentEngine();
@@ -715,39 +676,5 @@
if (evaluateDefaultLocale()) {
getSampleText();
}
-
- final int engineCount = mEnginePreferenceCategory.getPreferenceCount();
- for (int i = 0; i < engineCount; ++i) {
- final Preference p = mEnginePreferenceCategory.getPreference(i);
- if (p instanceof TtsEnginePreference) {
- TtsEnginePreference enginePref = (TtsEnginePreference) p;
- if (enginePref.getKey().equals(engine)) {
- enginePref.setVoiceDataDetails(data);
- break;
- }
- }
- }
}
-
- @Override
- public Checkable getCurrentChecked() {
- return mCurrentChecked;
- }
-
- @Override
- public String getCurrentKey() {
- return mCurrentEngine;
- }
-
- @Override
- public void setCurrentChecked(Checkable current) {
- mCurrentChecked = current;
- }
-
- @Override
- public void setCurrentKey(String key) {
- mCurrentEngine = key;
- updateDefaultEngine(mCurrentEngine);
- }
-
}
diff --git a/src/com/android/settings/tts/TtsEnginePreference.java b/src/com/android/settings/tts/TtsEnginePreference.java
index 385b861..9a84002 100644
--- a/src/com/android/settings/tts/TtsEnginePreference.java
+++ b/src/com/android/settings/tts/TtsEnginePreference.java
@@ -20,19 +20,16 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.os.Bundle;
import android.speech.tts.TextToSpeech.EngineInfo;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceViewHolder;
import android.util.Log;
-import android.view.View;
import android.widget.Checkable;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
-import com.android.settings.Utils;
public class TtsEnginePreference extends Preference {
@@ -40,31 +37,6 @@
private static final String TAG = "TtsEnginePreference";
/**
- * Key for the name of the TTS engine passed in to the engine
- * settings fragment {@link TtsEngineSettingsFragment}.
- */
- static final String FRAGMENT_ARGS_NAME = "name";
-
- /**
- * Key for the label of the TTS engine passed in to the engine
- * settings fragment. This is used as the title of the fragment
- * {@link TtsEngineSettingsFragment}.
- */
- static final String FRAGMENT_ARGS_LABEL = "label";
-
- /**
- * Key for the voice data data passed in to the engine settings
- * fragmetn {@link TtsEngineSettingsFragment}.
- */
- static final String FRAGMENT_ARGS_VOICES = "voices";
-
- /**
- * The preference activity that owns this preference. Required
- * for instantiating the engine specific settings screen.
- */
- private final SettingsActivity mSettingsActivity;
-
- /**
* The engine information for the engine this preference represents.
* Contains it's name, label etc. which are used for display.
*/
@@ -81,7 +53,6 @@
*/
private volatile boolean mPreventRadioButtonCallbacks;
- private View mSettingsIcon;
private RadioButton mRadioButton;
private Intent mVoiceCheckData;
@@ -99,7 +70,6 @@
setLayoutResource(R.layout.preference_tts_engine);
mSharedState = state;
- mSettingsActivity = prefActivity;
mEngineInfo = info;
mPreventRadioButtonCallbacks = false;
@@ -130,52 +100,10 @@
mPreventRadioButtonCallbacks = false;
mRadioButton = rb;
-
- mSettingsIcon = view.findViewById(R.id.tts_engine_settings);
- // Will be enabled only the engine has passed the voice check, and
- // is currently enabled.
- mSettingsIcon.setEnabled(isChecked && mVoiceCheckData != null);
- if (!isChecked) {
- mSettingsIcon.setAlpha(Utils.DISABLED_ALPHA);
- }
- mSettingsIcon.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Bundle args = new Bundle();
- args.putString(FRAGMENT_ARGS_NAME, mEngineInfo.name);
- args.putString(FRAGMENT_ARGS_LABEL, mEngineInfo.label);
- if (mVoiceCheckData != null) {
- args.putParcelable(FRAGMENT_ARGS_VOICES, mVoiceCheckData);
- }
-
- // Note that we use this instead of the (easier to use)
- // SettingsActivity.startPreferenceFragment because the
- // title will not be updated correctly in the fragment
- // breadcrumb since it isn't inflated from the XML layout.
- mSettingsActivity.startPreferencePanel(
- TtsEngineSettingsFragment.class.getName(),
- args, 0, mEngineInfo.label, null, 0);
- }
- });
-
- if (mVoiceCheckData != null) {
- mSettingsIcon.setEnabled(mRadioButton.isChecked());
- }
}
public void setVoiceDataDetails(Intent data) {
mVoiceCheckData = data;
- // This might end up running before getView aboive, in which
- // case mSettingsIcon && mRadioButton will be null. In this case
- // getView will set the right values.
- if (mSettingsIcon != null && mRadioButton != null) {
- if (mRadioButton.isChecked()) {
- mSettingsIcon.setEnabled(true);
- } else {
- mSettingsIcon.setEnabled(false);
- mSettingsIcon.setAlpha(Utils.DISABLED_ALPHA);
- }
- }
}
private boolean shouldDisplayDataAlert() {
@@ -227,8 +155,6 @@
// Privileged engine, set it current
makeCurrentEngine(buttonView);
}
- } else {
- mSettingsIcon.setEnabled(false);
}
}
@@ -239,7 +165,6 @@
mSharedState.setCurrentChecked(current);
mSharedState.setCurrentKey(getKey());
callChangeListener(mSharedState.getCurrentKey());
- mSettingsIcon.setEnabled(true);
}
diff --git a/src/com/android/settings/tts/TtsEnginePreferenceFragment.java b/src/com/android/settings/tts/TtsEnginePreferenceFragment.java
new file mode 100644
index 0000000..e0ed8b7
--- /dev/null
+++ b/src/com/android/settings/tts/TtsEnginePreferenceFragment.java
@@ -0,0 +1,194 @@
+package com.android.settings.tts;
+
+import android.speech.tts.TextToSpeech;
+import com.android.settings.R;
+import android.os.Bundle;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import android.support.v7.preference.PreferenceCategory;
+import android.speech.tts.TtsEngines;
+import android.speech.tts.TextToSpeech.EngineInfo;
+import com.android.settings.SettingsActivity;
+import com.android.settings.tts.TtsEnginePreference.RadioButtonGroupState;
+import android.widget.Checkable;
+import android.util.Log;
+import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.BaseSearchIndexProvider;
+import android.content.Context;
+import android.provider.SearchIndexableResource;
+
+import java.util.List;
+import java.util.Arrays;
+
+public class TtsEnginePreferenceFragment extends SettingsPreferenceFragment //implements
+ implements RadioButtonGroupState, Indexable {
+ private static final String TAG = "TtsEnginePreferenceFragment";
+
+ private static final int VOICE_DATA_INTEGRITY_CHECK = 1977;
+
+ /** The currently selected engine. */
+ private String mCurrentEngine;
+
+ /**
+ * The engine checkbox that is currently checked. Saves us a bit of effort in deducing the right
+ * one from the currently selected engine.
+ */
+ private Checkable mCurrentChecked;
+
+ /**
+ * The previously selected TTS engine. Useful for rollbacks if the users choice is not loaded or
+ * fails a voice integrity check.
+ */
+ private String mPreviousEngine;
+
+ private PreferenceCategory mEnginePreferenceCategory;
+
+ private TextToSpeech mTts = null;
+ private TtsEngines mEnginesHelper = null;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.tts_engine_picker);
+
+ mEnginePreferenceCategory =
+ (PreferenceCategory) findPreference("tts_engine_preference_category");
+ mEnginesHelper = new TtsEngines(getActivity().getApplicationContext());
+
+ mTts = new TextToSpeech(getActivity().getApplicationContext(), null);
+
+ initSettings();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.TTS_ENGINE_SETTINGS;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mTts != null) {
+ mTts.shutdown();
+ mTts = null;
+ }
+ }
+
+ private void initSettings() {
+ if (mTts != null) {
+ mCurrentEngine = mTts.getCurrentEngine();
+ }
+
+ mEnginePreferenceCategory.removeAll();
+
+ SettingsActivity activity = (SettingsActivity) getActivity();
+
+ List<EngineInfo> engines = mEnginesHelper.getEngines();
+ for (EngineInfo engine : engines) {
+ TtsEnginePreference enginePref =
+ new TtsEnginePreference(getPrefContext(), engine, this, activity);
+ mEnginePreferenceCategory.addPreference(enginePref);
+ }
+ }
+
+ @Override
+ public Checkable getCurrentChecked() {
+ return mCurrentChecked;
+ }
+
+ @Override
+ public String getCurrentKey() {
+ return mCurrentEngine;
+ }
+
+ @Override
+ public void setCurrentChecked(Checkable current) {
+ mCurrentChecked = current;
+ }
+
+ /**
+ * The initialization listener used when the user changes his choice of engine (as opposed to
+ * when then screen is being initialized for the first time).
+ */
+ private final TextToSpeech.OnInitListener mUpdateListener =
+ new TextToSpeech.OnInitListener() {
+ @Override
+ public void onInit(int status) {
+ onUpdateEngine(status);
+ }
+ };
+
+ private void updateDefaultEngine(String engine) {
+ Log.d(TAG, "Updating default synth to : " + engine);
+
+ // Keep track of the previous engine that was being used. So that
+ // we can reuse the previous engine.
+ //
+ // Note that if TextToSpeech#getCurrentEngine is not null, it means at
+ // the very least that we successfully bound to the engine service.
+ mPreviousEngine = mTts.getCurrentEngine();
+
+ // Step 1: Shut down the existing TTS engine.
+ Log.i(TAG, "Shutting down current tts engine");
+ if (mTts != null) {
+ try {
+ mTts.shutdown();
+ mTts = null;
+ } catch (Exception e) {
+ Log.e(TAG, "Error shutting down TTS engine" + e);
+ }
+ }
+
+ // Step 2: Connect to the new TTS engine.
+ // Step 3 is continued on #onUpdateEngine (below) which is called when
+ // the app binds successfully to the engine.
+ Log.i(TAG, "Updating engine : Attempting to connect to engine: " + engine);
+ mTts = new TextToSpeech(getActivity().getApplicationContext(), mUpdateListener, engine);
+ Log.i(TAG, "Success");
+ }
+
+ /**
+ * Step 3: We have now bound to the TTS engine the user requested. We will attempt to check
+ * voice data for the engine if we successfully bound to it, or revert to the previous engine if
+ * we didn't.
+ */
+ public void onUpdateEngine(int status) {
+ if (status == TextToSpeech.SUCCESS) {
+ Log.d(
+ TAG,
+ "Updating engine: Successfully bound to the engine: "
+ + mTts.getCurrentEngine());
+ android.provider.Settings.Secure.putString(
+ getContentResolver(), TTS_DEFAULT_SYNTH, mTts.getCurrentEngine());
+ } else {
+ Log.d(TAG, "Updating engine: Failed to bind to engine, reverting.");
+ if (mPreviousEngine != null) {
+ // This is guaranteed to at least bind, since mPreviousEngine would be
+ // null if the previous bind to this engine failed.
+ mTts =
+ new TextToSpeech(
+ getActivity().getApplicationContext(), null, mPreviousEngine);
+ }
+ mPreviousEngine = null;
+ }
+ }
+
+ @Override
+ public void setCurrentKey(String key) {
+ mCurrentEngine = key;
+ updateDefaultEngine(mCurrentEngine);
+ }
+
+ public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(
+ Context context, boolean enabled) {
+ Log.i(TAG, "Indexing");
+ final SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.tts_engine_picker;
+ return Arrays.asList(sir);
+ }
+ };
+}
diff --git a/src/com/android/settings/tts/TtsEngineSettingsFragment.java b/src/com/android/settings/tts/TtsEngineSettingsFragment.java
deleted file mode 100644
index 42222df..0000000
--- a/src/com/android/settings/tts/TtsEngineSettingsFragment.java
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
- * Copyright (C) 2011 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.tts;
-
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.speech.tts.TextToSpeech;
-import android.speech.tts.TtsEngines;
-import android.support.v7.preference.ListPreference;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.Preference.OnPreferenceChangeListener;
-import android.support.v7.preference.Preference.OnPreferenceClickListener;
-import android.support.v7.preference.PreferenceScreen;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Locale;
-
-
-public class TtsEngineSettingsFragment extends SettingsPreferenceFragment implements
- OnPreferenceClickListener, OnPreferenceChangeListener {
- private static final String TAG = "TtsEngineSettings";
- private static final boolean DBG = false;
-
- private static final String KEY_ENGINE_LOCALE = "tts_default_lang";
- private static final String KEY_ENGINE_SETTINGS = "tts_engine_settings";
- private static final String KEY_INSTALL_DATA = "tts_install_data";
-
- private static final String STATE_KEY_LOCALE_ENTRIES = "locale_entries";
- private static final String STATE_KEY_LOCALE_ENTRY_VALUES= "locale_entry_values";
- private static final String STATE_KEY_LOCALE_VALUE = "locale_value";
-
- private static final int VOICE_DATA_INTEGRITY_CHECK = 1977;
-
- private TtsEngines mEnginesHelper;
- private ListPreference mLocalePreference;
- private Preference mEngineSettingsPreference;
- private Preference mInstallVoicesPreference;
- private Intent mEngineSettingsIntent;
- private Intent mVoiceDataDetails;
-
- private TextToSpeech mTts;
-
- private int mSelectedLocaleIndex = -1;
-
- private final TextToSpeech.OnInitListener mTtsInitListener = new TextToSpeech.OnInitListener() {
- @Override
- public void onInit(int status) {
- if (status != TextToSpeech.SUCCESS) {
- finishFragment();
- } else {
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mLocalePreference.setEnabled(true);
- }
- });
- }
- }
- };
-
- private final BroadcastReceiver mLanguagesChangedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- // Installed or uninstalled some data packs
- if (TextToSpeech.Engine.ACTION_TTS_DATA_INSTALLED.equals(intent.getAction())) {
- checkTtsData();
- }
- }
- };
-
- public TtsEngineSettingsFragment() {
- super();
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.TTS_ENGINE_SETTINGS;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.tts_engine_settings);
- mEnginesHelper = new TtsEngines(getActivity());
-
- final PreferenceScreen root = getPreferenceScreen();
- mLocalePreference = (ListPreference) root.findPreference(KEY_ENGINE_LOCALE);
- mLocalePreference.setOnPreferenceChangeListener(this);
- mEngineSettingsPreference = root.findPreference(KEY_ENGINE_SETTINGS);
- mEngineSettingsPreference.setOnPreferenceClickListener(this);
- mInstallVoicesPreference = root.findPreference(KEY_INSTALL_DATA);
- mInstallVoicesPreference.setOnPreferenceClickListener(this);
-
- root.setTitle(getEngineLabel());
- root.setKey(getEngineName());
- mEngineSettingsPreference.setTitle(getResources().getString(
- R.string.tts_engine_settings_title, getEngineLabel()));
-
- mEngineSettingsIntent = mEnginesHelper.getSettingsIntent(getEngineName());
- if (mEngineSettingsIntent == null) {
- mEngineSettingsPreference.setEnabled(false);
- }
- mInstallVoicesPreference.setEnabled(false);
-
- if (savedInstanceState == null) {
- mLocalePreference.setEnabled(false);
- mLocalePreference.setEntries(new CharSequence[0]);
- mLocalePreference.setEntryValues(new CharSequence[0]);
- } else {
- // Repopulate mLocalePreference with saved state. Will be updated later with
- // up-to-date values when checkTtsData() calls back with results.
- final CharSequence[] entries =
- savedInstanceState.getCharSequenceArray(STATE_KEY_LOCALE_ENTRIES);
- final CharSequence[] entryValues =
- savedInstanceState.getCharSequenceArray(STATE_KEY_LOCALE_ENTRY_VALUES);
- final CharSequence value =
- savedInstanceState.getCharSequence(STATE_KEY_LOCALE_VALUE);
-
- mLocalePreference.setEntries(entries);
- mLocalePreference.setEntryValues(entryValues);
- mLocalePreference.setValue(value != null ? value.toString() : null);
- mLocalePreference.setEnabled(entries.length > 0);
- }
-
- mVoiceDataDetails = getArguments().getParcelable(TtsEnginePreference.FRAGMENT_ARGS_VOICES);
-
- mTts = new TextToSpeech(getActivity().getApplicationContext(), mTtsInitListener,
- getEngineName());
-
- // Check if data packs changed
- checkTtsData();
-
- getActivity().registerReceiver(mLanguagesChangedReceiver,
- new IntentFilter(TextToSpeech.Engine.ACTION_TTS_DATA_INSTALLED));
- }
-
- @Override
- public void onDestroy() {
- getActivity().unregisterReceiver(mLanguagesChangedReceiver);
- mTts.shutdown();
- super.onDestroy();
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
-
- // Save the mLocalePreference values, so we can repopulate it with entries.
- outState.putCharSequenceArray(STATE_KEY_LOCALE_ENTRIES,
- mLocalePreference.getEntries());
- outState.putCharSequenceArray(STATE_KEY_LOCALE_ENTRY_VALUES,
- mLocalePreference.getEntryValues());
- outState.putCharSequence(STATE_KEY_LOCALE_VALUE,
- mLocalePreference.getValue());
- }
-
- private final void checkTtsData() {
- Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
- intent.setPackage(getEngineName());
- try {
- if (DBG) Log.d(TAG, "Updating engine: Checking voice data: " + intent.toUri(0));
- startActivityForResult(intent, VOICE_DATA_INTEGRITY_CHECK);
- } catch (ActivityNotFoundException ex) {
- Log.e(TAG, "Failed to check TTS data, no activity found for " + intent + ")");
- }
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == VOICE_DATA_INTEGRITY_CHECK) {
- if (resultCode != TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL) {
- updateVoiceDetails(data);
- } else {
- Log.e(TAG, "CheckVoiceData activity failed");
- }
- }
- }
-
- private void updateVoiceDetails(Intent data) {
- if (data == null){
- Log.e(TAG, "Engine failed voice data integrity check (null return)" +
- mTts.getCurrentEngine());
- return;
- }
- mVoiceDataDetails = data;
-
- if (DBG) Log.d(TAG, "Parsing voice data details, data: " + mVoiceDataDetails.toUri(0));
-
- final ArrayList<String> available = mVoiceDataDetails.getStringArrayListExtra(
- TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);
- final ArrayList<String> unavailable = mVoiceDataDetails.getStringArrayListExtra(
- TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES);
-
- if (unavailable != null && unavailable.size() > 0) {
- mInstallVoicesPreference.setEnabled(true);
- } else {
- mInstallVoicesPreference.setEnabled(false);
- }
-
- if (available == null){
- Log.e(TAG, "TTS data check failed (available == null).");
- mLocalePreference.setEnabled(false);
- return;
- } else {
- updateDefaultLocalePref(available);
- }
- }
-
- private void updateDefaultLocalePref(ArrayList<String> availableLangs) {
- if (availableLangs == null || availableLangs.size() == 0) {
- mLocalePreference.setEnabled(false);
- return;
- }
- Locale currentLocale = null;
- if (!mEnginesHelper.isLocaleSetToDefaultForEngine(getEngineName())) {
- currentLocale = mEnginesHelper.getLocalePrefForEngine(getEngineName());
- }
-
- ArrayList<Pair<String, Locale>> entryPairs =
- new ArrayList<Pair<String, Locale>>(availableLangs.size());
- for (int i = 0; i < availableLangs.size(); i++) {
- Locale locale = mEnginesHelper.parseLocaleString(availableLangs.get(i));
- if (locale != null){
- entryPairs.add(new Pair<String, Locale>(
- locale.getDisplayName(), locale));
- }
- }
-
- // Sort it
- Collections.sort(entryPairs, new Comparator<Pair<String, Locale>>() {
- @Override
- public int compare(Pair<String, Locale> lhs, Pair<String, Locale> rhs) {
- return lhs.first.compareToIgnoreCase(rhs.first);
- }
- });
-
- // Get two arrays out of one of pairs
- mSelectedLocaleIndex = 0; // Will point to the R.string.tts_lang_use_system value
- CharSequence[] entries = new CharSequence[availableLangs.size()+1];
- CharSequence[] entryValues = new CharSequence[availableLangs.size()+1];
-
- entries[0] = getActivity().getString(R.string.tts_lang_use_system);
- entryValues[0] = "";
-
- int i = 1;
- for (Pair<String, Locale> entry : entryPairs) {
- if (entry.second.equals(currentLocale)) {
- mSelectedLocaleIndex = i;
- }
- entries[i] = entry.first;
- entryValues[i++] = entry.second.toString();
- }
-
- mLocalePreference.setEntries(entries);
- mLocalePreference.setEntryValues(entryValues);
- mLocalePreference.setEnabled(true);
- setLocalePreference(mSelectedLocaleIndex);
- }
-
- /** Set entry from entry table in mLocalePreference */
- private void setLocalePreference(int index) {
- if (index < 0) {
- mLocalePreference.setValue("");
- mLocalePreference.setSummary(R.string.tts_lang_not_selected);
- } else {
- mLocalePreference.setValueIndex(index);
- mLocalePreference.setSummary(mLocalePreference.getEntries()[index]);
- }
- }
-
- /**
- * Ask the current default engine to launch the matching INSTALL_TTS_DATA activity
- * so the required TTS files are properly installed.
- */
- private void installVoiceData() {
- if (TextUtils.isEmpty(getEngineName())) return;
- Intent intent = new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
- intent.setPackage(getEngineName());
- try {
- Log.v(TAG, "Installing voice data: " + intent.toUri(0));
- startActivity(intent);
- } catch (ActivityNotFoundException ex) {
- Log.e(TAG, "Failed to install TTS data, no acitivty found for " + intent + ")");
- }
- }
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- if (preference == mInstallVoicesPreference) {
- installVoiceData();
- return true;
- } else if (preference == mEngineSettingsPreference) {
- startActivity(mEngineSettingsIntent);
- return true;
- }
-
- return false;
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (preference == mLocalePreference) {
- String localeString = (String) newValue;
- updateLanguageTo((!TextUtils.isEmpty(localeString) ?
- mEnginesHelper.parseLocaleString(localeString) : null));
- return true;
- }
- return false;
- }
-
- private void updateLanguageTo(Locale locale) {
- int selectedLocaleIndex = -1;
- String localeString = (locale != null) ? locale.toString() : "";
- for (int i=0; i < mLocalePreference.getEntryValues().length; i++) {
- if (localeString.equalsIgnoreCase(mLocalePreference.getEntryValues()[i].toString())) {
- selectedLocaleIndex = i;
- break;
- }
- }
-
- if (selectedLocaleIndex == -1) {
- Log.w(TAG, "updateLanguageTo called with unknown locale argument");
- return;
- }
- mLocalePreference.setSummary(mLocalePreference.getEntries()[selectedLocaleIndex]);
- mSelectedLocaleIndex = selectedLocaleIndex;
-
- mEnginesHelper.updateLocalePrefForEngine(getEngineName(), locale);
-
- if (getEngineName().equals(mTts.getCurrentEngine())) {
- // Null locale means "use system default"
- mTts.setLanguage((locale != null) ? locale : Locale.getDefault());
- }
- }
-
- private String getEngineName() {
- return getArguments().getString(TtsEnginePreference.FRAGMENT_ARGS_NAME);
- }
-
- private String getEngineLabel() {
- return getArguments().getString(TtsEnginePreference.FRAGMENT_ARGS_LABEL);
- }
-}
diff --git a/src/com/android/settings/tts/TtsSlidersFragment.java b/src/com/android/settings/tts/TtsSlidersFragment.java
new file mode 100644
index 0000000..7fdbd62
--- /dev/null
+++ b/src/com/android/settings/tts/TtsSlidersFragment.java
@@ -0,0 +1,207 @@
+package com.android.settings.tts;
+
+import android.speech.tts.TextToSpeech;
+import com.android.settings.R;
+import android.os.Bundle;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import android.util.Log;
+import com.android.settings.SeekBarPreference;
+import android.support.v7.preference.Preference;
+import android.content.ContentResolver;
+import com.android.settings.search.Indexable;
+import com.android.settings.search.BaseSearchIndexProvider;
+import android.content.Context;
+import android.provider.SearchIndexableResource;
+
+import java.util.List;
+import java.util.Arrays;
+
+import static android.provider.Settings.Secure.TTS_DEFAULT_PITCH;
+import static android.provider.Settings.Secure.TTS_DEFAULT_RATE;
+
+public class TtsSlidersFragment extends SettingsPreferenceFragment
+ implements Preference.OnPreferenceChangeListener,
+ Preference.OnPreferenceClickListener,
+ Indexable {
+ private static final String TAG = TtsSlidersFragment.class.getSimpleName();
+ private static final boolean DBG = false;
+
+ /** Preference key for the TTS pitch selection slider. */
+ private static final String KEY_DEFAULT_PITCH = "tts_default_pitch";
+
+ /** Preference key for the TTS rate selection slider. */
+ private static final String KEY_DEFAULT_RATE = "tts_default_rate";
+
+ /** Preference key for the TTS reset speech rate preference. */
+ private static final String KEY_RESET_SPEECH_RATE = "reset_speech_rate";
+
+ /** Preference key for the TTS reset speech pitch preference. */
+ private static final String KEY_RESET_SPEECH_PITCH = "reset_speech_pitch";
+
+ /**
+ * Speech rate value. This value should be kept in sync with the max value set in tts_settings
+ * xml.
+ */
+ private static final int MAX_SPEECH_RATE = 600;
+
+ private static final int MIN_SPEECH_RATE = 10;
+
+ /**
+ * Speech pitch value. TTS pitch value varies from 25 to 400, where 100 is the value for normal
+ * pitch. The max pitch value is set to 400, based on feedback from users and the GoogleTTS
+ * pitch variation range. The range for pitch is not set in stone and should be readjusted based
+ * on user need. This value should be kept in sync with the max value set in tts_settings xml.
+ */
+ private static final int MAX_SPEECH_PITCH = 400;
+
+ private static final int MIN_SPEECH_PITCH = 25;
+
+ private int mDefaultPitch = TextToSpeech.Engine.DEFAULT_PITCH;
+ private int mDefaultRate = TextToSpeech.Engine.DEFAULT_RATE;
+
+ private SeekBarPreference mDefaultPitchPref;
+ private SeekBarPreference mDefaultRatePref;
+ private Preference mResetSpeechRate;
+ private Preference mResetSpeechPitch;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.tts_sliders);
+
+ mResetSpeechRate = findPreference(KEY_RESET_SPEECH_RATE);
+ mResetSpeechRate.setOnPreferenceClickListener(this);
+ mResetSpeechPitch = findPreference(KEY_RESET_SPEECH_PITCH);
+ mResetSpeechPitch.setOnPreferenceClickListener(this);
+
+ mDefaultPitchPref = (SeekBarPreference) findPreference(KEY_DEFAULT_PITCH);
+ mDefaultRatePref = (SeekBarPreference) findPreference(KEY_DEFAULT_RATE);
+
+ initSettings();
+ }
+
+ private void initSettings() {
+ final ContentResolver resolver = getContentResolver();
+ // Set up the default rate and pitch.
+ mDefaultRate =
+ android.provider.Settings.Secure.getInt(
+ resolver, TTS_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE);
+ mDefaultPitch =
+ android.provider.Settings.Secure.getInt(
+ resolver, TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH);
+
+ mDefaultRatePref.setProgress(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, mDefaultRate));
+ mDefaultRatePref.setOnPreferenceChangeListener(this);
+ mDefaultRatePref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, MAX_SPEECH_RATE));
+
+ mDefaultPitchPref.setProgress(
+ getSeekBarProgressFromValue(KEY_DEFAULT_PITCH, mDefaultPitch));
+ mDefaultPitchPref.setOnPreferenceChangeListener(this);
+ mDefaultPitchPref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_PITCH, MAX_SPEECH_PITCH));
+ }
+
+ /**
+ * The minimum speech pitch/rate value should be > 0 but the minimum value of a seekbar in
+ * android is fixed at 0. Therefore, we increment the seekbar progress with MIN_SPEECH_VALUE so
+ * that the minimum seekbar progress value is MIN_SPEECH_PITCH/RATE. SPEECH_VALUE =
+ * MIN_SPEECH_VALUE + SEEKBAR_PROGRESS
+ */
+ private int getValueFromSeekBarProgress(String preferenceKey, int progress) {
+ if (preferenceKey.equals(KEY_DEFAULT_RATE)) {
+ return MIN_SPEECH_RATE + progress;
+ } else if (preferenceKey.equals(KEY_DEFAULT_PITCH)) {
+ return MIN_SPEECH_PITCH + progress;
+ }
+ return progress;
+ }
+
+ /**
+ * Since we are appending the MIN_SPEECH value to the speech seekbar progress, the speech
+ * seekbar progress should be set to (speechValue - MIN_SPEECH value).
+ */
+ private int getSeekBarProgressFromValue(String preferenceKey, int value) {
+ if (preferenceKey.equals(KEY_DEFAULT_RATE)) {
+ return value - MIN_SPEECH_RATE;
+ } else if (preferenceKey.equals(KEY_DEFAULT_PITCH)) {
+ return value - MIN_SPEECH_PITCH;
+ }
+ return value;
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object objValue) {
+ if (KEY_DEFAULT_RATE.equals(preference.getKey())) {
+ updateSpeechRate((Integer) objValue);
+ } else if (KEY_DEFAULT_PITCH.equals(preference.getKey())) {
+ updateSpeechPitchValue((Integer) objValue);
+ }
+ return true;
+ }
+
+ /** Called when mPlayExample, mResetSpeechRate or mResetSpeechPitch is clicked. */
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference == mResetSpeechRate) {
+ int speechRateSeekbarProgress =
+ getSeekBarProgressFromValue(KEY_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE);
+ mDefaultRatePref.setProgress(speechRateSeekbarProgress);
+ updateSpeechRate(speechRateSeekbarProgress);
+ return true;
+ } else if (preference == mResetSpeechPitch) {
+ int pitchSeekbarProgress =
+ getSeekBarProgressFromValue(
+ KEY_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH);
+ mDefaultPitchPref.setProgress(pitchSeekbarProgress);
+ updateSpeechPitchValue(pitchSeekbarProgress);
+ return true;
+ }
+ return false;
+ }
+
+ private void updateSpeechRate(int speechRateSeekBarProgress) {
+ mDefaultRate = getValueFromSeekBarProgress(KEY_DEFAULT_RATE, speechRateSeekBarProgress);
+ try {
+ android.provider.Settings.Secure.putInt(
+ getContentResolver(), TTS_DEFAULT_RATE, mDefaultRate);
+ if (DBG) Log.d(TAG, "TTS default rate changed, now " + mDefaultRate);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "could not persist default TTS rate setting", e);
+ }
+ return;
+ }
+
+ private void updateSpeechPitchValue(int speechPitchSeekBarProgress) {
+ mDefaultPitch = getValueFromSeekBarProgress(KEY_DEFAULT_PITCH, speechPitchSeekBarProgress);
+ try {
+ android.provider.Settings.Secure.putInt(
+ getContentResolver(), TTS_DEFAULT_PITCH, mDefaultPitch);
+ if (DBG) Log.d(TAG, "TTS default pitch changed, now" + mDefaultPitch);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "could not persist default TTS pitch setting", e);
+ }
+ return;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.TTS_SLIDERS;
+ }
+
+ public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(
+ Context context, boolean enabled) {
+ Log.i(TAG, "Indexing");
+ final SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.tts_sliders;
+ return Arrays.asList(sir);
+ }
+ };
+}
diff --git a/src/com/android/settings/webview/UserPackageWrapper.java b/src/com/android/settings/webview/UserPackageWrapper.java
new file mode 100644
index 0000000..8fbb10c
--- /dev/null
+++ b/src/com/android/settings/webview/UserPackageWrapper.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.webview;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.UserInfo;
+import android.webkit.UserPackage;
+
+import java.util.List;
+
+/**
+ * Wrapper class around android.webkit.UserPackage - to be able to use UserPackage in Robolectric
+ * tests (such tests currently don't support mocking hidden classes).
+ */
+interface UserPackageWrapper {
+ UserInfo getUserInfo();
+ PackageInfo getPackageInfo();
+ boolean isEnabledPackage();
+ boolean isInstalledPackage();
+}
diff --git a/src/com/android/settings/webview/UserPackageWrapperImpl.java b/src/com/android/settings/webview/UserPackageWrapperImpl.java
new file mode 100644
index 0000000..1ea7c2e
--- /dev/null
+++ b/src/com/android/settings/webview/UserPackageWrapperImpl.java
@@ -0,0 +1,50 @@
+/*
+ * 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.webview;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.UserInfo;
+import android.webkit.UserPackage;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Default implementation of UserPackageWrapper.
+ */
+class UserPackageWrapperImpl implements UserPackageWrapper {
+ private final UserPackage mUserPackage;
+
+ UserPackageWrapperImpl(UserPackage userPackage) {
+ mUserPackage = userPackage;
+ }
+
+ public UserInfo getUserInfo() {
+ return mUserPackage.getUserInfo();
+ }
+
+ public PackageInfo getPackageInfo() {
+ return mUserPackage.getPackageInfo();
+ }
+
+ public boolean isEnabledPackage() {
+ return mUserPackage.isEnabledPackage();
+ }
+
+ public boolean isInstalledPackage() {
+ return mUserPackage.isInstalledPackage();
+ }
+}
diff --git a/src/com/android/settings/webview/WebViewAppListAdapter.java b/src/com/android/settings/webview/WebViewAppListAdapter.java
new file mode 100644
index 0000000..4c36a47
--- /dev/null
+++ b/src/com/android/settings/webview/WebViewAppListAdapter.java
@@ -0,0 +1,138 @@
+/*
+ * 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.webview;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.graphics.Color;
+import android.support.annotation.VisibleForTesting;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+import com.android.settings.applications.AppViewHolder;
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Custom list adapter for Settings to choose WebView package.
+ * Note: parts of this class are copied from AppPicker.java.
+ */
+class WebViewAppListAdapter extends ArrayAdapter<WebViewApplicationInfo> {
+ private final LayoutInflater mInflater;
+ private final String mCurrentWebViewPackageName;
+
+ public WebViewAppListAdapter(Context context,
+ WebViewUpdateServiceWrapper webviewUpdateServiceWrapper) {
+ super(context, 0);
+ mInflater = LayoutInflater.from(context);
+
+ final List<WebViewApplicationInfo> packageInfoList =
+ new ArrayList<WebViewApplicationInfo>();
+ List<ApplicationInfo> pkgs =
+ webviewUpdateServiceWrapper.getValidWebViewApplicationInfos(getContext());
+ for (ApplicationInfo ai : pkgs) {
+ WebViewApplicationInfo info = new WebViewApplicationInfo(ai,
+ ai.loadLabel(context.getPackageManager()).toString(),
+ getDisabledReason(webviewUpdateServiceWrapper, context, ai.packageName));
+ packageInfoList.add(info);
+ }
+ addAll(packageInfoList);
+
+ PackageInfo currentWebViewPackage = webviewUpdateServiceWrapper.getCurrentWebViewPackage();
+ mCurrentWebViewPackageName =
+ currentWebViewPackage == null ? null : currentWebViewPackage.packageName;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // A ViewHolder keeps references to children views to avoid unnecessary calls
+ // to findViewById() on each row.
+ AppViewHolder holder = AppViewHolder.createOrRecycle(mInflater, convertView);
+ convertView = holder.rootView;
+ WebViewApplicationInfo info = getItem(position);
+ holder.appName.setText(info.label);
+ if (info.info != null) {
+ holder.appIcon.setImageDrawable(info.info.loadIcon(getContext().getPackageManager()));
+ // Allow disable-description to wrap - to be able to show several lines of text in case
+ // a package is disabled/uninstalled for several users.
+ holder.summary.setSingleLine(false);
+ if (!isEnabled(position)) {
+ holder.summary.setText(info.disabledReason);
+ } else {
+ holder.summary.setText("");
+ }
+ } else {
+ holder.appIcon.setImageDrawable(null);
+ holder.summary.setText("");
+ }
+ holder.disabled.setVisibility(View.GONE);
+ // Only allow a package to be chosen if it is enabled and installed for all users.
+ convertView.setEnabled(isEnabled(position));
+ if (info.info.packageName.equals(mCurrentWebViewPackageName)) {
+ convertView.setBackgroundColor(Color.GRAY);
+ } else {
+ convertView.setBackgroundColor(Color.WHITE);
+ }
+ return convertView;
+ }
+
+ @Override
+ public boolean isEnabled (int position) {
+ WebViewApplicationInfo info = getItem(position);
+ return info.disabledReason == null;
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ int numItems = getCount();
+ for (int n = 0; n < numItems; n++) {
+ if (!isEnabled(n)) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns the reason why a package cannot be used as WebView implementation.
+ * This is either because of it being disabled, uninstalled, or hidden for any user.
+ */
+ @VisibleForTesting
+ static String getDisabledReason(WebViewUpdateServiceWrapper webviewUpdateServiceWrapper,
+ Context context, String packageName) {
+ StringBuilder disabledReason = new StringBuilder();
+ List<UserPackageWrapper> userPackages =
+ webviewUpdateServiceWrapper.getPackageInfosAllUsers(context, packageName);
+ for (UserPackageWrapper userPackage : userPackages) {
+ if (!userPackage.isInstalledPackage()) {
+ // Package uninstalled/hidden
+ disabledReason.append(context.getString(
+ R.string.webview_uninstalled_for_user, userPackage.getUserInfo().name));
+ } else if (!userPackage.isEnabledPackage()) {
+ // Package disabled
+ disabledReason.append(context.getString(
+ R.string.webview_disabled_for_user, userPackage.getUserInfo().name));
+ }
+ }
+ if (disabledReason.length() == 0) return null;
+ return disabledReason.toString();
+ }
+}
+
diff --git a/src/com/android/settings/webview/WebViewAppPicker.java b/src/com/android/settings/webview/WebViewAppPicker.java
new file mode 100644
index 0000000..2417b00
--- /dev/null
+++ b/src/com/android/settings/webview/WebViewAppPicker.java
@@ -0,0 +1,95 @@
+/*
+ * 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.webview;
+
+import android.app.ListActivity;
+import android.content.pm.PackageInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+import android.view.View;
+import android.webkit.WebViewFactory;
+import android.widget.ListView;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settings.core.instrumentation.Instrumentable;
+import com.android.settings.core.instrumentation.VisibilityLoggerMixin;
+
+public class WebViewAppPicker extends ListActivity implements Instrumentable {
+ private static final String TAG = WebViewAppPicker.class.getSimpleName();
+ private WebViewAppListAdapter mAdapter;
+ private WebViewUpdateServiceWrapper mWebViewUpdateServiceWrapper;
+
+ private final VisibilityLoggerMixin mVisibilityLoggerMixin =
+ new VisibilityLoggerMixin(getMetricsCategory());
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ if (mWebViewUpdateServiceWrapper == null) {
+ setWebViewUpdateServiceWrapper(createDefaultWebViewUpdateServiceWrapper());
+ }
+ mAdapter = new WebViewAppListAdapter(this, mWebViewUpdateServiceWrapper);
+ setListAdapter(mAdapter);
+
+ mVisibilityLoggerMixin.onAttach(this);
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ WebViewApplicationInfo app = mAdapter.getItem(position);
+
+ if (mWebViewUpdateServiceWrapper.setWebViewProvider(app.info.packageName)) {
+ Intent intent = new Intent();
+ intent.setAction(app.info.packageName);
+ setResult(RESULT_OK, intent);
+ } else {
+ mWebViewUpdateServiceWrapper.showInvalidChoiceToast(this);
+ }
+ finish();
+ }
+
+ private WebViewUpdateServiceWrapper createDefaultWebViewUpdateServiceWrapper() {
+ return new WebViewUpdateServiceWrapper();
+ }
+
+ @VisibleForTesting
+ void setWebViewUpdateServiceWrapper(WebViewUpdateServiceWrapper wvusWrapper) {
+ mWebViewUpdateServiceWrapper = wvusWrapper;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mVisibilityLoggerMixin.onResume();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mVisibilityLoggerMixin.onPause();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.WEBVIEW_IMPLEMENTATION;
+ }
+}
diff --git a/src/com/android/settings/webview/WebViewAppPreferenceController.java b/src/com/android/settings/webview/WebViewAppPreferenceController.java
new file mode 100644
index 0000000..cfb358e
--- /dev/null
+++ b/src/com/android/settings/webview/WebViewAppPreferenceController.java
@@ -0,0 +1,104 @@
+/*
+ * 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.webview;
+
+import android.app.Activity;
+import android.content.pm.PackageInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.DevelopmentSettings;
+import com.android.settings.core.PreferenceController;
+
+public class WebViewAppPreferenceController extends PreferenceController {
+
+ private static final String WEBVIEW_APP_KEY = "select_webview_provider";
+
+ private Context mContext;
+ private Preference mPreference;
+ private final WebViewUpdateServiceWrapper mWebViewUpdateServiceWrapper;
+
+ public WebViewAppPreferenceController(Context context) {
+ this(context, new WebViewUpdateServiceWrapper());
+ }
+
+ public WebViewAppPreferenceController(Context context,
+ WebViewUpdateServiceWrapper webviewUpdateServiceWrapper) {
+ super(context);
+ mContext = context;
+ mWebViewUpdateServiceWrapper = webviewUpdateServiceWrapper;
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (getPreferenceKey().equals(preference.getKey())) {
+ return true;
+ }
+ return false;
+ }
+
+ public Intent getActivityIntent() {
+ return new Intent(mContext, WebViewAppPicker.class);
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ mPreference.setSummary(getCurrentWebViewPackageLabel(mContext));
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ if (isAvailable()) {
+ mPreference = screen.findPreference(WEBVIEW_APP_KEY);
+ }
+ }
+
+ /**
+ * Handle the return-value from the WebViewAppPicker Activity.
+ */
+ public void onActivityResult(int resultCode, Intent data) {
+ // Update the preference summary no matter whether we succeeded to change the webview
+ // implementation correctly - we might have changed implementation to one the user did not
+ // choose.
+ updateState(null);
+ }
+
+ private String getCurrentWebViewPackageLabel(Context context) {
+ PackageInfo webViewPackage = mWebViewUpdateServiceWrapper.getCurrentWebViewPackage();
+ if (webViewPackage == null) return "";
+ return webViewPackage.applicationInfo.loadLabel(context.getPackageManager()).toString();
+ }
+
+
+ @Override
+ public String getPreferenceKey() {
+ return WEBVIEW_APP_KEY;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ public void enablePreference(boolean enabled) {
+ if (isAvailable()) {
+ mPreference.setEnabled(enabled);
+ }
+ }
+}
diff --git a/src/com/android/settings/webview/WebViewApplicationInfo.java b/src/com/android/settings/webview/WebViewApplicationInfo.java
new file mode 100644
index 0000000..6879c59
--- /dev/null
+++ b/src/com/android/settings/webview/WebViewApplicationInfo.java
@@ -0,0 +1,29 @@
+/*
+ * 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.webview;
+
+import android.content.pm.ApplicationInfo;
+
+final class WebViewApplicationInfo {
+ final ApplicationInfo info;
+ final String label;
+ final String disabledReason;
+
+ public WebViewApplicationInfo(ApplicationInfo info, String label, String disabledReason) {
+ this.info = info;
+ this.label = label;
+ this.disabledReason = disabledReason;
+ }
+}
diff --git a/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java b/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java
new file mode 100644
index 0000000..b40be19
--- /dev/null
+++ b/src/com/android/settings/webview/WebViewUpdateServiceWrapper.java
@@ -0,0 +1,113 @@
+/*
+ * 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.webview;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.util.Log;
+import android.webkit.UserPackage;
+import android.webkit.WebViewFactory;
+import android.webkit.WebViewProviderInfo;
+import android.widget.Toast;
+
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class WebViewUpdateServiceWrapper {
+ private static final String TAG = "WVUSWrapper";
+
+ public WebViewUpdateServiceWrapper() {}
+
+ /**
+ * Fetch the package currently used as WebView implementation.
+ */
+ public PackageInfo getCurrentWebViewPackage() {
+ try {
+ return WebViewFactory.getUpdateService().getCurrentWebViewPackage();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ return null;
+ }
+
+ /**
+ * Fetches ApplicationInfo objects for all currently valid WebView packages.
+ * A WebView package is considered valid if it can be used as a WebView implementation. The
+ * validity of a package is not dependent on whether the package is installed/enabled.
+ */
+ public List<ApplicationInfo> getValidWebViewApplicationInfos(Context context) {
+ WebViewProviderInfo[] providers = null;
+ try {
+ providers = WebViewFactory.getUpdateService().getValidWebViewPackages();
+ } catch (RemoteException e) {
+ }
+ List<ApplicationInfo> pkgs = new ArrayList<>();
+ for (WebViewProviderInfo provider : providers) {
+ try {
+ pkgs.add(context.getPackageManager().getApplicationInfo(
+ provider.packageName, PACKAGE_FLAGS));
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+ return pkgs;
+ }
+
+ /**
+ * Change WebView provider to {@param packageName}.
+ * @return whether the change succeeded.
+ */
+ public boolean setWebViewProvider(String packageName) {
+ try {
+ return packageName.equals(
+ WebViewFactory.getUpdateService().changeProviderAndSetting(packageName));
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException when trying to change provider to " + packageName, e);
+ }
+ return false;
+ }
+
+ /**
+ * Fetch PackageInfos for the package named {@param packageName} for all users on the device.
+ */
+ public List<UserPackageWrapper> getPackageInfosAllUsers(Context context, String packageName) {
+ List<UserPackageWrapper> userPackageWrappers = new ArrayList<>();
+ List<UserPackage> userPackages =
+ UserPackage.getPackageInfosAllUsers(context, packageName, PACKAGE_FLAGS);
+ for (UserPackage userPackage : userPackages) {
+ userPackageWrappers.add(new UserPackageWrapperImpl(userPackage));
+ }
+ return userPackageWrappers;
+ }
+
+ /**
+ * Show a toast to explain the chosen package can no longer be chosen.
+ */
+ public void showInvalidChoiceToast(Context context) {
+ // The user chose a package that became invalid since the list was last updated,
+ // show a Toast to explain the situation.
+ Toast toast = Toast.makeText(context,
+ R.string.select_webview_provider_toast_text, Toast.LENGTH_SHORT);
+ toast.show();
+ }
+
+ static final int PACKAGE_FLAGS = PackageManager.MATCH_ANY_USER;
+}
diff --git a/src/com/android/settings/wifi/ConfigureWifiSettings.java b/src/com/android/settings/wifi/ConfigureWifiSettings.java
index 6cbdb29..682662a 100644
--- a/src/com/android/settings/wifi/ConfigureWifiSettings.java
+++ b/src/com/android/settings/wifi/ConfigureWifiSettings.java
@@ -15,8 +15,6 @@
*/
package com.android.settings.wifi;
-import static android.content.Context.WIFI_SERVICE;
-
import android.content.Context;
import android.net.wifi.WifiManager;
import android.provider.SearchIndexableResource;
@@ -32,6 +30,8 @@
import java.util.Arrays;
import java.util.List;
+import static android.content.Context.WIFI_SERVICE;
+
public class ConfigureWifiSettings extends DashboardFragment {
private static final String TAG = "ConfigureWifiSettings";
@@ -44,12 +44,6 @@
}
@Override
- protected String getCategoryKey() {
- // We don't want to inject any external settings into this screen.
- return null;
- }
-
- @Override
protected String getLogTag() {
return TAG;
}
@@ -64,7 +58,6 @@
mWifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
final List<PreferenceController> controllers = new ArrayList<>();
controllers.add(new WifiInfoPreferenceController(context, getLifecycle(), mWifiManager));
- controllers.add(new SavedNetworkPreferenceController(context, mWifiManager));
controllers.add(new CellularFallbackPreferenceController(context));
controllers.add(new AllowRecommendationPreferenceController(context));
controllers.add(new NotifyOpenNetworksPreferenceController(context, getLifecycle()));
diff --git a/src/com/android/settings/wifi/LinkablePreference.java b/src/com/android/settings/wifi/LinkablePreference.java
new file mode 100644
index 0000000..6b1b87d
--- /dev/null
+++ b/src/com/android/settings/wifi/LinkablePreference.java
@@ -0,0 +1,119 @@
+/*
+ * 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.wifi;
+
+import android.content.Context;
+import android.support.annotation.Nullable;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceViewHolder;
+import android.text.Spannable;
+import android.text.style.TextAppearanceSpan;
+import android.util.AttributeSet;
+import android.widget.TextView;
+import com.android.settings.LinkifyUtils;
+
+/**
+ * A preference with a title that can have linkable content on click.
+ */
+public class LinkablePreference extends Preference {
+
+ private LinkifyUtils.OnClickListener mClickListener;
+ private CharSequence mContentTitle;
+ private CharSequence mContentDescription;
+
+ public LinkablePreference(Context ctx, AttributeSet attrs, int defStyle) {
+ super(ctx, attrs, defStyle);
+ setSelectable(false);
+ }
+
+ public LinkablePreference(Context ctx, AttributeSet attrs) {
+ super(ctx, attrs);
+ setSelectable(false);
+ }
+
+ public LinkablePreference(Context ctx) {
+ super(ctx);
+ setSelectable(false);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder view) {
+ super.onBindViewHolder(view);
+
+ TextView textView = (TextView) view.findViewById(android.R.id.title);
+ if (textView == null || mContentTitle == null || mClickListener == null) {
+ return;
+ }
+
+ textView.setSingleLine(false);
+ StringBuilder contentBuilder = new StringBuilder().append(mContentTitle);
+ if (mContentDescription != null) {
+ contentBuilder.append("\n\n");
+ contentBuilder.append(mContentDescription);
+ }
+
+ boolean linked = LinkifyUtils.linkify(textView, contentBuilder, mClickListener);
+ if (linked && mContentTitle != null) {
+ // Embolden and enlarge the title.
+ Spannable boldSpan = (Spannable) textView.getText();
+ boldSpan.setSpan(
+ new TextAppearanceSpan(
+ getContext(), android.R.style.TextAppearance_Medium),
+ 0,
+ mContentTitle.length(),
+ Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+ textView.setText(boldSpan);
+ }
+ }
+
+ /**
+ * Sets the linkable text for the Preference title.
+ * @param contentTitle text to set the Preference title.
+ * @param contentDescription description text to append underneath title, can be null.
+ * @param clickListener OnClickListener for the link portion of the text.
+ */
+ public void setText(
+ CharSequence contentTitle,
+ @Nullable CharSequence contentDescription,
+ LinkifyUtils.OnClickListener clickListener) {
+ mContentTitle = contentTitle;
+ mContentDescription = contentDescription;
+ mClickListener = clickListener;
+ // sets the title so that the title TextView is not hidden in super.onBindViewHolder()
+ super.setTitle(contentTitle);
+ }
+
+ /**
+ * Sets the title of the LinkablePreference. resets linkable text for reusability.
+ */
+ @Override
+ public void setTitle(int titleResId) {
+ mContentTitle = null;
+ mContentDescription = null;
+ super.setTitle(titleResId);
+ }
+
+ /**
+ * Sets the title of the LinkablePreference. resets linkable text for reusability.
+ */
+ @Override
+ public void setTitle(CharSequence title) {
+ mContentTitle = null;
+ mContentDescription = null;
+ super.setTitle(title);
+ }
+}
diff --git a/src/com/android/settings/wifi/SavedNetworkPreferenceController.java b/src/com/android/settings/wifi/SavedNetworkPreferenceController.java
deleted file mode 100644
index c3ad355..0000000
--- a/src/com/android/settings/wifi/SavedNetworkPreferenceController.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.wifi;
-
-import android.content.Context;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-
-import com.android.settings.core.PreferenceController;
-
-import java.util.List;
-
-/**
- * {@link PreferenceController} that opens saved network subsetting.
- */
-public class SavedNetworkPreferenceController extends PreferenceController {
-
- private static final String KEY_SAVED_NETWORKS = "saved_networks";
-
- private final WifiManager mWifiManager;
-
- public SavedNetworkPreferenceController(Context context, WifiManager wifiManager) {
- super(context);
- mWifiManager = wifiManager;
- }
-
- @Override
- public boolean isAvailable() {
- final List<WifiConfiguration> config = mWifiManager.getConfiguredNetworks();
- return config != null && !config.isEmpty();
- }
-
- @Override
- public String getPreferenceKey() {
- return KEY_SAVED_NETWORKS;
- }
-}
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index 1dd18bd..4fd93b5 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -40,11 +40,10 @@
import android.os.Process;
import android.provider.Settings;
import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.preference.PreferenceViewHolder;
-import android.text.Spannable;
import android.text.TextUtils;
-import android.text.style.TextAppearanceSpan;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
@@ -53,8 +52,6 @@
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.TextView.BufferType;
import android.widget.Toast;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -102,7 +99,6 @@
private static final int MENU_ID_FORGET = Menu.FIRST + 7;
private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
- private static final int MENU_ID_CONFIGURE = Menu.FIRST + 10;
public static final int WIFI_DIALOG_ID = 1;
/* package */ static final int WPS_PBC_DIALOG_ID = 2;
@@ -115,6 +111,9 @@
private static final String SAVED_WIFI_NFC_DIALOG_STATE = "wifi_nfc_dlg_state";
private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list";
+ private static final String PREF_KEY_ACCESS_POINTS = "access_points";
+ private static final String PREF_KEY_ADDITIONAL_SETTINGS = "additional_settings";
+ private static final String PREF_KEY_SAVED_NETWORKS = "saved_networks";
protected WifiManager mWifiManager;
private WifiManager.ActionListener mConnectListener;
@@ -152,7 +151,12 @@
private HandlerThread mBgThread;
private AccessPointPreference.UserBadgeCache mUserBadgeCache;
+
+ private PreferenceCategory mAccessPointsPreferenceCategory;
+ private PreferenceCategory mAdditionalSettingsPreferenceCategory;
private Preference mAddPreference;
+ private Preference mSavedNetworksPreference;
+ private LinkablePreference mStatusMessagePreference;
private MenuItem mScanMenuItem;
@@ -177,9 +181,18 @@
getPreferenceManager().setPreferenceComparisonCallback(
new PreferenceManager.SimplePreferenceComparisonCallback());
addPreferencesFromResource(R.xml.wifi_settings);
- mAddPreference = new Preference(getContext());
+
+ mAccessPointsPreferenceCategory =
+ (PreferenceCategory) findPreference(PREF_KEY_ACCESS_POINTS);
+ mAdditionalSettingsPreferenceCategory =
+ (PreferenceCategory) findPreference(PREF_KEY_ADDITIONAL_SETTINGS);
+ mSavedNetworksPreference = findPreference(PREF_KEY_SAVED_NETWORKS);
+
+ Context prefContext = getPrefContext();
+ mAddPreference = new Preference(prefContext);
mAddPreference.setIcon(R.drawable.ic_menu_add_inset);
mAddPreference.setTitle(R.string.wifi_add_network);
+ mStatusMessagePreference = new LinkablePreference(prefContext);
mUserBadgeCache = new AccessPointPreference.UserBadgeCache(getPackageManager());
@@ -305,7 +318,7 @@
/**
* @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
*/
- /* package */ WifiEnabler createWifiEnabler() {
+ private WifiEnabler createWifiEnabler() {
final SettingsActivity activity = (SettingsActivity) getActivity();
return new WifiEnabler(activity, activity.getSwitchBar(), mMetricsFeatureProvider);
}
@@ -314,7 +327,6 @@
public void onResume() {
final Activity activity = getActivity();
super.onResume();
- removePreference("dummy");
if (mWifiEnabler != null) {
mWifiEnabler.resume(activity);
}
@@ -346,14 +358,13 @@
* @param menu
*/
void addOptionsMenuItems(Menu menu) {
- final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
- mScanMenuItem = menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh);
- mScanMenuItem.setEnabled(wifiIsEnabled)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- menu.add(Menu.NONE, MENU_ID_CONFIGURE, 0, R.string.wifi_menu_configure)
- .setIcon(R.drawable.ic_settings_24dp)
+
+ final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
+ mScanMenuItem = menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh)
+ .setIcon(com.android.internal.R.drawable.ic_menu_refresh);
+ mScanMenuItem.setEnabled(wifiIsEnabled)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
}
@@ -424,18 +435,6 @@
null);
}
return true;
- case MENU_ID_CONFIGURE:
- if (getActivity() instanceof SettingsActivity) {
- ((SettingsActivity) getActivity()).startPreferencePanel(
- ConfigureWifiSettings.class.getCanonicalName(), null,
- R.string.wifi_configure_titlebar, null, this, 0);
- } else {
- startFragment(this, ConfigureWifiSettings.class.getCanonicalName(),
- R.string.wifi_configure_titlebar, -1 /* Do not request a results */,
- null);
- }
- return true;
-
}
return super.onOptionsItemSelected(item);
}
@@ -622,10 +621,10 @@
// Safeguard from some delayed event handling
if (getActivity() == null) return;
if (isUiRestricted()) {
+ mAccessPointsPreferenceCategory.removeAll();
if (!isUiRestrictedByOnlyAdmin()) {
addMessagePreference(R.string.wifi_empty_list_user_restricted);
}
- getPreferenceScreen().removeAll();
return;
}
final int wifiState = mWifiManager.getWifiState();
@@ -638,7 +637,8 @@
boolean hasAvailableAccessPoints = false;
int index = 0;
- cacheRemoveAllPrefs(getPreferenceScreen());
+ mAccessPointsPreferenceCategory.removePreference(mStatusMessagePreference);
+ cacheRemoveAllPrefs(mAccessPointsPreferenceCategory);
for (AccessPoint accessPoint : accessPoints) {
// Ignore access points that are out of range.
if (accessPoint.getLevel() != -1) {
@@ -665,12 +665,12 @@
onPreferenceTreeClick(preference);
mOpenSsid = null;
}
- getPreferenceScreen().addPreference(preference);
+ mAccessPointsPreferenceCategory.addPreference(preference);
accessPoint.setListener(this);
preference.refresh();
}
}
- removeCachedPrefs(getPreferenceScreen());
+ removeCachedPrefs(mAccessPointsPreferenceCategory);
if (!hasAvailableAccessPoints) {
setProgressBarVisible(true);
Preference pref = new Preference(getContext()) {
@@ -683,14 +683,16 @@
};
pref.setSelectable(false);
pref.setSummary(R.string.wifi_empty_list_wifi_on);
- pref.setOrder(0);
+ pref.setOrder(index++);
pref.setKey(PREF_KEY_EMPTY_WIFI_LIST);
- getPreferenceScreen().addPreference(pref);
- mAddPreference.setOrder(1);
- getPreferenceScreen().addPreference(mAddPreference);
+ mAccessPointsPreferenceCategory.addPreference(pref);
+ mAddPreference.setOrder(index++);
+ mAccessPointsPreferenceCategory.addPreference(mAddPreference);
+ setSavedNetworkPreferenceVisibility();
} else {
mAddPreference.setOrder(index++);
- getPreferenceScreen().addPreference(mAddPreference);
+ mAccessPointsPreferenceCategory.addPreference(mAddPreference);
+ setSavedNetworkPreferenceVisibility();
setProgressBarVisible(false);
}
if (mScanMenuItem != null) {
@@ -699,7 +701,7 @@
break;
case WifiManager.WIFI_STATE_ENABLING:
- getPreferenceScreen().removeAll();
+ mAccessPointsPreferenceCategory.removeAll();
setProgressBarVisible(true);
break;
@@ -710,6 +712,7 @@
case WifiManager.WIFI_STATE_DISABLED:
setOffMessage();
+ setSavedNetworkPreferenceVisibility();
setProgressBarVisible(false);
if (mScanMenuItem != null) {
mScanMenuItem.setEnabled(false);
@@ -718,17 +721,20 @@
}
}
+ private void setSavedNetworkPreferenceVisibility() {
+ if (mWifiTracker.doSavedNetworksExist()) {
+ mAdditionalSettingsPreferenceCategory.addPreference(mSavedNetworksPreference);
+ } else {
+ mAdditionalSettingsPreferenceCategory.removePreference(mSavedNetworksPreference);
+ }
+ }
+
private void setOffMessage() {
if (isUiRestricted()) {
if (!isUiRestrictedByOnlyAdmin()) {
addMessagePreference(R.string.wifi_empty_list_user_restricted);
}
- getPreferenceScreen().removeAll();
- return;
- }
-
- TextView emptyTextView = getEmptyTextView();
- if (emptyTextView == null) {
+ mAccessPointsPreferenceCategory.removeAll();
return;
}
@@ -744,35 +750,27 @@
if (!wifiScanningMode) {
// Show only the brief text if the user is not allowed to configure scanning settings,
// or the scanning mode has been turned off.
- emptyTextView.setText(briefText, BufferType.SPANNABLE);
+ mStatusMessagePreference.setTitle(briefText);
} else {
- // Append the description of scanning settings with link.
- final StringBuilder contentBuilder = new StringBuilder();
- contentBuilder.append(briefText);
- contentBuilder.append("\n\n");
- contentBuilder.append(getText(R.string.wifi_scan_notify_text));
- LinkifyUtils.linkify(emptyTextView, contentBuilder, new LinkifyUtils.OnClickListener() {
+ LinkifyUtils.OnClickListener clickListener = new LinkifyUtils.OnClickListener() {
@Override
public void onClick() {
- final SettingsActivity activity =
- (SettingsActivity) WifiSettings.this.getActivity();
+ final SettingsActivity activity = (SettingsActivity) getActivity();
activity.startPreferencePanel(ScanningSettings.class.getName(), null,
R.string.location_scanning_screen_title, null, null, 0);
}
- });
+ };
+ mStatusMessagePreference.setText(
+ briefText, getText(R.string.wifi_scan_notify_text), clickListener);
}
- // Embolden and enlarge the brief description anyway.
- Spannable boldSpan = (Spannable) emptyTextView.getText();
- boldSpan.setSpan(
- new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0,
- briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- getPreferenceScreen().removeAll();
+ mAccessPointsPreferenceCategory.removeAll();
+ mAccessPointsPreferenceCategory.addPreference(mStatusMessagePreference);
}
private void addMessagePreference(int messageId) {
- TextView emptyTextView = getEmptyTextView();
- if (emptyTextView != null) emptyTextView.setText(messageId);
- getPreferenceScreen().removeAll();
+ mStatusMessagePreference.setTitle(messageId);
+ mAccessPointsPreferenceCategory.removeAll();
+ mAccessPointsPreferenceCategory.addPreference(mStatusMessagePreference);
}
protected void setProgressBarVisible(boolean visible) {
diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable
index a178596..d774779 100644
--- a/tests/robotests/assets/grandfather_not_implementing_indexable
+++ b/tests/robotests/assets/grandfather_not_implementing_indexable
@@ -91,3 +91,4 @@
com.android.settings.localepicker.LocaleListEditor
com.android.settings.qstile.DevelopmentTileConfigActivity$DevelopmentTileConfigFragment
com.android.settings.applications.ExternalSourcesDetails
+com.android.settings.applications.PictureInPictureSettings
diff --git a/tests/robotests/src/com/android/settings/BackupSettingsHelperTest.java b/tests/robotests/src/com/android/settings/BackupSettingsHelperTest.java
new file mode 100644
index 0000000..b14debc
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/BackupSettingsHelperTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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;
+
+import android.app.backup.BackupManager;
+import android.app.backup.IBackupManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+
+
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+ shadows = {BackupSettingsHelperTest.ShadowBackupManagerStub.class})
+public class BackupSettingsHelperTest {
+
+ private BackupSettingsHelper mBackupSettingsHelper;
+
+ @Mock
+ private static IBackupManager mBackupManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mBackupManager.getCurrentTransport()).thenReturn("test_transport");
+ mBackupSettingsHelper = new BackupSettingsHelper();
+ }
+
+ @Test
+ public void testGetIntentFromBackupTransport() throws Exception {
+ Intent intent = new Intent();
+
+ when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+
+ Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+ verify(mBackupManager).getDataManagementIntent(anyString());
+ }
+
+ @Test
+ public void testGetIntentFromBackupTransport_WithIntent() throws Exception {
+ Intent intent = mock(Intent.class);
+
+ when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+
+ Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+ assertThat(backupIntent).isEqualTo(intent);
+ }
+
+ @Test
+ public void testGetIntentFromBackupTransport_WithNullIntent() throws Exception {
+ when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(null);
+
+ Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+ assertThat(backupIntent).isNull();
+ }
+
+ @Test
+ public void testGetIntentFromBackupTransport_RemoteException() throws Exception {
+ when(mBackupManager.getDataManagementIntent(anyString())).thenThrow(new RemoteException());
+
+ Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+ assertThat(backupIntent).isNull();
+ }
+
+ @Test
+ public void testGetIntentFromBackupTransport_BackupEnabled() throws Exception {
+ Intent intent = new Intent("test_intent");
+
+ when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+ when(mBackupManager.isBackupServiceActive(anyInt())).thenReturn(true);
+
+ Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+ assertThat(backupIntent.getExtras().get(BackupManager.EXTRA_BACKUP_SERVICES_AVAILABLE))
+ .isEqualTo(true);
+ }
+
+ @Test
+ public void testGetIntentFromBackupTransport_BackupDisabled() throws Exception {
+ Intent intent = new Intent("test_intent");
+
+ when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+ when(mBackupManager.isBackupServiceActive(anyInt())).thenReturn(false);
+
+ Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+ assertThat(backupIntent.getExtras().get(BackupManager.EXTRA_BACKUP_SERVICES_AVAILABLE))
+ .isEqualTo(false);
+ }
+
+ @Test
+ public void testGetIntentFromBackupTransport_BackupStatusException() throws Exception {
+ Intent intent = new Intent("test_intent");
+
+ when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+ when(mBackupManager.isBackupServiceActive(anyInt())).thenThrow(new RemoteException());
+
+ Intent backupIntent = mBackupSettingsHelper.getIntentForBackupSettings();
+
+ assertThat(backupIntent.getExtras().get(BackupManager.EXTRA_BACKUP_SERVICES_AVAILABLE))
+ .isEqualTo(false);
+ }
+
+ @Test
+ public void testIsIntentProvidedByTransport_WithNullIntent() throws Exception {
+ when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(null);
+
+ PackageManager packageManager = mock(PackageManager.class);
+
+ boolean isIntentProvided = mBackupSettingsHelper.isIntentProvidedByTransport(packageManager);
+
+ assertThat(isIntentProvided).isFalse();
+ }
+
+ @Test
+ public void testIsIntentProvidedByTransport_WithInvalidIntent() throws Exception {
+ Intent intent = mock(Intent.class);
+
+ when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+
+ PackageManager packageManager = mock(PackageManager.class);
+ when(intent.resolveActivity(packageManager)).thenReturn(null);
+
+ boolean isIntentProvided = mBackupSettingsHelper.isIntentProvidedByTransport(packageManager);
+
+ assertThat(isIntentProvided).isFalse();
+ }
+
+ @Test
+ public void testIsIntentProvidedByTransport_WithIntent() throws Exception {
+ Intent intent = mock(Intent.class);
+
+ when(mBackupManager.getDataManagementIntent(anyString())).thenReturn(intent);
+
+ PackageManager packageManager = mock(PackageManager.class);
+ when(intent.resolveActivity(packageManager)).thenReturn(mock(ComponentName.class));
+
+ boolean isIntentProvided = mBackupSettingsHelper.isIntentProvidedByTransport(packageManager);
+
+ assertThat(isIntentProvided).isTrue();
+ }
+
+ @Implements(IBackupManager.Stub.class)
+ public static class ShadowBackupManagerStub {
+ @Implementation
+ public static IBackupManager asInterface(IBinder iBinder) {
+ return mBackupManager;
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java
index 3ba4d02..31090be 100644
--- a/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java
@@ -82,7 +82,8 @@
@Test
public void testCategory_isAccount() {
- assertThat(mFragment.getCategoryKey()).isEqualTo(CategoryKey.CATEGORY_ACCOUNT);
+ assertThat(new AccountDetailDashboardFragment().getCategoryKey())
+ .isEqualTo(CategoryKey.CATEGORY_ACCOUNT);
}
@Test
diff --git a/tests/robotests/src/com/android/settings/applications/AdvancedAppSettingsTest.java b/tests/robotests/src/com/android/settings/applications/AdvancedAppSettingsTest.java
index 7da30ed..b8f3fc4 100644
--- a/tests/robotests/src/com/android/settings/applications/AdvancedAppSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/applications/AdvancedAppSettingsTest.java
@@ -21,7 +21,6 @@
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
-import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
import org.junit.Test;
@@ -33,7 +32,6 @@
import org.robolectric.shadows.ShadowApplication;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -42,27 +40,20 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
- private FakeFeatureFactory mFeatureFactory;
private AdvancedAppSettings mFragment;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- FakeFeatureFactory.setupForTest(mContext);
- mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+
mFragment = new AdvancedAppSettings();
mFragment.onAttach(ShadowApplication.getInstance().getApplicationContext());
}
@Test
- public void getPreferenceScreenResId_differentIAEnabledState_shouldUseDifferentPrefLayout() {
- when(mFeatureFactory.dashboardFeatureProvider.isEnabled()).thenReturn(true);
+ public void getPreferenceScreenResId_shouldUseAppDefaultSettingPrefLayout() {
assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(
R.xml.app_default_settings);
-
- when(mFeatureFactory.dashboardFeatureProvider.isEnabled()).thenReturn(false);
- assertThat(mFragment.getPreferenceScreenResId()).isEqualTo(
- R.xml.advanced_apps);
}
}
diff --git a/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounterTest.java b/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounterTest.java
index 9fc416d..3dd3a65 100644
--- a/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounterTest.java
+++ b/tests/robotests/src/com/android/settings/applications/AppWithAdminGrantedPermissionsCounterTest.java
@@ -18,7 +18,6 @@
import android.app.admin.DevicePolicyManager;
import android.content.Context;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Build;
@@ -79,7 +78,7 @@
@Mock private Context mContext;
@Mock private PackageManagerWrapper mPackageManager;
- @Mock private IPackageManager mPackageManagerService;
+ @Mock private IPackageManagerWrapper mPackageManagerService;
@Mock private DevicePolicyManagerWrapper mDevicePolicyManager;
private List<UserInfo> mUsersToCount;
diff --git a/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
index aba4a12..2f344dc 100644
--- a/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/applications/ApplicationFeatureProviderImplTest.java
@@ -18,12 +18,15 @@
import android.app.admin.DevicePolicyManager;
import android.content.Context;
-import android.content.pm.IPackageManager;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.os.Build;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.ArraySet;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
@@ -39,10 +42,11 @@
import org.robolectric.shadows.ShadowApplication;
import java.util.Arrays;
+import java.util.Set;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
-import org.robolectric.shadows.ShadowApplication;
+
/**
* Tests for {@link ApplicationFeatureProviderImpl}.
*/
@@ -66,7 +70,7 @@
private @Mock UserManager mUserManager;
private @Mock Context mContext;
private @Mock PackageManagerWrapper mPackageManager;
- @Mock private IPackageManager mPackageManagerService;
+ @Mock private IPackageManagerWrapper mPackageManagerService;
@Mock private DevicePolicyManagerWrapper mDevicePolicyManager;
private ApplicationFeatureProvider mProvider;
@@ -139,6 +143,43 @@
}
+ @Test
+ public void testFindPersistentPreferredActivities() throws Exception {
+ when(mUserManager.getUserProfiles()).thenReturn(Arrays.asList(new UserHandle(MAIN_USER_ID),
+ new UserHandle(MANAGED_PROFILE_ID)));
+
+ final Intent viewIntent = new Intent(Intent.ACTION_VIEW);
+ final Intent editIntent = new Intent(Intent.ACTION_EDIT);
+ final Intent sendIntent = new Intent(Intent.ACTION_SEND);
+
+ final ResolveInfo app1 = createResolveInfo(APP_1);
+ final ResolveInfo app2 = createResolveInfo(APP_2);
+ when(mPackageManagerService.findPersistentPreferredActivity(viewIntent, MAIN_USER_ID))
+ .thenReturn(app1);
+ when(mPackageManagerService.findPersistentPreferredActivity(viewIntent, MANAGED_PROFILE_ID))
+ .thenReturn(app1);
+ when(mPackageManagerService.findPersistentPreferredActivity(editIntent, MAIN_USER_ID))
+ .thenReturn(null);
+ when(mPackageManagerService.findPersistentPreferredActivity(editIntent, MANAGED_PROFILE_ID))
+ .thenReturn(app2);
+ when(mPackageManagerService.findPersistentPreferredActivity(sendIntent, MAIN_USER_ID))
+ .thenReturn(app1);
+ when(mPackageManagerService.findPersistentPreferredActivity(sendIntent, MANAGED_PROFILE_ID))
+ .thenReturn(null);
+
+ final Set<ApplicationFeatureProvider.PersistentPreferredActivityInfo> expectedActivities
+ = new ArraySet<>();
+ expectedActivities.add(new ApplicationFeatureProvider.PersistentPreferredActivityInfo(APP_1,
+ MAIN_USER_ID));
+ expectedActivities.add(new ApplicationFeatureProvider.PersistentPreferredActivityInfo(APP_1,
+ MANAGED_PROFILE_ID));
+ expectedActivities.add(new ApplicationFeatureProvider.PersistentPreferredActivityInfo(APP_2,
+ MANAGED_PROFILE_ID));
+
+ assertThat(mProvider.findPersistentPreferredActivities(
+ new Intent[] {viewIntent, editIntent, sendIntent})).isEqualTo(expectedActivities);
+ }
+
private void setUpUsersAndInstalledApps() {
when(mUserManager.getUsers(true)).thenReturn(Arrays.asList(
new UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_ADMIN),
@@ -156,4 +197,12 @@
ApplicationTestUtils.buildInfo(APP_2_UID, APP_2, 0 /* flags */,
Build.VERSION_CODES.LOLLIPOP)));
}
+
+ private ResolveInfo createResolveInfo(String packageName) {
+ final ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.packageName = packageName;
+ final ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = activityInfo;
+ return resolveInfo;
+ }
}
diff --git a/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java b/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java
new file mode 100644
index 0000000..262c9e0
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/PictureInPictureSettingsTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.applications;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+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 org.robolectric.util.ReflectionHelpers;
+
+import java.lang.reflect.Field;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class PictureInPictureSettingsTest {
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+
+ private FakeFeatureFactory mFeatureFactory;
+ private PictureInPictureSettings mFragment;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ FakeFeatureFactory.setupForTest(mContext);
+ mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+ mFragment = new PictureInPictureSettings();
+ }
+
+ @Test
+ public void testIgnoredApp() {
+ for (String ignoredPackage : mFragment.IGNORE_PACKAGE_LIST) {
+ assertThat(checkPackageHasPictureInPictureActivities(ignoredPackage, true))
+ .isFalse();
+ }
+ }
+
+ @Test
+ public void testNonPippableApp() {
+ assertThat(checkPackageHasPictureInPictureActivities("com.android.dummypackage")).isFalse();
+ assertThat(checkPackageHasPictureInPictureActivities("com.android.dummypackage",
+ false, false, false)).isFalse();
+ }
+
+ @Test
+ public void testPippableApp() {
+ assertThat(checkPackageHasPictureInPictureActivities("com.android.dummypackage",
+ true)).isTrue();
+ assertThat(checkPackageHasPictureInPictureActivities("com.android.dummypackage",
+ false, true)).isTrue();
+ assertThat(checkPackageHasPictureInPictureActivities("com.android.dummypackage",
+ true, false)).isTrue();
+ }
+
+ @Test
+ public void logSpecialPermissionChange() {
+ mFragment.logSpecialPermissionChange(true, "app");
+ verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+ eq(MetricsProto.MetricsEvent.APP_PICTURE_IN_PICTURE_ON_HIDE_ALLOW), eq("app"));
+
+ mFragment.logSpecialPermissionChange(false, "app");
+ verify(mFeatureFactory.metricsFeatureProvider).action(any(Context.class),
+ eq(MetricsProto.MetricsEvent.APP_PICTURE_IN_PICTURE_ON_HIDE_DENY), eq("app"));
+ }
+
+ private boolean checkPackageHasPictureInPictureActivities(String packageName,
+ boolean... resizeableActivityState) {
+ ActivityInfoWrapper[] activities = null;
+ if (resizeableActivityState.length > 0) {
+ activities = new ActivityInfoWrapper[resizeableActivityState.length];
+ for (int i = 0; i < activities.length; i++) {
+ activities[i] = new MockActivityInfo(resizeableActivityState[i]);
+ }
+ }
+ return PictureInPictureSettings.checkPackageHasPictureInPictureActivities(packageName,
+ activities);
+ }
+
+ private class MockActivityInfo implements ActivityInfoWrapper {
+
+ private boolean mSupportsPictureInPicture;
+
+ public MockActivityInfo(boolean supportsPictureInPicture) {
+ mSupportsPictureInPicture = supportsPictureInPicture;
+ }
+
+ @Override
+ public boolean supportsPictureInPicture() {
+ return mSupportsPictureInPicture;
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppInfoTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppInfoTest.java
new file mode 100644
index 0000000..6c31927
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppInfoTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.applications.defaultapps;
+
+
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+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;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultAppInfoTest {
+
+ @Mock
+ private ActivityInfo mActivityInfo;
+ @Mock
+ private ApplicationInfo mApplicationInfo;
+ @Mock
+ private ComponentName mComponentName;
+ @Mock
+ private PackageManager mPackageManager;
+
+ private DefaultAppInfo mInfo;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void initInfoWithActivityInfo_shouldLoadInfo() {
+ mActivityInfo.packageName = "test";
+ mInfo = new DefaultAppInfo(mActivityInfo);
+ mInfo.loadLabel(mPackageManager);
+ mInfo.loadIcon(mPackageManager);
+
+ assertThat(mInfo.getKey()).isEqualTo(mActivityInfo.packageName);
+ verify(mActivityInfo).loadLabel(mPackageManager);
+ verify(mActivityInfo).loadIcon(mPackageManager);
+ }
+
+ @Test
+ public void initInfoWithApplicationInfo_shouldLoadInfo() {
+ mApplicationInfo.packageName = "test";
+
+ mInfo = new DefaultAppInfo(mApplicationInfo);
+ mInfo.loadLabel(mPackageManager);
+ mInfo.loadIcon(mPackageManager);
+
+ assertThat(mInfo.getKey()).isEqualTo(mApplicationInfo.packageName);
+ verify(mApplicationInfo).loadLabel(mPackageManager);
+ verify(mApplicationInfo).loadIcon(mPackageManager);
+ }
+
+ @Test
+ public void initInfoWithComponent_shouldLoadInfo() {
+ when(mComponentName.getPackageName()).thenReturn("com.android.settings");
+
+ mInfo = new DefaultAppInfo(0 /* uid */, mComponentName, null /*summary */);
+ mInfo.getKey();
+
+ verify(mComponentName).flattenToString();
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java
new file mode 100644
index 0000000..4f560c9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPickerFragmentTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.applications.defaultapps;
+
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.support.v4.app.FragmentManager;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.widget.RadioButtonPreference;
+
+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.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultAppPickerFragmentTest {
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Activity mActivity;
+ @Mock
+ private PreferenceScreen mScreen;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private FragmentManager mFragmentManager;
+
+ private TestFragment mFragment;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mFragment = spy(new TestFragment());
+ final Bundle bundle = new Bundle();
+ bundle.putBoolean(DefaultAppPickerFragment.EXTRA_FOR_WORK, false);
+ mFragment.setArguments(bundle);
+
+ when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ doReturn(mActivity).when(mFragment).getContext();
+ doReturn(mScreen).when(mFragment).getPreferenceScreen();
+ }
+
+ @Test
+ public void onAttach_userIsInitialized() {
+ mFragment.onAttach((Context) mActivity);
+
+ verify(mActivity).getPackageManager();
+ verify(mActivity).getSystemService(Context.USER_SERVICE);
+ }
+
+ @Test
+ public void clickPreference_noCofirmation_shouldDirectlyConfirm() {
+ final RadioButtonPreference pref =
+ new RadioButtonPreference(RuntimeEnvironment.application);
+ pref.setKey("TEST");
+
+ mFragment.onRadioButtonClicked(pref);
+
+ assertThat(mFragment.setDefaultAppKeyCalled).isTrue();
+ }
+
+ @Test
+ public void clickPreference_hasCofirmation_shouldShowConfirmation() {
+ final RadioButtonPreference pref =
+ new RadioButtonPreference(RuntimeEnvironment.application);
+ pref.setKey("TEST");
+ doReturn("confirmation_text").when(mFragment)
+ .getConfirmationMessage(any(DefaultAppInfo.class));
+ doReturn(mActivity).when(mFragment).getActivity();
+
+ mFragment.onRadioButtonClicked(pref);
+ }
+
+ public static class TestFragment extends DefaultAppPickerFragment {
+
+ boolean setDefaultAppKeyCalled;
+
+ @Override
+ public int getMetricsCategory() {
+ return 0;
+ }
+
+ @Override
+ protected List<DefaultAppInfo> getCandidates() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ protected String getDefaultAppKey() {
+ return null;
+ }
+
+ @Override
+ protected boolean setDefaultAppKey(String key) {
+ setDefaultAppKeyCalled = true;
+ return true;
+ }
+
+ @Override
+ public Context getContext() {
+ return RuntimeEnvironment.application;
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceControllerTest.java
new file mode 100644
index 0000000..f8d4d12
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultAppPreferenceControllerTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.applications.defaultapps;
+
+
+import android.content.Context;
+import android.os.UserManager;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+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 static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultAppPreferenceControllerTest {
+
+ private static final String TEST_APP_NAME = "test";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private Preference mPreference;
+
+ private TestPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ }
+
+ @Test
+ public void updateState_hasDefaultApp_shouldUpdateAppName() {
+ mController = new TestPreferenceController(mContext);
+
+ when(mController.mAppInfo.loadLabel(mContext.getPackageManager()))
+ .thenReturn(TEST_APP_NAME);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setSummary(TEST_APP_NAME);
+ }
+
+ @Test
+ public void updateState_hasNoApp_shouldNotUpdateAppName() {
+ mController = new TestPreferenceController(mContext);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference, never()).setSummary(any(CharSequence.class));
+ }
+
+ private static class TestPreferenceController extends DefaultAppPreferenceController {
+
+ private DefaultAppInfo mAppInfo;
+
+ public TestPreferenceController(Context context) {
+ super(context);
+ mAppInfo = mock(DefaultAppInfo.class);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return "test";
+ }
+
+ @Override
+ protected DefaultAppInfo getDefaultAppInfo() {
+ return mAppInfo;
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPickerTest.java
new file mode 100644
index 0000000..e7b11d9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPickerTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.applications.defaultapps;
+
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+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.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultBrowserPickerTest {
+
+ private static final String TEST_APP_KEY = "";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Activity mActivity;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private PackageManagerWrapper mPackageManager;
+
+ private DefaultBrowserPicker mPicker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+ mPicker = new DefaultBrowserPicker();
+ mPicker.onAttach((Context) mActivity);
+
+ ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
+ }
+
+ @Test
+ public void setDefaultAppKey_shouldUpdateDefaultBrowser() {
+ mPicker.setDefaultAppKey(TEST_APP_KEY);
+ verify(mPackageManager)
+ .setDefaultBrowserPackageNameAsUser(eq(TEST_APP_KEY), anyInt());
+ }
+
+ @Test
+ public void getDefaultAppKey_shouldReturnDefaultBrowser() {
+ mPicker.getDefaultAppKey();
+ verify(mPackageManager)
+ .getDefaultBrowserPackageNameAsUser(anyInt());
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceControllerTest.java
new file mode 100644
index 0000000..e06dfee
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultBrowserPreferenceControllerTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.applications.defaultapps;
+
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserManager;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+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 org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultBrowserPreferenceControllerTest {
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private UserManager mUserManager;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private PackageManagerWrapper mPackageManager;
+
+ private DefaultBrowserPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+ mController = new DefaultBrowserPreferenceController(mContext);
+ ReflectionHelpers.setField(mController, "mPackageManager", mPackageManager);
+ }
+
+ @Test
+ public void isAlwaysAvailable() {
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void getSoleAppLabel_hasNoApp_shouldNotReturnLabel() {
+ when(mPackageManager.queryIntentActivitiesAsUser(any(Intent.class), anyInt(), anyInt()))
+ .thenReturn(null);
+ final Preference pref = mock(Preference.class);
+
+ mController.updateState(pref);
+ verify(pref, never()).setSummary(any(String.class));
+ }
+
+ @Test
+ public void getDefaultApp_shouldGetDefaultBrowserPackage() {
+ mController.getDefaultAppInfo();
+
+ verify(mPackageManager).getDefaultBrowserPackageNameAsUser(anyInt());
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultEmergencyPickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultEmergencyPickerTest.java
new file mode 100644
index 0000000..ceccba1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultEmergencyPickerTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.applications.defaultapps;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+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.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultEmergencyPickerTest {
+
+ private static final String TEST_APP_KEY = "test_app";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Activity mActivity;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private PackageManagerWrapper mPackageManager;
+
+ private DefaultEmergencyPicker mPicker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+ mPicker = spy(new DefaultEmergencyPicker());
+ mPicker.onAttach((Context) mActivity);
+
+ ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
+
+ doReturn(RuntimeEnvironment.application).when(mPicker).getContext();
+ }
+
+ @Test
+ public void setDefaultAppKey_shouldUpdateDefault() {
+ assertThat(mPicker.setDefaultAppKey(TEST_APP_KEY)).isTrue();
+ assertThat(mPicker.getDefaultAppKey()).isEqualTo(TEST_APP_KEY);
+ }
+
+ @Test
+ public void getDefaultAppKey_shouldReturnDefault() {
+ Settings.Secure.putString(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
+ TEST_APP_KEY);
+
+ assertThat(mPicker.getDefaultAppKey()).isEqualTo(TEST_APP_KEY);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePickerTest.java
new file mode 100644
index 0000000..d63ae6d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePickerTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.applications.defaultapps;
+
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.os.UserManager;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+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 org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultHomePickerTest {
+
+ private static final String TEST_APP_KEY = "com.android.settings/DefaultEmergencyPickerTest";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Activity mActivity;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private PackageManagerWrapper mPackageManager;
+
+ private DefaultHomePicker mPicker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+ mPicker = new DefaultHomePicker();
+ mPicker.onAttach((Context) mActivity);
+
+ ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
+ }
+
+ @Test
+ public void setDefaultAppKey_shouldUpdateDefault() {
+ assertThat(mPicker.setDefaultAppKey(TEST_APP_KEY)).isTrue();
+
+ verify(mPackageManager).replacePreferredActivity(any(IntentFilter.class),
+ anyInt(), any(ComponentName[].class), any(ComponentName.class));
+ }
+
+ @Test
+ public void getDefaultAppKey_shouldReturnDefault() {
+ final ComponentName cn = mock(ComponentName.class);
+ when(mPackageManager.getHomeActivities(anyList()))
+ .thenReturn(cn);
+ mPicker.getDefaultAppKey();
+ verify(cn).flattenToString();
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceControllerTest.java
new file mode 100644
index 0000000..6d6e2f8
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultHomePreferenceControllerTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.applications.defaultapps;
+
+
+import android.content.Context;
+import android.os.UserManager;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+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 org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyList;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultHomePreferenceControllerTest {
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private UserManager mUserManager;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private PackageManagerWrapper mPackageManager;
+
+ private DefaultHomePreferenceController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+ mController = spy(new DefaultHomePreferenceController(mContext));
+ ReflectionHelpers.setField(mController, "mPackageManager", mPackageManager);
+ }
+
+ @Test
+ public void isAlwaysAvailable() {
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void getDefaultApp_shouldGetDefaultBrowserPackage() {
+ assertThat(mController.getDefaultAppInfo()).isNotNull();
+
+ verify(mPackageManager).getHomeActivities(anyList());
+ }
+
+ @Test
+ public void updateState_noDefaultApp_shouldAskPackageManagerForOnlyApp() {
+ doReturn(null).when(mController).getDefaultAppInfo();
+
+ mController.updateState(mock(Preference.class));
+
+ verify(mPackageManager).getHomeActivities(anyList());
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPickerTest.java
new file mode 100644
index 0000000..322830c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultNotificationAssistantPickerTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.applications.defaultapps;
+
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+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.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultNotificationAssistantPickerTest {
+
+ private static final String TEST_APP_KEY = "com.android.settings/PickerTest";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Activity mActivity;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private PackageManagerWrapper mPackageManager;
+
+ private DefaultNotificationAssistantPicker mPicker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+ mPicker = spy(new DefaultNotificationAssistantPicker());
+ mPicker.onAttach((Context) mActivity);
+
+ ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
+ doReturn(RuntimeEnvironment.application).when(mPicker).getContext();
+ }
+
+ @Test
+ public void setDefaultAppKey_shouldUpdateDefault() {
+ mPicker.setDefaultAppKey(TEST_APP_KEY);
+
+ assertThat(mPicker.getDefaultAppKey()).isEqualTo(TEST_APP_KEY);
+ }
+
+ @Test
+ public void getDefaultAppKey_shouldReturnDefault() {
+ Settings.Secure.putString(RuntimeEnvironment.application.getContentResolver(),
+ Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT,
+ TEST_APP_KEY);
+
+ assertThat(mPicker.getDefaultAppKey()).isEqualTo(TEST_APP_KEY);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultPhonePickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultPhonePickerTest.java
new file mode 100644
index 0000000..0c5d5f1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultPhonePickerTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.applications.defaultapps;
+
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+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.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultPhonePickerTest {
+
+ private static final String TEST_APP_KEY = "com.android.settings/PickerTest";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Activity mActivity;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private DefaultPhonePicker.DefaultKeyUpdater mDefaultKeyUpdater;
+ @Mock
+ private PackageManagerWrapper mPackageManager;
+
+ private DefaultPhonePicker mPicker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ when(mActivity.getSystemService(Context.TELECOM_SERVICE)).thenReturn(null);
+ mPicker = spy(new DefaultPhonePicker());
+ mPicker.onAttach((Context) mActivity);
+
+ ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
+ ReflectionHelpers.setField(mPicker, "mDefaultKeyUpdater", mDefaultKeyUpdater);
+ doReturn(RuntimeEnvironment.application).when(mPicker).getContext();
+ }
+
+ @Test
+ public void getSystemDefaultPackage_shouldAskDefaultKeyUpdater() {
+ mPicker.getSystemDefaultAppKey();
+
+ verify(mDefaultKeyUpdater).getSystemDialerPackage();
+ }
+
+ @Test
+ public void setDefaultAppKey_shouldUpdateDefault() {
+ mPicker.setDefaultAppKey(TEST_APP_KEY);
+
+ verify(mDefaultKeyUpdater).setDefaultDialerApplication(
+ any(Context.class), eq(TEST_APP_KEY), anyInt());
+ }
+
+ @Test
+ public void getDefaultAppKey_shouldReturnDefault() {
+ mPicker.getDefaultAppKey();
+ verify(mDefaultKeyUpdater).getDefaultDialerApplication(any(Context.class), anyInt());
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultSmsPickerTest.java b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultSmsPickerTest.java
new file mode 100644
index 0000000..3da6fec
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/defaultapps/DefaultSmsPickerTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.applications.defaultapps;
+
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.PackageManagerWrapper;
+
+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.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DefaultSmsPickerTest {
+
+ private static final String TEST_APP_KEY = "com.android.settings/PickerTest";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Activity mActivity;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private DefaultSmsPicker.DefaultKeyUpdater mDefaultKeyUpdater;
+ @Mock
+ private PackageManagerWrapper mPackageManager;
+
+ private DefaultSmsPicker mPicker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ mPicker = spy(new DefaultSmsPicker());
+ mPicker.onAttach((Context) mActivity);
+
+ ReflectionHelpers.setField(mPicker, "mPm", mPackageManager);
+ ReflectionHelpers.setField(mPicker, "mDefaultKeyUpdater", mDefaultKeyUpdater);
+ doReturn(RuntimeEnvironment.application).when(mPicker).getContext();
+ }
+
+ @Test
+ public void setDefaultAppKey_shouldUpdateDefault() {
+ mPicker.setDefaultAppKey(TEST_APP_KEY);
+
+ verify(mDefaultKeyUpdater).setDefaultApplication(any(Context.class), eq(TEST_APP_KEY));
+ }
+
+ @Test
+ public void getDefaultAppKey_shouldReturnDefault() {
+ mPicker.getDefaultAppKey();
+
+ verify(mDefaultKeyUpdater).getDefaultApplication(any(Context.class));
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java
index d479e0a..327575e 100644
--- a/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFragmentTest.java
@@ -26,7 +26,6 @@
import com.android.settings.core.PreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.testutils.FakeFeatureFactory;
-import com.android.settingslib.drawer.CategoryKey;
import com.android.settingslib.drawer.DashboardCategory;
import com.android.settingslib.drawer.Tile;
@@ -200,11 +199,6 @@
}
@Override
- protected String getCategoryKey() {
- return CategoryKey.CATEGORY_HOMEPAGE;
- }
-
- @Override
public PreferenceScreen getPreferenceScreen() {
return mScreen;
}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
index 7776633..a377505 100644
--- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java
@@ -140,6 +140,8 @@
assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName());
assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)).isEqualTo(
ManageApplications.class.getName());
+ assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0))
+ .isEqualTo(R.string.apps_storage);
}
@Test
@@ -159,6 +161,24 @@
}
@Test
+ public void testClickGames() {
+ mPreference.setKey("pref_games");
+ mController.handlePreferenceTreeClick(mPreference);
+
+ final ArgumentCaptor<Intent> argumentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mFragment.getActivity()).startActivityAsUser(argumentCaptor.capture(),
+ any(UserHandle.class));
+
+ Intent intent = argumentCaptor.getValue();
+ assertThat(intent.getAction()).isEqualTo(Intent.ACTION_MAIN);
+ assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName());
+ assertThat(intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT)).isEqualTo(
+ ManageApplications.class.getName());
+ assertThat(intent.getIntExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, 0))
+ .isEqualTo(R.string.game_storage_settings);
+ }
+
+ @Test
public void testMeasurementCompletedUpdatesPreferences() {
StorageItemPreferenceAlternate audio = new StorageItemPreferenceAlternate(mContext);
StorageItemPreferenceAlternate image = new StorageItemPreferenceAlternate(mContext);
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java
index 3dd1fd7..b55b512 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterpriseInstalledPackagesPreferenceControllerTest.java
@@ -81,6 +81,10 @@
final Preference preference = new Preference(mContext, null, 0, 0);
preference.setVisible(true);
+ setNumberOfEnterpriseInstalledPackages(0);
+ mController.updateState(preference);
+ assertThat(preference.isVisible()).isFalse();
+
setNumberOfEnterpriseInstalledPackages(20);
when(mContext.getResources().getQuantityString(
R.plurals.enterprise_privacy_number_enterprise_installed_packages, 20, 20))
@@ -88,10 +92,6 @@
mController.updateState(preference);
assertThat(preference.getTitle()).isEqualTo("20 packages");
assertThat(preference.isVisible()).isTrue();
-
- setNumberOfEnterpriseInstalledPackages(0);
- mController.updateState(preference);
- assertThat(preference.isVisible()).isFalse();
}
@Test
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java
index de4d02e..2559769 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacySettingsTest.java
@@ -73,7 +73,7 @@
final List<PreferenceController> controllers = mSettings.getPreferenceControllers(
ShadowApplication.getInstance().getApplicationContext());
assertThat(controllers).isNotNull();
- assertThat(controllers.size()).isEqualTo(11);
+ assertThat(controllers.size()).isEqualTo(12);
assertThat(controllers.get(0)).isInstanceOf(InstalledPackagesPreferenceController.class);
assertThat(controllers.get(1)).isInstanceOf(NetworkLogsPreferenceController.class);
assertThat(controllers.get(2)).isInstanceOf(BugReportsPreferenceController.class);
@@ -87,9 +87,11 @@
assertThat(controllers.get(7)).isInstanceOf(
AdminGrantedCameraPermissionPreferenceController.class);
assertThat(controllers.get(8)).isInstanceOf(
- AlwaysOnVpnPrimaryUserPreferenceController.class);
+ EnterpriseSetDefaultAppsPreferenceController.class);
assertThat(controllers.get(9)).isInstanceOf(
+ AlwaysOnVpnPrimaryUserPreferenceController.class);
+ assertThat(controllers.get(10)).isInstanceOf(
AlwaysOnVpnManagedProfilePreferenceController.class);
- assertThat(controllers.get(10)).isInstanceOf(GlobalHttpProxyPreferenceController.class);
+ assertThat(controllers.get(11)).isInstanceOf(GlobalHttpProxyPreferenceController.class);
}
}
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceControllerTest.java
new file mode 100644
index 0000000..84520a5
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterpriseSetDefaultAppsPreferenceControllerTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+import android.support.v7.preference.Preference;
+import android.util.ArraySet;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.ApplicationFeatureProvider;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import java.util.Set;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link EnterpriseSetDefaultAppsPreferenceController}.
+ */
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public final class EnterpriseSetDefaultAppsPreferenceControllerTest {
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+ private FakeFeatureFactory mFeatureFactory;
+
+ private EnterpriseSetDefaultAppsPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ FakeFeatureFactory.setupForTest(mContext);
+ mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+ mController = new EnterpriseSetDefaultAppsPreferenceController(mContext);
+ }
+
+ private static Intent buildIntent(String action, String category, String protocol,
+ String type) {
+ final Intent intent = new Intent(action);
+ if (category != null) {
+ intent.addCategory(category);
+ }
+ if (protocol != null) {
+ intent.setData(Uri.parse(protocol));
+ }
+ if (type != null) {
+ intent.setType(type);
+ }
+ return intent;
+ }
+
+ private void setEnterpriseSetDefaultApps(Intent[] intents, int number) {
+ final Set<ApplicationFeatureProvider.PersistentPreferredActivityInfo> apps
+ = new ArraySet<>(number);
+ for (int i = 0; i < number; i++) {
+ apps.add(new ApplicationFeatureProvider.PersistentPreferredActivityInfo("app", i));
+ }
+ when(mFeatureFactory.applicationFeatureProvider.findPersistentPreferredActivities(
+ argThat(new MatchesIntents(intents)))).thenReturn(apps);
+ }
+
+ @Test
+ public void testUpdateState() {
+ final Preference preference = new Preference(mContext, null, 0, 0);
+ preference.setVisible(true);
+
+ when(mFeatureFactory.applicationFeatureProvider.findPersistentPreferredActivities(
+ anyObject())).thenReturn(
+ new ArraySet<ApplicationFeatureProvider.PersistentPreferredActivityInfo>());
+ mController.updateState(preference);
+ assertThat(preference.isVisible()).isFalse();
+
+ setEnterpriseSetDefaultApps(new Intent[] {buildIntent(Intent.ACTION_VIEW,
+ Intent.CATEGORY_BROWSABLE, "http:", null)}, 1);
+ setEnterpriseSetDefaultApps(new Intent[] {new Intent(MediaStore.ACTION_IMAGE_CAPTURE),
+ new Intent(MediaStore.ACTION_VIDEO_CAPTURE)}, 2);
+ setEnterpriseSetDefaultApps(new Intent[] {buildIntent(Intent.ACTION_VIEW, null, "geo:",
+ null)}, 4);
+ setEnterpriseSetDefaultApps(new Intent[] {new Intent(Intent.ACTION_SENDTO),
+ new Intent(Intent.ACTION_SEND), new Intent(Intent.ACTION_SEND_MULTIPLE)}, 8);
+ setEnterpriseSetDefaultApps(new Intent[] {buildIntent(Intent.ACTION_INSERT, null, null,
+ "vnd.android.cursor.dir/event")}, 16);
+ setEnterpriseSetDefaultApps(new Intent[] {buildIntent(Intent.ACTION_PICK, null, null,
+ ContactsContract.Contacts.CONTENT_TYPE)}, 32);
+ setEnterpriseSetDefaultApps(new Intent[] {new Intent(Intent.ACTION_DIAL),
+ new Intent(Intent.ACTION_CALL)}, 64);
+ when(mContext.getResources().getQuantityString(
+ R.plurals.enterprise_privacy_number_enterprise_set_default_apps, 127, 127))
+ .thenReturn("127 apps");
+ mController.updateState(preference);
+ assertThat(preference.getTitle()).isEqualTo("127 apps");
+ assertThat(preference.isVisible()).isTrue();
+ }
+
+ @Test
+ public void testIsAvailable() {
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void testHandlePreferenceTreeClick() {
+ assertThat(mController.handlePreferenceTreeClick(new Preference(mContext, null, 0, 0)))
+ .isFalse();
+ }
+
+ @Test
+ public void testGetPreferenceKey() {
+ assertThat(mController.getPreferenceKey())
+ .isEqualTo("number_enterprise_set_default_apps");
+ }
+
+ private static class MatchesIntents extends ArgumentMatcher<Intent[]> {
+ private final Intent[] mExpectedIntents;
+
+ MatchesIntents(Intent[] intents) {
+ mExpectedIntents = intents;
+ }
+
+ @Override
+ public boolean matches(Object object) {
+ final Intent[] actualIntents = (Intent[]) object;
+ if (actualIntents == null) {
+ return false;
+ }
+ if (actualIntents.length != mExpectedIntents.length) {
+ return false;
+ }
+ for (int i = 0; i < mExpectedIntents.length; i++) {
+ if (!mExpectedIntents[i].filterEquals(actualIntents[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
index 35df218..ea678b6 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java
@@ -36,6 +36,9 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
+import java.util.ArrayList;
+import java.util.List;
+
import static com.android.settings.fuelgauge.PowerUsageBase.MENU_STATS_REFRESH;
import static com.android.settings.fuelgauge.PowerUsageSummary.MENU_ADDITIONAL_BATTERY_INFO;
import static com.google.common.truth.Truth.assertThat;
@@ -53,7 +56,8 @@
private static final String[] PACKAGE_NAMES = {"com.app1", "com.app2"};
private static final int UID = 123;
private static final int POWER_MAH = 100;
-
+ private static final double BATTERY_SCREEN_USAGE = 300;
+ private static final double PRECISION = 0.001;
private static final Intent ADDITIONAL_BATTERY_INFO_INTENT =
new Intent("com.example.app.ADDITIONAL_BATTERY_INFO");
@@ -68,7 +72,9 @@
@Mock
private MenuInflater mMenuInflater;
@Mock
- private BatterySipper mBatterySipper;
+ private BatterySipper mNormalBatterySipper;
+ @Mock
+ private BatterySipper mScreenBatterySipper;
private TestFragment mFragment;
private FakeFeatureFactory mFeatureFactory;
@@ -95,9 +101,12 @@
mPowerUsageSummary = new PowerUsageSummary();
- when(mBatterySipper.getPackages()).thenReturn(PACKAGE_NAMES);
- when(mBatterySipper.getUid()).thenReturn(UID);
- mBatterySipper.totalPowerMah = POWER_MAH;
+ when(mNormalBatterySipper.getPackages()).thenReturn(PACKAGE_NAMES);
+ when(mNormalBatterySipper.getUid()).thenReturn(UID);
+ mNormalBatterySipper.totalPowerMah = POWER_MAH;
+
+ mScreenBatterySipper.drainType = BatterySipper.DrainType.SCREEN;
+ mScreenBatterySipper.totalPowerMah = BATTERY_SCREEN_USAGE;
}
@Test
@@ -129,62 +138,73 @@
@Test
public void testExtractKeyFromSipper_TypeAPPUidObjectNull_ReturnPackageNames() {
- mBatterySipper.uidObj = null;
- mBatterySipper.drainType = BatterySipper.DrainType.APP;
+ mNormalBatterySipper.uidObj = null;
+ mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
- final String key = mPowerUsageSummary.extractKeyFromSipper(mBatterySipper);
- assertThat(key).isEqualTo(TextUtils.concat(mBatterySipper.getPackages()).toString());
+ final String key = mPowerUsageSummary.extractKeyFromSipper(mNormalBatterySipper);
+ assertThat(key).isEqualTo(TextUtils.concat(mNormalBatterySipper.getPackages()).toString());
}
@Test
public void testExtractKeyFromSipper_TypeOther_ReturnDrainType() {
- mBatterySipper.uidObj = null;
- mBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH;
+ mNormalBatterySipper.uidObj = null;
+ mNormalBatterySipper.drainType = BatterySipper.DrainType.BLUETOOTH;
- final String key = mPowerUsageSummary.extractKeyFromSipper(mBatterySipper);
- assertThat(key).isEqualTo(mBatterySipper.drainType.toString());
+ final String key = mPowerUsageSummary.extractKeyFromSipper(mNormalBatterySipper);
+ assertThat(key).isEqualTo(mNormalBatterySipper.drainType.toString());
+ }
+
+ @Test
+ public void testRemoveScreenBatterySipper_ContainsScreenSipper_RemoveAndReturnValue() {
+ final List<BatterySipper> sippers = new ArrayList<>();
+ sippers.add(mNormalBatterySipper);
+ sippers.add(mScreenBatterySipper);
+
+ final double screenUsage = mPowerUsageSummary.removeScreenBatterySipper(sippers);
+ assertThat(sippers).containsExactly(mNormalBatterySipper);
+ assertThat(screenUsage).isWithin(PRECISION).of(BATTERY_SCREEN_USAGE);
}
@Test
public void testExtractKeyFromSipper_TypeAPPUidObjectNotNull_ReturnUid() {
- mBatterySipper.uidObj = new BatteryStatsImpl.Uid(new BatteryStatsImpl(), UID);
- mBatterySipper.drainType = BatterySipper.DrainType.APP;
+ mNormalBatterySipper.uidObj = new BatteryStatsImpl.Uid(new BatteryStatsImpl(), UID);
+ mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
- final String key = mPowerUsageSummary.extractKeyFromSipper(mBatterySipper);
- assertThat(key).isEqualTo(Integer.toString(mBatterySipper.getUid()));
+ final String key = mPowerUsageSummary.extractKeyFromSipper(mNormalBatterySipper);
+ assertThat(key).isEqualTo(Integer.toString(mNormalBatterySipper.getUid()));
}
@Test
public void testShouldHideSipper_TypeIdle_ReturnTrue() {
- mBatterySipper.drainType = BatterySipper.DrainType.IDLE;
- assertThat(mPowerUsageSummary.shouldHideSipper(mBatterySipper)).isTrue();
+ mNormalBatterySipper.drainType = BatterySipper.DrainType.IDLE;
+ assertThat(mPowerUsageSummary.shouldHideSipper(mNormalBatterySipper)).isTrue();
}
@Test
public void testShouldHideSipper_TypeCell_ReturnTrue() {
- mBatterySipper.drainType = BatterySipper.DrainType.CELL;
- assertThat(mPowerUsageSummary.shouldHideSipper(mBatterySipper)).isTrue();
+ mNormalBatterySipper.drainType = BatterySipper.DrainType.CELL;
+ assertThat(mPowerUsageSummary.shouldHideSipper(mNormalBatterySipper)).isTrue();
}
@Test
public void testShouldHideSipper_UidRoot_ReturnTrue() {
- mBatterySipper.drainType = BatterySipper.DrainType.APP;
- when(mBatterySipper.getUid()).thenReturn(Process.ROOT_UID);
- assertThat(mPowerUsageSummary.shouldHideSipper(mBatterySipper)).isTrue();
+ mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
+ when(mNormalBatterySipper.getUid()).thenReturn(Process.ROOT_UID);
+ assertThat(mPowerUsageSummary.shouldHideSipper(mNormalBatterySipper)).isTrue();
}
@Test
public void testShouldHideSipper_UidSystem_ReturnTrue() {
- mBatterySipper.drainType = BatterySipper.DrainType.APP;
- when(mBatterySipper.getUid()).thenReturn(Process.SYSTEM_UID);
- assertThat(mPowerUsageSummary.shouldHideSipper(mBatterySipper)).isTrue();
+ mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
+ when(mNormalBatterySipper.getUid()).thenReturn(Process.SYSTEM_UID);
+ assertThat(mPowerUsageSummary.shouldHideSipper(mNormalBatterySipper)).isTrue();
}
@Test
public void testShouldHideSipper_UidNormal_ReturnFalse() {
- mBatterySipper.drainType = BatterySipper.DrainType.APP;
- when(mBatterySipper.getUid()).thenReturn(UID);
- assertThat(mPowerUsageSummary.shouldHideSipper(mBatterySipper)).isFalse();
+ mNormalBatterySipper.drainType = BatterySipper.DrainType.APP;
+ when(mNormalBatterySipper.getUid()).thenReturn(UID);
+ assertThat(mPowerUsageSummary.shouldHideSipper(mNormalBatterySipper)).isFalse();
}
public static class TestFragment extends PowerUsageSummary {
diff --git a/tests/robotests/src/com/android/settings/network/NetworkDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/network/NetworkDashboardFragmentTest.java
index b30e3ff..dc96ed6 100644
--- a/tests/robotests/src/com/android/settings/network/NetworkDashboardFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/network/NetworkDashboardFragmentTest.java
@@ -21,6 +21,7 @@
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.accounts.UserAndAccountDashboardFragment;
+import com.android.settings.dashboard.DashboardFragmentRegistry;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settingslib.drawer.CategoryKey;
import org.junit.Before;
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
index e55dc10..e89f009 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
+++ b/tests/robotests/src/com/android/settings/search/DatabaseIndexingManagerTest.java
@@ -25,6 +25,7 @@
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.search.IndexDatabaseHelper.SiteMapColumns;
import com.android.settings.search2.DatabaseIndexingManager;
import com.android.settings.testutils.DatabaseTestUtils;
@@ -40,7 +41,9 @@
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
+import static com.android.settings.dashboard.SiteMapManager.SITE_MAP_COLUMNS;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
@@ -49,7 +52,7 @@
public class DatabaseIndexingManagerTest {
private final String localeStr = "en_US";
- private final int rank = 42;
+ private final int rank = 8;
private final String title = "title\u2011title";
private final String updatedTitle = "title-title";
private final String normalizedTitle = "titletitle";
@@ -57,7 +60,7 @@
private final String updatedSummaryOn = "summary-on";
private final String normalizedSummaryOn = "summaryon";
private final String summaryOff = "summary\u2011off";
- private final String updatedSummaryOff ="summary-off";
+ private final String updatedSummaryOff = "summary-off";
private final String normalizedSummaryOff = "summaryoff";
private final String entries = "entries";
private final String keywords = "keywords, keywordss, keywordsss";
@@ -94,7 +97,7 @@
Cursor dbCursor = mDb.query("prefs_index", null, null, null, null, null, null);
List<String> columnNames = new ArrayList<>(Arrays.asList(dbCursor.getColumnNames()));
// Note that docid is not included.
- List<String> expColumnNames = new ArrayList<>(Arrays.asList(new String[ ]{
+ List<String> expColumnNames = new ArrayList<>(Arrays.asList(new String[]{
"locale",
"data_rank",
"data_title",
@@ -211,6 +214,20 @@
}
@Test
+ public void testAddResourceWithNIKs_RowsInsertedDisabled() {
+ SearchIndexableResource resource = getFakeResource(R.xml.gesture_settings);
+ // Only add 2 of 6 items to be disabled.
+ String[] keys = {"gesture_double_tap_power", "gesture_swipe_down_fingerprint"};
+ Map<String, List<String>> niks = getNonIndexableKeys(keys);
+ mManager.indexOneSearchIndexableData(mDb, localeStr, resource, niks);
+
+ Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 0", null);
+ assertThat(cursor.getCount()).isEqualTo(2);
+ cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
+ assertThat(cursor.getCount()).isEqualTo(4);
+ }
+
+ @Test
public void testAddResourceHeader_RowsMatch() {
SearchIndexableResource resource = getFakeResource(R.xml.application_settings);
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
@@ -264,6 +281,29 @@
}
@Test
+ public void testAddResourceWithChildFragment_shouldUpdateSiteMapDb() {
+ SearchIndexableResource resource = getFakeResource(R.xml.network_and_internet);
+ mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
+ new HashMap<>());
+ Cursor query = mDb.query(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, SITE_MAP_COLUMNS,
+ null, null, null, null, null);
+ query.moveToPosition(-1);
+ int count = 0;
+ while (query.moveToNext()) {
+ count++;
+ assertThat(query.getString(query.getColumnIndex(SiteMapColumns.PARENT_CLASS)))
+ .isEqualTo(className);
+ assertThat(query.getString(query.getColumnIndex(SiteMapColumns.PARENT_TITLE)))
+ .isEqualTo(mContext.getString(R.string.network_dashboard_title));
+ assertThat(query.getString(query.getColumnIndex(SiteMapColumns.CHILD_CLASS)))
+ .isNotEmpty();
+ assertThat(query.getString(query.getColumnIndex(SiteMapColumns.CHILD_TITLE)))
+ .isNotEmpty();
+ }
+ assertThat(count).isEqualTo(5);
+ }
+
+ @Test
public void testAddResourceCustomSetting_RowsMatch() {
SearchIndexableResource resource = getFakeResource(R.xml.gesture_settings);
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
@@ -393,16 +433,18 @@
// Normalized Title
assertThat(cursor.getString(3)).isEqualTo("preferred install location");
// Summary On
- assertThat(cursor.getString(4)).isEqualTo("Change the preferred installation location for new apps");
+ assertThat(cursor.getString(4)).isEqualTo(
+ "Change the preferred installation location for new apps");
// Summary On Normalized
- assertThat(cursor.getString(5)).isEqualTo("change the preferred installation location for new apps");
+ assertThat(cursor.getString(5)).isEqualTo(
+ "change the preferred installation location for new apps");
// Summary Off - only on for checkbox preferences
assertThat(cursor.getString(6)).isEmpty();
// Summary off normalized - only on for checkbox preferences
assertThat(cursor.getString(7)).isEmpty();
// Entries - only on for list preferences
assertThat(cursor.getString(8)).isEqualTo("Internal device storage|Removable SD card|" +
- "Let the system decide|");
+ "Let the system decide|");
// Keywords
assertThat(cursor.getString(9)).isEmpty();
// Screen Title
@@ -502,21 +544,19 @@
@Test
public void testResourceProvider_ResourceRowInserted() {
- SearchIndexableResource resource = getFakeResource(R.xml.gesture_settings);
- resource.xmlResId = 0;
+ SearchIndexableResource resource = getFakeResource(0);
resource.className = "com.android.settings.LegalSettings";
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
new HashMap<>());
Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index", null);
- assertThat(cursor.getCount()).isEqualTo(2);
+ assertThat(cursor.getCount()).isEqualTo(6);
}
@Test
public void testResourceProvider_ResourceRowMatches() {
- SearchIndexableResource resource = getFakeResource(R.xml.gesture_settings);
- resource.xmlResId = 0;
- resource.className = "com.android.settings.LegalSettings";
+ SearchIndexableResource resource = getFakeResource(0);
+ resource.className = "com.android.settings.display.ScreenZoomSettings";
mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
new HashMap<>());
@@ -528,9 +568,9 @@
// Data Rank
assertThat(cursor.getInt(1)).isEqualTo(rank);
// Data Title
- assertThat(cursor.getString(2)).isEqualTo("Legal information");
+ assertThat(cursor.getString(2)).isEqualTo("Display size");
// Normalized Title
- assertThat(cursor.getString(3)).isEqualTo("legal information");
+ assertThat(cursor.getString(3)).isEqualTo("display size");
// Summary On
assertThat(cursor.getString(4)).isEmpty();
// Summary On Normalized
@@ -542,12 +582,13 @@
// Entries - only on for list preferences
assertThat(cursor.getString(8)).isNull();
// Keywords
- assertThat(cursor.getString(9)).isEmpty();
+ assertThat(cursor.getString(9)).isEqualTo(
+ "display density screen zoom scale scaling");
// Screen Title
- assertThat(cursor.getString(10)).isEqualTo("Legal information");
+ assertThat(cursor.getString(10)).isEqualTo("Display size");
// Class Name
assertThat(cursor.getString(11))
- .isEqualTo("com.android.settings.LegalSettings");
+ .isEqualTo("com.android.settings.display.ScreenZoomSettings");
// Icon
assertThat(cursor.getInt(12)).isEqualTo(iconResId);
// Intent Action
@@ -568,6 +609,20 @@
assertThat(cursor.getBlob(20)).isNull();
}
+ @Test
+ public void testResourceProvider_DisabledResourceRowsInserted() {
+ SearchIndexableResource resource = getFakeResource(0);
+ resource.className = "com.android.settings.LegalSettings";
+
+ mManager.indexOneSearchIndexableData(mDb, localeStr, resource,
+ new HashMap<String, List<String>>());
+
+ Cursor cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 1", null);
+ assertThat(cursor.getCount()).isEqualTo(2);
+ cursor = mDb.rawQuery("SELECT * FROM prefs_index WHERE enabled = 0", null);
+ assertThat(cursor.getCount()).isEqualTo(4);
+ }
+
// Util functions
private SearchIndexableRaw getFakeRaw() {
@@ -609,4 +664,11 @@
sir.enabled = enabled;
return sir;
}
-}
+
+ private Map<String, List<String>> getNonIndexableKeys(String[] keys) {
+ Map<String, List<String>> niks = new HashMap<>();
+ List<String> keysList = new ArrayList<>(Arrays.asList(keys));
+ niks.put(packageName, keysList);
+ return niks;
+ }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
index 2b29a16..31e6e6c 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
@@ -23,31 +23,51 @@
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
+import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.search2.DatabaseIndexingUtils;
import com.android.settings.search2.DatabaseResultLoader;
import com.android.settings.testutils.DatabaseTestUtils;
+import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.After;
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.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class DatabaseResultLoaderTest {
- private Context mContext;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mMockContext;
+ @Mock
+ private SiteMapManager mSiteMapManager;
+ private Context mContext;
private DatabaseResultLoader loader;
SQLiteDatabase mDb;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
+ FakeFeatureFactory.setupForTest(mMockContext);
+ FakeFeatureFactory factory =
+ (FakeFeatureFactory) FakeFeatureFactory.getFactory(mMockContext);
+ when(factory.searchFeatureProvider.getSiteMapManager())
+ .thenReturn(mSiteMapManager);
mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
setUpDb();
}
@@ -60,25 +80,26 @@
@Test
public void testMatchTitle() {
loader = new DatabaseResultLoader(mContext, "title");
- assertThat(loader.loadInBackground().size()).isEqualTo(3);
+ assertThat(loader.loadInBackground().size()).isEqualTo(2);
+ verify(mSiteMapManager, times(2)).buildBreadCrumb(eq(mContext), anyString(), anyString());
}
@Test
public void testMatchSummary() {
loader = new DatabaseResultLoader(mContext, "summary");
- assertThat(loader.loadInBackground().size()).isEqualTo(3);
+ assertThat(loader.loadInBackground().size()).isEqualTo(2);
}
@Test
public void testMatchKeywords() {
loader = new DatabaseResultLoader(mContext, "keywords");
- assertThat(loader.loadInBackground().size()).isEqualTo(3);
+ assertThat(loader.loadInBackground().size()).isEqualTo(2);
}
@Test
public void testMatchEntries() {
loader = new DatabaseResultLoader(mContext, "entries");
- assertThat(loader.loadInBackground().size()).isEqualTo(3);
+ assertThat(loader.loadInBackground().size()).isEqualTo(2);
}
@Test
@@ -146,7 +167,7 @@
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, "");
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
- values.put(IndexDatabaseHelper.IndexColumns.ENABLED, "");
+ values.put(IndexDatabaseHelper.IndexColumns.ENABLED, true);
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power");
values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
@@ -175,7 +196,7 @@
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, "");
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
- values.put(IndexDatabaseHelper.IndexColumns.ENABLED, "");
+ values.put(IndexDatabaseHelper.IndexColumns.ENABLED, true);
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power");
values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
@@ -202,7 +223,7 @@
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, "");
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
- values.put(IndexDatabaseHelper.IndexColumns.ENABLED, "");
+ values.put(IndexDatabaseHelper.IndexColumns.ENABLED, true);
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power");
values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
@@ -228,7 +249,7 @@
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_PACKAGE, "");
values.put(IndexDatabaseHelper.IndexColumns.INTENT_TARGET_CLASS, "");
values.put(IndexDatabaseHelper.IndexColumns.ICON, "");
- values.put(IndexDatabaseHelper.IndexColumns.ENABLED, "");
+ values.put(IndexDatabaseHelper.IndexColumns.ENABLED, false);
values.put(IndexDatabaseHelper.IndexColumns.DATA_KEY_REF, "gesture_double_tap_power");
values.put(IndexDatabaseHelper.IndexColumns.USER_ID, 0);
values.put(IndexDatabaseHelper.IndexColumns.PAYLOAD_TYPE, 0);
diff --git a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
index c0b1b3d..c314728 100644
--- a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java
@@ -19,17 +19,13 @@
import android.app.Activity;
import android.content.Context;
-import android.content.res.Configuration;
import android.view.Menu;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.search2.DatabaseIndexingManager;
+import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.search2.SearchFeatureProviderImpl;
-import com.android.settings.testutils.FakeFeatureFactory;
-import com.android.settingslib.drawer.DashboardCategory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -77,6 +73,14 @@
}
@Test
+ public void getSiteMapManager_shouldCacheInstanec() {
+ final SiteMapManager manager1 = mProvider.getSiteMapManager();
+ final SiteMapManager manager2 = mProvider.getSiteMapManager();
+
+ assertThat(manager1).isSameAs(manager2);
+ }
+
+ @Test
public void testUpdateIndexNewSearch_UsesDatabaseIndexingManager() {
mProvider = spy(new SearchFeatureProviderImpl());
when(mProvider.isEnabled(mActivity)).thenReturn(true);
diff --git a/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java b/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java
index 6ad7501..d69ba3e 100644
--- a/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java
+++ b/tests/robotests/src/com/android/settings/search2/CursorToSearchResultConverterTest.java
@@ -28,12 +28,16 @@
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.SubSettings;
import com.android.settings.TestConfig;
+import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.gestures.GestureSettings;
import com.android.settings.search2.ResultPayload.PayloadType;
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.Robolectric;
import org.robolectric.annotation.Config;
@@ -57,11 +61,14 @@
private static final int BASE_RANK = 1;
private static final int EXAMPLES = 3;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private SiteMapManager mSiteMapManager;
private Drawable mDrawable;
private CursorToSearchResultConverter mConverter;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
Context context = Robolectric.buildActivity(Activity.class).get();
mDrawable = context.getDrawable(ICON);
mConverter = new CursorToSearchResultConverter(context, QUERY);
@@ -69,19 +76,21 @@
@Test
public void testParseNullResults_ReturnsNull() {
- List<SearchResult> results = mConverter.convertCursor(null, BASE_RANK);
+ List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, null, BASE_RANK);
assertThat(results).isNull();
}
@Test
public void testParseCursor_NotNull() {
- List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+ List<SearchResult> results = mConverter.convertCursor(
+ mSiteMapManager, getDummyCursor(), BASE_RANK);
assertThat(results).isNotNull();
}
@Test
public void testParseCursor_MatchesRank() {
- List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+ List<SearchResult> results = mConverter.convertCursor(
+ mSiteMapManager, getDummyCursor(), BASE_RANK);
for (int i = 0; i < EXAMPLES; i++) {
assertThat(results.get(i).rank).isEqualTo(BASE_RANK);
}
@@ -89,7 +98,8 @@
@Test
public void testParseCursor_MatchesTitle() {
- List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+ List<SearchResult> results = mConverter.convertCursor(
+ mSiteMapManager, getDummyCursor(), BASE_RANK);
for (int i = 0; i < EXAMPLES; i++) {
assertThat(results.get(i).title).isEqualTo(TITLES[i]);
}
@@ -97,7 +107,8 @@
@Test
public void testParseCursor_MatchesSummary() {
- List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+ List<SearchResult> results = mConverter.convertCursor(
+ mSiteMapManager, getDummyCursor(), BASE_RANK);
for (int i = 0; i < EXAMPLES; i++) {
assertThat(results.get(i).summary).isEqualTo(SUMMARY);
}
@@ -105,7 +116,8 @@
@Test
public void testParseCursor_MatchesIcon() {
- List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+ List<SearchResult> results = mConverter.convertCursor(
+ mSiteMapManager, getDummyCursor(), BASE_RANK);
for (int i = 0; i < EXAMPLES; i++) {
Drawable resultDrawable = results.get(i).icon;
assertThat(resultDrawable).isNotNull();
@@ -116,7 +128,7 @@
@Test
public void testParseCursor_NoIcon() {
List<SearchResult> results = mConverter.convertCursor(
- getDummyCursor(false /* hasIcon */), BASE_RANK);
+ mSiteMapManager, getDummyCursor(false /* hasIcon */), BASE_RANK);
for (int i = 0; i < EXAMPLES; i++) {
Drawable resultDrawable = results.get(i).icon;
assertThat(resultDrawable).isNull();
@@ -125,7 +137,8 @@
@Test
public void testParseCursor_MatchesPayloadType() {
- List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+ List<SearchResult> results = mConverter.convertCursor(
+ mSiteMapManager, getDummyCursor(), BASE_RANK);
ResultPayload payload;
for (int i = 0; i < EXAMPLES; i++) {
payload = results.get(i).payload;
@@ -152,7 +165,7 @@
0, // Payload Type
null // Payload
});
- List<SearchResult> results = mConverter.convertCursor(cursor, BASE_RANK);
+ List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK);
IntentPayload payload = (IntentPayload) results.get(0).payload;
Intent intent = payload.intent;
assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName());
@@ -160,7 +173,8 @@
@Test
public void testParseCursor_MatchesIntentPayload() {
- List<SearchResult> results = mConverter.convertCursor(getDummyCursor(), BASE_RANK);
+ List<SearchResult> results = mConverter.convertCursor(
+ mSiteMapManager, getDummyCursor(), BASE_RANK);
IntentPayload payload;
for (int i = 0; i < EXAMPLES; i++) {
payload = (IntentPayload) results.get(i).payload;
@@ -187,7 +201,7 @@
PayloadType.INTENT, // Payload Type
null // Payload
});
- List<SearchResult> results = mConverter.convertCursor(cursor, BASE_RANK);
+ List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK);
IntentPayload payload = (IntentPayload) results.get(0).payload;
Intent intent = payload.intent;
@@ -222,7 +236,7 @@
type, // Payload Type
ResultPayloadUtils.marshall(payload) // Payload
});
- List<SearchResult> results = mConverter.convertCursor(cursor, BASE_RANK);
+ List<SearchResult> results = mConverter.convertCursor(mSiteMapManager, cursor, BASE_RANK);
InlineSwitchPayload newPayload = (InlineSwitchPayload) results.get(0).payload;
assertThat(newPayload.settingsUri).isEqualTo(uri);
diff --git a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
index 4f62a9e..e6397e1 100644
--- a/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search2/InstalledAppResultLoaderTest.java
@@ -22,10 +22,13 @@
import android.content.pm.UserInfo;
import android.os.UserManager;
+import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.applications.PackageManagerWrapper;
+import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.testutils.ApplicationTestUtils;
+import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
import org.junit.Test;
@@ -44,7 +47,12 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class)
@@ -57,16 +65,24 @@
private PackageManagerWrapper mPackageManagerWrapper;
@Mock
private UserManager mUserManager;
+ @Mock
+ private SiteMapManager mSiteMapManager;
private InstalledAppResultLoader mLoader;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ FakeFeatureFactory.setupForTest(mContext);
+ FakeFeatureFactory factory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+ when(factory.searchFeatureProvider.getSiteMapManager())
+ .thenReturn(mSiteMapManager);
final List<UserInfo> infos = new ArrayList<>();
infos.add(new UserInfo(1, "user 1", 0));
when(mUserManager.getProfiles(anyInt())).thenReturn(infos);
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ when(mContext.getString(R.string.applications_settings))
+ .thenReturn("app");
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
@@ -94,9 +110,14 @@
public void query_matchingQuery_shouldReturnNonSystemApps() {
final String query = "app";
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query));
+ when(mLoader.getContext()).thenReturn(mContext);
+ when(mSiteMapManager.buildBreadCrumb(eq(mContext), anyString(), anyString()))
+ .thenReturn(Arrays.asList(new String[]{"123"}));
assertThat(mLoader.loadInBackground().size()).isEqualTo(2);
+ verify(mSiteMapManager)
+ .buildBreadCrumb(eq(mContext), anyString(), anyString());
}
@Test
@@ -107,9 +128,12 @@
0 /* targetSdkVersion */)));
final String query = "app";
- mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
+ mLoader = spy(new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query));
+ when(mLoader.getContext()).thenReturn(mContext);
assertThat(mLoader.loadInBackground().size()).isEqualTo(1);
+ verify(mSiteMapManager)
+ .buildBreadCrumb(eq(mContext), anyString(), anyString());
}
@Test
@@ -132,7 +156,7 @@
}
@Test
- public void query_matchingQuery_shouldNOtReturnSystemAppIfNotLaunchable() {
+ public void query_matchingQuery_shouldNotReturnSystemAppIfNotLaunchable() {
when(mPackageManagerWrapper.getInstalledApplicationsAsUser(anyInt(), anyInt()))
.thenReturn(Arrays.asList(
ApplicationTestUtils.buildInfo(0 /* uid */, "app1", FLAG_SYSTEM,
@@ -146,6 +170,8 @@
mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query);
assertThat(mLoader.loadInBackground()).isEmpty();
+ verify(mSiteMapManager, never())
+ .buildBreadCrumb(eq(mContext), anyString(), anyString());
}
@Test
diff --git a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
index 7a0bb54..2d4ac54 100644
--- a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
@@ -17,6 +17,7 @@
package com.android.settings.search2;
import android.content.Context;
+import android.content.Loader;
import android.os.Bundle;
import com.android.internal.logging.nano.MetricsProto;
@@ -35,6 +36,9 @@
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
@@ -97,6 +101,28 @@
}
@Test
+ public void screenRotateEmptyString_ShouldNotCrash() {
+ final Bundle bundle = new Bundle();
+ ActivityController<SearchActivity> activityController =
+ Robolectric.buildActivity(SearchActivity.class);
+ activityController.setup();
+ SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
+ .findFragmentById(R.id.main_content);
+
+ fragment.mQuery = "";
+
+ activityController.saveInstanceState(bundle).pause().stop().destroy();
+
+ activityController = Robolectric.buildActivity(SearchActivity.class);
+ activityController.setup(bundle);
+
+ verify(mFeatureFactory.searchFeatureProvider)
+ .getDatabaseSearchLoader(any(Context.class), anyString());
+ verify(mFeatureFactory.searchFeatureProvider)
+ .getInstalledAppSearchLoader(any(Context.class), anyString());
+ }
+
+ @Test
public void queryTextChange_shouldTriggerLoader() {
final String testQuery = "test";
ActivityController<SearchActivity> activityController =
diff --git a/tests/robotests/src/com/android/settings/search2/SiteMapManagerTest.java b/tests/robotests/src/com/android/settings/search2/SiteMapManagerTest.java
new file mode 100644
index 0000000..b8ac8fe
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search2/SiteMapManagerTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.search2;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.Bundle;
+
+import com.android.settings.SettingsActivity;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.dashboard.SiteMapManager;
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.search.IndexDatabaseHelper.SiteMapColumns;
+import com.android.settings.system.SystemDashboardFragment;
+import com.android.settings.testutils.DatabaseTestUtils;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.drawer.CategoryKey;
+import com.android.settingslib.drawer.DashboardCategory;
+import com.android.settingslib.drawer.Tile;
+
+import org.junit.After;
+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.RuntimeEnvironment;
+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.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SiteMapManagerTest {
+
+ private static final int STATIC_DB_DEPTH = 4;
+ private static final String CLASS_PREFIX = "class_";
+ private static final String TITLE_PREFIX = "title_";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mMockContext;
+ private Context mContext;
+ private SQLiteDatabase mDb;
+ private SiteMapManager mSiteMapManager;
+ private FakeFeatureFactory mFeatureFactory;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ FakeFeatureFactory.setupForTest(mMockContext);
+ mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mMockContext);
+
+ mContext = RuntimeEnvironment.application;
+ mDb = IndexDatabaseHelper.getInstance(mContext).getWritableDatabase();
+ buildDb();
+ mSiteMapManager = new SiteMapManager();
+ }
+
+ @After
+ public void cleanUp() {
+ DatabaseTestUtils.clearDb();
+ }
+
+ @Test
+ public void buildBreadCrumb_onlyFromSiteMapDb_breadcrumbShouldLinkUp() {
+ List<String> breadcrumb = mSiteMapManager.buildBreadCrumb(mContext,
+ CLASS_PREFIX + 0, TITLE_PREFIX + 0);
+ assertThat(breadcrumb.size()).isEqualTo(STATIC_DB_DEPTH + 1);
+ for (int i = 0; i < STATIC_DB_DEPTH; i++) {
+ assertThat(breadcrumb.get(i)).isEqualTo(TITLE_PREFIX + (STATIC_DB_DEPTH - i));
+ }
+ }
+
+ @Test
+ public void buildBreadCrumb_fromSiteMapDbAndDashboardProvider_breadcrumbShouldLinkUp() {
+ final String iaClass = SystemDashboardFragment.class.getName();
+ final String iaTitle = "ia_title";
+
+ ContentValues index = new ContentValues();
+ index.put(IndexDatabaseHelper.IndexColumns.CLASS_NAME, iaClass);
+ index.put(IndexDatabaseHelper.IndexColumns.SCREEN_TITLE, iaTitle);
+ mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, index);
+
+ final DashboardCategory category = new DashboardCategory();
+ category.key = CategoryKey.CATEGORY_SYSTEM;
+ category.tiles.add(new Tile());
+ category.tiles.get(0).title = TITLE_PREFIX + STATIC_DB_DEPTH;
+ category.tiles.get(0).metaData = new Bundle();
+ category.tiles.get(0).metaData.putString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS,
+ CLASS_PREFIX + STATIC_DB_DEPTH);
+ when(mFeatureFactory.dashboardFeatureProvider.getAllCategories())
+ .thenReturn(Arrays.asList(category));
+
+ final List<String> breadcrumb = mSiteMapManager.buildBreadCrumb(mContext,
+ CLASS_PREFIX + 0, TITLE_PREFIX + 0);
+
+ assertThat(breadcrumb.size()).isEqualTo(STATIC_DB_DEPTH + 2);
+ assertThat(breadcrumb.get(0))
+ .isEqualTo(iaTitle);
+ }
+
+ @Test
+ public void buildBreadCrumb_classNotIndexed_shouldNotHaveBreadCrumb() {
+ final String title = "wrong_title";
+
+ final List<String> breadcrumb = mSiteMapManager.buildBreadCrumb(mContext,
+ "wrong_class", title);
+
+ assertThat(breadcrumb.size()).isEqualTo(1);
+ assertThat(breadcrumb.get(0)).isEqualTo(title);
+ }
+
+ private void buildDb() {
+ for (int i = 0; i < STATIC_DB_DEPTH; i++) {
+ final ContentValues siteMapPair = new ContentValues();
+ siteMapPair.put(SiteMapColumns.DOCID, i);
+ siteMapPair.put(SiteMapColumns.PARENT_CLASS, CLASS_PREFIX + (i + 1));
+ siteMapPair.put(SiteMapColumns.PARENT_TITLE, TITLE_PREFIX + (i + 1));
+ siteMapPair.put(SiteMapColumns.CHILD_CLASS, CLASS_PREFIX + i);
+ siteMapPair.put(SiteMapColumns.CHILD_TITLE, TITLE_PREFIX + i);
+ mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, null, siteMapPair);
+ }
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/webview/WebViewAppListAdapterTest.java b/tests/robotests/src/com/android/settings/webview/WebViewAppListAdapterTest.java
new file mode 100644
index 0000000..a8ab5d4
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/webview/WebViewAppListAdapterTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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.webview;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import java.util.Arrays;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WebViewAppListAdapterTest {
+ private Context mContext = RuntimeEnvironment.application;
+
+ private final static UserInfo FIRST_USER = new UserInfo(0, "FIRST_USER", 0);
+ private final static UserInfo SECOND_USER = new UserInfo(0, "SECOND_USER", 0);
+
+ private final static String DEFAULT_PACKAGE_NAME = "DEFAULT_PACKAGE_NAME";
+
+ @Test
+ public void testDisabledReasonNullIfPackagesOk() {
+ UserPackageWrapper packageForFirstUser = mock(UserPackageWrapper.class);
+ when(packageForFirstUser.isEnabledPackage()).thenReturn(true);
+ when(packageForFirstUser.isInstalledPackage()).thenReturn(true);
+
+ UserPackageWrapper packageForSecondUser = mock(UserPackageWrapper.class);
+ when(packageForSecondUser.isEnabledPackage()).thenReturn(true);
+ when(packageForSecondUser.isInstalledPackage()).thenReturn(true);
+
+ WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+ when(wvusWrapper.getPackageInfosAllUsers(
+ any(), eq(DEFAULT_PACKAGE_NAME))).thenReturn(
+ Arrays.asList(packageForFirstUser, packageForSecondUser));
+
+ assertThat(WebViewAppListAdapter.getDisabledReason(
+ wvusWrapper, mContext, DEFAULT_PACKAGE_NAME)).isNull();
+ }
+
+ @Test
+ public void testDisabledReasonForSingleUserDisabledPackage() {
+ UserPackageWrapper packageForFirstUser = mock(UserPackageWrapper.class);
+ when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
+ when(packageForFirstUser.isInstalledPackage()).thenReturn(true);
+ when(packageForFirstUser.getUserInfo()).thenReturn(FIRST_USER);
+
+ WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+ when(wvusWrapper.getPackageInfosAllUsers(any(), eq(DEFAULT_PACKAGE_NAME)
+ )).thenReturn(Arrays.asList(packageForFirstUser));
+
+ assertThat(WebViewAppListAdapter.getDisabledReason(wvusWrapper, mContext,
+ DEFAULT_PACKAGE_NAME)).isEqualTo("Disabled for user " + FIRST_USER.name + "\n");
+ }
+
+ @Test
+ public void testDisabledReasonForSingleUserUninstalledPackage() {
+ UserPackageWrapper packageForFirstUser = mock(UserPackageWrapper.class);
+ when(packageForFirstUser.isEnabledPackage()).thenReturn(true);
+ when(packageForFirstUser.isInstalledPackage()).thenReturn(false);
+ when(packageForFirstUser.getUserInfo()).thenReturn(FIRST_USER);
+
+ WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+ when(wvusWrapper.getPackageInfosAllUsers(any(), eq(DEFAULT_PACKAGE_NAME)
+ )).thenReturn(Arrays.asList(packageForFirstUser));
+
+ assertThat(WebViewAppListAdapter.getDisabledReason(wvusWrapper, mContext,
+ DEFAULT_PACKAGE_NAME)).isEqualTo("Uninstalled for user " + FIRST_USER.name + "\n");
+ }
+
+ @Test
+ public void testDisabledReasonSeveralUsers() {
+ UserPackageWrapper packageForFirstUser = mock(UserPackageWrapper.class);
+ when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
+ when(packageForFirstUser.isInstalledPackage()).thenReturn(true);
+ when(packageForFirstUser.getUserInfo()).thenReturn(FIRST_USER);
+
+ UserPackageWrapper packageForSecondUser = mock(UserPackageWrapper.class);
+ when(packageForSecondUser.isEnabledPackage()).thenReturn(true);
+ when(packageForSecondUser.isInstalledPackage()).thenReturn(false);
+ when(packageForSecondUser.getUserInfo()).thenReturn(SECOND_USER);
+
+ WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+ when(wvusWrapper.getPackageInfosAllUsers(any(), eq(DEFAULT_PACKAGE_NAME)
+ )).thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser));
+
+ final String EXPECTED_DISABLED_REASON = String.format(
+ "Disabled for user %s\nUninstalled for user %s\n",
+ FIRST_USER.name, SECOND_USER.name);
+ assertThat(WebViewAppListAdapter.getDisabledReason(
+ wvusWrapper, mContext,DEFAULT_PACKAGE_NAME)).isEqualTo(EXPECTED_DISABLED_REASON);
+ }
+
+ /**
+ * Ensure we only proclaim a package as uninstalled for a certain user if it's both uninstalled
+ * and disabled.
+ */
+ @Test
+ public void testDisabledReasonUninstalledAndDisabled() {
+ UserPackageWrapper packageForFirstUser = mock(UserPackageWrapper.class);
+ when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
+ when(packageForFirstUser.isInstalledPackage()).thenReturn(false);
+ when(packageForFirstUser.getUserInfo()).thenReturn(FIRST_USER);
+
+ UserPackageWrapper packageForSecondUser = mock(UserPackageWrapper.class);
+ when(packageForSecondUser.isEnabledPackage()).thenReturn(true);
+ when(packageForSecondUser.isInstalledPackage()).thenReturn(true);
+ when(packageForSecondUser.getUserInfo()).thenReturn(SECOND_USER);
+
+ WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+ when(wvusWrapper.getPackageInfosAllUsers(any(), eq(DEFAULT_PACKAGE_NAME)
+ )).thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser));
+
+ final String EXPECTED_DISABLED_REASON = String.format(
+ "Uninstalled for user %s\n", FIRST_USER.name);
+ assertThat(WebViewAppListAdapter.getDisabledReason(wvusWrapper, mContext,
+ DEFAULT_PACKAGE_NAME)).isEqualTo(EXPECTED_DISABLED_REASON);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
new file mode 100644
index 0000000..8ace8aa
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/webview/WebViewAppPickerTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.webview;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.shadows.ShadowView.clickOn;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Activity;
+import android.content.pm.ApplicationInfo;
+import android.view.View;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import java.util.Arrays;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ActivityController;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WebViewAppPickerTest {
+
+ private static final String DEFAULT_PACKAGE_NAME = "DEFAULT_PACKAGE_NAME";
+
+ private static ApplicationInfo createApplicationInfo(String packageName) {
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ return ai;
+ }
+
+ @Test
+ public void testClickingItemChangesProvider() {
+ ActivityController<WebViewAppPicker> controller =
+ Robolectric.buildActivity(WebViewAppPicker.class);
+ WebViewAppPicker webviewAppPicker = controller.get();
+
+ WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+ when(wvusWrapper.getValidWebViewApplicationInfos(any())).thenReturn(
+ Arrays.asList(createApplicationInfo(DEFAULT_PACKAGE_NAME)));
+ when(wvusWrapper.setWebViewProvider(eq(DEFAULT_PACKAGE_NAME))).thenReturn(true);
+
+ webviewAppPicker.setWebViewUpdateServiceWrapper(wvusWrapper);
+
+ controller.create().start().postCreate(null).resume().visible();
+ WebViewApplicationInfo firstItem =
+ (WebViewApplicationInfo) webviewAppPicker.getListView().getItemAtPosition(0);
+ assertThat(firstItem.info.packageName).isEqualTo(DEFAULT_PACKAGE_NAME);
+
+ webviewAppPicker.onListItemClick(webviewAppPicker.getListView(), null, 0, 0);
+
+ verify(wvusWrapper, times(1)).setWebViewProvider(eq(DEFAULT_PACKAGE_NAME));
+ assertThat(shadowOf(webviewAppPicker).getResultCode()).isEqualTo(Activity.RESULT_OK);
+ verify(wvusWrapper, never()).showInvalidChoiceToast(any());
+ }
+
+ @Test
+ public void testFailingPackageChangeReturnsCancelled() {
+ ActivityController<WebViewAppPicker> controller =
+ Robolectric.buildActivity(WebViewAppPicker.class);
+ WebViewAppPicker webviewAppPicker = controller.get();
+
+ WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+ when(wvusWrapper.getValidWebViewApplicationInfos(any())).thenReturn(
+ Arrays.asList(createApplicationInfo(DEFAULT_PACKAGE_NAME)));
+ when(wvusWrapper.setWebViewProvider(eq(DEFAULT_PACKAGE_NAME))).thenReturn(false);
+
+ webviewAppPicker.setWebViewUpdateServiceWrapper(wvusWrapper);
+
+ controller.create().start().postCreate(null).resume().visible();
+ WebViewApplicationInfo firstItem =
+ (WebViewApplicationInfo) webviewAppPicker.getListView().getItemAtPosition(0);
+ assertThat(firstItem.info.packageName).isEqualTo(DEFAULT_PACKAGE_NAME);
+
+ webviewAppPicker.onListItemClick(webviewAppPicker.getListView(), null, 0, 0);
+
+ verify(wvusWrapper, times(1)).setWebViewProvider(eq(DEFAULT_PACKAGE_NAME));
+ assertThat(shadowOf(webviewAppPicker).getResultCode()).isEqualTo(Activity.RESULT_CANCELED);
+ verify(wvusWrapper, times(1)).showInvalidChoiceToast(any());
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/webview/WebViewAppPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/webview/WebViewAppPreferenceControllerTest.java
new file mode 100644
index 0000000..e0a32a4
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/webview/WebViewAppPreferenceControllerTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.webview;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WebViewAppPreferenceControllerTest {
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+
+ @Mock
+ private PreferenceScreen mPreferenceScreen;
+ @Mock
+ private Preference mPreference;
+
+ private static final String DEFAULT_PACKAGE_NAME = "DEFAULT_PACKAGE_NAME";
+
+ @Before public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mPreferenceScreen.findPreference(any())).thenReturn(mPreference);
+ }
+
+ @Test public void testOnActivityResultUpdatesStateOnSuccess() {
+ WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+ WebViewAppPreferenceController controller =
+ spy(new WebViewAppPreferenceController(mContext, wvusWrapper));
+
+ controller.displayPreference(mPreferenceScreen); // Makes sure Preference is non-null
+ controller.onActivityResult(Activity.RESULT_OK, new Intent(DEFAULT_PACKAGE_NAME));
+ verify(controller, times(1)).updateState(any());
+ }
+
+ @Test public void testOnActivityResultWithFailure() {
+ WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
+
+ WebViewAppPreferenceController controller =
+ spy(new WebViewAppPreferenceController(mContext, wvusWrapper));
+
+ controller.displayPreference(mPreferenceScreen); // Makes sure Preference is non-null
+ controller.onActivityResult(Activity.RESULT_CANCELED, new Intent(DEFAULT_PACKAGE_NAME));
+ verify(controller, times(1)).updateState(any());
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/SavedNetworkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/SavedNetworkPreferenceControllerTest.java
deleted file mode 100644
index 657c21e..0000000
--- a/tests/robotests/src/com/android/settings/wifi/SavedNetworkPreferenceControllerTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.wifi;
-
-import android.content.Context;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-
-import com.android.settings.SettingsRobolectricTestRunner;
-import com.android.settings.TestConfig;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.annotation.Config;
-
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-@RunWith(SettingsRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
-public class SavedNetworkPreferenceControllerTest {
-
- @Mock
- private Context mContext;
- @Mock
- private WifiManager mWifiManager;
-
- private SavedNetworkPreferenceController mController;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mController = new SavedNetworkPreferenceController(mContext, mWifiManager);
- }
-
- @Test
- public void isAvailable_noSavedNetwork_shouldReturnFalse() {
- when(mWifiManager.getConfiguredNetworks()).thenReturn(null);
- assertThat(mController.isAvailable()).isFalse();
- }
-
- @Test
- public void isAvailable_hasSavedNetwork_shouldReturnTrue() {
- List<WifiConfiguration> configs = mock(List.class);
- when(configs.isEmpty()).thenReturn(false);
- when(mWifiManager.getConfiguredNetworks()).thenReturn(configs);
-
- assertThat(mController.isAvailable()).isTrue();
- }
-}
diff --git a/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java b/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java
new file mode 100644
index 0000000..231787e
--- /dev/null
+++ b/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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;
+
+import static junit.framework.TestCase.assertNotNull;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.preference.ListPreference;
+
+import com.android.settings.R;
+import com.android.settings.display.ThemePreferenceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ThemePreferenceControllerTest {
+
+ private UiModeManager mMockUiModeManager;
+ private ContextWrapper mContext;
+ private ThemePreferenceController mPreferenceController;
+
+ @Before
+ public void setup() {
+ mMockUiModeManager = mock(UiModeManager.class);
+ mContext = new ContextWrapper(InstrumentationRegistry.getTargetContext()) {
+ @Override
+ public Object getSystemService(String name) {
+ if (Context.UI_MODE_SERVICE.equals(name)) {
+ return mMockUiModeManager;
+ }
+ return super.getSystemService(name);
+ }
+ };
+ mPreferenceController = new ThemePreferenceController(mContext);
+ }
+
+ @Test
+ public void testUpdateState() {
+ when(mMockUiModeManager.getAvailableThemes()).thenReturn(new String[] {
+ null,
+ "Theme1",
+ "Theme2",
+ });
+ when(mMockUiModeManager.getTheme()).thenReturn("Theme1");
+ ListPreference pref = mock(ListPreference.class);
+ mPreferenceController.updateState(pref);
+ ArgumentCaptor<String[]> arg = ArgumentCaptor.forClass(String[].class);
+ verify(pref).setEntries(arg.capture());
+
+ String[] entries = arg.getValue();
+ assertEquals(3, entries.length);
+ assertNotNull(entries[0]);
+ assertEquals("Theme1", entries[1]);
+ assertEquals("Theme2", entries[2]);
+
+ verify(pref).setEntryValues(arg.capture());
+ String[] entryValues = arg.getValue();
+ assertEquals(3, entryValues.length);
+ assertNotNull(entryValues[0]);
+ assertEquals("Theme1", entryValues[1]);
+ assertEquals("Theme2", entryValues[2]);
+
+ verify(pref).setValue(eq("Theme1"));
+ }
+
+ @Test
+ public void testAvailable_false() {
+ when(mMockUiModeManager.getAvailableThemes()).thenReturn(new String[1]);
+ assertFalse(mPreferenceController.isAvailable());
+ }
+
+ @Test
+ public void testAvailable_true() {
+ when(mMockUiModeManager.getAvailableThemes()).thenReturn(new String[2]);
+ assertTrue(mPreferenceController.isAvailable());
+ }
+}