Merge "Migrate media output switcher metrics - 3/n"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e917b5c..9b8327c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3644,6 +3644,25 @@
</intent-filter>/>
</receiver>
+ <receiver
+ android:name=".sim.receivers.SimSlotChangeReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.telephony.action.SIM_SLOT_STATUS_CHANGED" />
+ </intent-filter>
+ </receiver>
+
+ <receiver
+ android:name=".sim.receivers.SimCompleteBootReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED"/>
+ </intent-filter>
+ </receiver>
+
+ <service android:name=".sim.SimNotificationService"
+ android:permission="android.permission.BIND_JOB_SERVICE" />
+
<!-- This is the longest AndroidManifest.xml ever. -->
</application>
</manifest>
diff --git a/res/drawable/ic_arrow_downward.xml b/res/drawable/ic_arrow_downward.xml
new file mode 100644
index 0000000..0def170
--- /dev/null
+++ b/res/drawable/ic_arrow_downward.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorAccent">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_undo_24.xml b/res/drawable/ic_undo_24.xml
new file mode 100644
index 0000000..0a8e149
--- /dev/null
+++ b/res/drawable/ic_undo_24.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M14.1,8H7.83l2.59,-2.59L9,4 4,9l5,5 1.41,-1.41L7.83,10h6.27c2.15,0 3.9,1.57 3.9,3.5S16.25,17 14.1,17H7v2h7.1c3.25,0 5.9,-2.47 5.9,-5.5S17.35,8 14.1,8z"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/wifi_friction.xml b/res/drawable/wifi_friction.xml
index fa8268d..6e9010f 100644
--- a/res/drawable/wifi_friction.xml
+++ b/res/drawable/wifi_friction.xml
@@ -15,7 +15,7 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:settings="http://schemas.android.com/apk/res/com.android.settings">
+ xmlns:settings="http://schemas.android.com/apk/res-auto">
<item
settings:state_encrypted="true"
android:drawable="@drawable/ic_friction_lock_closed"/>
diff --git a/res/drawable/wifi_signal.xml b/res/drawable/wifi_signal.xml
index 7854075..fba7792 100644
--- a/res/drawable/wifi_signal.xml
+++ b/res/drawable/wifi_signal.xml
@@ -15,7 +15,7 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:settings="http://schemas.android.com/apk/res/com.android.settings">
+ xmlns:settings="http://schemas.android.com/apk/res-auto">
<item settings:state_encrypted="true">
<layer-list>
<item>
diff --git a/res/layout/app_authentication_item.xml b/res/layout/app_authentication_item.xml
index 2df923f..423722e 100644
--- a/res/layout/app_authentication_item.xml
+++ b/res/layout/app_authentication_item.xml
@@ -30,6 +30,7 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/app_icon"
+ android:layout_toLeftOf="@id/expand"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:orientation="vertical">
@@ -40,6 +41,13 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/number_of_uris"
+ style="@style/AppAuthenticationPolicyNumberOfUrisText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"/>
+
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/uris"
android:layout_width="wrap_content"
@@ -47,4 +55,11 @@
</LinearLayout>
+ <ImageView
+ android:id="@+id/expand"
+ style="@style/AppAuthenticationExpander"
+ android:layout_width="30dp"
+ android:layout_height="30dp"
+ android:visibility="gone"/>
+
</RelativeLayout>
diff --git a/res/layout/biometric_enroll_layout.xml b/res/layout/biometric_enroll_layout.xml
index bf41fcc..2f3b34c 100644
--- a/res/layout/biometric_enroll_layout.xml
+++ b/res/layout/biometric_enroll_layout.xml
@@ -18,7 +18,6 @@
<com.google.android.setupdesign.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:BiometricEnrollCheckbox="http://schemas.android.com/apk/res/com.android.settings"
style="?attr/face_layout_theme"
app:sucHeaderText="@string/multi_biometric_enroll_title"
android:id="@+id/setup_wizard_layout"
@@ -59,9 +58,9 @@
android:id="@+id/checkbox_face"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- BiometricEnrollCheckbox:title="@string/multi_biometric_enroll_face_unlock_title"
- BiometricEnrollCheckbox:description="@string/multi_biometric_enroll_face_unlock_description"
- BiometricEnrollCheckbox:icon="@drawable/ic_face_24dp"/>
+ app:title="@string/multi_biometric_enroll_face_unlock_title"
+ app:description="@string/multi_biometric_enroll_face_unlock_description"
+ app:icon="@drawable/ic_face_24dp"/>
<include layout="@layout/horizontal_divider"/>
@@ -69,9 +68,9 @@
android:id="@+id/checkbox_fingerprint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- BiometricEnrollCheckbox:title="@string/multi_biometric_enroll_fingerprint_unlock_title"
- BiometricEnrollCheckbox:description="@string/multi_biometric_enroll_fingerprint_unlock_description"
- BiometricEnrollCheckbox:icon="@drawable/ic_fingerprint_24dp"/>
+ app:title="@string/multi_biometric_enroll_fingerprint_unlock_title"
+ app:description="@string/multi_biometric_enroll_fingerprint_unlock_description"
+ app:icon="@drawable/ic_fingerprint_24dp"/>
<include layout="@layout/horizontal_divider"/>
diff --git a/res/layout/credential_management_app_policy.xml b/res/layout/credential_management_app_policy.xml
new file mode 100644
index 0000000..15153e9
--- /dev/null
+++ b/res/layout/credential_management_app_policy.xml
@@ -0,0 +1,31 @@
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="5dip"
+ android:orientation="vertical">
+
+ <androidx.recyclerview.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingTop="8dp"
+ android:scrollbars="vertical"/>
+
+</LinearLayout>
diff --git a/res/layout/face_enroll_education.xml b/res/layout/face_enroll_education.xml
index d7b95b6..d94e7c6 100644
--- a/res/layout/face_enroll_education.xml
+++ b/res/layout/face_enroll_education.xml
@@ -18,7 +18,6 @@
<com.google.android.setupdesign.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:FaceEnrollAccessibilitySwitch="http://schemas.android.com/apk/res/com.android.settings"
style="?attr/face_layout_theme"
android:id="@+id/setup_wizard_layout"
android:layout_width="match_parent"
@@ -94,7 +93,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
- FaceEnrollAccessibilitySwitch:messageText="@string/security_settings_face_enroll_introduction_accessibility_diversity"/>
+ app:messageText="@string/security_settings_face_enroll_introduction_accessibility_diversity"/>
</FrameLayout>
diff --git a/res/layout/face_enroll_introduction.xml b/res/layout/face_enroll_introduction.xml
index 55ac6f9..826f5fe 100644
--- a/res/layout/face_enroll_introduction.xml
+++ b/res/layout/face_enroll_introduction.xml
@@ -18,7 +18,6 @@
<com.google.android.setupdesign.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:FaceEnrollAccessibilitySwitch="http://schemas.android.com/apk/res/com.android.settings"
style="?attr/face_layout_theme"
android:id="@+id/setup_wizard_layout"
android:layout_width="match_parent"
diff --git a/res/layout/request_manage_credentials.xml b/res/layout/request_manage_credentials.xml
index eb4c9e8..a2350ac 100644
--- a/res/layout/request_manage_credentials.xml
+++ b/res/layout/request_manage_credentials.xml
@@ -59,4 +59,17 @@
</RelativeLayout>
+ <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
+ android:id="@+id/extended_fab"
+ style="@style/RequestManageCredentialsFab"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/request_manage_credentials_more"
+ app:layout_anchor="@id/apps_list"
+ app:layout_anchorGravity="bottom|center"
+ app:elevation="3dp"
+ app:icon="@drawable/ic_arrow_downward"
+ app:iconTint="?android:attr/colorAccent"
+ app:backgroundTint="?android:attr/colorPrimary"/>
+
</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/res/layout/udfps_enroll_enrolling.xml b/res/layout/udfps_enroll_enrolling.xml
new file mode 100644
index 0000000..03b6528
--- /dev/null
+++ b/res/layout/udfps_enroll_enrolling.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<com.google.android.setupdesign.GlifLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/setup_wizard_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout="@layout/sud_glif_blank_template"
+ style="?attr/fingerprint_layout_theme">
+
+</com.google.android.setupdesign.GlifLayout>
diff --git a/res/layout/udfps_enroll_layout.xml b/res/layout/udfps_enroll_layout.xml
new file mode 100644
index 0000000..51c788b
--- /dev/null
+++ b/res/layout/udfps_enroll_layout.xml
@@ -0,0 +1,96 @@
+<?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
+ -->
+
+<com.android.settings.biometrics.fingerprint.UdfpsEnrollLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/setup_wizard_layout"
+ style="?attr/fingerprint_layout_theme"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/sud_layout_icon"
+ style="@style/SudGlifIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="?attr/sudGlifHeaderGravity"
+ android:layout_marginEnd="0dp"
+ android:layout_marginStart="0dp"
+ android:src="@drawable/ic_fingerprint_header" />
+
+ <TextView
+ android:id="@+id/suc_layout_title"
+ style="@style/SudGlifHeaderTitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="0dp"
+ android:layout_marginStart="0dp" />
+
+ <Space
+ android:id="@+id/space_below_title"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <FrameLayout
+ android:id="@+id/description_area"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/sud_layout_description"
+ style="@style/SudDescription.Glif"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/security_settings_fingerprint_enroll_start_message" />
+
+ <TextView
+ android:id="@+id/repeat_message"
+ style="@style/SudDescription.Glif"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/security_settings_fingerprint_enroll_repeat_message"
+ android:visibility="invisible" />
+
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/error_text"
+ style="@style/TextAppearance.ErrorText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginBottom="16dp"
+ android:accessibilityLiveRegion="polite"
+ android:gravity="center_horizontal"
+ android:paddingEnd="5dp"
+ android:paddingStart="5dp"
+ android:visibility="invisible" />
+
+ <Space
+ android:id="@+id/space_above_animation"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ <include
+ layout="@layout/fingerprint_enroll_enrolling_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+
+</com.android.settings.biometrics.fingerprint.UdfpsEnrollLayout>
diff --git a/res/values-ca/arrays.xml b/res/values-ca/arrays.xml
index 5c5291f..638f2f9 100644
--- a/res/values-ca/arrays.xml
+++ b/res/values-ca/arrays.xml
@@ -418,7 +418,7 @@
<item msgid="747238414788976867">"Personalitzat"</item>
</string-array>
<string-array name="vpn_types_long">
- <item msgid="6621806338070912611">"VPN de PPTP"</item>
+ <item msgid="6621806338070912611">"VPN PPTP"</item>
<item msgid="2552427673212085780">"VPN L2TP/IPSec amb claus prèviament compartides"</item>
<item msgid="7378096704485168082">"VPN L2TP/IPSec amb certificats"</item>
<item msgid="3792393562235791509">"VPN IPSec amb claus prèviament compartides i autenticació Xauth"</item>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index 7a6e0aa..3d73f64 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -20,4 +20,5 @@
<integer name="job_anomaly_config_update">101</integer>
<integer name="job_anomaly_detection">102</integer>
<integer name="device_index_update">103</integer>
+ <integer name="sim_notification_send">104</integer>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 041dbdd..8aeefdb 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2289,8 +2289,8 @@
<string name="wifi_saved_access_points_label">Saved networks</string>
<!-- Tab title for showing subscribed WiFi access points. [CHAR LIMIT=20] -->
<string name="wifi_subscribed_access_points_tab">Subscriptions</string>
- <!-- Tab title for showing saved WiFi access points. -->
- <string name="wifi_saved_access_points_tab">@string/wifi_access_points</string>
+ <!-- Tab title for showing saved other networks. -->
+ <string name="wifi_saved_other_networks_tab">Other networks</string>
<!-- Wifi Advanced settings. Used as a label under the shortcut icon that goes to Wifi advanced settings. [CHAR LIMIT=20] -->
<string name="wifi_advanced_settings_label">IP settings</string>
<!-- Error message for users that aren't allowed to see or modify WiFi advanced settings [CHAR LIMIT=NONE] -->
@@ -5720,9 +5720,9 @@
<!-- Summary for the battery high usage tip, which presents battery may run out earlier [CHAR LIMIT=NONE] -->
<string name="battery_tip_high_usage_summary">Battery may run out earlier than usual</string>
<!-- Title for the battery limited temporarily tip [CHAR LIMIT=NONE] -->
- <string name="battery_tip_limited_temporarily_title">Battery limited temporarily</string>
+ <string name="battery_tip_limited_temporarily_title">Preserving battery health</string>
<!-- Summary for the battery limited temporarily tip [CHAR LIMIT=NONE] -->
- <string name="battery_tip_limited_temporarily_summary">Helps preserve battery health. Tap to learn more.</string>
+ <string name="battery_tip_limited_temporarily_summary">Battery limited temporarily. Tap to learn more.</string>
<!-- Message for battery tip dialog to show the status about the battery [CHAR LIMIT=NONE] -->
<string name="battery_tip_dialog_message" product="default">Your phone has been used more than usual. Your battery may run out sooner than expected.\n\nTop apps by battery usage:</string>
<!-- Message for battery tip dialog to show the status about the battery [CHAR LIMIT=NONE] -->
@@ -6335,6 +6335,14 @@
<string name="request_manage_credentials_allow">Allow</string>
<!-- Label for floating action button to scroll to the end of the authentication policy list [CHAR LIMIT=30] -->
<string name="request_manage_credentials_more">Show more</string>
+ <!-- Title of preference for the certificate management app [CHAR LIMIT=30] -->
+ <string name="certificate_management_app">Certificate management app</string>
+ <!-- Summary if there is no certificate management app [CHAR_LIMIT=NONE] -->
+ <string name="no_certificate_management_app">None</string>
+ <!-- Summary of preference if there is a certificate management app [CHAR LIMIT=NONE] -->
+ <string name="certificate_management_app_description">Certificates installed by this app identify you to the apps and URLs below</string>
+ <!-- Label for button to remove the credential management app [CHAR LIMIT=30] -->
+ <string name="remove_credential_management_app">Remove</string>
<!-- Sound settings screen, setting check box label -->
<string name="emergency_tone_title">Emergency dialing signal</string>
@@ -7262,6 +7270,8 @@
<string name="help_url_sound" translatable="false"></string>
<!-- Help URL, Battery [DO NOT TRANSLATE] -->
<string name="help_url_battery" translatable="false"></string>
+ <!-- Help URL, Battery Defender [DO NOT TRANSLATE] -->
+ <string name="help_url_battery_defender" translatable="false"></string>
<!-- Help URL, Accounts [DO NOT TRANSLATE] -->
<string name="help_url_accounts" translatable="false"></string>
<!-- Help URL, Choose lockscreen [DO NOT TRANSLATE] -->
@@ -11649,7 +11659,7 @@
<string name="pref_title_network_details">Network details</string>
<!-- Warning text about the visibility of device name. [CHAR LIMIT=NONE] -->
- <string name="about_phone_device_name_warning">Your device name is visible to apps on your phone. It may also be seen by other people when you connect to Bluetooth devices or set up a Wi-Fi hotspot.</string>
+ <string name="about_phone_device_name_warning">Your device name is visible to apps on your phone. It may also be seen by other people when you connect to Bluetooth devices, connect to a Wi-Fi network or set up a Wi-Fi hotspot.</string>
<!-- Title for Connected device shortcut [CHAR LIMIT=30] -->
<string name="devices_title">Devices</string>
@@ -12083,6 +12093,17 @@
<!-- Body text of DSDS activation failure dialog. Users could toggle the selected SIM again or reboot to recover. [CHAR LIMIT=NONE] -->
<string name="dsds_activation_failure_body_msg2">Try turning on the SIM again. If the problem continues, restart your device.</string>
+ <!-- Strings for SIM push notifications -->
+ <!-- Category name of the notifications related to SIM setup. [CHAR LIMIT=NONE] -->
+ <string name="sim_setup_channel_id">Network activation</string>
+ <!-- The title of post DSDS reboot notification. The title includes carrier's name. [CHAR LIMIT=NONE] -->
+ <string name="post_dsds_reboot_notification_title_with_carrier"><xliff:g id="carrier_name" example="Google Fi">%1$s</xliff:g> is active</string>
+ <!-- The body text of post DSDS reboot notification. [CHAR LIMIT=NONE] -->
+ <string name="post_dsds_reboot_notification_text">Tap to update SIM settings</string>
+
+ <!-- Button label of the removable sim card. [CHAR LIMIT=NONE] -->
+ <string name="sim_card_label">SIM card</string>
+
<!-- Strings for deleting eUICC subscriptions dialog activity -->
<!-- Title on confirmation dialog asking the user if they want to erase the downloaded SIM from the device. [CHAR_LIMIT=NONE] -->
<string name="erase_sim_dialog_title">Erase this downloaded SIM?</string>
@@ -12480,6 +12501,10 @@
<string name="network_and_internet_preferences_summary">Connect to public networks</string>
<!-- Search keywords for "Internet" settings [CHAR_LIMIT=NONE] -->
<string name="keywords_internet">network connection, internet, wireless, data, wifi, wi-fi, wi fi, cellular, mobile, cell carrier, 4g, 3g, 2g, lte</string>
+ <!-- Label text to view airplane-safe networks. [CHAR LIMIT=40] -->
+ <string name="view_airplane_safe_networks">View airplane-safe networks</string>
+ <!-- Label text to turn off airplane mode. [CHAR LIMIT=40] -->
+ <string name="turn_off_airplane_mode">Turn off Airplane Mode</string>
<!-- Summary for preference when Bedtime mode is on [CHAR LIMIT=NONE] -->
<string name="aware_summary_when_bedtime_on">Unavailable because bedtime mode is on</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index c447dec..fd4d268 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -805,6 +805,13 @@
<item name="android:textColor">?android:attr/colorAccent</item>
</style>
+ <style name="RequestManageCredentialsFab">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textAllCaps">false</item>
+ <item name="android:textColor">?android:attr/colorAccent</item>
+ <item name="android:layout_marginBottom">12dp</item>
+ </style>
+
<style name="RequestManageCredentialsHeader">
<item name="android:paddingStart">40dp</item>
<item name="android:paddingEnd">24dp</item>
@@ -816,13 +823,13 @@
<style name="RequestManageCredentialsTitle">
<item name="android:layout_marginTop">24dp</item>
<item name="android:textSize">36sp</item>
- <item name="android:textColor">#202124</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="RequestManageCredentialsDescription">
<item name="android:layout_marginTop">24dp</item>
<item name="android:textSize">18sp</item>
- <item name="android:textColor">#202124</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="AppAuthenticationPolicyItem">
@@ -838,12 +845,25 @@
<style name="AppAuthenticationPolicyText">
<item name="android:maxLines">1</item>
<item name="android:textSize">20sp</item>
- <item name="android:textColor">#202124</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
+
+ <style name="AppAuthenticationPolicyNumberOfUrisText">
+ <item name="android:layout_marginTop">4dp</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:alpha">0.7</item>
+ </style>
+
+ <style name="AppAuthenticationExpander">
+ <item name="android:layout_marginTop">24dp</item>
+ <item name="android:layout_alignParentEnd">true</item>
+ <item name="android:scaleType">fitEnd</item>
</style>
<style name="AppUriAuthenticationPolicyText">
<item name="android:maxLines">1</item>
<item name="android:textSize">16sp</item>
- <item name="android:textColor">#5F6368</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
</resources>
diff --git a/res/xml/credential_management_app_fragment.xml b/res/xml/credential_management_app_fragment.xml
new file mode 100644
index 0000000..9392414
--- /dev/null
+++ b/res/xml/credential_management_app_fragment.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/certificate_management_app">
+
+ <!-- Header -->
+ <com.android.settingslib.widget.LayoutPreference
+ android:key="header"
+ android:layout="@layout/settings_entity_header"
+ android:selectable="false"
+ android:order="-10000"
+ settings:allowDividerBelow="true"
+ settings:controller="com.android.settings.security.CredentialManagementAppHeaderController"/>
+
+ <!-- Buttons -->
+ <com.android.settingslib.widget.ActionButtonsPreference
+ android:key="buttons"
+ android:selectable="true"
+ android:order="-9999"
+ settings:allowDividerAbove="true"
+ settings:allowDividerBelow="true"
+ settings:controller="com.android.settings.security.CredentialManagementAppButtonsController"/>
+
+ <!-- Authentication Policy -->
+ <PreferenceCategory
+ android:key="authentication_policy"
+ android:layout="@layout/preference_category_no_label"
+ android:title="@string/summary_placeholder"
+ settings:controller="com.android.settings.security.CredentialManagementAppPolicyController"/>
+
+</PreferenceScreen>
diff --git a/res/xml/encryption_and_credential.xml b/res/xml/encryption_and_credential.xml
index fe0498d..f107b58 100644
--- a/res/xml/encryption_and_credential.xml
+++ b/res/xml/encryption_and_credential.xml
@@ -69,6 +69,13 @@
</com.android.settingslib.RestrictedPreference>
+ <Preference
+ android:key="certificate_management_app"
+ android:title="@string/certificate_management_app"
+ android:summary="@string/summary_placeholder"
+ android:fragment="com.android.settings.security.CredentialManagementAppFragment"
+ settings:controller="com.android.settings.security.CredentialManagementAppPreferenceController"/>
+
</PreferenceCategory>
</PreferenceScreen>
diff --git a/res/xml/network_provider_internet.xml b/res/xml/network_provider_internet.xml
index a079478..20cf2be 100644
--- a/res/xml/network_provider_internet.xml
+++ b/res/xml/network_provider_internet.xml
@@ -57,18 +57,17 @@
android:key="airplane_mode"
android:title="@string/airplane_mode"
android:icon="@drawable/ic_airplanemode_active"
- android:disableDependentsState="true"
android:order="-5"
settings:controller="com.android.settings.network.AirplaneModePreferenceController"
settings:userRestriction="no_airplane_mode"/>
- <SwitchPreference
+ <com.android.settingslib.RestrictedSwitchPreference
android:key="airplane_safe_networks"
android:title="@string/airplane_safe_networks"
android:icon="@drawable/ic_airplanemode_active"
- android:order="-4"
android:summary="@string/airplane_safe_networks_summary"
- settings:controller="com.android.settings.network.AirplaneSafeNetworkModePreferenceController"
+ android:order="-4"
+ settings:userRestriction="no_airplane_mode"
settings:keywords="@string/keywords_airplane_safe_networks" />
<com.android.settingslib.RestrictedPreference
diff --git a/res/xml/network_provider_settings.xml b/res/xml/network_provider_settings.xml
index c0f2edd..c374469 100644
--- a/res/xml/network_provider_settings.xml
+++ b/res/xml/network_provider_settings.xml
@@ -28,6 +28,12 @@
android:layout="@layout/preference_category_no_label"/>
<PreferenceCategory
+ android:key="provider_model_mobile_network"
+ android:title="@string/summary_placeholder"
+ android:layout="@layout/preference_category_no_label"
+ settings:controller="com.android.settings.network.NetworkMobileProviderController"/>
+
+ <PreferenceCategory
android:key="access_points"
android:layout="@layout/preference_category_no_label"/>
diff --git a/res/xml/transcode_settings.xml b/res/xml/transcode_settings.xml
index c6fbdfd..65f6cd8 100644
--- a/res/xml/transcode_settings.xml
+++ b/res/xml/transcode_settings.xml
@@ -22,10 +22,20 @@
settings:searchable="false">
<SwitchPreference
+ android:key="transcode_user_control"
+ android:title="@string/transcode_user_control"
+ settings:controller="com.android.settings.development.transcode.TranscodeUserControlPreferenceController" />
+
+ <SwitchPreference
android:key="transcode_enable_all"
android:title="@string/transcode_enable_all"
settings:controller="com.android.settings.development.transcode.TranscodeGlobalTogglePreferenceController" />
+ <SwitchPreference
+ android:key="transcode_default"
+ android:title="@string/transcode_default"
+ settings:controller="com.android.settings.development.transcode.TranscodeDefaultOptionPreferenceController" />
+
<PreferenceCategory
android:key="transcode_skip_apps"
android:title="@string/transcode_skip_apps"
diff --git a/res/xml/wifi_display_saved_access_points2.xml b/res/xml/wifi_display_saved_access_points2.xml
index 02c732a..8cbea66 100644
--- a/res/xml/wifi_display_saved_access_points2.xml
+++ b/res/xml/wifi_display_saved_access_points2.xml
@@ -27,7 +27,7 @@
<PreferenceCategory
android:key="saved_access_points_category"
- android:title="@string/wifi_saved_access_points_tab"
+ android:title="@string/wifi_saved_other_networks_tab"
settings:controller="com.android.settings.wifi.savedaccesspoints2.SavedAccessPointsPreferenceController2"/>
</PreferenceScreen>
diff --git a/src/com/android/settings/PointerSpeedPreference.java b/src/com/android/settings/PointerSpeedPreference.java
index d06a374..d48d26c 100644
--- a/src/com/android/settings/PointerSpeedPreference.java
+++ b/src/com/android/settings/PointerSpeedPreference.java
@@ -16,6 +16,8 @@
package com.android.settings;
+import static android.view.HapticFeedbackConstants.CLOCK_TICK;
+
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -37,6 +39,7 @@
private boolean mRestoredOldState;
private boolean mTouchInProgress;
+ private int mLastProgress = -1;
private ContentObserver mSpeedObserver = new ContentObserver(new Handler()) {
@Override
@@ -76,6 +79,10 @@
if (!mTouchInProgress) {
mIm.tryPointerSpeed(progress + InputManager.MIN_POINTER_SPEED);
}
+ if (progress != mLastProgress) {
+ seekBar.performHapticFeedback(CLOCK_TICK);
+ mLastProgress = progress;
+ }
}
public void onStartTrackingTouch(SeekBar seekBar) {
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 588a2db..b226133 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -23,6 +23,7 @@
import com.android.settings.core.FeatureFlags;
import com.android.settings.enterprise.EnterprisePrivacySettings;
+import com.android.settings.overlay.FeatureFactory;
/**
* Top-level Settings activity
@@ -213,7 +214,11 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (!EnterprisePrivacySettings.isPageEnabled(this)) {
+ if (FeatureFactory.getFactory(this)
+ .getEnterprisePrivacyFeatureProvider(this)
+ .showParentalControls()) {
+ finish();
+ } else if (!EnterprisePrivacySettings.isPageEnabled(this)) {
finish();
}
}
diff --git a/src/com/android/settings/accessibility/BalanceSeekBar.java b/src/com/android/settings/accessibility/BalanceSeekBar.java
index 15526b6..8a88d6c 100644
--- a/src/com/android/settings/accessibility/BalanceSeekBar.java
+++ b/src/com/android/settings/accessibility/BalanceSeekBar.java
@@ -16,6 +16,7 @@
package com.android.settings.accessibility;
+import static android.view.HapticFeedbackConstants.CLOCK_TICK;
import static com.android.settings.Utils.isNightMode;
import android.content.Context;
@@ -44,6 +45,7 @@
private final Context mContext;
private final Object mListenerLock = new Object();
private OnSeekBarChangeListener mOnSeekBarChangeListener;
+ private int mLastProgress = -1;
private final OnSeekBarChangeListener mProxySeekBarListener = new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
@@ -73,6 +75,12 @@
progress = mCenter;
seekBar.setProgress(progress); // direct update (fromUser becomes false)
}
+ if (progress != mLastProgress) {
+ if (progress == mCenter || progress == getMin() || progress == getMax()) {
+ seekBar.performHapticFeedback(CLOCK_TICK);
+ }
+ mLastProgress = progress;
+ }
final float balance = (progress - mCenter) * 0.01f;
Settings.System.putFloatForUser(mContext.getContentResolver(),
Settings.System.MASTER_BALANCE, balance, UserHandle.USER_CURRENT);
@@ -152,10 +160,5 @@
canvas.restore();
super.onDraw(canvas);
}
-
- @VisibleForTesting
- OnSeekBarChangeListener getProxySeekBarListener() {
- return mProxySeekBarListener;
- }
}
diff --git a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java
index e25bb1e..2126109 100644
--- a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java
+++ b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminListPreferenceController.java
@@ -46,7 +46,7 @@
import com.android.settings.core.BasePreferenceController;
import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.widget.FilterTouchesSwitchPreference;
+import com.android.settings.widget.AppSwitchPreference;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
@@ -168,32 +168,32 @@
if (mFooterPreference != null) {
mFooterPreference.setVisible(mAdmins.isEmpty());
}
- final Map<String, FilterTouchesSwitchPreference> preferenceCache = new ArrayMap<>();
+ final Map<String, AppSwitchPreference> preferenceCache = new ArrayMap<>();
final Context prefContext = mPreferenceGroup.getContext();
final int childrenCount = mPreferenceGroup.getPreferenceCount();
for (int i = 0; i < childrenCount; i++) {
final Preference pref = mPreferenceGroup.getPreference(i);
- if (!(pref instanceof FilterTouchesSwitchPreference)) {
+ if (!(pref instanceof AppSwitchPreference)) {
continue;
}
- final FilterTouchesSwitchPreference appSwitch = (FilterTouchesSwitchPreference) pref;
+ final AppSwitchPreference appSwitch = (AppSwitchPreference) pref;
preferenceCache.put(appSwitch.getKey(), appSwitch);
}
for (DeviceAdminListItem item : mAdmins) {
final String key = item.getKey();
- FilterTouchesSwitchPreference pref = preferenceCache.remove(key);
+ AppSwitchPreference pref = preferenceCache.remove(key);
if (pref == null) {
- pref = new FilterTouchesSwitchPreference(prefContext);
+ pref = new AppSwitchPreference(prefContext);
mPreferenceGroup.addPreference(pref);
}
bindPreference(item, pref);
}
- for (FilterTouchesSwitchPreference unusedCacheItem : preferenceCache.values()) {
+ for (AppSwitchPreference unusedCacheItem : preferenceCache.values()) {
mPreferenceGroup.removePreference(unusedCacheItem);
}
}
- private void bindPreference(DeviceAdminListItem item, FilterTouchesSwitchPreference pref) {
+ private void bindPreference(DeviceAdminListItem item, AppSwitchPreference pref) {
pref.setKey(item.getKey());
pref.setTitle(item.getName());
pref.setIcon(item.getIcon());
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java
index fc85f7d..5da2990 100644
--- a/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/FriendlyWarningDialogFragment.java
@@ -22,6 +22,7 @@
import android.text.TextUtils;
import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
@@ -39,10 +40,12 @@
return SettingsEnums.DIALOG_ZEN_ACCESS_REVOKE;
}
- public FriendlyWarningDialogFragment setPkgInfo(String pkg, CharSequence label) {
+ public FriendlyWarningDialogFragment setPkgInfo(String pkg, CharSequence label,
+ Fragment target) {
Bundle args = new Bundle();
args.putString(KEY_PKG, pkg);
args.putString(KEY_LABEL, TextUtils.isEmpty(label) ? pkg : label.toString());
+ setTargetFragment(target, 0);
setArguments(args);
return this;
}
@@ -58,6 +61,8 @@
R.string.zen_access_revoke_warning_dialog_title, label);
final String summary = getResources()
.getString(R.string.zen_access_revoke_warning_dialog_summary);
+
+ ZenAccessDetails parent = (ZenAccessDetails) getTargetFragment();
return new AlertDialog.Builder(getContext())
.setMessage(summary)
.setTitle(title)
@@ -66,6 +71,7 @@
(dialog, id) -> {
ZenAccessController.deleteRules(getContext(), pkg);
ZenAccessController.setAccess(getContext(), pkg, false);
+ parent.refreshUi();
})
.setNegativeButton(R.string.cancel,
(dialog, id) -> {
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java b/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java
index 778206b..e4ef48b 100644
--- a/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ScaryWarningDialogFragment.java
@@ -22,6 +22,7 @@
import android.text.TextUtils;
import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
@@ -38,10 +39,11 @@
return SettingsEnums.DIALOG_ZEN_ACCESS_GRANT;
}
- public ScaryWarningDialogFragment setPkgInfo(String pkg, CharSequence label) {
+ public ScaryWarningDialogFragment setPkgInfo(String pkg, CharSequence label, Fragment target) {
Bundle args = new Bundle();
args.putString(KEY_PKG, pkg);
args.putString(KEY_LABEL, TextUtils.isEmpty(label) ? pkg : label.toString());
+ setTargetFragment(target, 0);
setArguments(args);
return this;
}
@@ -57,12 +59,18 @@
label);
final String summary = getResources()
.getString(R.string.zen_access_warning_dialog_summary);
+
+ ZenAccessDetails parent = (ZenAccessDetails) getTargetFragment();
+
return new AlertDialog.Builder(getContext())
.setMessage(summary)
.setTitle(title)
.setCancelable(true)
.setPositiveButton(R.string.allow,
- (dialog, id) -> ZenAccessController.setAccess(getContext(), pkg, true))
+ (dialog, id) -> {
+ ZenAccessController.setAccess(getContext(), pkg, true);
+ parent.refreshUi();
+ })
.setNegativeButton(R.string.deny,
(dialog, id) -> {
// pass
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java
index ba6bb1d..c608b5b 100644
--- a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessDetails.java
@@ -79,11 +79,11 @@
final boolean access = (Boolean) newValue;
if (access) {
new ScaryWarningDialogFragment()
- .setPkgInfo(mPackageName, label)
+ .setPkgInfo(mPackageName, label, ZenAccessDetails.this)
.show(getFragmentManager(), "dialog");
} else {
new FriendlyWarningDialogFragment()
- .setPkgInfo(mPackageName, label)
+ .setPkgInfo(mPackageName, label, ZenAccessDetails.this)
.show(getFragmentManager(), "dialog");
}
return false;
diff --git a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixin.java b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixin.java
index da238f6..f378e7b 100644
--- a/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixin.java
+++ b/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixin.java
@@ -54,11 +54,6 @@
@Override
public void onStart() {
mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(
- Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES),
- false /* notifyForDescendants */,
- this /* observer */);
- mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS),
false /* notifyForDescendants */,
this /* observer */);
diff --git a/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java b/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java
index f131e35..9b86e78 100644
--- a/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/BiometricsEnrollEnrolling.java
@@ -16,6 +16,7 @@
package com.android.settings.biometrics;
+import android.annotation.Nullable;
import android.content.Intent;
import android.os.UserHandle;
import android.view.View;
@@ -33,6 +34,7 @@
private static final String TAG_SIDECAR = "sidecar";
+ @Nullable
protected BiometricEnrollSidecar mSidecar;
/**
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
index 813c384..b33113b 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java
@@ -27,11 +27,13 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.media.AudioAttributes;
import android.os.Bundle;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.text.TextUtils;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AnimationUtils;
@@ -51,11 +53,14 @@
import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.util.DescriptionStyler;
+import java.util.List;
+
/**
* Activity which handles the actual enrolling for fingerprint.
*/
public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling {
+ private static final String TAG = "FingerprintEnrollEnrolling";
static final String TAG_SIDECAR = "sidecar";
private static final int PROGRESS_BAR_MAX = 10000;
@@ -86,6 +91,7 @@
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.build();
+ private boolean mCanAssumeUdfps;
private ProgressBar mProgressBar;
private ObjectAnimator mProgressAnim;
private TextView mStartMessage;
@@ -130,14 +136,35 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.fingerprint_enroll_enrolling);
+
+ final FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class);
+ final List<FingerprintSensorPropertiesInternal> props =
+ fingerprintManager.getSensorPropertiesInternal();
+ mCanAssumeUdfps = props.size() == 1 && props.get(0).isAnyUdfpsType();
+
+ if (mCanAssumeUdfps) {
+ // Use a custom layout since animations, etc must be based off of the sensor's physical
+ // location.
+ setContentView(R.layout.udfps_enroll_enrolling);
+ final UdfpsEnrollLayout udfpsEnrollLayout = (UdfpsEnrollLayout) getLayoutInflater()
+ .inflate(R.layout.udfps_enroll_layout, null /* root */);
+ getLayout().addView(udfpsEnrollLayout);
+ } else {
+ setContentView(R.layout.fingerprint_enroll_enrolling);
+ }
+
setHeaderText(R.string.security_settings_fingerprint_enroll_repeat_title);
- mStartMessage = (TextView) findViewById(R.id.sud_layout_description);
- mRepeatMessage = (TextView) findViewById(R.id.repeat_message);
- mErrorText = (TextView) findViewById(R.id.error_text);
- mProgressBar = (ProgressBar) findViewById(R.id.fingerprint_progress_bar);
+
+ mStartMessage = findViewById(R.id.sud_layout_description);
+ mRepeatMessage = findViewById(R.id.repeat_message);
+ mErrorText = findViewById(R.id.error_text);
+ mProgressBar = findViewById(R.id.fingerprint_progress_bar);
mVibrator = getSystemService(Vibrator.class);
+ if (mCanAssumeUdfps) {
+ mProgressBar.setVisibility(View.INVISIBLE);
+ }
+
if (getLayout().shouldApplyPartnerHeavyThemeResource()) {
DescriptionStyler.applyPartnerCustomizationHeavyStyle(mRepeatMessage);
} else if (getLayout().shouldApplyPartnerResource()) {
@@ -193,7 +220,7 @@
@Override
protected boolean shouldStartAutomatically() {
- return true;
+ return !mCanAssumeUdfps;
}
@Override
@@ -209,6 +236,12 @@
@Override
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
+
+ if (mCanAssumeUdfps) {
+ startEnrollment();
+ mProgressBar.setVisibility(View.VISIBLE);
+ }
+
mAnimationCancelled = false;
startIconAnimation();
}
@@ -252,7 +285,7 @@
}
private void updateDescription() {
- if (mSidecar.getEnrollmentSteps() == -1) {
+ if (mSidecar == null || mSidecar.getEnrollmentSteps() == -1) {
mStartMessage.setVisibility(View.VISIBLE);
mRepeatMessage.setVisibility(View.INVISIBLE);
} else {
@@ -299,6 +332,11 @@
}
private void updateProgress(boolean animate) {
+ if (mSidecar == null || !mSidecar.isEnrolling()) {
+ Log.d(TAG, "Enrollment not started yet");
+ return;
+ }
+
int progress = getProgress(
mSidecar.getEnrollmentSteps(), mSidecar.getEnrollmentRemaining());
if (animate) {
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollLayout.java b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollLayout.java
new file mode 100644
index 0000000..ca27e84
--- /dev/null
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollLayout.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 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.biometrics.fingerprint;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+
+import com.android.settings.R;
+
+public class UdfpsEnrollLayout extends LinearLayout {
+
+ private static final String TAG = "UdfpsEnrollLayout";
+
+ private final FingerprintSensorPropertiesInternal mSensorProps;
+
+ public UdfpsEnrollLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mSensorProps = context.getSystemService(FingerprintManager.class)
+ .getSensorPropertiesInternal().get(0);
+ }
+
+ @Override
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ final View animation = findViewById(R.id.fingerprint_progress_bar);
+ final WindowManager wm = getContext().getSystemService(WindowManager.class);
+ final int statusbarHeight = Math.abs(wm.getCurrentWindowMetrics().getWindowInsets()
+ .getInsets(WindowInsets.Type.statusBars()).toRect().height());
+
+ // Calculate the amount of translation required. This is just re-arranged from
+ // animation.setY(mSensorProps.sensorLocationY-statusbarHeight-mSensorProps.sensorRadius)
+ // The translationY is the amount of extra height that should be added to the spacer
+ // above the animation
+ final int spaceHeight = mSensorProps.sensorLocationY - statusbarHeight
+ - mSensorProps.sensorRadius - animation.getTop();
+ animation.setTranslationY(spaceHeight);
+ }
+
+
+
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ final View animation = findViewById(R.id.fingerprint_progress_bar);
+ final int sensorDiameter = mSensorProps.sensorRadius * 2;
+ // Multiply it slightly so that the progress bar is outside the UDFPS affordance, and that
+ // the animation is within the UDFPS affordance.
+ final int animationDiameter = (int) (sensorDiameter * 1);
+ animation.measure(MeasureSpec.makeMeasureSpec(animationDiameter, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(animationDiameter, MeasureSpec.EXACTLY));
+ }
+}
diff --git a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
index 1ab3a65..7f8ade1 100644
--- a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
+++ b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
@@ -129,8 +129,6 @@
super.displayPreference(screen);
mLayoutPreference = screen.findPreference(getPreferenceKey());
mLayoutPreference.setVisible(isAvailable());
-
- refresh();
}
@Override
@@ -142,6 +140,8 @@
mCachedDevice.registerCallback(this);
mBluetoothAdapter.addOnMetadataChangedListener(mCachedDevice.getDevice(),
mContext.getMainExecutor(), mMetadataListener);
+
+ refresh();
}
@Override
diff --git a/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java b/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java
index 4dd9a40..be383dc 100644
--- a/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java
+++ b/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java
@@ -34,6 +34,8 @@
import com.android.internal.app.AlertController;
import com.android.settings.R;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
/**
* BluetoothPermissionActivity shows a dialog for accepting incoming
* profile connection request from untrusted devices.
@@ -76,6 +78,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ getWindow().addPrivateFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
Intent i = getIntent();
String action = i.getAction();
if (!action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST)) {
diff --git a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
index ae3b08a..684f90f 100644
--- a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
+++ b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
@@ -131,6 +131,7 @@
// "Clear All Notifications" button
Intent deleteIntent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
+ deleteIntent.setPackage("com.android.bluetooth");
deleteIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
deleteIntent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
BluetoothDevice.CONNECTION_ACCESS_NO);
diff --git a/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceController.java b/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceController.java
new file mode 100644
index 0000000..3cbf3cb
--- /dev/null
+++ b/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceController.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.development.transcode;
+
+import android.content.Context;
+import android.os.SystemProperties;
+
+import com.android.settings.core.TogglePreferenceController;
+
+/**
+ * The controller (on the transcode settings screen) indicating that by default we assume that apps
+ * support modern formats.
+ */
+public class TranscodeDefaultOptionPreferenceController extends TogglePreferenceController {
+ private static final String TRANSCODE_DEFAULT_SYS_PROP_KEY =
+ "persist.sys.fuse.transcode_default";
+
+ public TranscodeDefaultOptionPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public boolean isChecked() {
+ return !SystemProperties.getBoolean(TRANSCODE_DEFAULT_SYS_PROP_KEY, false);
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ SystemProperties.set(TRANSCODE_DEFAULT_SYS_PROP_KEY, String.valueOf(!isChecked));
+ return true;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+}
diff --git a/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceController.java b/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceController.java
index 643adfc..f36f4cb 100644
--- a/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceController.java
+++ b/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceController.java
@@ -22,12 +22,12 @@
import com.android.settings.core.TogglePreferenceController;
/**
- * The controller for the "Disable transcoding for all apps" switch on the transcode settings
+ * The controller for the "Enabling transcoding for all apps" switch on the transcode settings
* screen.
*/
public class TranscodeGlobalTogglePreferenceController extends TogglePreferenceController {
- private static final String TRANSCODE_ENABLED_PROP_KEY = "persist.sys.fuse.transcode";
+ private static final String TRANSCODE_ENABLED_PROP_KEY = "persist.sys.fuse.transcode_enabled";
public TranscodeGlobalTogglePreferenceController(Context context,
String preferenceKey) {
@@ -41,12 +41,12 @@
@Override
public boolean isChecked() {
- return !SystemProperties.getBoolean(TRANSCODE_ENABLED_PROP_KEY, false);
+ return SystemProperties.getBoolean(TRANSCODE_ENABLED_PROP_KEY, false);
}
@Override
public boolean setChecked(boolean isChecked) {
- SystemProperties.set(TRANSCODE_ENABLED_PROP_KEY, String.valueOf(!isChecked));
+ SystemProperties.set(TRANSCODE_ENABLED_PROP_KEY, String.valueOf(isChecked));
return true;
}
}
diff --git a/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceController.java b/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceController.java
new file mode 100644
index 0000000..49456ff
--- /dev/null
+++ b/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceController.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.development.transcode;
+
+import android.content.Context;
+import android.os.SystemProperties;
+
+import com.android.settings.core.TogglePreferenceController;
+
+/**
+ * The controller for the User's control (over other transcoding preferences) preference switch on
+ * the transcode settings screen.
+ */
+public class TranscodeUserControlPreferenceController extends TogglePreferenceController {
+ private static final String TRANSCODE_USER_CONTROL_SYS_PROP_KEY =
+ "persist.sys.fuse.transcode_user_control";
+
+ public TranscodeUserControlPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public boolean isChecked() {
+ return SystemProperties.getBoolean(TRANSCODE_USER_CONTROL_SYS_PROP_KEY, false);
+ }
+
+ @Override
+ public boolean setChecked(boolean isChecked) {
+ SystemProperties.set(TRANSCODE_USER_CONTROL_SYS_PROP_KEY, String.valueOf(isChecked));
+ return true;
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceController.java b/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceController.java
index 8fec0c4..f733c72 100644
--- a/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/AutomaticStorageManagementSwitchPreferenceController.java
@@ -29,7 +29,7 @@
import com.android.settings.core.BasePreferenceController;
import com.android.settings.deletionhelper.ActivationWarningFragment;
import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.widget.PrimarySwitchController;
+import com.android.settings.widget.GenericSwitchController;
import com.android.settings.widget.PrimarySwitchPreference;
import com.android.settings.widget.SwitchWidgetController;
import com.android.settingslib.Utils;
@@ -44,7 +44,7 @@
static final String STORAGE_MANAGER_ENABLED_BY_DEFAULT_PROPERTY = "ro.storage_manager.enabled";
private final MetricsFeatureProvider mMetricsFeatureProvider;
private PrimarySwitchPreference mSwitch;
- private PrimarySwitchController mSwitchController;
+ private GenericSwitchController mSwitchController;
private FragmentManager mFragmentManager;
public AutomaticStorageManagementSwitchPreferenceController(Context context, String key) {
@@ -77,7 +77,7 @@
mSwitch.setChecked(Utils.isStorageManagerEnabled(mContext));
if (mSwitch != null) {
- mSwitchController = new PrimarySwitchController(mSwitch);
+ mSwitchController = new GenericSwitchController(mSwitch);
mSwitchController.setListener(this);
mSwitchController.startListening();
}
diff --git a/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProvider.java b/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProvider.java
index 46f9b71..62f15b9 100644
--- a/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProvider.java
+++ b/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProvider.java
@@ -137,4 +137,10 @@
* info" page. Returns {@code true} if the activity has indeed been launched.
*/
boolean showWorkPolicyInfo();
+
+ /**
+ * Launches the parental controls settings page. Returns {@code true} if the activity has
+ * been launched.
+ */
+ boolean showParentalControls();
}
diff --git a/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImpl.java b/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImpl.java
index 429c537..7a5b489 100644
--- a/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImpl.java
+++ b/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImpl.java
@@ -40,6 +40,9 @@
public class EnterprisePrivacyFeatureProviderImpl implements EnterprisePrivacyFeatureProvider {
+ public static final String ACTION_PARENTAL_CONTROLS =
+ "android.settings.SHOW_PARENTAL_CONTROLS";
+
private final Context mContext;
private final DevicePolicyManager mDpm;
private final PackageManager mPm;
@@ -246,6 +249,34 @@
return false;
}
+ @Override
+ public boolean showParentalControls() {
+ Intent intent = getParentalControlsIntent();
+ if (intent != null) {
+ mContext.startActivity(intent);
+ return true;
+ }
+
+ return false;
+ }
+
+ private Intent getParentalControlsIntent() {
+ final ComponentName componentName =
+ mDpm.getProfileOwnerOrDeviceOwnerSupervisionComponent(new UserHandle(MY_USER_ID));
+ if (componentName == null) {
+ return null;
+ }
+
+ final Intent intent = new Intent(ACTION_PARENTAL_CONTROLS)
+ .setPackage(componentName.getPackageName())
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final List<ResolveInfo> activities = mPm.queryIntentActivitiesAsUser(intent, 0, MY_USER_ID);
+ if (activities.size() != 0) {
+ return intent;
+ }
+ return null;
+ }
+
private ComponentName getDeviceOwnerComponent() {
if (!mPm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
return null;
diff --git a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
index 2041543..71e65bf 100644
--- a/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/BatteryBroadcastReceiver.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.BatteryManager;
import android.os.PowerManager;
import android.util.Log;
@@ -39,6 +40,7 @@
* 1. Battery level(e.g. 100%->99%)
* 2. Battery status(e.g. plugged->unplugged)
* 3. Battery saver(e.g. off->on)
+ * 4. Battery health(e.g. good->overheat)
*/
public class BatteryBroadcastReceiver extends BroadcastReceiver {
@@ -49,6 +51,7 @@
* Battery level(e.g. 100%->99%)
* Battery status(e.g. plugged->unplugged)
* Battery saver(e.g. off->on)
+ * Battery health(e.g. good->overheat)
*/
public interface OnBatteryChangedListener {
void onBatteryChanged(@BatteryUpdateType int type);
@@ -59,19 +62,23 @@
BatteryUpdateType.BATTERY_LEVEL,
BatteryUpdateType.BATTERY_SAVER,
BatteryUpdateType.BATTERY_STATUS,
+ BatteryUpdateType.BATTERY_HEALTH,
BatteryUpdateType.BATTERY_NOT_PRESENT})
public @interface BatteryUpdateType {
int MANUAL = 0;
int BATTERY_LEVEL = 1;
int BATTERY_SAVER = 2;
int BATTERY_STATUS = 3;
- int BATTERY_NOT_PRESENT = 4;
+ int BATTERY_HEALTH = 4;
+ int BATTERY_NOT_PRESENT = 5;
}
@VisibleForTesting
String mBatteryLevel;
@VisibleForTesting
String mBatteryStatus;
+ @VisibleForTesting
+ int mBatteryHealth;
private OnBatteryChangedListener mBatteryListener;
private Context mContext;
@@ -106,11 +113,15 @@
if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
final String batteryLevel = Utils.getBatteryPercentage(intent);
final String batteryStatus = Utils.getBatteryStatus(mContext, intent);
+ final int batteryHealth = intent.getIntExtra(
+ BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN);
if (!Utils.isBatteryPresent(intent)) {
Log.w(TAG, "Problem reading the battery meter.");
mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_NOT_PRESENT);
} else if (forceUpdate) {
mBatteryListener.onBatteryChanged(BatteryUpdateType.MANUAL);
+ } else if (batteryHealth != mBatteryHealth) {
+ mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_HEALTH);
} else if(!batteryLevel.equals(mBatteryLevel)) {
mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_LEVEL);
} else if (!batteryStatus.equals(mBatteryStatus)) {
@@ -118,6 +129,7 @@
}
mBatteryLevel = batteryLevel;
mBatteryStatus = batteryStatus;
+ mBatteryHealth = batteryHealth;
} else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
mBatteryListener.onBatteryChanged(BatteryUpdateType.BATTERY_SAVER);
}
diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
index 7b910a1..9066444 100644
--- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java
@@ -124,7 +124,9 @@
public void updateHeaderPreference(BatteryInfo info) {
mBatteryPercentText.setText(formatBatteryPercentageText(info.batteryLevel));
if (!mBatteryStatusFeatureProvider.triggerBatteryStatusUpdate(this, info)) {
- if (info.remainingLabel == null) {
+ if (BatteryUtils.isBatteryDefenderOn(info)) {
+ mSummary1.setText(null);
+ } else if (info.remainingLabel == null) {
mSummary1.setText(info.statusLabel);
} else {
mSummary1.setText(info.remainingLabel);
diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java
index 1935c35..5d7b325 100644
--- a/src/com/android/settings/fuelgauge/BatteryInfo.java
+++ b/src/com/android/settings/fuelgauge/BatteryInfo.java
@@ -45,6 +45,7 @@
public CharSequence remainingLabel;
public int batteryLevel;
public boolean discharging = true;
+ public boolean isOverheated;
public long remainingTimeUs = 0;
public long averageTimeToDischarge = EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN;
public String batteryPercentString;
@@ -232,6 +233,9 @@
info.batteryPercentString = Utils.formatPercentage(info.batteryLevel);
info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
info.averageTimeToDischarge = estimate.getAverageDischargeTime();
+ info.isOverheated = batteryBroadcast.getIntExtra(
+ BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN)
+ == BatteryManager.BATTERY_HEALTH_OVERHEAT;
info.statusLabel = Utils.getBatteryStatus(context, batteryBroadcast);
if (!info.mCharging) {
@@ -251,7 +255,12 @@
BatteryManager.BATTERY_STATUS_UNKNOWN);
info.discharging = false;
info.suggestionLabel = null;
- if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
+ if (info.isOverheated && status != BatteryManager.BATTERY_STATUS_FULL) {
+ info.remainingLabel = null;
+ int chargingLimitedResId = R.string.power_charging_limited;
+ info.chargeLabel =
+ context.getString(chargingLimitedResId, info.batteryPercentString);
+ } else if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
info.remainingTimeUs = chargeTime;
CharSequence timeString = StringUtil.formatElapsedTime(context,
PowerUtil.convertUsToMs(info.remainingTimeUs), false /* withSeconds */);
diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java
index 4725459..5b1f096 100644
--- a/src/com/android/settings/fuelgauge/BatteryUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryUtils.java
@@ -404,6 +404,13 @@
}
/**
+ * Return {@code true} if battery is overheated and charging.
+ */
+ public static boolean isBatteryDefenderOn(BatteryInfo batteryInfo) {
+ return batteryInfo.isOverheated && !batteryInfo.discharging;
+ }
+
+ /**
* Find package uid from package name
*
* @param packageName used to find the uid
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
index 8d6e07d..0c916b2 100644
--- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java
@@ -23,6 +23,7 @@
import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.fuelgauge.batterytip.detectors.BatteryDefenderDetector;
import com.android.settings.fuelgauge.batterytip.detectors.EarlyWarningDetector;
import com.android.settings.fuelgauge.batterytip.detectors.HighUsageDetector;
import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector;
@@ -72,6 +73,7 @@
batteryInfo.discharging).detect());
tips.add(new SmartBatteryDetector(policy, context.getContentResolver()).detect());
tips.add(new EarlyWarningDetector(policy, context).detect());
+ tips.add(new BatteryDefenderDetector(batteryInfo).detect());
tips.add(new SummaryDetector(policy, batteryInfo.averageTimeToDischarge).detect());
// Disable this feature now since it introduces false positive cases. We will try to improve
// it in the future.
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java
index cdefe4d..e88a494 100644
--- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java
@@ -29,6 +29,7 @@
import com.android.internal.util.CollectionUtils;
import com.android.settings.SettingsActivity;
import com.android.settings.core.InstrumentedPreferenceFragment;
+import com.android.settings.fuelgauge.batterytip.actions.BatteryDefenderAction;
import com.android.settings.fuelgauge.batterytip.actions.BatterySaverAction;
import com.android.settings.fuelgauge.batterytip.actions.BatteryTipAction;
import com.android.settings.fuelgauge.batterytip.actions.OpenBatterySaverAction;
@@ -111,6 +112,8 @@
}
case BatteryTip.TipType.REMOVE_APP_RESTRICTION:
return new UnrestrictAppAction(settingsActivity, (UnrestrictAppTip) batteryTip);
+ case BatteryTip.TipType.BATTERY_DEFENDER:
+ return new BatteryDefenderAction(settingsActivity);
default:
return null;
}
diff --git a/src/com/android/settings/fuelgauge/batterytip/actions/BatteryDefenderAction.java b/src/com/android/settings/fuelgauge/batterytip/actions/BatteryDefenderAction.java
new file mode 100644
index 0000000..b8f5483
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/actions/BatteryDefenderAction.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batterytip.actions;
+
+import android.content.Intent;
+
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settingslib.HelpUtils;
+
+/**
+ * Action to open the Support Center article
+ */
+public class BatteryDefenderAction extends BatteryTipAction {
+ private SettingsActivity mSettingsActivity;
+
+ public BatteryDefenderAction(SettingsActivity settingsActivity) {
+ super(settingsActivity.getApplicationContext());
+ mSettingsActivity = settingsActivity;
+ }
+
+ /**
+ * Handle the action when user clicks positive button
+ */
+ @Override
+ public void handlePositiveAction(int metricsKey) {
+ final Intent intent = HelpUtils.getHelpIntent(
+ mContext,
+ mContext.getString(R.string.help_url_battery_defender),
+ getClass().getName());
+ if (intent != null) {
+ mSettingsActivity.startActivityForResult(intent, 0);
+ }
+ // TODO(b/173985153): Add logging enums for Battery Defender.
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetector.java
new file mode 100644
index 0000000..dc33026
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetector.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batterytip.detectors;
+
+import com.android.settings.fuelgauge.BatteryInfo;
+import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.fuelgauge.batterytip.tips.BatteryDefenderTip;
+import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
+
+/**
+ * Detect whether the battery is overheated
+ */
+public class BatteryDefenderDetector implements BatteryTipDetector {
+ private BatteryInfo mBatteryInfo;
+
+ public BatteryDefenderDetector(BatteryInfo batteryInfo) {
+ mBatteryInfo = batteryInfo;
+ }
+
+ @Override
+ public BatteryTip detect() {
+ final int state =
+ BatteryUtils.isBatteryDefenderOn(mBatteryInfo)
+ ? BatteryTip.StateType.NEW
+ : BatteryTip.StateType.INVISIBLE;
+ return new BatteryDefenderTip(state);
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java
new file mode 100644
index 0000000..cd23e50
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTip.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batterytip.tips;
+
+import android.content.Context;
+import android.os.Parcel;
+
+import com.android.settings.R;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+/**
+ * Tip to show current battery is overheated
+ */
+public class BatteryDefenderTip extends BatteryTip {
+
+ public BatteryDefenderTip(@StateType int state) {
+ super(TipType.BATTERY_DEFENDER, state, false /* showDialog */);
+ }
+
+ private BatteryDefenderTip(Parcel in) {
+ super(in);
+ }
+
+ @Override
+ public CharSequence getTitle(Context context) {
+ return context.getString(R.string.battery_tip_limited_temporarily_title);
+ }
+
+ @Override
+ public CharSequence getSummary(Context context) {
+ return context.getString(R.string.battery_tip_limited_temporarily_summary);
+ }
+
+ @Override
+ public int getIconId() {
+ return R.drawable.ic_battery_status_good_24dp;
+ }
+
+ @Override
+ public void updateState(BatteryTip tip) {
+ mState = tip.mState;
+ }
+
+ @Override
+ public void log(Context context, MetricsFeatureProvider metricsFeatureProvider) {
+ // TODO(b/173985153): Add logging enums for Battery Defender.
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ public BatteryTip createFromParcel(Parcel in) {
+ return new BatteryDefenderTip(in);
+ }
+
+ public BatteryTip[] newArray(int size) {
+ return new BatteryDefenderTip[size];
+ }
+ };
+
+}
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
index 12fcaba..3b849be 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java
@@ -57,7 +57,8 @@
TipType.APP_RESTRICTION,
TipType.REDUCED_BATTERY,
TipType.LOW_BATTERY,
- TipType.REMOVE_APP_RESTRICTION})
+ TipType.REMOVE_APP_RESTRICTION,
+ TipType.BATTERY_DEFENDER})
public @interface TipType {
int SMART_BATTERY_MANAGER = 0;
int APP_RESTRICTION = 1;
@@ -67,20 +68,22 @@
int LOW_BATTERY = 5;
int SUMMARY = 6;
int REMOVE_APP_RESTRICTION = 7;
+ int BATTERY_DEFENDER = 8;
}
@VisibleForTesting
static final SparseIntArray TIP_ORDER;
static {
TIP_ORDER = new SparseIntArray();
- TIP_ORDER.append(TipType.APP_RESTRICTION, 0);
- TIP_ORDER.append(TipType.BATTERY_SAVER, 1);
- TIP_ORDER.append(TipType.HIGH_DEVICE_USAGE, 2);
- TIP_ORDER.append(TipType.LOW_BATTERY, 3);
- TIP_ORDER.append(TipType.SUMMARY, 4);
- TIP_ORDER.append(TipType.SMART_BATTERY_MANAGER, 5);
- TIP_ORDER.append(TipType.REDUCED_BATTERY, 6);
- TIP_ORDER.append(TipType.REMOVE_APP_RESTRICTION, 7);
+ TIP_ORDER.append(TipType.BATTERY_DEFENDER, 0);
+ TIP_ORDER.append(TipType.APP_RESTRICTION, 1);
+ TIP_ORDER.append(TipType.BATTERY_SAVER, 2);
+ TIP_ORDER.append(TipType.HIGH_DEVICE_USAGE, 3);
+ TIP_ORDER.append(TipType.LOW_BATTERY, 4);
+ TIP_ORDER.append(TipType.SUMMARY, 5);
+ TIP_ORDER.append(TipType.SMART_BATTERY_MANAGER, 6);
+ TIP_ORDER.append(TipType.REDUCED_BATTERY, 7);
+ TIP_ORDER.append(TipType.REMOVE_APP_RESTRICTION, 8);
}
private static final String KEY_PREFIX = "key_battery_tip";
diff --git a/src/com/android/settings/network/AirplaneSafeNetworksPreferenceController.java b/src/com/android/settings/network/AirplaneSafeNetworksPreferenceController.java
new file mode 100644
index 0000000..fa80c38
--- /dev/null
+++ b/src/com/android/settings/network/AirplaneSafeNetworksPreferenceController.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.AirplaneModeEnabler;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.widget.GenericSwitchController;
+import com.android.settings.wifi.WifiEnabler;
+import com.android.settingslib.RestrictedSwitchPreference;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+public class AirplaneSafeNetworksPreferenceController extends AbstractPreferenceController
+ implements LifecycleObserver, AirplaneModeEnabler.OnAirplaneModeChangedListener {
+
+ private static final String PREFERENCE_KEY = "airplane_safe_networks";
+
+ private RestrictedSwitchPreference mPreference;
+
+ private AirplaneModeEnabler mAirplaneModeEnabler;
+ private WifiEnabler mWifiEnabler;
+
+ public AirplaneSafeNetworksPreferenceController(Context context, Lifecycle lifecycle) {
+ super(context);
+ if (lifecycle == null) {
+ throw new IllegalArgumentException("Lifecycle must be set");
+ }
+
+ mAirplaneModeEnabler = new AirplaneModeEnabler(mContext, this);
+ lifecycle.addObserver(this);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return PREFERENCE_KEY;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mAirplaneModeEnabler.isAirplaneModeOn();
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ mPreference = screen.findPreference(getPreferenceKey());
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_START)
+ public void onStart() {
+ mAirplaneModeEnabler.start();
+ if (mPreference != null) {
+ mWifiEnabler = new WifiEnabler(mContext, new GenericSwitchController(mPreference),
+ FeatureFactory.getFactory(mContext).getMetricsFeatureProvider());
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+ public void onStop() {
+ mAirplaneModeEnabler.stop();
+ if (mWifiEnabler != null) {
+ mWifiEnabler.teardownSwitchController();
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ public void onResume() {
+ if (mWifiEnabler != null) {
+ mWifiEnabler.resume(mContext);
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+ public void onPause() {
+ if (mWifiEnabler != null) {
+ mWifiEnabler.pause();
+ }
+ }
+
+ @Override
+ public void onAirplaneModeChanged(boolean isAirplaneModeOn) {
+ if (mPreference != null) {
+ mPreference.setVisible(isAirplaneModeOn);
+ }
+ }
+}
diff --git a/src/com/android/settings/network/AirplaneSafeNetworksSlice.java b/src/com/android/settings/network/AirplaneSafeNetworksSlice.java
new file mode 100644
index 0000000..fbef282
--- /dev/null
+++ b/src/com/android/settings/network/AirplaneSafeNetworksSlice.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.util.Log;
+
+import androidx.annotation.IntDef;
+import androidx.core.graphics.drawable.IconCompat;
+import androidx.slice.Slice;
+import androidx.slice.builders.ListBuilder;
+import androidx.slice.builders.ListBuilder.RowBuilder;
+import androidx.slice.builders.SliceAction;
+
+import com.android.settings.AirplaneModeEnabler;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settings.slices.CustomSliceRegistry;
+import com.android.settings.slices.CustomSliceable;
+import com.android.settings.slices.SliceBackgroundWorker;
+import com.android.settings.slices.SliceBroadcastReceiver;
+import com.android.settingslib.WirelessUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * {@link CustomSliceable} for airplane-safe networks, used by generic clients.
+ */
+// TODO(b/173413889): Need to update the slice to Button style.
+public class AirplaneSafeNetworksSlice implements CustomSliceable,
+ AirplaneModeEnabler.OnAirplaneModeChangedListener {
+
+ private static final String TAG = "AirplaneSafeNetworksSlice";
+
+ public static final String ACTION_INTENT_EXTRA = "action";
+
+ /**
+ * Annotation for different action of the slice.
+ *
+ * {@code VIEW_AIRPLANE_SAFE_NETWORKS} for action of turning on Wi-Fi.
+ * {@code TURN_OFF_AIRPLANE_MODE} for action of turning off Airplane Mode.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ Action.VIEW_AIRPLANE_SAFE_NETWORKS,
+ Action.TURN_OFF_AIRPLANE_MODE,
+ })
+ public @interface Action {
+ int VIEW_AIRPLANE_SAFE_NETWORKS = 1;
+ int TURN_OFF_AIRPLANE_MODE = 2;
+ }
+
+ private final Context mContext;
+ private final AirplaneModeEnabler mAirplaneModeEnabler;
+ private final WifiManager mWifiManager;
+
+ public AirplaneSafeNetworksSlice(Context context) {
+ mContext = context;
+ mAirplaneModeEnabler = new AirplaneModeEnabler(context, this);
+ mWifiManager = mContext.getSystemService(WifiManager.class);
+ }
+
+ private static void logd(String s) {
+ Log.d(TAG, s);
+ }
+
+ @Override
+ public Slice getSlice() {
+ if (!WirelessUtils.isAirplaneModeOn(mContext)) {
+ return null;
+ }
+
+ return new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
+ .addRow(new RowBuilder()
+ .setTitle(getTitle())
+ .setPrimaryAction(getSliceAction()))
+ .build();
+ }
+
+ @Override
+ public Uri getUri() {
+ return CustomSliceRegistry.AIRPLANE_SAFE_NETWORKS_SLICE_URI;
+ }
+
+ @Override
+ public void onNotifyChange(Intent intent) {
+ final int action = intent.getIntExtra(ACTION_INTENT_EXTRA, 0);
+ if (action == Action.VIEW_AIRPLANE_SAFE_NETWORKS) {
+ if (!mWifiManager.isWifiEnabled()) {
+ logd("Action: turn on WiFi");
+ mWifiManager.setWifiEnabled(true);
+ }
+ } else if (action == Action.TURN_OFF_AIRPLANE_MODE) {
+ if (WirelessUtils.isAirplaneModeOn(mContext)) {
+ logd("Action: turn off Airplane mode");
+ mAirplaneModeEnabler.setAirplaneMode(false);
+ }
+ }
+ }
+
+ @Override
+ public void onAirplaneModeChanged(boolean isAirplaneModeOn) {
+ final AirplaneSafeNetworksWorker worker = SliceBackgroundWorker.getInstance(getUri());
+ if (worker != null) {
+ worker.updateSlice();
+ }
+ }
+
+ @Override
+ public Intent getIntent() {
+ return new Intent(getUri().toString())
+ .setData(getUri())
+ .setClass(mContext, SliceBroadcastReceiver.class)
+ .putExtra(ACTION_INTENT_EXTRA, getAction());
+ }
+
+ @Action
+ private int getAction() {
+ return mWifiManager.isWifiEnabled()
+ ? Action.TURN_OFF_AIRPLANE_MODE
+ : Action.VIEW_AIRPLANE_SAFE_NETWORKS;
+ }
+
+ private String getTitle() {
+ return mContext.getText(
+ (getAction() == Action.VIEW_AIRPLANE_SAFE_NETWORKS)
+ ? R.string.view_airplane_safe_networks
+ : R.string.turn_off_airplane_mode).toString();
+ }
+
+ private SliceAction getSliceAction() {
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
+ 0 /* requestCode */, getIntent(),
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ final IconCompat icon = Utils.createIconWithDrawable(new ColorDrawable(Color.TRANSPARENT));
+ return SliceAction.createDeeplink(pendingIntent, icon, ListBuilder.ACTION_WITH_LABEL,
+ getTitle());
+ }
+
+ @Override
+ public Class getBackgroundWorkerClass() {
+ return AirplaneSafeNetworksWorker.class;
+ }
+
+ public static class AirplaneSafeNetworksWorker extends SliceBackgroundWorker {
+
+ private final IntentFilter mIntentFilter;
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) {
+ notifySliceChange();
+ }
+ }
+ };
+
+ public AirplaneSafeNetworksWorker(Context context, Uri uri) {
+ super(context, uri);
+ mIntentFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ }
+
+ @Override
+ protected void onSlicePinned() {
+ getContext().registerReceiver(mBroadcastReceiver, mIntentFilter);
+ }
+
+ @Override
+ protected void onSliceUnpinned() {
+ getContext().unregisterReceiver(mBroadcastReceiver);
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+
+ public void updateSlice() {
+ notifySliceChange();
+ }
+ }
+}
diff --git a/src/com/android/settings/network/AllInOneTetherPreferenceController.java b/src/com/android/settings/network/AllInOneTetherPreferenceController.java
index cc55e7a..0f4905a 100644
--- a/src/com/android/settings/network/AllInOneTetherPreferenceController.java
+++ b/src/com/android/settings/network/AllInOneTetherPreferenceController.java
@@ -42,7 +42,7 @@
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.FeatureFlags;
-import com.android.settings.widget.PrimarySwitchController;
+import com.android.settings.widget.GenericSwitchController;
import com.android.settings.widget.PrimarySwitchPreference;
import com.android.settingslib.TetherUtil;
@@ -191,7 +191,7 @@
void initEnabler(Lifecycle lifecycle) {
if (mPreference != null) {
mTetherEnabler = new TetherEnabler(
- mContext, new PrimarySwitchController(mPreference), mBluetoothPan);
+ mContext, new GenericSwitchController(mPreference), mBluetoothPan);
if (lifecycle != null) {
lifecycle.addObserver(mTetherEnabler);
}
diff --git a/src/com/android/settings/network/MobileNetworkListController.java b/src/com/android/settings/network/MobileNetworkListController.java
index f1980b2..77c93ff 100644
--- a/src/com/android/settings/network/MobileNetworkListController.java
+++ b/src/com/android/settings/network/MobileNetworkListController.java
@@ -128,7 +128,7 @@
pref.setOnPreferenceClickListener(clickedPref -> {
if (!info.isEmbedded() && !mSubscriptionManager.isActiveSubscriptionId(subId)
&& !SubscriptionUtil.showToggleForPhysicalSim(mSubscriptionManager)) {
- mSubscriptionManager.setSubscriptionEnabled(subId, true);
+ SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, subId, true);
} else {
final Intent intent = new Intent(mContext, MobileNetworkActivity.class);
intent.putExtra(Settings.EXTRA_SUB_ID, info.getSubscriptionId());
diff --git a/src/com/android/settings/network/MobileNetworkSummaryController.java b/src/com/android/settings/network/MobileNetworkSummaryController.java
index 405d365..08da41a 100644
--- a/src/com/android/settings/network/MobileNetworkSummaryController.java
+++ b/src/com/android/settings/network/MobileNetworkSummaryController.java
@@ -189,7 +189,8 @@
final int subId = info.getSubscriptionId();
if (!info.isEmbedded() && !mSubscriptionManager.isActiveSubscriptionId(subId)
&& !SubscriptionUtil.showToggleForPhysicalSim(mSubscriptionManager)) {
- mSubscriptionManager.setSubscriptionEnabled(subId, true);
+ SubscriptionUtil.startToggleSubscriptionDialogActivity(
+ mContext, subId, true);
} else {
final Intent intent = new Intent(mContext, MobileNetworkActivity.class);
intent.putExtra(Settings.EXTRA_SUB_ID, subs.get(0).getSubscriptionId());
diff --git a/src/com/android/settings/network/MultiNetworkHeaderController.java b/src/com/android/settings/network/MultiNetworkHeaderController.java
index e99cbb6..1143546 100644
--- a/src/com/android/settings/network/MultiNetworkHeaderController.java
+++ b/src/com/android/settings/network/MultiNetworkHeaderController.java
@@ -27,9 +27,14 @@
import com.android.settings.wifi.WifiConnectionPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
-// This controls a header at the top of the Network & internet page that only appears when there
-// are two or more active mobile subscriptions. It shows an overview of available network
-// connections with an entry for wifi (if connected) and an entry for each subscription.
+/**
+ * This controls a header at the top of the Network & internet page that only appears when there
+ * are two or more active mobile subscriptions. It shows an overview of available network
+ * connections with an entry for wifi (if connected) and an entry for each subscription.
+ *
+ * TODO(tomhsu) when provider model is completed, this class will be replaced
+ * by {@link NetworkMobileProviderController}
+ */
public class MultiNetworkHeaderController extends BasePreferenceController implements
WifiConnectionPreferenceController.UpdateListener,
SubscriptionsPreferenceController.UpdateListener {
diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java
index 76a84bb..e56db27 100644
--- a/src/com/android/settings/network/NetworkDashboardFragment.java
+++ b/src/com/android/settings/network/NetworkDashboardFragment.java
@@ -147,6 +147,7 @@
controllers.add(privateDnsPreferenceController);
if (Utils.isProviderModelEnabled(context)) {
controllers.add(new NetworkProviderCallsSmsController(context, lifecycle));
+ controllers.add(new AirplaneSafeNetworksPreferenceController(context, lifecycle));
}
return controllers;
}
diff --git a/src/com/android/settings/network/NetworkMobileProviderController.java b/src/com/android/settings/network/NetworkMobileProviderController.java
new file mode 100644
index 0000000..4c29256
--- /dev/null
+++ b/src/com/android/settings/network/NetworkMobileProviderController.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+/**
+ * This controls mobile network display of the internet page that only appears when there
+ * are active mobile subscriptions. It shows an overview of available mobile network
+ * connections with an entry for each subscription.
+ *
+ * {@link NetworkMobileProviderController} is used to show subscription status on internet
+ * page for provider model. This original class can refer to {@link MultiNetworkHeaderController},
+ *
+ */
+public class NetworkMobileProviderController extends BasePreferenceController implements
+ SubscriptionsPreferenceController.UpdateListener {
+
+ private static final String TAG = NetworkMobileProviderController.class.getSimpleName();
+
+ public static final String PREF_KEY_PROVIDER_MOBILE_NETWORK = "provider_model_mobile_network";
+ private static final int PREFERENCE_START_ORDER = 10;
+
+ private PreferenceCategory mPreferenceCategory;
+ private PreferenceScreen mPreferenceScreen;
+
+ private SubscriptionsPreferenceController mSubscriptionsController;
+
+ private int mOriginalExpandedChildrenCount;
+
+ public NetworkMobileProviderController(Context context, String key) {
+ super(context, key);
+ }
+
+ /**
+ * Initialize NetworkMobileProviderController
+ * @param lifecycle Lifecycle of Settings
+ */
+ public void init(Lifecycle lifecycle) {
+ mSubscriptionsController = createSubscriptionsController(lifecycle);
+ }
+
+ @VisibleForTesting
+ SubscriptionsPreferenceController createSubscriptionsController(Lifecycle lifecycle) {
+ if (mSubscriptionsController == null) {
+ return new SubscriptionsPreferenceController(
+ mContext,
+ lifecycle,
+ this,
+ PREF_KEY_PROVIDER_MOBILE_NETWORK,
+ PREFERENCE_START_ORDER);
+ }
+ return mSubscriptionsController;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+ if (mSubscriptionsController == null) {
+ Log.e(TAG, "[displayPreference] SubscriptionsController is null.");
+ return;
+ }
+ mPreferenceScreen = screen;
+ mOriginalExpandedChildrenCount = mPreferenceScreen.getInitialExpandedChildrenCount();
+ mPreferenceCategory = screen.findPreference(PREF_KEY_PROVIDER_MOBILE_NETWORK);
+ mPreferenceCategory.setVisible(isAvailable());
+ // TODO(tomhsu) For the provider model, subscriptionsController shall do more
+ // implementation of preference type change and summary control.
+ mSubscriptionsController.displayPreference(screen);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ if (mSubscriptionsController == null) {
+ return CONDITIONALLY_UNAVAILABLE;
+ }
+ return mSubscriptionsController.isAvailable() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
+ }
+
+ @Override
+ public void onChildrenUpdated() {
+ final boolean available = isAvailable();
+ // TODO(b/129893781) we need a better way to express where the advanced collapsing starts
+ // for preference groups that have items dynamically added/removed in the top expanded
+ // section.
+ if (mOriginalExpandedChildrenCount != Integer.MAX_VALUE) {
+ if (available) {
+ mPreferenceScreen.setInitialExpandedChildrenCount(
+ mOriginalExpandedChildrenCount + mPreferenceCategory.getPreferenceCount());
+ } else {
+ mPreferenceScreen.setInitialExpandedChildrenCount(mOriginalExpandedChildrenCount);
+ }
+ }
+ mPreferenceCategory.setVisible(available);
+ }
+}
diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java
index df62190..90e3ac4 100644
--- a/src/com/android/settings/network/NetworkProviderSettings.java
+++ b/src/com/android/settings/network/NetworkProviderSettings.java
@@ -208,6 +208,12 @@
private LinkablePreference mStatusMessagePreference;
/**
+ * Mobile networks list for provider model
+ */
+ private static final String PREF_KEY_PROVIDER_MOBILE_NETWORK = "provider_model_mobile_network";
+ private NetworkMobileProviderController mNetworkMobileProviderController;
+
+ /**
* Tracks whether the user initiated a connection via clicking in order to autoscroll to the
* network once connected.
*/
@@ -255,6 +261,16 @@
mDataUsagePreference.setTemplate(NetworkTemplate.buildTemplateWifiWildcard(),
0 /*subId*/,
null /*service*/);
+ addNetworkMobileProviderController();
+ }
+
+ private void addNetworkMobileProviderController() {
+ if (mNetworkMobileProviderController == null) {
+ mNetworkMobileProviderController = new NetworkMobileProviderController(
+ getContext(), PREF_KEY_PROVIDER_MOBILE_NETWORK);
+ }
+ mNetworkMobileProviderController.init(getSettingsLifecycle());
+ mNetworkMobileProviderController.displayPreference(getPreferenceScreen());
}
@Override
@@ -341,6 +357,12 @@
}
@Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+
+ }
+
+ @Override
public void onDestroyView() {
mWorkerThread.quit();
diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java
index e2c8997..cff8f55 100644
--- a/src/com/android/settings/network/SubscriptionUtil.java
+++ b/src/com/android/settings/network/SubscriptionUtil.java
@@ -28,6 +28,7 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.UiccSlotInfo;
+import android.util.Log;
import androidx.annotation.VisibleForTesting;
@@ -284,14 +285,31 @@
}
}
- /** Starts a dialog activity to handle SIM enabling/disabling. */
+ /**
+ * Starts a dialog activity to handle SIM enabling/disabling.
+ * @param context {@code Context}
+ * @param subId The id of subscription need to be enabled or disabled.
+ * @param enable Whether the subscription with {@code subId} should be enabled or disabled.
+ */
public static void startToggleSubscriptionDialogActivity(
Context context, int subId, boolean enable) {
+ if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
+ Log.i(TAG, "Unable to toggle subscription due to invalid subscription ID.");
+ return;
+ }
context.startActivity(ToggleSubscriptionDialogActivity.getIntent(context, subId, enable));
}
- /** Starts a dialog activity to handle eSIM deletion. */
+ /**
+ * Starts a dialog activity to handle eSIM deletion.
+ * @param context {@code Context}
+ * @param subId The id of subscription need to be deleted.
+ */
public static void startDeleteEuiccSubscriptionDialogActivity(Context context, int subId) {
+ if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
+ Log.i(TAG, "Unable to delete subscription due to invalid subscription ID.");
+ return;
+ }
context.startActivity(DeleteEuiccSubscriptionDialogActivity.getIntent(context, subId));
}
diff --git a/src/com/android/settings/network/SubscriptionsPreferenceController.java b/src/com/android/settings/network/SubscriptionsPreferenceController.java
index 53d6c30..864078c 100644
--- a/src/com/android/settings/network/SubscriptionsPreferenceController.java
+++ b/src/com/android/settings/network/SubscriptionsPreferenceController.java
@@ -41,6 +41,7 @@
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
+import com.android.settings.Utils;
import com.android.settings.network.telephony.DataConnectivityListener;
import com.android.settings.network.telephony.MobileNetworkActivity;
import com.android.settings.network.telephony.MobileNetworkUtils;
@@ -77,7 +78,6 @@
// Map of subscription id to Preference
private Map<Integer, Preference> mSubscriptionPreferences;
private int mStartOrder;
-
/**
* This interface lets a parent of this class know that some change happened - this could
* either be because overall availability changed, or because we've added/removed/updated some
@@ -291,7 +291,7 @@
// subscriptions with same group UUID.
.filter(subInfo ->
isSubscriptionCanBeDisplayed(mContext, subInfo.getSubscriptionId()))
- .count() >= 2;
+ .count() >= (Utils.isProviderModelEnabled(mContext) ? 1 : 2);
}
@Override
diff --git a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java
index 46c5234..1fdc191 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkSwitchController.java
@@ -78,8 +78,8 @@
mSwitchBar.getSwitch().setOnBeforeCheckedChangeListener((toggleSwitch, isChecked) -> {
// TODO b/135222940: re-evaluate whether to use
// mSubscriptionManager#isSubscriptionEnabled
- if (mSubscriptionManager.isActiveSubscriptionId(mSubId) != isChecked
- && (!mSubscriptionManager.setSubscriptionEnabled(mSubId, isChecked))) {
+ if (mSubscriptionManager.isActiveSubscriptionId(mSubId) != isChecked) {
+ SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, mSubId, isChecked);
return true;
}
return false;
diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
index 919415b..4af42ba 100644
--- a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
+++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java
@@ -27,6 +27,7 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.SidecarFragment;
import com.android.settings.network.EnableMultiSimSidecar;
@@ -34,6 +35,7 @@
import com.android.settings.network.SwitchToEuiccSubscriptionSidecar;
import com.android.settings.network.SwitchToRemovableSlotSidecar;
import com.android.settings.network.UiccSlotUtil;
+import com.android.settings.sim.SimActivationNotifier;
import com.google.common.collect.ImmutableList;
@@ -45,7 +47,8 @@
private static final String TAG = "ToggleSubscriptionDialogActivity";
// Arguments
- private static final String ARG_enable = "enable";
+ @VisibleForTesting
+ public static final String ARG_enable = "enable";
// Dialog tags
private static final int DIALOG_TAG_DISABLE_SIM_CONFIRMATION = 1;
private static final int DIALOG_TAG_ENABLE_SIM_CONFIRMATION = 2;
@@ -189,9 +192,8 @@
return;
}
Log.i(TAG, "User confirmed reboot to enable DSDS.");
+ SimActivationNotifier.setShowSimSettingsNotification(this, true);
mTelMgr.switchMultiSimConfig(NUM_OF_SIMS_FOR_DSDS);
- // TODO(b/170507290): Store a bit in preferences for displaying the notification
- // after the reboot.
break;
case DIALOG_TAG_ENABLE_SIM_CONFIRMATION:
Log.i(TAG, "User confirmed to enable the subscription.");
@@ -294,6 +296,7 @@
private void handleTogglePsimAction() {
if (mSubscriptionManager.canDisablePhysicalSubscription() && mSubInfo != null) {
mSubscriptionManager.setUiccApplicationsEnabled(mSubInfo.getSubscriptionId(), mEnable);
+ finish();
} else {
Log.i(
TAG,
diff --git a/src/com/android/settings/notification/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java
index 9df548c..65ad571 100644
--- a/src/com/android/settings/notification/NotificationAccessSettings.java
+++ b/src/com/android/settings/notification/NotificationAccessSettings.java
@@ -35,7 +35,6 @@
import android.view.View;
import android.widget.Toast;
-import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
@@ -48,6 +47,7 @@
import com.android.settings.widget.EmptyTextSettings;
import com.android.settingslib.applications.ServiceListing;
import com.android.settingslib.search.SearchIndexable;
+import com.android.settingslib.widget.apppreference.AppPreference;
import java.util.List;
@@ -140,7 +140,7 @@
Log.e(TAG, "can't find package name", e);
}
- final Preference pref = new Preference(getPrefContext());
+ final AppPreference pref = new AppPreference(getPrefContext());
pref.setTitle(title);
pref.setIcon(mIconDrawableFactory.getBadgedIcon(service, service.applicationInfo,
UserHandle.getUserId(service.applicationInfo.uid)));
diff --git a/src/com/android/settings/notification/history/NotificationSbnAdapter.java b/src/com/android/settings/notification/history/NotificationSbnAdapter.java
index 0eb14ba..f836855 100644
--- a/src/com/android/settings/notification/history/NotificationSbnAdapter.java
+++ b/src/com/android/settings/notification/history/NotificationSbnAdapter.java
@@ -52,6 +52,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.ContrastColorUtil;
import com.android.settings.R;
+import com.android.settingslib.Utils;
import java.util.ArrayList;
import java.util.HashMap;
@@ -79,8 +80,8 @@
mPm = pm;
mUserBadgeCache = new HashMap<>();
mValues = new ArrayList<>();
- mBackgroundColor = mContext.getColor(
- com.android.internal.R.color.notification_material_background_color);
+ mBackgroundColor = Utils.getColorAttrDefaultColor(context,
+ android.R.attr.colorBackground);
Configuration currentConfig = mContext.getResources().getConfiguration();
mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
== Configuration.UI_MODE_NIGHT_YES;
diff --git a/src/com/android/settings/panel/InternetConnectivityPanel.java b/src/com/android/settings/panel/InternetConnectivityPanel.java
index db0c5e3..6ae7089 100644
--- a/src/com/android/settings/panel/InternetConnectivityPanel.java
+++ b/src/com/android/settings/panel/InternetConnectivityPanel.java
@@ -23,6 +23,7 @@
import android.provider.Settings;
import com.android.settings.R;
+import com.android.settings.Utils;
import com.android.settings.network.AirplaneModePreferenceController;
import com.android.settings.slices.CustomSliceRegistry;
@@ -58,7 +59,11 @@
final List<Uri> uris = new ArrayList<>();
uris.add(CustomSliceRegistry.WIFI_SLICE_URI);
uris.add(CustomSliceRegistry.MOBILE_DATA_SLICE_URI);
- uris.add(AirplaneModePreferenceController.SLICE_URI);
+ if (Utils.isProviderModelEnabled(mContext)) {
+ uris.add(CustomSliceRegistry.AIRPLANE_SAFE_NETWORKS_SLICE_URI);
+ } else {
+ uris.add(AirplaneModePreferenceController.SLICE_URI);
+ }
return uris;
}
diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java
index 35e369e..8b0c2c9 100644
--- a/src/com/android/settings/password/ChooseLockGeneric.java
+++ b/src/com/android/settings/password/ChooseLockGeneric.java
@@ -221,9 +221,19 @@
mForBiometrics = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
- mRequestedMinComplexity = intent
+ final int complexityFromIntent = intent
.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_NONE);
- mCallerAppName =
+ final int complexityFromAdmin = mLockPatternUtils.getRequestedPasswordComplexity(
+ mUserId);
+ mRequestedMinComplexity = Math.max(complexityFromIntent, complexityFromAdmin);
+ final boolean isComplexityProvidedByAdmin = (complexityFromAdmin > complexityFromIntent)
+ && mRequestedMinComplexity > PASSWORD_COMPLEXITY_NONE;
+
+ // If the complexity is provided by the admin, do not get the caller app's name.
+ // If the app requires, for example, low complexity, and the admin requires high
+ // complexity, it does not make sense to show a footer telling the user it's the app
+ // requesting a particular complexity because the admin-set complexity will override it.
+ mCallerAppName = isComplexityProvidedByAdmin ? null :
intent.getStringExtra(EXTRA_KEY_CALLER_APP_NAME);
mIsCallingAppAdmin = intent
.getBooleanExtra(EXTRA_KEY_IS_CALLING_APP_ADMIN, /* defValue= */ false);
@@ -669,8 +679,9 @@
final PreferenceScreen entries = getPreferenceScreen();
int adminEnforcedQuality = mDpm.getPasswordQuality(null, mUserId);
- EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal.checkIfPasswordQualityIsSet(
- getActivity(), mUserId);
+ EnforcedAdmin enforcedAdmin =
+ RestrictedLockUtilsInternal.checkIfPasswordQualityIsSet(getActivity(),
+ mUserId);
// If we are to unify a work challenge at the end of the credential enrollment, manually
// merge any password policy from that profile here, so we are enrolling a compliant
// password. This is because once unified, the profile's password policy will
diff --git a/src/com/android/settings/password/ChooseLockPassword.java b/src/com/android/settings/password/ChooseLockPassword.java
index a73b73a..0c84ba9 100644
--- a/src/com/android/settings/password/ChooseLockPassword.java
+++ b/src/com/android/settings/password/ChooseLockPassword.java
@@ -421,6 +421,8 @@
if (mUnificationProfileId != UserHandle.USER_NULL) {
mMinMetrics.maxWith(
mLockPatternUtils.getRequestedPasswordMetrics(mUnificationProfileId));
+ mMinComplexity = Math.max(mMinComplexity,
+ mLockPatternUtils.getRequestedPasswordComplexity(mUnificationProfileId));
}
if (intent.getBooleanExtra(
diff --git a/src/com/android/settings/security/CredentialManagementAppAdapter.java b/src/com/android/settings/security/CredentialManagementAppAdapter.java
index 707f598..e56fc63 100644
--- a/src/com/android/settings/security/CredentialManagementAppAdapter.java
+++ b/src/com/android/settings/security/CredentialManagementAppAdapter.java
@@ -59,6 +59,9 @@
private final PackageManager mPackageManager;
private final RecyclerView.RecycledViewPool mViewPool;
+ private final boolean mIncludeHeader;
+ private final boolean mIncludeExpander;
+
/**
* View holder for the header in the request manage credentials screen.
*/
@@ -96,13 +99,29 @@
public class AppAuthenticationViewHolder extends RecyclerView.ViewHolder {
private final ImageView mAppIconView;
private final TextView mAppNameView;
- RecyclerView mChildRecyclerView;
+ private final TextView mNumberOfUrisView;
+ private final ImageView mExpanderIconView;
+ private final RecyclerView mChildRecyclerView;
+ private final List<String> mExpandedApps;
public AppAuthenticationViewHolder(View view) {
super(view);
mAppIconView = view.findViewById(R.id.app_icon);
mAppNameView = view.findViewById(R.id.app_name);
+ mNumberOfUrisView = view.findViewById(R.id.number_of_uris);
+ mExpanderIconView = view.findViewById(R.id.expand);
mChildRecyclerView = view.findViewById(R.id.uris);
+ mExpandedApps = new ArrayList<>();
+
+ mExpanderIconView.setOnClickListener(view1 -> {
+ final String appName = mSortedAppNames.get(getBindingAdapterPosition());
+ if (mExpandedApps.contains(appName)) {
+ mExpandedApps.remove(appName);
+ } else {
+ mExpandedApps.add(appName);
+ }
+ bindPolicyView(appName);
+ });
}
/**
@@ -119,32 +138,63 @@
mAppIconView.setImageDrawable(null);
mAppNameView.setText(appName);
}
- bindChildView(mAppUriAuthentication.get(appName));
+ bindPolicyView(appName);
+ }
+
+ private void bindPolicyView(String appName) {
+ if (mIncludeExpander) {
+ mExpanderIconView.setVisibility(View.VISIBLE);
+ if (mExpandedApps.contains(appName)) {
+ mNumberOfUrisView.setVisibility(View.GONE);
+ mExpanderIconView.setImageResource(R.drawable.ic_expand_less);
+ bindChildView(mAppUriAuthentication.get(appName));
+ } else {
+ mChildRecyclerView.setVisibility(View.GONE);
+ mNumberOfUrisView.setVisibility(View.VISIBLE);
+ mNumberOfUrisView.setText(
+ getNumberOfUrlsText(mAppUriAuthentication.get(appName)));
+ mExpanderIconView.setImageResource(
+ com.android.internal.R.drawable.ic_expand_more);
+ }
+ } else {
+ mNumberOfUrisView.setVisibility(View.GONE);
+ mExpanderIconView.setVisibility(View.GONE);
+ bindChildView(mAppUriAuthentication.get(appName));
+ }
}
/**
* Bind the list of URIs for an app.
*/
- public void bindChildView(Map<Uri, String> urisToAliases) {
+ private void bindChildView(Map<Uri, String> urisToAliases) {
LinearLayoutManager layoutManager = new LinearLayoutManager(
mChildRecyclerView.getContext(), RecyclerView.VERTICAL, false);
layoutManager.setInitialPrefetchItemCount(urisToAliases.size());
UriAuthenticationPolicyAdapter childItemAdapter =
new UriAuthenticationPolicyAdapter(new ArrayList<>(urisToAliases.keySet()));
mChildRecyclerView.setLayoutManager(layoutManager);
+ mChildRecyclerView.setVisibility(View.VISIBLE);
mChildRecyclerView.setAdapter(childItemAdapter);
mChildRecyclerView.setRecycledViewPool(mViewPool);
}
+
+ private String getNumberOfUrlsText(Map<Uri, String> urisToAliases) {
+ String url = urisToAliases.size() > 1 ? " URLs" : " URL";
+ return urisToAliases.size() + url;
+ }
}
public CredentialManagementAppAdapter(Context context, String credentialManagerPackage,
- Map<String, Map<Uri, String>> appUriAuthentication) {
+ Map<String, Map<Uri, String>> appUriAuthentication,
+ boolean includeHeader, boolean includeExpander) {
mContext = context;
mCredentialManagerPackage = credentialManagerPackage;
mPackageManager = context.getPackageManager();
mAppUriAuthentication = appUriAuthentication;
mSortedAppNames = sortPackageNames(mAppUriAuthentication);
mViewPool = new RecyclerView.RecycledViewPool();
+ mIncludeHeader = includeHeader;
+ mIncludeExpander = includeExpander;
}
/**
@@ -198,19 +248,20 @@
if (viewHolder instanceof HeaderViewHolder) {
((HeaderViewHolder) viewHolder).bindView();
} else if (viewHolder instanceof AppAuthenticationViewHolder) {
- ((AppAuthenticationViewHolder) viewHolder).bindView(i - 1);
+ int position = mIncludeHeader ? i - 1 : i;
+ ((AppAuthenticationViewHolder) viewHolder).bindView(position);
}
}
@Override
public int getItemCount() {
// Add an extra view to show the header view
- return mAppUriAuthentication.size() + 1;
+ return mIncludeHeader ? mAppUriAuthentication.size() + 1 : mAppUriAuthentication.size();
}
@Override
public int getItemViewType(int position) {
- if (position == 0) {
+ if (mIncludeHeader && position == 0) {
return HEADER_VIEW;
}
return super.getItemViewType(position);
diff --git a/src/com/android/settings/security/CredentialManagementAppButtonsController.java b/src/com/android/settings/security/CredentialManagementAppButtonsController.java
new file mode 100644
index 0000000..9efa098
--- /dev/null
+++ b/src/com/android/settings/security/CredentialManagementAppButtonsController.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.security.IKeyChainService;
+import android.security.KeyChain;
+import android.util.Log;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.ActionButtonsPreference;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Controller that shows the remove button of the credential management app, which allows the user
+ * to remove the credential management app and its certificates.
+ */
+public class CredentialManagementAppButtonsController extends BasePreferenceController {
+
+ private static final String TAG = "CredentialManagementApp";
+
+ private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ private final PackageManager mPackageManager;
+ private final AppOpsManager mAppOpsManager;
+ private boolean mHasCredentialManagerPackage;
+ private String mCredentialManagerPackageName;
+
+ public CredentialManagementAppButtonsController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mPackageManager = context.getPackageManager();
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE_UNSEARCHABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ mExecutor.execute(() -> {
+ try {
+ IKeyChainService service = KeyChain.bind(mContext).getService();
+ mHasCredentialManagerPackage = service.hasCredentialManagementApp();
+ mCredentialManagerPackageName = service.getCredentialManagementAppPackageName();
+ } catch (InterruptedException | RemoteException e) {
+ Log.e(TAG, "Unable to display credential management app buttons");
+ }
+ mHandler.post(() -> displayButtons(screen));
+ });
+ }
+
+ private void displayButtons(PreferenceScreen screen) {
+ if (mHasCredentialManagerPackage) {
+ ((ActionButtonsPreference) screen.findPreference(getPreferenceKey()))
+ .setButton1Text(R.string.remove_credential_management_app)
+ .setButton1Icon(R.drawable.ic_undo_24)
+ .setButton1OnClickListener(view -> removeCredentialManagementApp());
+ }
+ }
+
+ private void removeCredentialManagementApp() {
+ try {
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
+ mCredentialManagerPackageName, 0);
+ mAppOpsManager.setMode(AppOpsManager.OP_MANAGE_CREDENTIALS,
+ appInfo.uid, mCredentialManagerPackageName, AppOpsManager.MODE_DEFAULT);
+ mExecutor.execute(() -> {
+ try {
+ IKeyChainService service = KeyChain.bind(mContext).getService();
+ service.removeCredentialManagementApp();
+ } catch (InterruptedException | RemoteException e) {
+ Log.e(TAG, "Unable to remove the credential management app");
+ }
+ });
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to remove the credential management app");
+ }
+ }
+}
diff --git a/src/com/android/settings/security/CredentialManagementAppFragment.java b/src/com/android/settings/security/CredentialManagementAppFragment.java
new file mode 100644
index 0000000..5544ee6
--- /dev/null
+++ b/src/com/android/settings/security/CredentialManagementAppFragment.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import android.app.settings.SettingsEnums;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.search.SearchIndexable;
+
+/**
+ * Settings fragment containing the credential management app. The credential management app has
+ * the ability to manage the user's credentials on unmanaged devices.
+ */
+@SearchIndexable
+public class CredentialManagementAppFragment extends DashboardFragment {
+
+ private static final String TAG = "CredentialManagementApp";
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.credential_management_app_fragment;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.CREDENTIAL_MANAGEMENT_APP;
+ }
+
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.credential_management_app_fragment);
+}
diff --git a/src/com/android/settings/security/CredentialManagementAppHeaderController.java b/src/com/android/settings/security/CredentialManagementAppHeaderController.java
new file mode 100644
index 0000000..975c49d
--- /dev/null
+++ b/src/com/android/settings/security/CredentialManagementAppHeaderController.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.security.IKeyChainService;
+import android.security.KeyChain;
+import android.util.Log;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.LayoutPreference;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Controller that shows the header of the credential management app, which includes credential
+ * management app's name, icon and a description.
+ */
+public class CredentialManagementAppHeaderController extends BasePreferenceController {
+
+ private static final String TAG = "CredentialManagementApp";
+
+ private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ public CredentialManagementAppHeaderController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mPackageManager = context.getPackageManager();
+ }
+
+ private final PackageManager mPackageManager;
+ private boolean mHasCredentialManagerPackage;
+ private String mCredentialManagerPackageName;
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE_UNSEARCHABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ mExecutor.execute(() -> {
+ try {
+ IKeyChainService service = KeyChain.bind(mContext).getService();
+ mHasCredentialManagerPackage = service.hasCredentialManagementApp();
+ mCredentialManagerPackageName = service.getCredentialManagementAppPackageName();
+ } catch (InterruptedException | RemoteException e) {
+ Log.e(TAG, "Unable to display credential management app header");
+ }
+ mHandler.post(() -> displayHeader(screen));
+ });
+ }
+
+ private void displayHeader(PreferenceScreen screen) {
+ LayoutPreference headerPref = screen.findPreference(getPreferenceKey());
+ ImageView mAppIconView = headerPref.findViewById(R.id.entity_header_icon);
+ TextView mTitleView = headerPref.findViewById(R.id.entity_header_title);
+ TextView mDescriptionView = headerPref.findViewById(R.id.entity_header_summary);
+
+ try {
+ ApplicationInfo applicationInfo =
+ mPackageManager.getApplicationInfo(mCredentialManagerPackageName, 0);
+ mAppIconView.setImageDrawable(mPackageManager.getApplicationIcon(applicationInfo));
+ mTitleView.setText(applicationInfo.loadLabel(mPackageManager));
+ } catch (PackageManager.NameNotFoundException e) {
+ mAppIconView.setImageDrawable(null);
+ mTitleView.setText(mCredentialManagerPackageName);
+ }
+ // TODO (b/165641221): The description should be multi-lined, which is currently a
+ // limitation of using Settings entity header. However, the Settings entity header
+ // should be used to be consistent with the rest of Settings.
+ mDescriptionView.setText(
+ mContext.getString(R.string.request_manage_credentials_description));
+ }
+}
diff --git a/src/com/android/settings/security/CredentialManagementAppPolicyController.java b/src/com/android/settings/security/CredentialManagementAppPolicyController.java
new file mode 100644
index 0000000..9561c5f
--- /dev/null
+++ b/src/com/android/settings/security/CredentialManagementAppPolicyController.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
+
+import android.content.Context;
+
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.core.BasePreferenceController;
+
+/**
+ * Controller that shows the credential management app's authentication policy.
+ */
+public class CredentialManagementAppPolicyController extends BasePreferenceController {
+
+ public CredentialManagementAppPolicyController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE_UNSEARCHABLE;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ PreferenceGroup group = screen.findPreference(getPreferenceKey());
+ group.addPreference(new CredentialManagementAppPolicyPreference(group.getContext()));
+ }
+}
diff --git a/src/com/android/settings/security/CredentialManagementAppPolicyPreference.java b/src/com/android/settings/security/CredentialManagementAppPolicyPreference.java
new file mode 100644
index 0000000..1747be3
--- /dev/null
+++ b/src/com/android/settings/security/CredentialManagementAppPolicyPreference.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.security.AppUriAuthenticationPolicy;
+import android.security.IKeyChainService;
+import android.security.KeyChain;
+import android.util.Log;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.settings.R;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Preference that shows the credential management app's authentication policy.
+ */
+public class CredentialManagementAppPolicyPreference extends Preference {
+
+ private static final String TAG = "CredentialManagementApp";
+
+ private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ private final Context mContext;
+
+ private boolean mHasCredentialManagerPackage;
+ private String mCredentialManagerPackageName;
+ private AppUriAuthenticationPolicy mCredentialManagerPolicy;
+
+ public CredentialManagementAppPolicyPreference(Context context) {
+ super(context);
+ setLayoutResource(R.layout.credential_management_app_policy);
+ mContext = context;
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder view) {
+ super.onBindViewHolder(view);
+
+ mExecutor.execute(() -> {
+ try {
+ IKeyChainService service = KeyChain.bind(mContext).getService();
+ mHasCredentialManagerPackage = service.hasCredentialManagementApp();
+ mCredentialManagerPackageName = service.getCredentialManagementAppPackageName();
+ mCredentialManagerPolicy = service.getCredentialManagementAppPolicy();
+ } catch (InterruptedException | RemoteException e) {
+ Log.e(TAG, "Unable to display credential management app policy");
+ }
+ mHandler.post(() -> displayPolicy(view));
+ });
+ }
+
+ private void displayPolicy(PreferenceViewHolder view) {
+ if (mHasCredentialManagerPackage) {
+ RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
+ recyclerView.setLayoutManager(new LinearLayoutManager(mContext));
+
+ CredentialManagementAppAdapter recyclerViewAdapter = new CredentialManagementAppAdapter(
+ mContext, mCredentialManagerPackageName,
+ mCredentialManagerPolicy.getAppAndUriMappings(),
+ /* include header= */ false, /* include expander= */ true);
+ recyclerView.setAdapter(recyclerViewAdapter);
+ }
+ }
+}
diff --git a/src/com/android/settings/security/CredentialManagementAppPreferenceController.java b/src/com/android/settings/security/CredentialManagementAppPreferenceController.java
new file mode 100644
index 0000000..107b8f2
--- /dev/null
+++ b/src/com/android/settings/security/CredentialManagementAppPreferenceController.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.security.IKeyChainService;
+import android.security.KeyChain;
+import android.util.Log;
+
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Controller that shows and updates the credential management app summary.
+ */
+public class CredentialManagementAppPreferenceController extends BasePreferenceController {
+
+ private static final String TAG = "CredentialManagementApp";
+
+ private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ private final PackageManager mPackageManager;
+ private boolean mHasCredentialManagerPackage;
+ private String mCredentialManagerPackageName;
+
+ public CredentialManagementAppPreferenceController(Context context, String key) {
+ super(context, key);
+ mPackageManager = context.getPackageManager();
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ mExecutor.execute(() -> {
+ try {
+ IKeyChainService service = KeyChain.bind(mContext).getService();
+ mHasCredentialManagerPackage = service.hasCredentialManagementApp();
+ mCredentialManagerPackageName = service.getCredentialManagementAppPackageName();
+ } catch (InterruptedException | RemoteException e) {
+ Log.e(TAG, "Unable to display credential management app preference");
+ }
+ mHandler.post(() -> displayPreference(preference));
+ });
+ }
+
+ private void displayPreference(Preference preference) {
+ if (mHasCredentialManagerPackage) {
+ preference.setEnabled(true);
+ try {
+ ApplicationInfo applicationInfo =
+ mPackageManager.getApplicationInfo(mCredentialManagerPackageName, 0);
+ preference.setSummary(applicationInfo.loadLabel(mPackageManager));
+ } catch (PackageManager.NameNotFoundException e) {
+ preference.setSummary(mCredentialManagerPackageName);
+ }
+ } else {
+ preference.setEnabled(false);
+ preference.setSummary(R.string.no_certificate_management_app);
+ }
+ }
+}
diff --git a/src/com/android/settings/security/RequestManageCredentials.java b/src/com/android/settings/security/RequestManageCredentials.java
index 9d2d51e..b30f5b6 100644
--- a/src/com/android/settings/security/RequestManageCredentials.java
+++ b/src/com/android/settings/security/RequestManageCredentials.java
@@ -35,6 +35,8 @@
import com.android.settings.R;
+import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
+
/**
* Displays a full screen to the user asking whether the calling app can manage the user's
* KeyChain credentials. This screen includes the authentication policy highlighting what apps and
@@ -62,6 +64,7 @@
private RecyclerView mRecyclerView;
private LinearLayoutManager mLayoutManager;
private LinearLayout mButtonPanel;
+ private ExtendedFloatingActionButton mExtendedFab;
private boolean mDisplayingButtonPanel = false;
@@ -79,6 +82,7 @@
loadRecyclerView();
loadButtons();
+ loadExtendedFloatingActionButton();
addOnScrollListener();
} else {
Log.e(TAG, "Unable to start activity because intent action is not "
@@ -93,7 +97,8 @@
mRecyclerView.setLayoutManager(mLayoutManager);
CredentialManagementAppAdapter recyclerViewAdapter = new CredentialManagementAppAdapter(
- this, mCredentialManagerPackage, mAuthenticationPolicy.getAppAndUriMappings());
+ this, mCredentialManagerPackage, mAuthenticationPolicy.getAppAndUriMappings(),
+ /* include header= */ true, /* include expander= */ false);
mRecyclerView.setAdapter(recyclerViewAdapter);
}
@@ -106,6 +111,15 @@
allowButton.setOnClickListener(setCredentialManagementApp());
}
+ private void loadExtendedFloatingActionButton() {
+ mExtendedFab = findViewById(R.id.extended_fab);
+ mExtendedFab.setOnClickListener(v -> {
+ mRecyclerView.scrollToPosition(mAuthenticationPolicy.getAppAndUriMappings().size());
+ mExtendedFab.hide();
+ showButtonPanel();
+ });
+ }
+
private View.OnClickListener finishRequestManageCredentials() {
return v -> {
Toast.makeText(this, R.string.request_manage_credentials_dont_allow,
@@ -130,9 +144,16 @@
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!mDisplayingButtonPanel) {
+ // On down scroll, hide text in floating action button by setting
+ // extended to false.
+ if (dy > 0 && mExtendedFab.getVisibility() == View.VISIBLE) {
+ mExtendedFab.setExtended(false);
+ }
if (isRecyclerScrollable()) {
+ mExtendedFab.show();
hideButtonPanel();
} else {
+ mExtendedFab.hide();
showButtonPanel();
}
}
diff --git a/src/com/android/settings/sim/SimActivationNotifier.java b/src/com/android/settings/sim/SimActivationNotifier.java
new file mode 100644
index 0000000..85d3da2
--- /dev/null
+++ b/src/com/android/settings/sim/SimActivationNotifier.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2020 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.sim;
+
+import static android.content.Context.MODE_PRIVATE;
+
+import android.annotation.IntDef;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.core.app.TaskStackBuilder;
+
+import com.android.settings.R;
+import com.android.settings.Settings;
+import com.android.settings.network.SubscriptionUtil;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class manages the notification of SIM activation notification including creating and
+ * canceling the notifications.
+ */
+public class SimActivationNotifier {
+
+ private static final String TAG = "SimActivationNotifier";
+ private static final String SIM_SETUP_CHANNEL_ID = "sim_setup";
+ private static final String SIM_PREFS = "sim_prefs";
+ private static final String KEY_SHOW_SIM_SETTINGS_NOTIFICATION =
+ "show_sim_settings_notification";
+
+ public static final int SIM_ACTIVATION_NOTIFICATION_ID = 1;
+
+ /** Notification types */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ value = {
+ NotificationType.NETWORK_CONFIG,
+ })
+ public @interface NotificationType {
+ // The notification to remind users to config network Settings.
+ int NETWORK_CONFIG = 1;
+ }
+
+ private final Context mContext;
+ private final NotificationManager mNotificationManager;
+
+ public SimActivationNotifier(Context context) {
+ mContext = context;
+ mNotificationManager = context.getSystemService(NotificationManager.class);
+ mNotificationManager.createNotificationChannel(
+ new NotificationChannel(
+ SIM_SETUP_CHANNEL_ID,
+ mContext.getString(R.string.sim_setup_channel_id),
+ NotificationManager.IMPORTANCE_HIGH));
+ }
+
+ /**
+ * Sets whether Settings should send a push notification for the SIM activation.
+ *
+ * @param context
+ * @param showNotification whether Settings should send a push notification for the SIM
+ * activation.
+ */
+ public static void setShowSimSettingsNotification(Context context, boolean showNotification) {
+ final SharedPreferences prefs = context.getSharedPreferences(SIM_PREFS, MODE_PRIVATE);
+ prefs.edit().putBoolean(KEY_SHOW_SIM_SETTINGS_NOTIFICATION, showNotification).apply();
+ }
+
+ /**
+ * Gets whether Settings should send a push notification for the SIM activation.
+ *
+ * @param context
+ * @return true if Settings should send a push notification for SIM activation. Otherwise,
+ * return false.
+ */
+ public static boolean getShowSimSettingsNotification(Context context) {
+ final SharedPreferences prefs = context.getSharedPreferences(SIM_PREFS, MODE_PRIVATE);
+ return prefs.getBoolean(KEY_SHOW_SIM_SETTINGS_NOTIFICATION, false);
+ }
+
+ /** Sends a push notification for the SIM activation. It should be called after DSDS reboot. */
+ public void sendNetworkConfigNotification() {
+ SubscriptionManager subscriptionManager =
+ mContext.getSystemService(SubscriptionManager.class);
+ SubscriptionInfo activeRemovableSub =
+ SubscriptionUtil.getActiveSubscriptions(subscriptionManager).stream()
+ .filter(sub -> !sub.isEmbedded())
+ .findFirst()
+ .orElse(null);
+
+ if (activeRemovableSub == null) {
+ Log.e(TAG, "No removable subscriptions found. Do not show notification.");
+ return;
+ }
+
+ String carrierName =
+ TextUtils.isEmpty(activeRemovableSub.getDisplayName())
+ ? mContext.getString(R.string.sim_card_label)
+ : activeRemovableSub.getDisplayName().toString();
+ String title =
+ mContext.getString(
+ R.string.post_dsds_reboot_notification_title_with_carrier, carrierName);
+ String text = mContext.getString(R.string.post_dsds_reboot_notification_text);
+ Intent clickIntent = new Intent(mContext, Settings.MobileNetworkListActivity.class);
+ TaskStackBuilder stackBuilder =
+ TaskStackBuilder.create(mContext).addNextIntent(clickIntent);
+ PendingIntent contentIntent =
+ stackBuilder.getPendingIntent(
+ 0 /* requestCode */, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ Notification.Builder builder =
+ new Notification.Builder(mContext, SIM_SETUP_CHANNEL_ID)
+ .setContentTitle(title)
+ .setContentText(text)
+ .setContentIntent(contentIntent)
+ .setSmallIcon(R.drawable.ic_sim_alert)
+ .setAutoCancel(true);
+ mNotificationManager.notify(SIM_ACTIVATION_NOTIFICATION_ID, builder.build());
+ }
+}
diff --git a/src/com/android/settings/sim/SimNotificationService.java b/src/com/android/settings/sim/SimNotificationService.java
new file mode 100644
index 0000000..303c21d
--- /dev/null
+++ b/src/com/android/settings/sim/SimNotificationService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 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.sim;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.util.Log;
+
+import com.android.settings.R;
+
+/** A JobService sends SIM notifications. */
+public class SimNotificationService extends JobService {
+
+ private static final String TAG = "SimNotificationService";
+ private static final String EXTRA_NOTIFICATION_TYPE = "notification_type";
+
+ /**
+ * Schedules a service to send SIM push notifications.
+ * @param context
+ * @param notificationType indicates which SIM notification to send.
+ */
+ public static void scheduleSimNotification(
+ Context context, @SimActivationNotifier.NotificationType int notificationType) {
+ final JobScheduler jobScheduler =
+ context.getApplicationContext().getSystemService(JobScheduler.class);
+ final ComponentName component =
+ new ComponentName(context.getApplicationContext(), SimNotificationService.class);
+ PersistableBundle extra = new PersistableBundle();
+ extra.putInt(EXTRA_NOTIFICATION_TYPE, notificationType);
+
+ jobScheduler.schedule(
+ new JobInfo.Builder(R.integer.sim_notification_send, component)
+ .setExtras(extra)
+ .build());
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ PersistableBundle extra = params.getExtras();
+ if (extra == null) {
+ Log.e(TAG, "Failed to get notification type.");
+ return false;
+ }
+ int notificationType = extra.getInt(EXTRA_NOTIFICATION_TYPE);
+ switch (notificationType) {
+ case SimActivationNotifier.NotificationType.NETWORK_CONFIG:
+ Log.i(TAG, "Sending SIM config notification.");
+ SimActivationNotifier.setShowSimSettingsNotification(this, false);
+ new SimActivationNotifier(this).sendNetworkConfigNotification();
+ break;
+ default:
+ Log.e(TAG, "Invalid notification type: " + notificationType);
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+}
diff --git a/src/com/android/settings/sim/receivers/SimCompleteBootReceiver.java b/src/com/android/settings/sim/receivers/SimCompleteBootReceiver.java
new file mode 100644
index 0000000..e9acf94
--- /dev/null
+++ b/src/com/android/settings/sim/receivers/SimCompleteBootReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 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.sim.receivers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.settings.sim.SimActivationNotifier;
+import com.android.settings.sim.SimNotificationService;
+
+/** This class manage all SIM operations after device boot up. */
+public class SimCompleteBootReceiver extends BroadcastReceiver {
+ private static final String TAG = "SimCompleteBootReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ Log.e(TAG, "Invalid broadcast received.");
+ return;
+ }
+ if (SimActivationNotifier.getShowSimSettingsNotification(context)) {
+ SimNotificationService.scheduleSimNotification(
+ context, SimActivationNotifier.NotificationType.NETWORK_CONFIG);
+ }
+ }
+}
diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java
new file mode 100644
index 0000000..814f1a4
--- /dev/null
+++ b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2020 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.sim.receivers;
+
+import static android.content.Context.MODE_PRIVATE;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Looper;
+import android.provider.Settings;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotInfo;
+import android.util.Log;
+
+import com.android.settings.network.SubscriptionUtil;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+/** Perform actions after a slot change event is triggered. */
+public class SimSlotChangeHandler {
+ private static final String TAG = "SimSlotChangeHandler";
+
+ private static final String EUICC_PREFS = "euicc_prefs";
+ private static final String KEY_REMOVABLE_SLOT_STATE = "removable_slot_state";
+
+ private static volatile SimSlotChangeHandler sSlotChangeHandler;
+
+ /** Returns a SIM slot change handler singleton. */
+ public static SimSlotChangeHandler get() {
+ if (sSlotChangeHandler == null) {
+ synchronized (SimSlotChangeHandler.class) {
+ if (sSlotChangeHandler == null) {
+ sSlotChangeHandler = new SimSlotChangeHandler();
+ }
+ }
+ }
+ return sSlotChangeHandler;
+ }
+
+ private SubscriptionManager mSubMgr;
+ private TelephonyManager mTelMgr;
+ private Context mContext;
+
+ void onSlotsStatusChange(Context context) {
+ init(context);
+
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ throw new IllegalStateException("Cannot be called from main thread.");
+ }
+
+ if (mTelMgr.getActiveModemCount() > 1) {
+ Log.i(TAG, "The device is already in DSDS mode. Do nothing.");
+ return;
+ }
+
+ UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo();
+ if (removableSlotInfo == null) {
+ Log.e(TAG, "Unable to find the removable slot. Do nothing.");
+ return;
+ }
+
+ int lastRemovableSlotState = getLastRemovableSimSlotState(mContext);
+ int currentRemovableSlotState = removableSlotInfo.getCardStateInfo();
+
+ // Sets the current removable slot state.
+ setRemovableSimSlotState(mContext, currentRemovableSlotState);
+
+ if (lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT
+ && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT) {
+ handleSimInsert(removableSlotInfo);
+ return;
+ }
+ if (lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT
+ && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT) {
+ handleSimRemove(removableSlotInfo);
+ return;
+ }
+ Log.i(TAG, "Do nothing on slot status changes.");
+ }
+
+ private void init(Context context) {
+ mSubMgr =
+ (SubscriptionManager)
+ context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ mTelMgr = context.getSystemService(TelephonyManager.class);
+ mContext = context;
+ }
+
+ private void handleSimInsert(UiccSlotInfo removableSlotInfo) {
+ Log.i(TAG, "Detect SIM inserted.");
+
+ if (!isSuwFinished(mContext)) {
+ // TODO(b/170508680): Store the action and handle it after SUW is finished.
+ Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished");
+ return;
+ }
+
+ if (removableSlotInfo.getIsActive()) {
+ Log.i(TAG, "The removable slot is already active. Do nothing.");
+ return;
+ }
+
+ if (!hasActiveEsimSubscription()) {
+ if (mTelMgr.isMultiSimEnabled()) {
+ Log.i(TAG, "Enabled profile exists. DSDS condition satisfied.");
+ // TODO(b/170508680): Display DSDS dialog to ask users whether to enable DSDS.
+ } else {
+ Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied.");
+ // TODO(b/170508680): Display Choose a number to use screen for subscription
+ // selection.
+ }
+ return;
+ }
+
+ Log.i(
+ TAG,
+ "No enabled eSIM profile. Ready to switch to removable slot and show"
+ + " notification.");
+ // TODO(b/170508680): Switch the slot to the removebale slot and show the notification.
+ }
+
+ private void handleSimRemove(UiccSlotInfo removableSlotInfo) {
+ Log.i(TAG, "Detect SIM removed.");
+
+ if (!isSuwFinished(mContext)) {
+ // TODO(b/170508680): Store the action and handle it after SUW is finished.
+ Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished");
+ return;
+ }
+
+ List<SubscriptionInfo> groupedEmbeddedSubscriptions = getGroupedEmbeddedSubscriptions();
+
+ if (groupedEmbeddedSubscriptions.size() == 0 || !removableSlotInfo.getIsActive()) {
+ Log.i(TAG, "eSIM slot is active or no subscriptions exist. Do nothing.");
+ return;
+ }
+
+ // If there is only 1 eSIM profile exists, we ask the user if they want to switch to that
+ // profile.
+ if (groupedEmbeddedSubscriptions.size() == 1) {
+ Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch.");
+ // TODO(b/170508680): Display a dialog to ask users to switch.
+ return;
+ }
+
+ // If there are more than 1 eSIM profiles installed, we show a screen to let users to choose
+ // the number they want to use.
+ Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use.");
+ // TODO(b/170508680): Display a dialog to ask user which SIM to switch.
+ }
+
+ private int getLastRemovableSimSlotState(Context context) {
+ final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
+ return prefs.getInt(KEY_REMOVABLE_SLOT_STATE, UiccSlotInfo.CARD_STATE_INFO_ABSENT);
+ }
+
+ private void setRemovableSimSlotState(Context context, int state) {
+ final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE);
+ prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply();
+ }
+
+ @Nullable
+ private UiccSlotInfo getRemovableUiccSlotInfo() {
+ UiccSlotInfo[] slotInfos = mTelMgr.getUiccSlotsInfo();
+ if (slotInfos == null) {
+ Log.e(TAG, "slotInfos is null. Unable to get slot infos.");
+ return null;
+ }
+ for (UiccSlotInfo slotInfo : slotInfos) {
+ if (slotInfo != null && slotInfo.isRemovable()) {
+
+ return slotInfo;
+ }
+ }
+ return null;
+ }
+
+ private static boolean isSuwFinished(Context context) {
+ try {
+ // DEVICE_PROVISIONED is 0 if still in setup wizard. 1 if setup completed.
+ return Settings.Global.getInt(
+ context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED)
+ == 1;
+ } catch (Settings.SettingNotFoundException e) {
+ Log.e(TAG, "Cannot get DEVICE_PROVISIONED from the device.", e);
+ return false;
+ }
+ }
+
+ private boolean hasActiveEsimSubscription() {
+ List<SubscriptionInfo> activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr);
+ return activeSubs.stream().anyMatch(SubscriptionInfo::isEmbedded);
+ }
+
+ private List<SubscriptionInfo> getGroupedEmbeddedSubscriptions() {
+ List<SubscriptionInfo> groupedSubscriptions =
+ SubscriptionUtil.getSelectableSubscriptionInfoList(mContext);
+ if (groupedSubscriptions == null) {
+ return ImmutableList.of();
+ }
+ return ImmutableList.copyOf(
+ groupedSubscriptions.stream()
+ .filter(sub -> sub.isEmbedded())
+ .collect(Collectors.toList()));
+ }
+
+ private SimSlotChangeHandler() {}
+}
diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java b/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java
new file mode 100644
index 0000000..17a1b8d
--- /dev/null
+++ b/src/com/android/settings/sim/receivers/SimSlotChangeReceiver.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 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.sim.receivers;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccCardInfo;
+import android.telephony.UiccSlotInfo;
+import android.telephony.euicc.EuiccManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.List;
+
+/** The receiver when the slot status changes. */
+public class SimSlotChangeReceiver extends BroadcastReceiver {
+ private static final String TAG = "SlotChangeReceiver";
+
+ private final SimSlotChangeHandler mSlotChangeHandler = SimSlotChangeHandler.get();
+ private final Object mLock = new Object();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ String action = intent.getAction();
+ if (!TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED.equals(action)) {
+ Log.e(TAG, "Ignore slot changes due to unexpected action: " + action);
+ return;
+ }
+
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ synchronized (mLock) {
+ if (!shouldHandleSlotChange(context)) {
+ return;
+ }
+ mSlotChangeHandler.onSlotsStatusChange(context);
+ }
+ });
+ }
+
+ // Checks whether the slot event should be handled.
+ private boolean shouldHandleSlotChange(Context context) {
+ final EuiccManager euiccManager = context.getSystemService(EuiccManager.class);
+ if (euiccManager == null || !euiccManager.isEnabled()) {
+ Log.i(TAG, "Ignore slot changes because EuiccManager is disabled.");
+ return false;
+ }
+
+ if (euiccManager.getOtaStatus() == EuiccManager.EUICC_OTA_IN_PROGRESS) {
+ Log.i(TAG, "Ignore slot changes because eSIM OTA is in progress.");
+ return false;
+ }
+
+ if (!isSimSlotStateValid(context)) {
+ Log.i(TAG, "Ignore slot changes because SIM states are not valid.");
+ return false;
+ }
+
+ return true;
+ }
+
+ // Checks whether the SIM slot state is valid for slot change event.
+ private boolean isSimSlotStateValid(Context context) {
+ final TelephonyManager telMgr = context.getSystemService(TelephonyManager.class);
+ UiccSlotInfo[] slotInfos = telMgr.getUiccSlotsInfo();
+ if (slotInfos == null) {
+ Log.e(TAG, "slotInfos is null. Unable to get slot infos.");
+ return false;
+ }
+
+ boolean isAllCardStringsEmpty = true;
+ for (int i = 0; i < slotInfos.length; i++) {
+ UiccSlotInfo slotInfo = slotInfos[i];
+
+ if (slotInfo == null) {
+ return false;
+ }
+
+ // After pSIM is inserted, there might be a short period that the status of both slots
+ // are not accurate. We drop the event if any of sim presence state is ERROR or
+ // RESTRICTED.
+ if (slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_ERROR
+ || slotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) {
+ Log.i(TAG, "The SIM state is in an error. Drop the event. SIM info: " + slotInfo);
+ return false;
+ }
+
+ UiccCardInfo cardInfo = findUiccCardInfoBySlot(telMgr, i);
+ if (cardInfo == null) {
+ continue;
+ }
+ if (!TextUtils.isEmpty(slotInfo.getCardId())
+ || !TextUtils.isEmpty(cardInfo.getIccId())) {
+ isAllCardStringsEmpty = false;
+ }
+ }
+
+ // We also drop the event if both the card strings are empty, which usually means it's
+ // between SIM slots switch the slot status is not stable at this moment.
+ if (isAllCardStringsEmpty) {
+ Log.i(TAG, "All UICC card strings are empty. Drop this event.");
+ return false;
+ }
+
+ return true;
+ }
+
+ @Nullable
+ private UiccCardInfo findUiccCardInfoBySlot(TelephonyManager telMgr, int physicalSlotIndex) {
+ List<UiccCardInfo> cardInfos = telMgr.getUiccCardsInfo();
+ if (cardInfos == null) {
+ return null;
+ }
+ return cardInfos.stream()
+ .filter(info -> info.getSlotIndex() == physicalSlotIndex)
+ .findFirst()
+ .orElse(null);
+ }
+}
diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java
index 3007953..ce98d27 100644
--- a/src/com/android/settings/slices/CustomSliceRegistry.java
+++ b/src/com/android/settings/slices/CustomSliceRegistry.java
@@ -41,6 +41,7 @@
import com.android.settings.media.MediaOutputIndicatorSlice;
import com.android.settings.media.MediaOutputSlice;
import com.android.settings.media.RemoteMediaSlice;
+import com.android.settings.network.AirplaneSafeNetworksSlice;
import com.android.settings.network.telephony.MobileDataSlice;
import com.android.settings.notification.zen.ZenModeButtonPreferenceController;
import com.android.settings.wifi.calling.WifiCallingSliceHelper;
@@ -314,6 +315,16 @@
.appendPath("always_on_display")
.build();
+ /**
+ * Backing Uri for the Always On Slice.
+ */
+ public static final Uri AIRPLANE_SAFE_NETWORKS_SLICE_URI = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+ .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+ .appendPath("airplane_safe_networks")
+ .build();
+
@VisibleForTesting
static final Map<Uri, Class<? extends CustomSliceable>> sUriToSlice;
@@ -336,6 +347,7 @@
sUriToSlice.put(REMOTE_MEDIA_SLICE_URI, RemoteMediaSlice.class);
sUriToSlice.put(MEDIA_OUTPUT_GROUP_SLICE_URI, MediaOutputGroupSlice.class);
sUriToSlice.put(ALWAYS_ON_SLICE_URI, AlwaysOnDisplaySlice.class);
+ sUriToSlice.put(AIRPLANE_SAFE_NETWORKS_SLICE_URI, AirplaneSafeNetworksSlice.class);
}
public static Class<? extends CustomSliceable> getSliceClassByUri(Uri uri) {
diff --git a/src/com/android/settings/tts/TextToSpeechSettings.java b/src/com/android/settings/tts/TextToSpeechSettings.java
index 3245657..30e9967 100644
--- a/src/com/android/settings/tts/TextToSpeechSettings.java
+++ b/src/com/android/settings/tts/TextToSpeechSettings.java
@@ -318,11 +318,15 @@
mDefaultRatePref.setProgress(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, mDefaultRate));
mDefaultRatePref.setOnPreferenceChangeListener(this);
mDefaultRatePref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, MAX_SPEECH_RATE));
+ mDefaultRatePref.setContinuousUpdates(true);
+ mDefaultRatePref.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS);
mDefaultPitchPref.setProgress(
getSeekBarProgressFromValue(KEY_DEFAULT_PITCH, mDefaultPitch));
mDefaultPitchPref.setOnPreferenceChangeListener(this);
mDefaultPitchPref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_PITCH, MAX_SPEECH_PITCH));
+ mDefaultPitchPref.setContinuousUpdates(true);
+ mDefaultPitchPref.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS);
if (mTts != null) {
mCurrentEngine = mTts.getCurrentEngine();
diff --git a/src/com/android/settings/widget/GenericSwitchController.java b/src/com/android/settings/widget/GenericSwitchController.java
new file mode 100644
index 0000000..b1b4342
--- /dev/null
+++ b/src/com/android/settings/widget/GenericSwitchController.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 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.widget;
+
+import androidx.preference.Preference;
+
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import com.android.settingslib.RestrictedSwitchPreference;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+
+/**
+ * The switch controller that is used to update the switch widget in the PrimarySwitchPreference
+ * and RestrictedSwitchPreference layouts.
+ */
+public class GenericSwitchController extends SwitchWidgetController implements
+ Preference.OnPreferenceChangeListener {
+
+ private Preference mPreference;
+ private MetricsFeatureProvider mMetricsFeatureProvider;
+
+ public GenericSwitchController(PrimarySwitchPreference preference) {
+ setPreference(preference);
+ }
+
+ public GenericSwitchController(RestrictedSwitchPreference preference) {
+ setPreference(preference);
+ }
+
+ private void setPreference(Preference preference) {
+ mPreference = preference;
+ mMetricsFeatureProvider =
+ FeatureFactory.getFactory(preference.getContext()).getMetricsFeatureProvider();
+ }
+
+ @Override
+ public void updateTitle(boolean isChecked) {
+ }
+
+ @Override
+ public void startListening() {
+ mPreference.setOnPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void stopListening() {
+ mPreference.setOnPreferenceChangeListener(null);
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ if (mPreference instanceof PrimarySwitchPreference) {
+ ((PrimarySwitchPreference) mPreference).setChecked(checked);
+ } else if (mPreference instanceof RestrictedSwitchPreference) {
+ ((RestrictedSwitchPreference) mPreference).setChecked(checked);
+ }
+ }
+
+ @Override
+ public boolean isChecked() {
+ if (mPreference instanceof PrimarySwitchPreference) {
+ return ((PrimarySwitchPreference) mPreference).isChecked();
+ } else if (mPreference instanceof RestrictedSwitchPreference) {
+ return ((RestrictedSwitchPreference) mPreference).isChecked();
+ }
+ return false;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (mPreference instanceof PrimarySwitchPreference) {
+ ((PrimarySwitchPreference) mPreference).setSwitchEnabled(enabled);
+ } else if (mPreference instanceof RestrictedSwitchPreference) {
+ ((RestrictedSwitchPreference) mPreference).setEnabled(enabled);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ if (mListener != null) {
+ final boolean result = mListener.onSwitchToggled((Boolean) newValue);
+ if (result) {
+ mMetricsFeatureProvider.logClickedPreference(preference,
+ preference.getExtras().getInt(DashboardFragment.CATEGORY));
+ }
+ return result;
+ }
+ return false;
+ }
+
+ @Override
+ public void setDisabledByAdmin(EnforcedAdmin admin) {
+ if (mPreference instanceof PrimarySwitchPreference) {
+ ((PrimarySwitchPreference) mPreference).setDisabledByAdmin(admin);
+ } else if (mPreference instanceof RestrictedSwitchPreference) {
+ ((RestrictedSwitchPreference) mPreference).setDisabledByAdmin(admin);
+ }
+ }
+}
diff --git a/src/com/android/settings/widget/LabeledSeekBar.java b/src/com/android/settings/widget/LabeledSeekBar.java
index 14c3d7d..5945c75 100644
--- a/src/com/android/settings/widget/LabeledSeekBar.java
+++ b/src/com/android/settings/widget/LabeledSeekBar.java
@@ -16,6 +16,8 @@
package com.android.settings.widget;
+import static android.view.HapticFeedbackConstants.CLOCK_TICK;
+
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
@@ -48,6 +50,8 @@
/** Labels for discrete progress values. */
private String[] mLabels;
+ private int mLastProgress = -1;
+
public LabeledSeekBar(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.seekBarStyle);
}
@@ -118,6 +122,10 @@
mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
sendClickEventForAccessibility(progress);
}
+ if (progress != mLastProgress) {
+ seekBar.performHapticFeedback(CLOCK_TICK);
+ mLastProgress = progress;
+ }
}
};
diff --git a/src/com/android/settings/widget/PrimarySwitchController.java b/src/com/android/settings/widget/PrimarySwitchController.java
deleted file mode 100644
index 3718a89..0000000
--- a/src/com/android/settings/widget/PrimarySwitchController.java
+++ /dev/null
@@ -1,88 +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.widget;
-
-import androidx.preference.Preference;
-
-import com.android.settings.dashboard.DashboardFragment;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-
-/**
- * The switch controller that is used to update the switch widget in the PrimarySwitchPreference
- * layout.
- */
-public class PrimarySwitchController extends SwitchWidgetController implements
- Preference.OnPreferenceChangeListener {
-
- private final PrimarySwitchPreference mPreference;
- private final MetricsFeatureProvider mMetricsFeatureProvider;
-
- public PrimarySwitchController(PrimarySwitchPreference preference) {
- mPreference = preference;
- mMetricsFeatureProvider = FeatureFactory.getFactory(preference.getContext())
- .getMetricsFeatureProvider();
- }
-
- @Override
- public void updateTitle(boolean isChecked) {
- }
-
- @Override
- public void startListening() {
- mPreference.setOnPreferenceChangeListener(this);
- }
-
- @Override
- public void stopListening() {
- mPreference.setOnPreferenceChangeListener(null);
- }
-
- @Override
- public void setChecked(boolean checked) {
- mPreference.setChecked(checked);
- }
-
- @Override
- public boolean isChecked() {
- return mPreference.isChecked();
- }
-
- @Override
- public void setEnabled(boolean enabled) {
- mPreference.setSwitchEnabled(enabled);
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (mListener != null) {
- final boolean result = mListener.onSwitchToggled((Boolean) newValue);
- if (result) {
- mMetricsFeatureProvider.logClickedPreference(preference,
- preference.getExtras().getInt(DashboardFragment.CATEGORY));
- }
- return result;
- }
- return false;
- }
-
- @Override
- public void setDisabledByAdmin(EnforcedAdmin admin) {
- mPreference.setDisabledByAdmin(admin);
- }
-}
diff --git a/src/com/android/settings/wifi/WifiPrimarySwitchPreferenceController.java b/src/com/android/settings/wifi/WifiPrimarySwitchPreferenceController.java
index b2bd67f..fdc12aa 100644
--- a/src/com/android/settings/wifi/WifiPrimarySwitchPreferenceController.java
+++ b/src/com/android/settings/wifi/WifiPrimarySwitchPreferenceController.java
@@ -21,7 +21,7 @@
import com.android.settings.R;
import com.android.settings.core.PreferenceControllerMixin;
-import com.android.settings.widget.PrimarySwitchController;
+import com.android.settings.widget.GenericSwitchController;
import com.android.settings.widget.PrimarySwitchPreference;
import com.android.settings.widget.SummaryUpdater;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -89,7 +89,7 @@
@Override
public void onStart() {
- mWifiEnabler = new WifiEnabler(mContext, new PrimarySwitchController(mWifiPreference),
+ mWifiEnabler = new WifiEnabler(mContext, new GenericSwitchController(mWifiPreference),
mMetricsFeatureProvider);
}
diff --git a/tests/robotests/res/xml-mcc999/about_legal.xml b/tests/robotests/res/xml-mcc999/about_legal.xml
index 3e008cb..cbbc99a 100644
--- a/tests/robotests/res/xml-mcc999/about_legal.xml
+++ b/tests/robotests/res/xml-mcc999/about_legal.xml
@@ -24,7 +24,7 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="bears_bears_bears"
settings:keywords="keywords">
diff --git a/tests/robotests/res/xml-mcc999/display_settings.xml b/tests/robotests/res/xml-mcc999/display_settings.xml
index fccad7f..b5bf789 100644
--- a/tests/robotests/res/xml-mcc999/display_settings.xml
+++ b/tests/robotests/res/xml-mcc999/display_settings.xml
@@ -23,7 +23,7 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="page_title"
settings:keywords="keywords">
diff --git a/tests/robotests/src/com/android/settings/PointerSpeedPreferenceTest.java b/tests/robotests/src/com/android/settings/PointerSpeedPreferenceTest.java
new file mode 100644
index 0000000..0925344
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/PointerSpeedPreferenceTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 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 static android.view.HapticFeedbackConstants.CLOCK_TICK;
+import static android.view.HapticFeedbackConstants.CONTEXT_CLICK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class PointerSpeedPreferenceTest {
+
+ private Context mContext;
+ private AttributeSet mAttrs;
+ private SeekBar mSeekBar;
+ private PointerSpeedPreference mPointerSpeedPreference;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mSeekBar = new SeekBar(mContext, mAttrs);
+ mPointerSpeedPreference = new PointerSpeedPreference(mContext, mAttrs);
+ }
+
+ @Test
+ public void onProgressChanged_minimumValue_clockTickFeedbackPerformed() {
+ mSeekBar.performHapticFeedback(CONTEXT_CLICK);
+ mPointerSpeedPreference.onProgressChanged(mSeekBar, 0, true);
+
+ assertThat(shadowOf(mSeekBar).lastHapticFeedbackPerformed()).isEqualTo(CLOCK_TICK);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java b/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java
index e0dc681..93f1a7b 100644
--- a/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/BalanceSeekBarTest.java
@@ -16,12 +16,16 @@
package com.android.settings.accessibility;
+import static android.view.HapticFeedbackConstants.CLOCK_TICK;
+import static android.view.HapticFeedbackConstants.CONTEXT_CLICK;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
import android.content.Context;
import android.util.AttributeSet;
@@ -49,7 +53,7 @@
public void setUp() {
mContext = RuntimeEnvironment.application;
mSeekBar = new BalanceSeekBar(mContext, mAttrs);
- mProxySeekBarListener = mSeekBar.getProxySeekBarListener();
+ mProxySeekBarListener = shadowOf(mSeekBar).getOnSeekBarChangeListener();
mockSeekBarChangeListener = mock(SeekBar.OnSeekBarChangeListener.class);
mSeekBar.setOnSeekBarChangeListener(mockSeekBarChangeListener);
}
@@ -78,6 +82,31 @@
}
@Test
+ public void onProgressChanged_minimumValue_clockTickFeedbackPerformed() {
+ mSeekBar.performHapticFeedback(CONTEXT_CLICK);
+ mProxySeekBarListener.onProgressChanged(mSeekBar, 0, true);
+
+ assertThat(shadowOf(mSeekBar).lastHapticFeedbackPerformed()).isEqualTo(CLOCK_TICK);
+ }
+
+ @Test
+ public void onProgressChanged_centerValue_clockTickFeedbackPerformed() {
+ mSeekBar.performHapticFeedback(CONTEXT_CLICK);
+ mProxySeekBarListener.onProgressChanged(mSeekBar, MAX_PROGRESS_VALUE / 2, true);
+
+ assertThat(shadowOf(mSeekBar).lastHapticFeedbackPerformed()).isEqualTo(CLOCK_TICK);
+ }
+
+ @Test
+ public void onProgressChanged_maximumValue_clockTickFeedbackPerformed() {
+ mSeekBar.setMax(MAX_PROGRESS_VALUE);
+ mSeekBar.performHapticFeedback(CONTEXT_CLICK);
+ mProxySeekBarListener.onProgressChanged(mSeekBar, MAX_PROGRESS_VALUE, true);
+
+ assertThat(shadowOf(mSeekBar).lastHapticFeedbackPerformed()).isEqualTo(CLOCK_TICK);
+ }
+
+ @Test
public void setMaxTest_shouldSetValue() {
mSeekBar.setMax(MAX_PROGRESS_VALUE);
diff --git a/tests/robotests/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixinTest.java b/tests/robotests/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixinTest.java
index fb565b6..afda8d5 100644
--- a/tests/robotests/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixinTest.java
+++ b/tests/robotests/src/com/android/settings/applications/specialaccess/zenaccess/ZenAccessSettingObserverMixinTest.java
@@ -73,7 +73,7 @@
mLifecycle.handleLifecycleEvent(ON_START);
mContext.getContentResolver().notifyChange(Settings.Secure.getUriFor(
- Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES), null);
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), null);
verify(mListener).onZenAccessPolicyChanged();
}
@@ -84,7 +84,7 @@
mLifecycle.handleLifecycleEvent(ON_STOP);
mContext.getContentResolver().notifyChange(Settings.Secure.getUriFor(
- Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES), null);
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), null);
verify(mListener, never()).onZenAccessPolicyChanged();
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsControllerTestBase.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsControllerTestBase.java
index 7313321..6401388 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsControllerTestBase.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsControllerTestBase.java
@@ -41,7 +41,7 @@
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
-public class BluetoothDetailsControllerTestBase {
+public abstract class BluetoothDetailsControllerTestBase {
protected Context mContext;
private LifecycleOwner mLifecycleOwner;
diff --git a/tests/robotests/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceControllerTest.java
new file mode 100644
index 0000000..06e66a1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/transcode/TranscodeDefaultOptionPreferenceControllerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.development.transcode;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.SystemProperties;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.core.BasePreferenceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class TranscodeDefaultOptionPreferenceControllerTest {
+ private static final String TRANSCODE_DEFAULT_SYS_PROP_KEY =
+ "persist.sys.fuse.transcode_default";
+
+ private TranscodeDefaultOptionPreferenceController mUnderTest;
+
+ @Before
+ public void setUp() {
+ Context context = ApplicationProvider.getApplicationContext();
+ mUnderTest = new TranscodeDefaultOptionPreferenceController(context, "some_key");
+ }
+
+ @Test
+ public void isChecked_whenSysPropSet_shouldReturnFalse() {
+ SystemProperties.set(TRANSCODE_DEFAULT_SYS_PROP_KEY, "true");
+ assertThat(mUnderTest.isChecked()).isFalse();
+ }
+
+ @Test
+ public void isChecked_whenSysPropUnset_shouldReturnTrue() {
+ SystemProperties.set(TRANSCODE_DEFAULT_SYS_PROP_KEY, "false");
+ assertThat(mUnderTest.isChecked()).isTrue();
+ }
+
+ @Test
+ public void setChecked_withTrue_shouldUnsetSysProp() {
+ mUnderTest.setChecked(true);
+ assertThat(
+ SystemProperties.getBoolean(TRANSCODE_DEFAULT_SYS_PROP_KEY, true)).isFalse();
+ }
+
+ @Test
+ public void setChecked_withFalse_shouldSetSysProp() {
+ mUnderTest.setChecked(false);
+ assertThat(
+ SystemProperties.getBoolean(TRANSCODE_DEFAULT_SYS_PROP_KEY, false)).isTrue();
+ }
+
+ @Test
+ public void getAvailabilityStatus_shouldReturn_isAvailable() {
+ assertThat(mUnderTest.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.AVAILABLE);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceControllerTest.java
index b22bf9d..aa2e672 100644
--- a/tests/robotests/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/development/transcode/TranscodeGlobalTogglePreferenceControllerTest.java
@@ -31,7 +31,7 @@
@RunWith(RobolectricTestRunner.class)
public class TranscodeGlobalTogglePreferenceControllerTest {
- private static final String TRANSCODE_ENABLED_PROP_KEY = "persist.sys.fuse.transcode";
+ private static final String TRANSCODE_ENABLED_PROP_KEY = "persist.sys.fuse.transcode_enabled";
private TranscodeGlobalTogglePreferenceController mController;
@@ -47,15 +47,15 @@
}
@Test
- public void isChecked_whenDisabled_shouldReturnTrue() {
+ public void isChecked_whenDisabled_shouldReturnFalse() {
SystemProperties.set(TRANSCODE_ENABLED_PROP_KEY, "false");
- assertThat(mController.isChecked()).isTrue();
+ assertThat(mController.isChecked()).isFalse();
}
@Test
- public void isChecked_whenEnabled_shouldReturnFalse() {
+ public void isChecked_whenEnabled_shouldReturnTrue() {
SystemProperties.set(TRANSCODE_ENABLED_PROP_KEY, "true");
- assertThat(mController.isChecked()).isFalse();
+ assertThat(mController.isChecked()).isTrue();
}
@Test
@@ -64,7 +64,7 @@
mController.setChecked(true);
// Verify the system property was updated.
- assertThat(SystemProperties.getBoolean(TRANSCODE_ENABLED_PROP_KEY, true)).isFalse();
+ assertThat(SystemProperties.getBoolean(TRANSCODE_ENABLED_PROP_KEY, false)).isTrue();
}
@Test
@@ -73,6 +73,6 @@
mController.setChecked(false);
// Verify the system property was updated.
- assertThat(SystemProperties.getBoolean(TRANSCODE_ENABLED_PROP_KEY, false)).isTrue();
+ assertThat(SystemProperties.getBoolean(TRANSCODE_ENABLED_PROP_KEY, true)).isFalse();
}
}
diff --git a/tests/robotests/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceControllerTest.java
new file mode 100644
index 0000000..8cba0c3
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/transcode/TranscodeUserControlPreferenceControllerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.development.transcode;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.SystemProperties;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.core.BasePreferenceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class TranscodeUserControlPreferenceControllerTest {
+ private static final String TRANSCODE_USER_CONTROL_SYS_PROP_KEY =
+ "persist.sys.fuse.transcode_user_control";
+
+ private TranscodeUserControlPreferenceController mUnderTest;
+
+ @Before
+ public void setUp() {
+ Context context = ApplicationProvider.getApplicationContext();
+ mUnderTest = new TranscodeUserControlPreferenceController(context, "some_key");
+ }
+
+ @Test
+ public void isChecked_whenSysPropSet_shouldReturnTrue() {
+ SystemProperties.set(TRANSCODE_USER_CONTROL_SYS_PROP_KEY, "true");
+ assertThat(mUnderTest.isChecked()).isTrue();
+ }
+
+ @Test
+ public void isChecked_whenSysPropUnset_shouldReturnFalse() {
+ SystemProperties.set(TRANSCODE_USER_CONTROL_SYS_PROP_KEY, "false");
+ assertThat(mUnderTest.isChecked()).isFalse();
+ }
+
+ @Test
+ public void setChecked_withTrue_shouldSetSysProp() {
+ mUnderTest.setChecked(true);
+ assertThat(
+ SystemProperties.getBoolean(TRANSCODE_USER_CONTROL_SYS_PROP_KEY, false)).isTrue();
+ }
+
+ @Test
+ public void setChecked_withFalse_shouldUnsetSysProp() {
+ mUnderTest.setChecked(false);
+ assertThat(
+ SystemProperties.getBoolean(TRANSCODE_USER_CONTROL_SYS_PROP_KEY, true)).isFalse();
+ }
+
+ @Test
+ public void getAvailabilityStatus_shouldReturn_isAvailable() {
+ assertThat(mUnderTest.getAvailabilityStatus()).isEqualTo(
+ BasePreferenceController.AVAILABLE);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImplTest.java
index b6b594c..c28267b 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImplTest.java
@@ -63,6 +63,7 @@
import java.util.Date;
import java.util.List;
+
@RunWith(RobolectricTestRunner.class)
public class EnterprisePrivacyFeatureProviderImplTest {
@@ -426,6 +427,17 @@
verify(mContext).startActivity(intentEquals(intent));
}
+ @Test
+ public void testShowParentalControls() {
+ when(mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(any()))
+ .thenReturn(mOwner);
+
+ // If the intent is resolved, then we can use it to launch the activity
+ Intent intent = addParentalControlsIntent(mOwner.getPackageName());
+ assertThat(mProvider.showParentalControls()).isTrue();
+ verify(mContext).startActivity(intentEquals(intent));
+ }
+
private Intent addWorkPolicyInfoIntent(
String packageName, boolean deviceOwner, boolean profileOwner) {
Intent intent = new Intent(Settings.ACTION_SHOW_WORK_POLICY_INFO);
@@ -450,6 +462,23 @@
return intent;
}
+ private Intent addParentalControlsIntent(String packageName) {
+ Intent intent = new Intent(EnterprisePrivacyFeatureProviderImpl.ACTION_PARENTAL_CONTROLS);
+ intent.setPackage(packageName);
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.resolvePackageName = packageName;
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.name = "activityName";
+ resolveInfo.activityInfo.packageName = packageName;
+
+ List<ResolveInfo> activities = ImmutableList.of(resolveInfo);
+ when(mPackageManager.queryIntentActivities(intentEquals(intent), anyInt()))
+ .thenReturn(activities);
+ when(mPackageManager.queryIntentActivitiesAsUser(intentEquals(intent), anyInt(), anyInt()))
+ .thenReturn(activities);
+ return intent;
+ }
+
private static class IntentMatcher implements ArgumentMatcher<Intent> {
private final Intent mExpectedIntent;
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
index 742daf2..a072988 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBroadcastReceiverTest.java
@@ -66,6 +66,7 @@
mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext);
mBatteryBroadcastReceiver.mBatteryLevel = BATTERY_INIT_LEVEL;
mBatteryBroadcastReceiver.mBatteryStatus = BATTERY_INIT_STATUS;
+ mBatteryBroadcastReceiver.mBatteryHealth = BatteryManager.BATTERY_HEALTH_UNKNOWN;
mBatteryBroadcastReceiver.setBatteryChangedListener(mBatteryListener);
mChargingIntent = new Intent(Intent.ACTION_BATTERY_CHANGED);
@@ -95,6 +96,21 @@
BatteryFixSliceTest.ShadowBatteryStatsHelperLoader.class,
BatteryFixSliceTest.ShadowBatteryTipLoader.class
})
+ public void testOnReceive_batteryHealthChanged_dataUpdated() {
+ mChargingIntent
+ .putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
+ mBatteryBroadcastReceiver.onReceive(mContext, mChargingIntent);
+
+ assertThat(mBatteryBroadcastReceiver.mBatteryHealth)
+ .isEqualTo(BatteryManager.BATTERY_HEALTH_OVERHEAT);
+ verify(mBatteryListener).onBatteryChanged(BatteryUpdateType.BATTERY_HEALTH);
+ }
+
+ @Test
+ @Config(shadows = {
+ BatteryFixSliceTest.ShadowBatteryStatsHelperLoader.class,
+ BatteryFixSliceTest.ShadowBatteryTipLoader.class
+ })
public void onReceive_batteryNotPresent_shouldShowHelpMessage() {
mChargingIntent.putExtra(BatteryManager.EXTRA_PRESENT, false);
@@ -131,6 +147,8 @@
assertThat(mBatteryBroadcastReceiver.mBatteryLevel).isEqualTo(batteryLevel);
assertThat(mBatteryBroadcastReceiver.mBatteryStatus).isEqualTo(batteryStatus);
+ assertThat(mBatteryBroadcastReceiver.mBatteryHealth)
+ .isEqualTo(BatteryManager.BATTERY_HEALTH_UNKNOWN);
verify(mBatteryListener, never()).onBatteryChanged(anyInt());
}
@@ -149,6 +167,8 @@
.isEqualTo(Utils.getBatteryPercentage(mChargingIntent));
assertThat(mBatteryBroadcastReceiver.mBatteryStatus)
.isEqualTo(Utils.getBatteryStatus(mContext, mChargingIntent));
+ assertThat(mBatteryBroadcastReceiver.mBatteryHealth)
+ .isEqualTo(BatteryManager.BATTERY_HEALTH_UNKNOWN);
// 2 times because register will force update the battery
verify(mBatteryListener, times(2)).onBatteryChanged(BatteryUpdateType.MANUAL);
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
index a6cf653..ac3c8f9 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java
@@ -176,6 +176,15 @@
}
@Test
+ public void updatePreference_isOverheat_showEmptyText() {
+ mBatteryInfo.isOverheated = true;
+
+ mController.updateHeaderPreference(mBatteryInfo);
+
+ assertThat(mSummary.getText().toString().isEmpty()).isTrue();
+ }
+
+ @Test
public void onStart_shouldStyleActionBar() {
when(mEntityHeaderController.setRecyclerView(nullable(RecyclerView.class), eq(mLifecycle)))
.thenReturn(mEntityHeaderController);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java
index e88295c..c81e81c 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryInfoTest.java
@@ -246,6 +246,22 @@
assertThat(info.chargeLabel).isEqualTo("100%");
}
+ @Test
+ public void testGetBatteryInfo_chargingWithOverheated_updateChargeLabel() {
+ doReturn(TEST_CHARGE_TIME_REMAINING)
+ .when(mBatteryStats)
+ .computeChargeTimeRemaining(anyLong());
+ mChargingBatteryBroadcast
+ .putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
+
+ BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast,
+ mBatteryStats, MOCK_ESTIMATE, SystemClock.elapsedRealtime() * 1000,
+ false /* shortString */);
+
+ assertThat(info.isOverheated).isTrue();
+ assertThat(info.chargeLabel).isEqualTo("50% - Battery limited temporarily");
+ }
+
// Make our battery stats return a sequence of battery events.
private void mockBatteryStatsHistory() {
// Mock out new data every time start...Locked is called.
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java
index 89f51a7..f393da8 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java
@@ -144,6 +144,8 @@
@Mock
private BatterySipper mIdleBatterySipper;
@Mock
+ private BatteryInfo mBatteryInfo;
+ @Mock
private Bundle mBundle;
@Mock
private UserManager mUserManager;
@@ -754,4 +756,36 @@
assertThat(estimate.isBasedOnUsage()).isTrue();
assertThat(estimate.getAverageDischargeTime()).isEqualTo(1000);
}
+
+ @Test
+ public void testIsBatteryDefenderOn_isOverheatedAndIsCharging_returnTrue() {
+ mBatteryInfo.isOverheated = true;
+ mBatteryInfo.discharging = false;
+
+ assertThat(mBatteryUtils.isBatteryDefenderOn(mBatteryInfo)).isTrue();
+ }
+
+ @Test
+ public void testIsBatteryDefenderOn_isOverheatedAndDischarging_returnFalse() {
+ mBatteryInfo.isOverheated = true;
+ mBatteryInfo.discharging = true;
+
+ assertThat(mBatteryUtils.isBatteryDefenderOn(mBatteryInfo)).isFalse();
+ }
+
+ @Test
+ public void testIsBatteryDefenderOn_notOverheatedAndDischarging_returnFalse() {
+ mBatteryInfo.isOverheated = false;
+ mBatteryInfo.discharging = true;
+
+ assertThat(mBatteryUtils.isBatteryDefenderOn(mBatteryInfo)).isFalse();
+ }
+
+ @Test
+ public void testIsBatteryDefenderOn_notOverheatedAndIsCharging_returnFalse() {
+ mBatteryInfo.isOverheated = false;
+ mBatteryInfo.discharging = false;
+
+ assertThat(mBatteryUtils.isBatteryDefenderOn(mBatteryInfo)).isFalse();
+ }
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
index 116033b..8cc17d8 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java
@@ -50,6 +50,7 @@
public class BatteryTipLoaderTest {
private static final int[] TIP_ORDER = {
+ BatteryTip.TipType.BATTERY_DEFENDER,
BatteryTip.TipType.BATTERY_SAVER,
BatteryTip.TipType.HIGH_DEVICE_USAGE,
BatteryTip.TipType.LOW_BATTERY,
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtilsTest.java
index 275bfe0..6199788 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtilsTest.java
@@ -23,10 +23,12 @@
import com.android.settings.SettingsActivity;
import com.android.settings.core.InstrumentedPreferenceFragment;
+import com.android.settings.fuelgauge.batterytip.actions.BatteryDefenderAction;
import com.android.settings.fuelgauge.batterytip.actions.BatterySaverAction;
import com.android.settings.fuelgauge.batterytip.actions.OpenBatterySaverAction;
import com.android.settings.fuelgauge.batterytip.actions.OpenRestrictAppFragmentAction;
import com.android.settings.fuelgauge.batterytip.actions.RestrictAppAction;
+import com.android.settings.fuelgauge.batterytip.tips.BatteryDefenderTip;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.EarlyWarningTip;
import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip;
@@ -53,6 +55,7 @@
private RestrictAppTip mRestrictAppTip;
private EarlyWarningTip mEarlyWarningTip;
private LowBatteryTip mLowBatteryTip;
+ private BatteryDefenderTip mBatteryDefenderTip;
@Before
public void setUp() {
@@ -67,6 +70,7 @@
mLowBatteryTip = spy(
new LowBatteryTip(BatteryTip.StateType.NEW, false /* powerSaveModeOn */,
"" /* summary */));
+ mBatteryDefenderTip = spy(new BatteryDefenderTip(BatteryTip.StateType.NEW));
}
@Test
@@ -116,4 +120,13 @@
assertThat(BatteryTipUtils.getActionForBatteryTip(mLowBatteryTip, mSettingsActivity,
mFragment)).isInstanceOf(OpenBatterySaverAction.class);
}
+
+ @Test
+ public void
+ testGetActionForBatteryTip_typeBatteryDefenderStateNew_returnActionBatteryDefender() {
+ when(mBatteryDefenderTip.getState()).thenReturn(BatteryTip.StateType.NEW);
+
+ assertThat(BatteryTipUtils.getActionForBatteryTip(mBatteryDefenderTip, mSettingsActivity,
+ mFragment)).isInstanceOf(BatteryDefenderAction.class);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetectorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetectorTest.java
new file mode 100644
index 0000000..a1f9d1f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/detectors/BatteryDefenderDetectorTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batterytip.detectors;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settings.fuelgauge.BatteryInfo;
+import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class BatteryDefenderDetectorTest {
+
+ @Mock
+ private BatteryInfo mBatteryInfo;
+ private BatteryDefenderDetector mBatteryDefenderDetector;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mBatteryInfo.discharging = false;
+
+ mBatteryDefenderDetector = new BatteryDefenderDetector(mBatteryInfo);
+ }
+
+ @Test
+ public void testDetect_notOverheated_tipInvisible() {
+ mBatteryInfo.isOverheated = false;
+
+ assertThat(mBatteryDefenderDetector.detect().isVisible()).isFalse();
+ }
+
+ @Test
+ public void testDetect_isOverheated_tipNew() {
+ mBatteryInfo.isOverheated = true;
+
+ assertThat(mBatteryDefenderDetector.detect().getState())
+ .isEqualTo(BatteryTip.StateType.NEW);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java
new file mode 100644
index 0000000..c6eb15d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/BatteryDefenderTipTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settings.fuelgauge.batterytip.tips;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import com.android.settings.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class BatteryDefenderTipTest {
+
+ private Context mContext;
+ private BatteryDefenderTip mBatteryDefenderTip;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ mBatteryDefenderTip = new BatteryDefenderTip(BatteryTip.StateType.NEW);
+ }
+
+ @Test
+ public void getTitle_showTitle() {
+ assertThat(mBatteryDefenderTip.getTitle(mContext))
+ .isEqualTo(mContext.getString(R.string.battery_tip_limited_temporarily_title));
+ }
+
+ @Test
+ public void getSummary_showSummary() {
+ assertThat(mBatteryDefenderTip.getSummary(mContext))
+ .isEqualTo(mContext.getString(R.string.battery_tip_limited_temporarily_summary));
+ }
+
+ @Test
+ public void getIcon_showIcon() {
+ assertThat(mBatteryDefenderTip.getIconId())
+ .isEqualTo(R.drawable.ic_battery_status_good_24dp);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
index 036df2c..6097980 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockGenericTest.java
@@ -21,6 +21,7 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment.KEY_LOCK_SETTINGS_FOOTER;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME;
@@ -90,6 +91,7 @@
Global.putInt(application.getContentResolver(), Global.DEVICE_PROVISIONED, 1);
ShadowStorageManager.reset();
ShadowPersistentDataBlockManager.reset();
+ ShadowLockPatternUtils.reset();
}
@Test
@@ -377,6 +379,64 @@
ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL)).isNotNull();
}
+ @Test
+ public void updatePreferencesOrFinish_ComplexityIsReadFromDPM() {
+ ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
+ ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+
+ initActivity(null);
+ mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
+
+ FooterPreference footer = mFragment.findPreference(KEY_LOCK_SETTINGS_FOOTER);
+ assertThat(footer.getTitle()).isEqualTo(null);
+
+ Intent intent = mFragment.getLockPasswordIntent(PASSWORD_QUALITY_COMPLEX);
+ assertThat(intent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY,
+ PASSWORD_COMPLEXITY_NONE)).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+ }
+
+ @Test
+ public void updatePreferencesOrFinish_ComplexityIsMergedWithDPM() {
+ ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
+ ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
+ Intent intent = new Intent()
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name")
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_LOW);
+ initActivity(intent);
+
+ mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
+
+ // Footer should be null because admin complexity wins.
+ FooterPreference footer = mFragment.findPreference(KEY_LOCK_SETTINGS_FOOTER);
+ assertThat(footer.getTitle()).isEqualTo(null);
+
+ Intent passwordIntent = mFragment.getLockPasswordIntent(PASSWORD_QUALITY_COMPLEX);
+ assertThat(passwordIntent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY,
+ PASSWORD_COMPLEXITY_NONE)).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+ }
+
+ @Test
+ public void updatePreferencesOrFinish_ComplexityIsMergedWithDPM_AppIsHigher() {
+ ShadowStorageManager.setIsFileEncryptedNativeOrEmulated(false);
+ ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+ Intent intent = new Intent()
+ .putExtra(EXTRA_KEY_CALLER_APP_NAME, "app name")
+ .putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, PASSWORD_COMPLEXITY_HIGH);
+ initActivity(intent);
+
+ mFragment.updatePreferencesOrFinish(false /* isRecreatingActivity */);
+
+ // Footer should include app name because app requirement is higher.
+ CharSequence expectedTitle =
+ mActivity.getString(R.string.unlock_footer_high_complexity_requested, "app name");
+ FooterPreference footer = mFragment.findPreference(KEY_LOCK_SETTINGS_FOOTER);
+ assertThat(footer.getTitle()).isEqualTo(expectedTitle);
+
+ Intent passwordIntent = mFragment.getLockPasswordIntent(PASSWORD_QUALITY_COMPLEX);
+ assertThat(passwordIntent.getIntExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY,
+ PASSWORD_COMPLEXITY_NONE)).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
+ }
+
private void initActivity(@Nullable Intent intent) {
if (intent == null) {
intent = new Intent();
diff --git a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
index 030bb80..0ca6a3e 100644
--- a/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
+++ b/tests/robotests/src/com/android/settings/password/ChooseLockPasswordTest.java
@@ -31,6 +31,7 @@
import static com.android.internal.widget.LockPatternUtils.PASSWORD_TYPE_KEY;
import static com.android.settings.password.ChooseLockGeneric.CONFIRM_CREDENTIALS;
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
+import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_ID;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -67,6 +68,7 @@
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
SettingsShadowResources.class,
+ ShadowLockPatternUtils.class,
ShadowUtils.class,
ShadowDevicePolicyManager.class,
})
@@ -84,6 +86,7 @@
@After
public void tearDown() {
SettingsShadowResources.reset();
+ ShadowLockPatternUtils.reset();
}
@Test
@@ -378,6 +381,29 @@
assertThat(drawable.getCreatedFromResId()).isNotEqualTo(R.drawable.ic_fingerprint_header);
}
+ @Test
+ public void validateComplexityMergedFromDpmOnCreate() {
+ ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+
+ assertPasswordValidationResult(
+ /* minComplexity= */ PASSWORD_COMPLEXITY_HIGH,
+ /* passwordType= */ PASSWORD_QUALITY_NUMERIC,
+ /* userEnteredPassword= */ LockscreenCredential.createNone(),
+ "PIN must be at least 8 digits");
+ }
+
+ @Test
+ public void validateComplexityMergedFromUnificationUserOnCreate() {
+ ShadowLockPatternUtils.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_LOW);
+ ShadowLockPatternUtils.setRequiredPasswordComplexity(123, PASSWORD_COMPLEXITY_HIGH);
+
+ Intent intent = createIntentForPasswordValidation(PASSWORD_COMPLEXITY_NONE,
+ PASSWORD_QUALITY_NUMERIC);
+ intent.putExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID, 123);
+ assertPasswordValidationResultForIntent(LockscreenCredential.createNone(), intent,
+ "PIN must be at least 8 digits");
+ }
+
private ChooseLockPassword buildChooseLockPasswordActivity(Intent intent) {
return Robolectric.buildActivity(ChooseLockPassword.class, intent).setup().get();
}
@@ -400,14 +426,27 @@
private void assertPasswordValidationResult(@PasswordComplexity int minComplexity,
int passwordType, LockscreenCredential userEnteredPassword,
String... expectedValidationResult) {
- Intent intent = new Intent();
- intent.putExtra(CONFIRM_CREDENTIALS, false);
- intent.putExtra(PASSWORD_TYPE_KEY, passwordType);
- intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, minComplexity);
+ Intent intent = createIntentForPasswordValidation(minComplexity, passwordType);
+ assertPasswordValidationResultForIntent(userEnteredPassword, intent,
+ expectedValidationResult);
+ }
+
+ private void assertPasswordValidationResultForIntent(LockscreenCredential userEnteredPassword,
+ Intent intent, String... expectedValidationResult) {
ChooseLockPassword activity = buildChooseLockPasswordActivity(intent);
ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(activity);
fragment.validatePassword(userEnteredPassword);
String[] messages = fragment.convertErrorCodeToMessages();
- assertThat(messages).asList().containsExactly((Object[]) expectedValidationResult);
+ assertThat(messages).asList().containsExactly(expectedValidationResult);
+ }
+
+ private Intent createIntentForPasswordValidation(
+ @PasswordComplexity int minComplexity,
+ int passwordType) {
+ Intent intent = new Intent();
+ intent.putExtra(CONFIRM_CREDENTIALS, false);
+ intent.putExtra(PASSWORD_TYPE_KEY, passwordType);
+ intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, minComplexity);
+ return intent;
}
}
diff --git a/tests/robotests/src/com/android/settings/security/CredentialManagementAppButtonsControllerTest.java b/tests/robotests/src/com/android/settings/security/CredentialManagementAppButtonsControllerTest.java
new file mode 100644
index 0000000..ecc2f2d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/security/CredentialManagementAppButtonsControllerTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import com.android.settings.core.BasePreferenceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class CredentialManagementAppButtonsControllerTest {
+
+ private Context mContext;
+ private CredentialManagementAppButtonsController mController;
+
+ private static final String PREF_KEY_CREDENTIAL_MANAGEMENT_APP = "certificate_management_app";
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mController = new CredentialManagementAppButtonsController(
+ mContext, PREF_KEY_CREDENTIAL_MANAGEMENT_APP);
+ }
+
+ @Test
+ public void getAvailabilityStatus_shouldAlwaysReturnAvailableUnsearchable() {
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.AVAILABLE_UNSEARCHABLE);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/security/CredentialManagementAppControllerTest.java b/tests/robotests/src/com/android/settings/security/CredentialManagementAppControllerTest.java
new file mode 100644
index 0000000..1e6f203
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/security/CredentialManagementAppControllerTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class CredentialManagementAppControllerTest {
+
+ private Context mContext;
+ private CredentialManagementAppPreferenceController mController;
+ private Preference mPreference;
+
+ private static final String PREF_KEY_CREDENTIAL_MANAGEMENT_APP = "certificate_management_app";
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mController = new CredentialManagementAppPreferenceController(
+ mContext, PREF_KEY_CREDENTIAL_MANAGEMENT_APP);
+ mPreference = new Preference(mContext);
+ }
+
+ @Test
+ public void getAvailabilityStatus_shouldAlwaysReturnAvailable() {
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.AVAILABLE);
+ }
+
+ @Test
+ public void updateState_noCredentialManagementApp_shouldDisablePreference() {
+ mController.updateState(mPreference);
+
+ assertThat(mPreference.isEnabled()).isEqualTo(false);
+ assertThat(mPreference.getSummary()).isEqualTo(
+ mContext.getText(R.string.no_certificate_management_app));
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/security/CredentialManagementAppFragmentTest.java b/tests/robotests/src/com/android/settings/security/CredentialManagementAppFragmentTest.java
new file mode 100644
index 0000000..de19e5c
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/security/CredentialManagementAppFragmentTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import static android.app.settings.SettingsEnums.CREDENTIAL_MANAGEMENT_APP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.provider.SearchIndexableResource;
+
+import com.android.settings.R;
+import com.android.settings.testutils.shadow.ShadowDashboardFragment;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = ShadowDashboardFragment.class)
+public class CredentialManagementAppFragmentTest {
+
+ private CredentialManagementAppFragment mFragment;
+ private Context mContext;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ mFragment = spy(new CredentialManagementAppFragment());
+
+ doReturn(mContext).when(mFragment).getContext();
+ }
+
+ @Test
+ public void searchIndexProvider_shouldIndexResource() {
+ final List<SearchIndexableResource> indexRes =
+ CredentialManagementAppFragment.SEARCH_INDEX_DATA_PROVIDER
+ .getXmlResourcesToIndex(mContext, true /* enabled */);
+
+ assertThat(indexRes).isNotNull();
+ assertThat(indexRes.get(0).xmlResId).isEqualTo(R.xml.credential_management_app_fragment);
+ }
+
+ @Test
+ public void getMetricsCategory_shouldReturnInstallCertificateFromStorage() {
+ assertThat(mFragment.getMetricsCategory()).isEqualTo(CREDENTIAL_MANAGEMENT_APP);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/security/CredentialManagementAppHeaderControllerTest.java b/tests/robotests/src/com/android/settings/security/CredentialManagementAppHeaderControllerTest.java
new file mode 100644
index 0000000..e77e4c1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/security/CredentialManagementAppHeaderControllerTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.security;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import com.android.settings.core.BasePreferenceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class CredentialManagementAppHeaderControllerTest {
+
+ private Context mContext;
+ private CredentialManagementAppHeaderController mController;
+
+ private static final String PREF_KEY_CREDENTIAL_MANAGEMENT_APP = "certificate_management_app";
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mController = new CredentialManagementAppHeaderController(
+ mContext, PREF_KEY_CREDENTIAL_MANAGEMENT_APP);
+ }
+
+ @Test
+ public void getAvailabilityStatus_shouldAlwaysReturnAvailableUnsearchable() {
+ assertThat(mController.getAvailabilityStatus())
+ .isEqualTo(BasePreferenceController.AVAILABLE_UNSEARCHABLE);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java
index 38756ac..3a159b2 100644
--- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowLockPatternUtils.java
@@ -18,19 +18,31 @@
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
+import android.os.UserHandle;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
@Implements(LockPatternUtils.class)
public class ShadowLockPatternUtils {
private static boolean sDeviceEncryptionEnabled;
+ private static Map<Integer, Integer> sUserToComplexityMap = new HashMap<>();
+
+
+ @Resetter
+ public static void reset() {
+ sUserToComplexityMap.clear();
+ sDeviceEncryptionEnabled = false;
+ }
@Implementation
protected boolean hasSecureLockScreen() {
@@ -76,4 +88,18 @@
protected boolean checkPasswordHistory(byte[] passwordToCheck, byte[] hashFactor, int userId) {
return false;
}
+
+ @Implementation
+ public @DevicePolicyManager.PasswordComplexity int getRequestedPasswordComplexity(int userId) {
+ return sUserToComplexityMap.getOrDefault(userId,
+ DevicePolicyManager.PASSWORD_COMPLEXITY_NONE);
+ }
+
+ public static void setRequiredPasswordComplexity(int userId, int complexity) {
+ sUserToComplexityMap.put(userId, complexity);
+ }
+
+ public static void setRequiredPasswordComplexity(int complexity) {
+ setRequiredPasswordComplexity(UserHandle.myUserId(), complexity);
+ }
}
diff --git a/tests/robotests/src/com/android/settings/widget/LabeledSeekBarTest.java b/tests/robotests/src/com/android/settings/widget/LabeledSeekBarTest.java
new file mode 100644
index 0000000..cd73089
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/widget/LabeledSeekBarTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 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.widget;
+
+import static android.view.HapticFeedbackConstants.CLOCK_TICK;
+import static android.view.HapticFeedbackConstants.CONTEXT_CLICK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class LabeledSeekBarTest {
+
+ private Context mContext;
+ private AttributeSet mAttrs;
+ private SeekBar mSeekBar;
+ private LabeledSeekBar mLabeledSeekBar;
+ private SeekBar.OnSeekBarChangeListener mProxySeekBarListener;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mSeekBar = new SeekBar(mContext, mAttrs);
+ mLabeledSeekBar = new LabeledSeekBar(mContext, mAttrs);
+ mProxySeekBarListener = shadowOf(mLabeledSeekBar).getOnSeekBarChangeListener();
+ }
+
+ @Test
+ public void onProgressChanged_minimumValue_clockTickFeedbackPerformed() {
+ mSeekBar.performHapticFeedback(CONTEXT_CLICK);
+ mProxySeekBarListener.onProgressChanged(mSeekBar, 0, true);
+
+ assertThat(shadowOf(mSeekBar).lastHapticFeedbackPerformed()).isEqualTo(CLOCK_TICK);
+ }
+}
diff --git a/tests/unit/src/com/android/settings/network/AirplaneSafeNetworksPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/AirplaneSafeNetworksPreferenceControllerTest.java
new file mode 100644
index 0000000..4c4c5bd
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/AirplaneSafeNetworksPreferenceControllerTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.os.Looper;
+import android.provider.Settings;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settingslib.RestrictedSwitchPreference;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class AirplaneSafeNetworksPreferenceControllerTest {
+
+ private static final String KEY_AIRPLANE_SAFE_NETWORKS = "airplane_safe_networks";
+
+ private static final int ON = 1;
+ private static final int OFF = 0;
+
+ private ContentResolver mResolver;
+ private PreferenceScreen mScreen;
+ private RestrictedSwitchPreference mPreference;
+ private AirplaneSafeNetworksPreferenceController mController;
+
+ @Mock
+ private WifiManager mWifiManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ final Context context = spy(ApplicationProvider.getApplicationContext());
+ mResolver = context.getContentResolver();
+ doReturn(mWifiManager).when(context).getSystemService(Context.WIFI_SERVICE);
+
+ mController = new AirplaneSafeNetworksPreferenceController(context, mock(Lifecycle.class));
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ final PreferenceManager preferenceManager = new PreferenceManager(context);
+ mScreen = preferenceManager.createPreferenceScreen(context);
+ mPreference = new RestrictedSwitchPreference(context);
+ mPreference.setKey(KEY_AIRPLANE_SAFE_NETWORKS);
+ mScreen.addPreference(mPreference);
+ }
+
+ @Test
+ public void isAvailable_airplaneModeOff_returnFalse() {
+ Settings.Global.putInt(mResolver, Settings.Global.AIRPLANE_MODE_ON, OFF);
+
+ mController.displayPreference(mScreen);
+ mController.onStart();
+
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ @Test
+ public void isAvailable_airplaneModeOn_returnTrue() {
+ Settings.Global.putInt(mResolver, Settings.Global.AIRPLANE_MODE_ON, ON);
+
+ mController.displayPreference(mScreen);
+ mController.onStart();
+
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void isChecked_wifiStateDisabled_returnFalse() {
+ doReturn(WifiManager.WIFI_STATE_DISABLED).when(mWifiManager).getWifiState();
+
+ mController.displayPreference(mScreen);
+ mController.onStart();
+
+ assertThat(mPreference.isChecked()).isFalse();
+ }
+
+ @Test
+ public void isChecked_wifiStateEnabled_returnTrue() {
+ doReturn(WifiManager.WIFI_STATE_ENABLED).when(mWifiManager).getWifiState();
+
+ mController.displayPreference(mScreen);
+ mController.onStart();
+
+ assertThat(mPreference.isChecked()).isTrue();
+ }
+}
diff --git a/tests/unit/src/com/android/settings/network/AirplaneSafeNetworksSliceTest.java b/tests/unit/src/com/android/settings/network/AirplaneSafeNetworksSliceTest.java
new file mode 100644
index 0000000..ce47eb7
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/AirplaneSafeNetworksSliceTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.WifiManager;
+
+import androidx.slice.Slice;
+import androidx.slice.SliceItem;
+import androidx.slice.SliceMetadata;
+import androidx.slice.SliceProvider;
+import androidx.slice.widget.SliceLiveData;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.AirplaneModeEnabler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(AndroidJUnit4.class)
+public class AirplaneSafeNetworksSliceTest {
+
+ private static final String VIEW_AIRPLANE_SAFE_NETWORKS = "View airplane-safe networks";
+ private static final String TURN_OFF_AIRPLANE_MODE = "Turn off Airplane Mode";
+
+ @Rule
+ public MockitoRule mMocks = MockitoJUnit.rule();
+ @Mock
+ private WifiManager mWifiManager;
+
+ private Context mContext;
+ private AirplaneModeEnabler mAirplaneModeEnabler;
+ private AirplaneSafeNetworksSlice mAirplaneSafeNetworksSlice;
+
+ @Before
+ public void setUp() {
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager);
+ mAirplaneModeEnabler =
+ new AirplaneModeEnabler(mContext, null /* OnAirplaneModeChangedListener */);
+
+ // Set-up specs for SliceMetadata.
+ SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
+
+ mAirplaneSafeNetworksSlice = new AirplaneSafeNetworksSlice(mContext);
+ }
+
+ @Test
+ public void getSlice_airplaneModeOff_shouldBeNull() {
+ mAirplaneModeEnabler.setAirplaneMode(false);
+
+ assertThat(mAirplaneSafeNetworksSlice.getSlice()).isNull();
+ }
+
+ @Test
+ public void getSlice_wifiDisabled_shouldShowViewAirplaneSafeNetworks() {
+ mAirplaneModeEnabler.setAirplaneMode(true);
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+
+ final Slice slice = mAirplaneSafeNetworksSlice.getSlice();
+
+ assertThat(slice).isNotNull();
+ final SliceItem sliceTitle =
+ SliceMetadata.from(mContext, slice).getListContent().getHeader().getTitleItem();
+ assertThat(sliceTitle.getText()).isEqualTo(VIEW_AIRPLANE_SAFE_NETWORKS);
+ }
+
+ @Test
+ public void getSlice_wifiEnabled_shouldShowTurnOffAirplaneMode() {
+ mAirplaneModeEnabler.setAirplaneMode(true);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+
+ final Slice slice = mAirplaneSafeNetworksSlice.getSlice();
+
+ assertThat(slice).isNotNull();
+ final SliceItem sliceTitle =
+ SliceMetadata.from(mContext, slice).getListContent().getHeader().getTitleItem();
+ assertThat(sliceTitle.getText()).isEqualTo(TURN_OFF_AIRPLANE_MODE);
+ }
+
+ @Test
+ public void onNotifyChange_viewAirplaneSafeNetworks_shouldSetWifiEnabled() {
+ mAirplaneModeEnabler.setAirplaneMode(true);
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+ Intent intent = mAirplaneSafeNetworksSlice.getIntent();
+
+ mAirplaneSafeNetworksSlice.onNotifyChange(intent);
+
+ verify(mWifiManager).setWifiEnabled(true);
+ }
+
+ @Test
+ public void onNotifyChange_turnOffAirplaneMode_shouldSetAirplaneModeOff() {
+ mAirplaneModeEnabler.setAirplaneMode(true);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ Intent intent = mAirplaneSafeNetworksSlice.getIntent();
+
+ mAirplaneSafeNetworksSlice.onNotifyChange(intent);
+
+ assertThat(mAirplaneModeEnabler.isAirplaneModeOn()).isFalse();
+ }
+}
diff --git a/tests/unit/src/com/android/settings/network/NetworkMobileProviderControllerTest.java b/tests/unit/src/com/android/settings/network/NetworkMobileProviderControllerTest.java
new file mode 100644
index 0000000..31c68da
--- /dev/null
+++ b/tests/unit/src/com/android/settings/network/NetworkMobileProviderControllerTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
+import static com.android.settings.network.NetworkMobileProviderController.PREF_KEY_PROVIDER_MOBILE_NETWORK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Looper;
+
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test for NetworkMobileProviderController.
+ *
+ * {@link NetworkMobileProviderController} is used to show subscription status on internet page for
+ * provider model. This original class can refer to {@link MultiNetworkHeaderController}, and
+ * NetworkMobileProviderControllerTest can also refer to {@link MultiNetworkHeaderControllerTest}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class NetworkMobileProviderControllerTest {
+
+ private static final int EXPANDED_CHILDREN_COUNT = 3;
+
+ @Mock
+ private Lifecycle mLifecycle;
+ @Mock
+ private PreferenceCategory mPreferenceCategory;
+ @Mock
+ private SubscriptionsPreferenceController mSubscriptionsController;
+
+ private Context mContext;
+ private PreferenceManager mPreferenceManager;
+ private PreferenceScreen mPreferenceScreen;
+ private NetworkMobileProviderController mNetworkMobileProviderController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ when(mPreferenceCategory.getKey()).thenReturn(PREF_KEY_PROVIDER_MOBILE_NETWORK);
+ when(mPreferenceCategory.getPreferenceCount()).thenReturn(3);
+
+ mContext = ApplicationProvider.getApplicationContext();
+ mNetworkMobileProviderController =
+ new NetworkMobileProviderController(mContext, PREF_KEY_PROVIDER_MOBILE_NETWORK) {
+ @Override
+ SubscriptionsPreferenceController createSubscriptionsController(
+ Lifecycle lifecycle) {
+ return mSubscriptionsController;
+ }
+ };
+
+ mPreferenceManager = new PreferenceManager(mContext);
+ mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
+ mPreferenceScreen.setInitialExpandedChildrenCount(EXPANDED_CHILDREN_COUNT);
+ mPreferenceScreen.addPreference(mPreferenceCategory);
+ }
+
+ @Test
+ public void testDisplayPreference_subscriptionsControllerAvailable() {
+ when(mSubscriptionsController.isAvailable()).thenReturn(true);
+ setupNetworkMobileProviderController();
+
+ assertTrue(mPreferenceCategory.isVisible());
+ }
+
+ @Test
+ public void testDisplayPreference_subscriptionsControllerUnAvailable() {
+ when(mSubscriptionsController.isAvailable()).thenReturn(false);
+ setupNetworkMobileProviderController();
+
+ assertFalse(mPreferenceCategory.isVisible());
+ }
+
+ @Test
+ public void testGetAvailabilityStatus_subscriptionsControllerIsNull() {
+ when(mSubscriptionsController.isAvailable()).thenReturn(false);
+ mNetworkMobileProviderController = new NetworkMobileProviderController(mContext,
+ PREF_KEY_PROVIDER_MOBILE_NETWORK) {
+ @Override
+ SubscriptionsPreferenceController createSubscriptionsController(Lifecycle lifecycle) {
+ return null;
+ }
+ };
+ setupNetworkMobileProviderController();
+
+ final int result = mNetworkMobileProviderController.getAvailabilityStatus();
+
+ assertEquals(result, CONDITIONALLY_UNAVAILABLE);
+ }
+
+ @Test
+ public void testGetAvailabilityStatus_subscriptionsControllerAvailable() {
+ when(mSubscriptionsController.isAvailable()).thenReturn(true);
+ setupNetworkMobileProviderController();
+
+ final int result = mNetworkMobileProviderController.getAvailabilityStatus();
+
+ assertEquals(result, AVAILABLE);
+ }
+
+ @Test
+ public void testOnChildUpdated_subscriptionsControllerAvailable_categoryIsVisible() {
+ when(mSubscriptionsController.isAvailable()).thenReturn(true);
+ setupNetworkMobileProviderController();
+
+ mNetworkMobileProviderController.onChildrenUpdated();
+
+ assertTrue(mPreferenceCategory.isVisible());
+ assertThat(mPreferenceScreen.getInitialExpandedChildrenCount()).isEqualTo(
+ EXPANDED_CHILDREN_COUNT + mPreferenceCategory.getPreferenceCount());
+ }
+
+ @Test
+ public void testOnChildUpdated_subscriptionsControllerUnavailable_categoryIsInvisible() {
+ when(mSubscriptionsController.isAvailable()).thenReturn(false);
+ setupNetworkMobileProviderController();
+
+ mNetworkMobileProviderController.onChildrenUpdated();
+
+ assertFalse(mPreferenceCategory.isVisible());
+ assertThat(mPreferenceScreen.getInitialExpandedChildrenCount()).isEqualTo(
+ EXPANDED_CHILDREN_COUNT);
+ }
+
+ @Test
+ public void testOnChildUpdated_noExpandedChildCountAndAvailable_doesNotSetExpandedCount() {
+ when(mSubscriptionsController.isAvailable()).thenReturn(true);
+ mPreferenceScreen.setInitialExpandedChildrenCount(Integer.MAX_VALUE);
+ setupNetworkMobileProviderController();
+
+ mNetworkMobileProviderController.onChildrenUpdated();
+
+ assertEquals(mPreferenceScreen.getInitialExpandedChildrenCount(), Integer.MAX_VALUE);
+ }
+
+ private void setupNetworkMobileProviderController() {
+ mNetworkMobileProviderController.init(mLifecycle);
+ mNetworkMobileProviderController.displayPreference(mPreferenceScreen);
+ }
+}
diff --git a/tests/unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java
index 39c7bbd..34849b9 100644
--- a/tests/unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java
+++ b/tests/unit/src/com/android/settings/network/telephony/MobileNetworkSwitchControllerTest.java
@@ -18,22 +18,27 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
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.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
import android.os.Looper;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
-import android.test.UiThreadTest;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -47,6 +52,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -168,9 +174,15 @@
assertThat(mSwitchBar.isShowing()).isTrue();
assertThat(mSwitchBar.isChecked()).isTrue();
+ final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ doNothing().when(mContext).startActivity(intentCaptor.capture());
mSwitchBar.setChecked(false);
+ Bundle extra = intentCaptor.getValue().getExtras();
- verify(mSubscriptionManager).setSubscriptionEnabled(eq(mSubId), eq(false));
+ verify(mContext, times(1)).startActivity(any());
+ assertThat(extra.getInt(ToggleSubscriptionDialogActivity.ARG_SUB_ID)).isEqualTo(mSubId);
+ assertThat(extra.getBoolean(ToggleSubscriptionDialogActivity.ARG_enable))
+ .isEqualTo(false);
}
@Test
@@ -183,9 +195,15 @@
assertThat(mSwitchBar.isShowing()).isTrue();
assertThat(mSwitchBar.isChecked()).isTrue();
+ final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ doNothing().when(mContext).startActivity(intentCaptor.capture());
mSwitchBar.setChecked(false);
+ Bundle extra = intentCaptor.getValue().getExtras();
- verify(mSubscriptionManager).setSubscriptionEnabled(eq(mSubId), eq(false));
+ verify(mContext, times(1)).startActivity(any());
+ assertThat(extra.getInt(ToggleSubscriptionDialogActivity.ARG_SUB_ID)).isEqualTo(mSubId);
+ assertThat(extra.getBoolean(ToggleSubscriptionDialogActivity.ARG_enable))
+ .isEqualTo(false);
assertThat(mSwitchBar.isChecked()).isTrue();
}
@@ -197,8 +215,13 @@
assertThat(mSwitchBar.isShowing()).isTrue();
assertThat(mSwitchBar.isChecked()).isFalse();
+ final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ doNothing().when(mContext).startActivity(intentCaptor.capture());
mSwitchBar.setChecked(true);
+ Bundle extra = intentCaptor.getValue().getExtras();
- verify(mSubscriptionManager).setSubscriptionEnabled(eq(mSubId), eq(true));
+ verify(mContext, times(1)).startActivity(any());
+ assertThat(extra.getInt(ToggleSubscriptionDialogActivity.ARG_SUB_ID)).isEqualTo(mSubId);
+ assertThat(extra.getBoolean(ToggleSubscriptionDialogActivity.ARG_enable)).isEqualTo(true);
}
}